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