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