001package org.hl7.fhir.r4.context;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032import java.io.ByteArrayInputStream;
033import java.io.File;
034import java.io.FileInputStream;
035import java.io.FileNotFoundException;
036import java.io.IOException;
037import java.io.InputStream;
038import java.net.URISyntaxException;
039import java.util.ArrayList;
040import java.util.Arrays;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.HashSet;
044import java.util.List;
045import java.util.Map;
046import java.util.Set;
047import java.util.zip.ZipEntry;
048import java.util.zip.ZipInputStream;
049
050import lombok.extern.slf4j.Slf4j;
051import org.apache.commons.io.IOUtils;
052import org.hl7.fhir.exceptions.DefinitionException;
053import org.hl7.fhir.exceptions.FHIRException;
054import org.hl7.fhir.exceptions.FHIRFormatError;
055import org.hl7.fhir.r4.conformance.ProfileUtilities;
056import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider;
057import org.hl7.fhir.r4.context.IWorkerContext.ILoggingService.LogCategory;
058import org.hl7.fhir.r4.formats.IParser;
059import org.hl7.fhir.r4.formats.JsonParser;
060import org.hl7.fhir.r4.formats.ParserType;
061import org.hl7.fhir.r4.formats.XmlParser;
062import org.hl7.fhir.r4.model.Bundle;
063import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
064import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
065import org.hl7.fhir.r4.model.MetadataResource;
066import org.hl7.fhir.r4.model.Questionnaire;
067import org.hl7.fhir.r4.model.Resource;
068import org.hl7.fhir.r4.model.ResourceType;
069import org.hl7.fhir.r4.model.StructureDefinition;
070import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
071import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
072import org.hl7.fhir.r4.model.StructureMap;
073import org.hl7.fhir.r4.model.StructureMap.StructureMapModelMode;
074import org.hl7.fhir.r4.model.StructureMap.StructureMapStructureComponent;
075import org.hl7.fhir.r4.terminologies.TerminologyClient;
076import org.hl7.fhir.r4.utils.INarrativeGenerator;
077import org.hl7.fhir.r4.utils.NarrativeGenerator;
078import org.hl7.fhir.r4.utils.validation.IResourceValidator;
079import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
080import org.hl7.fhir.utilities.Utilities;
081import org.hl7.fhir.utilities.filesystem.CSFileInputStream;
082import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
083import org.hl7.fhir.utilities.npm.NpmPackage;
084import org.hl7.fhir.utilities.validation.ValidationMessage;
085import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
086import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
087
088import ca.uhn.fhir.parser.DataFormatException;
089import org.slf4j.MarkerFactory;
090import org.slf4j.event.Level;
091
092/*
093 * This is a stand alone implementation of worker context for use inside a tool.
094 * It loads from the validation package (validation-min.xml.zip), and has a 
095 * very light client to connect to an open unauthenticated terminology service
096 */
097
098@MarkedToMoveToAdjunctPackage
099@Slf4j
100public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider {
101
102  public interface IContextResourceLoader {
103    Bundle loadBundle(InputStream stream, boolean isJson) throws FHIRException, IOException;
104  }
105
106  public interface IValidatorFactory {
107    IResourceValidator makeValidator(IWorkerContext ctxts) throws FHIRException;
108  }
109
110  private Questionnaire questionnaire;
111  private Map<String, byte[]> binaries = new HashMap<String, byte[]>();
112  private String version;
113  private String revision;
114  private String date;
115  private IValidatorFactory validatorFactory;
116  private boolean ignoreProfileErrors;
117
118  public SimpleWorkerContext() throws FileNotFoundException, IOException, FHIRException {
119    super();
120  }
121
122  public SimpleWorkerContext(SimpleWorkerContext other) throws FileNotFoundException, IOException, FHIRException {
123    super();
124    copy(other);
125  }
126
127  protected void copy(SimpleWorkerContext other) {
128    super.copy(other);
129    questionnaire = other.questionnaire;
130    binaries.putAll(other.binaries);
131    version = other.version;
132    revision = other.revision;
133    date = other.date;
134    validatorFactory = other.validatorFactory;
135  }
136
137  // -- Initializations
138  /**
139   * Load the working context from the validation pack
140   * 
141   * @param path filename of the validation pack
142   * @return
143   * @throws IOException
144   * @throws FileNotFoundException
145   * @throws FHIRException
146   * @throws Exception
147   */
148  public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException {
149    SimpleWorkerContext res = new SimpleWorkerContext();
150    res.loadFromPack(path, null);
151    return res;
152  }
153
154  public static SimpleWorkerContext fromNothing() throws FileNotFoundException, IOException, FHIRException {
155    SimpleWorkerContext res = new SimpleWorkerContext();
156    return res;
157  }
158
159  public static SimpleWorkerContext fromPackage(NpmPackage pi, boolean allowDuplicates)
160      throws FileNotFoundException, IOException, FHIRException {
161    SimpleWorkerContext res = new SimpleWorkerContext();
162    res.setAllowLoadingDuplicates(allowDuplicates);
163    res.loadFromPackage(pi, null);
164    return res;
165  }
166
167  public static SimpleWorkerContext fromPackage(NpmPackage pi)
168      throws FileNotFoundException, IOException, FHIRException {
169    SimpleWorkerContext res = new SimpleWorkerContext();
170    res.loadFromPackage(pi, null);
171    return res;
172  }
173
174  public static SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader)
175      throws FileNotFoundException, IOException, FHIRException {
176    SimpleWorkerContext res = new SimpleWorkerContext();
177    res.setAllowLoadingDuplicates(true);
178    res.version = pi.getNpm().asString("version");
179    res.loadFromPackage(pi, loader);
180    return res;
181  }
182
183  public static SimpleWorkerContext fromPack(String path, boolean allowDuplicates)
184      throws FileNotFoundException, IOException, FHIRException {
185    SimpleWorkerContext res = new SimpleWorkerContext();
186    res.setAllowLoadingDuplicates(allowDuplicates);
187    res.loadFromPack(path, null);
188    return res;
189  }
190
191  public static SimpleWorkerContext fromPack(String path, IContextResourceLoader loader)
192      throws FileNotFoundException, IOException, FHIRException {
193    SimpleWorkerContext res = new SimpleWorkerContext();
194    res.loadFromPack(path, loader);
195    return res;
196  }
197
198  public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
199    SimpleWorkerContext res = new SimpleWorkerContext();
200    res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null);
201    return res;
202  }
203
204  public static SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
205    InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
206    SimpleWorkerContext res = new SimpleWorkerContext();
207    res.loadFromStream(s, null);
208    return res;
209  }
210
211  public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source) throws IOException, FHIRException {
212    SimpleWorkerContext res = new SimpleWorkerContext();
213    for (String name : source.keySet()) {
214      res.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), null);
215    }
216    return res;
217  }
218
219  public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader)
220      throws FileNotFoundException, IOException, FHIRException {
221    SimpleWorkerContext res = new SimpleWorkerContext();
222    for (String name : source.keySet()) {
223      try {
224        res.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), loader);
225      } catch (Exception e) {
226        log.error("Error loading " + name + ": " + e.getMessage());
227        throw new FHIRException("Error loading " + name + ": " + e.getMessage(), e);
228      }
229    }
230    return res;
231  }
232
233  private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader)
234      throws IOException, FHIRException {
235    if (name.endsWith(".xml"))
236      loadFromFile(stream, name, loader);
237    else if (name.endsWith(".json"))
238      loadFromFileJson(stream, name, loader);
239    else if (name.equals("version.info"))
240      readVersionInfo(stream);
241    else
242      loadBytes(name, stream);
243  }
244
245  public String connectToTSServer(TerminologyClient client, String log) throws URISyntaxException, FHIRException, IOException {
246    tlog("Connect to " + client.getAddress());
247    txClient = client;
248    txLog = new HTMLClientLogger(log);
249    txClient.setLogger(txLog);
250    return txClient.getCapabilitiesStatementQuick().getSoftware().getVersion();
251  }
252
253  public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader)
254      throws IOException, FHIRException {
255    Resource f;
256    try {
257      if (loader != null)
258        f = loader.loadBundle(stream, false);
259      else {
260        XmlParser xml = new XmlParser();
261        f = xml.parse(stream);
262      }
263    } catch (DataFormatException e1) {
264      throw new org.hl7.fhir.exceptions.FHIRFormatError("Error parsing " + name + ":" + e1.getMessage(), e1);
265    } catch (Exception e1) {
266      throw new org.hl7.fhir.exceptions.FHIRFormatError("Error parsing " + name + ":" + e1.getMessage(), e1);
267    }
268    if (f instanceof Bundle) {
269      Bundle bnd = (Bundle) f;
270      for (BundleEntryComponent e : bnd.getEntry()) {
271        if (e.getFullUrl() == null) {
272          logContextDebugMessage("unidentified resource in " + name + " (no fullUrl)");
273        }
274        cacheResource(e.getResource());
275      }
276    } else if (f instanceof MetadataResource) {
277      MetadataResource m = (MetadataResource) f;
278      cacheResource(m);
279    }
280  }
281
282  private void logContextDebugMessage(String message) {
283    log.makeLoggingEventBuilder(Level.DEBUG)
284      .addMarker(MarkerFactory.getMarker(LogCategory.CONTEXT.name().toLowerCase()))
285      .setMessage(message)
286      .log();
287  }
288
289  private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader)
290      throws IOException, FHIRException {
291    Bundle f = null;
292    try {
293      if (loader != null)
294        f = loader.loadBundle(stream, true);
295      else {
296        JsonParser json = new JsonParser();
297        Resource r = json.parse(stream);
298        if (r instanceof Bundle)
299          f = (Bundle) r;
300        else
301          cacheResource(r);
302      }
303    } catch (FHIRFormatError e1) {
304      throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1);
305    }
306    if (f != null)
307      for (BundleEntryComponent e : f.getEntry()) {
308        cacheResource(e.getResource());
309      }
310  }
311
312  private void loadFromPack(String path, IContextResourceLoader loader)
313      throws FileNotFoundException, IOException, FHIRException {
314    loadFromStream(new CSFileInputStream(path), loader);
315  }
316
317  public void loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String... types)
318      throws FileNotFoundException, IOException, FHIRException {
319    if (types.length == 0)
320      types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition",
321          "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" };
322    for (String s : pi.listResources(types)) {
323      loadDefinitionItem(s, pi.load("package", s), loader);
324    }
325    version = pi.version();
326  }
327
328  public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException {
329    loadDefinitionItem(file, new CSFileInputStream(file), loader);
330  }
331
332  private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException {
333    ZipInputStream zip = new ZipInputStream(stream);
334    ZipEntry ze;
335    while ((ze = zip.getNextEntry()) != null) {
336      loadDefinitionItem(ze.getName(), zip, loader);
337      zip.closeEntry();
338    }
339    zip.close();
340  }
341
342  private void readVersionInfo(InputStream stream) throws IOException, DefinitionException {
343    byte[] bytes = IOUtils.toByteArray(stream);
344    binaries.put("version.info", bytes);
345
346    String[] vi = new String(bytes).split("\\r?\\n");
347    for (String s : vi) {
348      if (s.startsWith("version=")) {
349        if (version == null)
350          version = s.substring(8);
351        else if (!version.equals(s.substring(8)))
352          throw new DefinitionException("Version mismatch. The context has version " + version
353              + " loaded, and the new content being loaded is version " + s.substring(8));
354      }
355      if (s.startsWith("revision="))
356        revision = s.substring(9);
357      if (s.startsWith("date="))
358        date = s.substring(5);
359    }
360  }
361
362  private void loadBytes(String name, InputStream stream) throws IOException {
363    byte[] bytes = IOUtils.toByteArray(stream);
364    binaries.put(name, bytes);
365  }
366
367  @Override
368  public IParser getParser(ParserType type) {
369    switch (type) {
370    case JSON:
371      return newJsonParser();
372    case XML:
373      return newXmlParser();
374    default:
375      throw new Error("Parser Type " + type.toString() + " not supported");
376    }
377  }
378
379  @Override
380  public IParser getParser(String type) {
381    if (type.equalsIgnoreCase("JSON"))
382      return new JsonParser();
383    if (type.equalsIgnoreCase("XML"))
384      return new XmlParser();
385    throw new Error("Parser Type " + type.toString() + " not supported");
386  }
387
388  @Override
389  public IParser newJsonParser() {
390    return new JsonParser();
391  }
392
393  @Override
394  public IParser newXmlParser() {
395    return new XmlParser();
396  }
397
398  @Override
399  public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) {
400    return new NarrativeGenerator(prefix, basePath, this);
401  }
402
403  @Override
404  public IResourceValidator newValidator() throws FHIRException {
405    if (validatorFactory == null)
406      throw new Error("No validator configured");
407    return validatorFactory.makeValidator(this);
408  }
409
410  @Override
411  public List<String> getResourceNames() {
412    List<String> result = new ArrayList<String>();
413    for (StructureDefinition sd : listStructures()) {
414      if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
415        result.add(sd.getName());
416    }
417    Collections.sort(result);
418    return result;
419  }
420
421  @Override
422  public List<String> getTypeNames() {
423    List<String> result = new ArrayList<String>();
424    for (StructureDefinition sd : listStructures()) {
425      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
426        result.add(sd.getName());
427    }
428    Collections.sort(result);
429    return result;
430  }
431
432  @Override
433  public String getAbbreviation(String name) {
434    return "xxx";
435  }
436
437  @Override
438  public boolean isDatatype(String typeSimple) {
439    // TODO Auto-generated method stub
440    return false;
441  }
442
443  @Override
444  public boolean isResource(String t) {
445    StructureDefinition sd;
446    try {
447      sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + t);
448    } catch (Exception e) {
449      return false;
450    }
451    if (sd == null)
452      return false;
453    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
454      return false;
455    return sd.getKind() == StructureDefinitionKind.RESOURCE;
456  }
457
458  @Override
459  public boolean hasLinkFor(String typeSimple) {
460    return false;
461  }
462
463  @Override
464  public String getLinkFor(String corePath, String typeSimple) {
465    return null;
466  }
467
468  @Override
469  public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding,
470      String path) {
471    return null;
472  }
473
474  @Override
475  public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
476    return null;
477  }
478
479  @Override
480  public String getLinkForProfile(StructureDefinition profile, String url) {
481    return null;
482  }
483
484  public Questionnaire getQuestionnaire() {
485    return questionnaire;
486  }
487
488  public void setQuestionnaire(Questionnaire questionnaire) {
489    this.questionnaire = questionnaire;
490  }
491
492  @Override
493  public Set<String> typeTails() {
494    return new HashSet<String>(
495        Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant",
496            "String", "Uri", "Url", "Canonical", "Oid", "Uuid", "Id", "Boolean", "Code", "Markdown", "Base64Binary",
497            "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period",
498            "Ratio", "HumanName", "Address", "ContactPoint", "Timing", "Reference", "Annotation", "Signature", "Meta"));
499  }
500
501  @Override
502  public List<StructureDefinition> allStructures() {
503    List<StructureDefinition> result = new ArrayList<StructureDefinition>();
504    Set<StructureDefinition> set = new HashSet<StructureDefinition>();
505    for (StructureDefinition sd : listStructures()) {
506      if (!set.contains(sd)) {
507        try {
508          generateSnapshot(sd);
509        } catch (Exception e) {
510          log.warn("Unable to generate snapshot for " + sd.getUrl() + " because " + e.getMessage());
511        }
512        result.add(sd);
513        set.add(sd);
514      }
515    }
516    return result;
517  }
518
519  public void loadBinariesFromFolder(String folder) throws FileNotFoundException, Exception {
520    for (String n : ManagedFileAccess.file(folder).list()) {
521      loadBytes(n, ManagedFileAccess.inStream(Utilities.path(folder, n)));
522    }
523  }
524
525  public void loadBinariesFromFolder(NpmPackage pi) throws FileNotFoundException, Exception {
526    for (String n : pi.list("other")) {
527      loadBytes(n, pi.load("other", n));
528    }
529  }
530
531  public void loadFromFolder(String folder) throws FileNotFoundException, Exception {
532    for (String n : ManagedFileAccess.file(folder).list()) {
533      if (n.endsWith(".json"))
534        loadFromFile(Utilities.path(folder, n), new JsonParser());
535      else if (n.endsWith(".xml"))
536        loadFromFile(Utilities.path(folder, n), new XmlParser());
537    }
538  }
539
540  private void loadFromFile(String filename, IParser p) throws FileNotFoundException, Exception {
541    Resource r;
542    try {
543      r = p.parse(ManagedFileAccess.inStream(filename));
544      if (r.getResourceType() == ResourceType.Bundle) {
545        for (BundleEntryComponent e : ((Bundle) r).getEntry()) {
546          cacheResource(e.getResource());
547        }
548      } else {
549        cacheResource(r);
550      }
551    } catch (Exception e) {
552      return;
553    }
554  }
555
556  public Map<String, byte[]> getBinaries() {
557    return binaries;
558  }
559
560  @Override
561  public boolean prependLinks() {
562    return false;
563  }
564
565  @Override
566  public boolean hasCache() {
567    return false;
568  }
569
570  @Override
571  public String getVersion() {
572    return version;
573  }
574
575  public List<StructureMap> findTransformsforSource(String url) {
576    List<StructureMap> res = new ArrayList<StructureMap>();
577    for (StructureMap map : listTransforms()) {
578      boolean match = false;
579      boolean ok = true;
580      for (StructureMapStructureComponent t : map.getStructure()) {
581        if (t.getMode() == StructureMapModelMode.SOURCE) {
582          match = match || t.getUrl().equals(url);
583          ok = ok && t.getUrl().equals(url);
584        }
585      }
586      if (match && ok)
587        res.add(map);
588    }
589    return res;
590  }
591
592  public IValidatorFactory getValidatorFactory() {
593    return validatorFactory;
594  }
595
596  public void setValidatorFactory(IValidatorFactory validatorFactory) {
597    this.validatorFactory = validatorFactory;
598  }
599
600  @Override
601  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
602    T r = super.fetchResource(class_, uri);
603    if (r instanceof StructureDefinition) {
604      StructureDefinition p = (StructureDefinition) r;
605      try {
606        generateSnapshot(p);
607      } catch (Exception e) {
608        // not sure what to do in this case?
609        log.warn("Unable to generate snapshot for " + uri + ": " + e.getMessage());
610      }
611    }
612    return r;
613  }
614
615  public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
616    if (!p.hasSnapshot() && p.getKind() != StructureDefinitionKind.LOGICAL) {
617      if (!p.hasBaseDefinition())
618        throw new DefinitionException("Profile " + p.getName() + " (" + p.getUrl() + ") has no base and no snapshot");
619      StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition());
620      if (sd == null)
621        throw new DefinitionException("Profile " + p.getName() + " (" + p.getUrl() + ") base " + p.getBaseDefinition()
622            + " could not be resolved");
623      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
624      List<String> errors = new ArrayList<String>();
625      ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
626      pu.setThrowException(false);
627      pu.sortDifferential(sd, p, p.getUrl(), errors);
628      for (String err : errors)
629        msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"),
630            "Error sorting Differential: " + err, ValidationMessage.IssueSeverity.ERROR));
631      pu.generateSnapshot(sd, p, p.getUrl(), Utilities.extractBaseUrl(sd.getUserString("path")), p.getName());
632      for (ValidationMessage msg : msgs) {
633        if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR)
634            || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)
635          throw new DefinitionException(
636              "Profile " + p.getName() + " (" + p.getUrl() + "). Error generating snapshot: " + msg.getMessage());
637      }
638      if (!p.hasSnapshot())
639        throw new FHIRException("Profile " + p.getName() + " (" + p.getUrl() + "). Error generating snapshot");
640      pu = null;
641    }
642  }
643
644  public boolean isIgnoreProfileErrors() {
645    return ignoreProfileErrors;
646  }
647
648  public void setIgnoreProfileErrors(boolean ignoreProfileErrors) {
649    this.ignoreProfileErrors = ignoreProfileErrors;
650  }
651
652  public String listMapUrls() {
653    return Utilities.listCanonicalUrls(transforms.keySet());
654  }
655
656}