001package org.hl7.fhir.r5.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 032 033 034import java.io.ByteArrayInputStream; 035import java.io.File; 036import java.io.FileInputStream; 037import java.io.FileNotFoundException; 038import java.io.IOException; 039import java.io.InputStream; 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.HashSet; 043import java.util.List; 044import java.util.Locale; 045import java.util.Map; 046import java.util.Set; 047import java.util.zip.ZipEntry; 048import java.util.zip.ZipInputStream; 049 050import lombok.AccessLevel; 051import lombok.AllArgsConstructor; 052import lombok.With; 053import org.apache.commons.io.IOUtils; 054import org.hl7.fhir.exceptions.DefinitionException; 055import org.hl7.fhir.exceptions.FHIRException; 056import org.hl7.fhir.exceptions.FHIRFormatError; 057import org.hl7.fhir.r5.conformance.ProfileUtilities; 058import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider; 059import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy; 060import org.hl7.fhir.r5.context.IWorkerContext.ILoggingService.LogCategory; 061import org.hl7.fhir.r5.formats.IParser; 062import org.hl7.fhir.r5.formats.JsonParser; 063import org.hl7.fhir.r5.formats.ParserType; 064import org.hl7.fhir.r5.formats.XmlParser; 065import org.hl7.fhir.r5.model.*; 066import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; 067import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 068import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 069import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 070import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode; 071import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent; 072import org.hl7.fhir.r5.terminologies.TerminologyClient; 073import org.hl7.fhir.r5.utils.validation.IResourceValidator; 074import org.hl7.fhir.r5.utils.XVerExtensionManager; 075import org.hl7.fhir.utilities.CSFileInputStream; 076import org.hl7.fhir.utilities.TextFile; 077import org.hl7.fhir.utilities.TimeTracker; 078import org.hl7.fhir.utilities.Utilities; 079import org.hl7.fhir.utilities.VersionUtilities; 080import org.hl7.fhir.utilities.i18n.I18nConstants; 081import org.hl7.fhir.utilities.npm.BasePackageCacheManager; 082import org.hl7.fhir.utilities.npm.NpmPackage; 083import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation; 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; 089 090/* 091 * This is a stand alone implementation of worker context for use inside a tool. 092 * It loads from the validation package (validation-min.xml.zip), and has a 093 * very light client to connect to an open unauthenticated terminology service 094 */ 095 096public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider { 097 098 public static class PackageResourceLoader extends CanonicalResourceProxy { 099 100 private final String filename; 101 private final IContextResourceLoader loader; 102 103 public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) { 104 super(pri.getType(), pri.getId(), pri.getUrl(),pri.getVersion()); 105 this.filename = pri.getFilename(); 106 this.loader = loader; 107 } 108 109 @Override 110 public CanonicalResource loadResource() { 111 try { 112 FileInputStream f = new FileInputStream(filename); 113 try { 114 if (loader != null) { 115 return (CanonicalResource) loader.loadResource(f, true); 116 } else { 117 return (CanonicalResource) new JsonParser().parse(f); 118 } 119 } finally { 120 f.close(); 121 } 122 } catch (Exception e) { 123 throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e); 124 } 125 } 126 } 127 128 public interface ILoadFilter { 129 boolean isOkToLoad(Resource resource); 130 boolean isOkToLoad(String resourceType); 131 } 132 133 public interface IValidatorFactory { 134 IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException; 135 IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager) throws FHIRException; 136 } 137 138 private Questionnaire questionnaire; 139 private String revision; 140 private String date; 141 private IValidatorFactory validatorFactory; 142 private boolean ignoreProfileErrors; 143 private boolean progress; 144 private final List<String> loadedPackages = new ArrayList<>(); 145 private boolean canNoTS; 146 private XVerExtensionManager xverManager; 147 148 private SimpleWorkerContext() throws IOException, FHIRException { 149 super(); 150 } 151 152 private SimpleWorkerContext(Locale locale) throws IOException, FHIRException { 153 super(locale); 154 } 155 156 private SimpleWorkerContext(SimpleWorkerContext other) throws IOException, FHIRException { 157 super(); 158 copy(other); 159 } 160 161 private SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws IOException, FHIRException { 162 super(locale); 163 copy(other); 164 } 165 166 protected void copy(SimpleWorkerContext other) { 167 super.copy(other); 168 questionnaire = other.questionnaire; 169 binaries.putAll(other.binaries); 170 version = other.version; 171 revision = other.revision; 172 date = other.date; 173 validatorFactory = other.validatorFactory; 174 } 175 176 177 public List<String> getLoadedPackages() { 178 return loadedPackages; 179 } 180 181 // -- Initializations 182 @AllArgsConstructor(access = AccessLevel.PRIVATE) 183 public static class SimpleWorkerContextBuilder { 184 185 186 @With 187 private final String terminologyCachePath; 188 @With 189 private final boolean cacheTerminologyClientErrors; 190 @With 191 private final boolean alwaysUseTerminologyServer; 192 @With 193 private final boolean readOnlyCache; 194 195 @With 196 private final Locale locale; 197 198 @With 199 private final String userAgent; 200 201 @With 202 private final boolean allowLoadingDuplicates; 203 204 public SimpleWorkerContextBuilder() { 205 cacheTerminologyClientErrors = false; 206 alwaysUseTerminologyServer = false; 207 readOnlyCache = false; 208 terminologyCachePath = null; 209 locale = null; 210 userAgent = null; 211 allowLoadingDuplicates = false; 212 } 213 214 private SimpleWorkerContext getSimpleWorkerContextInstance() throws IOException { 215 if (locale != null) { 216 return new SimpleWorkerContext(locale); 217 } else { 218 return new SimpleWorkerContext(); 219 } 220 } 221 222 public SimpleWorkerContext build() throws IOException { 223 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 224 return build(context); 225 } 226 227 private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException { 228 context.initTS(terminologyCachePath); 229 context.setUserAgent(userAgent); 230 return context; 231 } 232 233 public SimpleWorkerContext fromPackage(NpmPackage pi) throws IOException, FHIRException { 234 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 235 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 236 context.loadFromPackage(pi, null); 237 return build(context); 238 } 239 240 public SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException { 241 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 242 context.setAllowLoadingDuplicates(true); 243 context.version = pi.getNpm().get("version").getAsString(); 244 context.loadFromPackage(pi, loader); 245 context.finishLoading(); 246 return build(context); 247 } 248 249 /** 250 * Load the working context from the validation pack 251 * 252 * @param path 253 * filename of the validation pack 254 * @return 255 * @throws IOException 256 * @throws FileNotFoundException 257 * @throws FHIRException 258 * @throws Exception 259 */ 260 public SimpleWorkerContext fromPack(String path) throws IOException, FHIRException { 261 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 262 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 263 context.loadFromPack(path, null); 264 return build(context); 265 } 266 267 public SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException { 268 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 269 context.loadFromPack(path, loader); 270 return build(context); 271 } 272 273 public SimpleWorkerContext fromClassPath() throws IOException, FHIRException { 274 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 275 context.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null); 276 return build(context); 277 } 278 279 public SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException { 280 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 281 InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name); 282 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 283 context.loadFromStream(s, null); 284 return build(context); 285 } 286 287 public SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader, PackageVersion pi) throws IOException, FHIRException { 288 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 289 for (String name : source.keySet()) { 290 try { 291 context.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), loader, null, pi); 292 } catch (Exception e) { 293 System.out.println("Error loading "+name+": "+e.getMessage()); 294 throw new FHIRException("Error loading "+name+": "+e.getMessage(), e); 295 } 296 } 297 return build(context); 298 } 299 public SimpleWorkerContext fromNothing() throws FHIRException, IOException { 300 return build(); 301 } 302 } 303 304 private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException { 305 if (name.endsWith(".xml")) 306 loadFromFile(stream, name, loader, filter); 307 else if (name.endsWith(".json")) 308 loadFromFileJson(stream, name, loader, filter, pi); 309 else if (name.equals("version.info")) 310 readVersionInfo(stream); 311 else 312 loadBytes(name, stream); 313 } 314 315 public String connectToTSServer(TerminologyClient client, String log) { 316 try { 317 tlog("Connect to "+client.getAddress()); 318 txClient = client; 319 if (log != null && log.endsWith(".txt")) { 320 txLog = new TextClientLogger(log); 321 } else { 322 txLog = new HTMLClientLogger(log); 323 } 324 txClient.setLogger(txLog); 325 txClient.setUserAgent(userAgent); 326 327 final CapabilityStatement capabilitiesStatementQuick = txCache.hasCapabilityStatement() ? txCache.getCapabilityStatement() : txClient.getCapabilitiesStatementQuick(); 328 txCache.cacheCapabilityStatement(capabilitiesStatementQuick); 329 330 final TerminologyCapabilities capabilityStatement = txCache.hasTerminologyCapabilities() ? txCache.getTerminologyCapabilities() : txClient.getTerminologyCapabilities(); 331 txCache.cacheTerminologyCapabilities(capabilityStatement); 332 333 setTxCaps(capabilityStatement); 334 return capabilitiesStatementQuick.getSoftware().getVersion(); 335 } catch (Exception e) { 336 throw new FHIRException(formatMessage(canNoTS ? I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ : I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER, e.getMessage()), e); 337 } 338 } 339 340 public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws FHIRException { 341 loadFromFile(stream, name, loader, null); 342 } 343 344 public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws FHIRException { 345 Resource f; 346 try { 347 if (loader != null) 348 f = loader.loadBundle(stream, false); 349 else { 350 XmlParser xml = new XmlParser(); 351 f = xml.parse(stream); 352 } 353 } catch (DataFormatException e1) { 354 throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1); 355 } catch (Exception e1) { 356 throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1); 357 } 358 if (f instanceof Bundle) { 359 Bundle bnd = (Bundle) f; 360 for (BundleEntryComponent e : bnd.getEntry()) { 361 if (e.getFullUrl() == null) { 362 logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)"); 363 } 364 if (filter == null || filter.isOkToLoad(e.getResource())) { 365 String path = loader != null ? loader.getResourcePath(e.getResource()) : null; 366 if (path != null) { 367 e.getResource().setUserData("path", path); 368 } 369 cacheResource(e.getResource()); 370 } 371 } 372 } else if (f instanceof CanonicalResource) { 373 if (filter == null || filter.isOkToLoad(f)) { 374 String path = loader != null ? loader.getResourcePath(f) : null; 375 if (path != null) { 376 f.setUserData("path", path); 377 } 378 cacheResource(f); 379 } 380 } 381 } 382 383 private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException { 384 Bundle f = null; 385 try { 386 if (loader != null) 387 f = loader.loadBundle(stream, true); 388 else { 389 JsonParser json = new JsonParser(); 390 Resource r = json.parse(stream); 391 if (r instanceof Bundle) 392 f = (Bundle) r; 393 else if (filter == null || filter.isOkToLoad(f)) { 394 cacheResourceFromPackage(r, pi); 395 } 396 } 397 } catch (FHIRFormatError e1) { 398 throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1); 399 } 400 if (f != null) 401 for (BundleEntryComponent e : f.getEntry()) { 402 if (filter == null || filter.isOkToLoad(e.getResource())) { 403 String path = loader != null ? loader.getResourcePath(e.getResource()) : null; 404 if (path != null) { 405 e.getResource().setUserData("path", path); 406 } 407 cacheResourceFromPackage(e.getResource(), pi); 408 } 409 } 410 } 411 412 private void loadFromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException { 413 loadFromStream(new CSFileInputStream(path), loader); 414 } 415 416 417 @Override 418 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException { 419 return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes()); 420 } 421 422 public static String[] defaultTypesToLoad() { 423 // there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions 424 return new String[] {"CodeSystem", "ValueSet", "ConceptMap", "NamingSystem", 425 "StructureDefinition", "StructureMap", 426 "SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance", 427 "Questionnaire", "ImplementationGuide", "Measure" }; 428 } 429 430 @Override 431 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws IOException, FHIRException { 432 return loadFromPackageInt(pi, loader, types); 433 } 434 435 @Override 436 public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws IOException, FHIRException { 437 return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version()); 438 } 439 public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws IOException, FHIRException { 440 int t = 0; 441 442 for (String e : pi.dependencies()) { 443 if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) { 444 NpmPackage npm = pcm.loadPackage(e); 445 if (!VersionUtilities.versionsMatch(version, npm.fhirVersion())) { 446 System.out.println(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path)); 447 } 448 t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version()); 449 } 450 } 451 t = t + loadFromPackageInt(pi, loader, loader.getTypes()); 452 return t; 453 } 454 455 456 public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, String... types) throws IOException, FHIRException { 457 int t = 0; 458 if (progress) { 459 System.out.println("Load Package "+pi.name()+"#"+pi.version()); 460 } 461 if (loadedPackages.contains(pi.id()+"#"+pi.version())) { 462 return 0; 463 } 464 loadedPackages.add(pi.id()+"#"+pi.version()); 465 466 467 if ((types == null || types.length == 0) && loader != null) { 468 types = loader.getTypes(); 469 } 470 if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad()) { 471 // can't lazy load R2 because of valueset/codesystem implementation 472 if (types.length == 0) { 473 types = new String[] { "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" }; 474 } 475 for (String s : pi.listResources(types)) { 476 try { 477 loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version())); 478 t++; 479 } catch (Exception e) { 480 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e); 481 } 482 } 483 } else { 484 if (types.length == 0) { 485 types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measures" }; 486 } 487 for (PackageResourceInformation pri : pi.listIndexedResources(types)) { 488 try { 489 registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version())); 490 t++; 491 } catch (FHIRException e) { 492 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e); 493 } 494 } 495 } 496 for (String s : pi.list("other")) { 497 binaries.put(s, TextFile.streamToBytes(pi.load("other", s))); 498 } 499 if (version == null) { 500 version = pi.version(); 501 } 502 return t; 503 } 504 505 public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException { 506 loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null); 507 } 508 509 private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException { 510 ZipInputStream zip = new ZipInputStream(stream); 511 ZipEntry ze; 512 while ((ze = zip.getNextEntry()) != null) { 513 loadDefinitionItem(ze.getName(), zip, loader, null, null); 514 zip.closeEntry(); 515 } 516 zip.close(); 517 } 518 519 private void readVersionInfo(InputStream stream) throws IOException, DefinitionException { 520 byte[] bytes = IOUtils.toByteArray(stream); 521 binaries.put("version.info", bytes); 522 523 String[] vi = new String(bytes).split("\\r?\\n"); 524 for (String s : vi) { 525 if (s.startsWith("version=")) { 526 if (version == null) 527 version = s.substring(8); 528 else if (!version.equals(s.substring(8))) 529 throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8))); 530 } 531 if (s.startsWith("revision=")) 532 revision = s.substring(9); 533 if (s.startsWith("date=")) 534 date = s.substring(5); 535 } 536 } 537 538 private void loadBytes(String name, InputStream stream) throws IOException { 539 byte[] bytes = IOUtils.toByteArray(stream); 540 binaries.put(name, bytes); 541 } 542 543 @Override 544 public IParser getParser(ParserType type) { 545 switch (type) { 546 case JSON: return newJsonParser(); 547 case XML: return newXmlParser(); 548 default: 549 throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString())); 550 } 551 } 552 553 @Override 554 public IParser getParser(String type) { 555 if (type.equalsIgnoreCase("JSON")) 556 return new JsonParser(); 557 if (type.equalsIgnoreCase("XML")) 558 return new XmlParser(); 559 throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString())); 560 } 561 562 @Override 563 public IParser newJsonParser() { 564 return new JsonParser(); 565 } 566 @Override 567 public IParser newXmlParser() { 568 return new XmlParser(); 569 } 570 571 @Override 572 public IResourceValidator newValidator() throws FHIRException { 573 if (validatorFactory == null) 574 throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED)); 575 return validatorFactory.makeValidator(this, xverManager); 576 } 577 578 579 580 581 @Override 582 public List<String> getResourceNames() { 583 List<String> result = new ArrayList<String>(); 584 for (StructureDefinition sd : listStructures()) { 585 if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 586 result.add(sd.getName()); 587 } 588 Collections.sort(result); 589 return result; 590 } 591 592 @Override 593 public List<String> getTypeNames() { 594 List<String> result = new ArrayList<String>(); 595 for (StructureDefinition sd : listStructures()) { 596 if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 597 result.add(sd.getName()); 598 } 599 Collections.sort(result); 600 return result; 601 } 602 603 @Override 604 public String getAbbreviation(String name) { 605 return "xxx"; 606 } 607 608 @Override 609 public boolean isDatatype(String typeSimple) { 610 // TODO Auto-generated method stub 611 return false; 612 } 613 614 @Override 615 public boolean isResource(String t) { 616 StructureDefinition sd; 617 try { 618 sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t); 619 } catch (Exception e) { 620 return false; 621 } 622 if (sd == null) 623 return false; 624 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) 625 return false; 626 return sd.getKind() == StructureDefinitionKind.RESOURCE; 627 } 628 629 @Override 630 public boolean hasLinkFor(String typeSimple) { 631 return false; 632 } 633 634 @Override 635 public String getLinkFor(String corePath, String typeSimple) { 636 return null; 637 } 638 639 @Override 640 public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) { 641 return null; 642 } 643 644 @Override 645 public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) { 646 return null; 647 } 648 649 @Override 650 public String getLinkForProfile(StructureDefinition profile, String url) { 651 return null; 652 } 653 654 public Questionnaire getQuestionnaire() { 655 return questionnaire; 656 } 657 658 public void setQuestionnaire(Questionnaire questionnaire) { 659 this.questionnaire = questionnaire; 660 } 661 662 @Override 663 public List<StructureDefinition> allStructures() { 664 List<StructureDefinition> result = new ArrayList<StructureDefinition>(); 665 Set<StructureDefinition> set = new HashSet<StructureDefinition>(); 666 for (StructureDefinition sd : listStructures()) { 667 if (!set.contains(sd)) { 668 try { 669 generateSnapshot(sd); 670 // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd); 671 } catch (Exception e) { 672 System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage()); 673 if (true) { 674 e.printStackTrace(); 675 } 676 } 677 result.add(sd); 678 set.add(sd); 679 } 680 } 681 return result; 682 } 683 684 685 public void loadBinariesFromFolder(String folder) throws IOException { 686 for (String n : new File(folder).list()) { 687 loadBytes(n, new FileInputStream(Utilities.path(folder, n))); 688 } 689 } 690 691 public void loadBinariesFromFolder(NpmPackage pi) throws IOException { 692 for (String n : pi.list("other")) { 693 loadBytes(n, pi.load("other", n)); 694 } 695 } 696 697 public void loadFromFolder(String folder) throws IOException { 698 for (String n : new File(folder).list()) { 699 if (n.endsWith(".json")) 700 loadFromFile(Utilities.path(folder, n), new JsonParser()); 701 else if (n.endsWith(".xml")) 702 loadFromFile(Utilities.path(folder, n), new XmlParser()); 703 } 704 } 705 706 private void loadFromFile(String filename, IParser p) { 707 Resource r; 708 try { 709 r = p.parse(new FileInputStream(filename)); 710 if (r.getResourceType() == ResourceType.Bundle) { 711 for (BundleEntryComponent e : ((Bundle) r).getEntry()) { 712 cacheResource(e.getResource()); 713 } 714 } else { 715 cacheResource(r); 716 } 717 } catch (Exception e) { 718 return; 719 } 720 } 721 722 @Override 723 public boolean prependLinks() { 724 return false; 725 } 726 727 @Override 728 public boolean hasCache() { 729 return true; 730 } 731 732 @Override 733 public String getVersion() { 734 return version; 735 } 736 737 738 public List<StructureMap> findTransformsforSource(String url) { 739 List<StructureMap> res = new ArrayList<StructureMap>(); 740 for (StructureMap map : listTransforms()) { 741 boolean match = false; 742 boolean ok = true; 743 for (StructureMapStructureComponent t : map.getStructure()) { 744 if (t.getMode() == StructureMapModelMode.SOURCE) { 745 match = match || t.getUrl().equals(url); 746 ok = ok && t.getUrl().equals(url); 747 } 748 } 749 if (match && ok) 750 res.add(map); 751 } 752 return res; 753 } 754 755 public IValidatorFactory getValidatorFactory() { 756 return validatorFactory; 757 } 758 759 public void setValidatorFactory(IValidatorFactory validatorFactory) { 760 this.validatorFactory = validatorFactory; 761 } 762 763 @Override 764 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 765 T r = super.fetchResource(class_, uri); 766 if (r instanceof StructureDefinition) { 767 StructureDefinition p = (StructureDefinition)r; 768 try { 769 generateSnapshot(p); 770 } catch (Exception e) { 771 // not sure what to do in this case? 772 System.out.println("Unable to generate snapshot for "+uri+": "+e.getMessage()); 773 } 774 } 775 return r; 776 } 777 778 @Override 779 public StructureDefinition fetchRawProfile(String uri) { 780 StructureDefinition r = super.fetchResource(StructureDefinition.class, uri); 781 return r; 782 } 783 784 @Override 785 public void generateSnapshot(StructureDefinition p) throws FHIRException { 786 generateSnapshot(p, false); 787 } 788 789 @Override 790 public void generateSnapshot(StructureDefinition p, boolean logical) throws FHIRException { 791 if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p) ) && (logical || p.getKind() != StructureDefinitionKind.LOGICAL)) { 792 if (!p.hasBaseDefinition()) 793 throw new DefinitionException(formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl())); 794 StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition()); 795 if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) { 796 sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion()); 797 } 798 if (sd == null) { 799 throw new DefinitionException(formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition())); 800 } 801 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 802 List<String> errors = new ArrayList<String>(); 803 ProfileUtilities pu = new ProfileUtilities(this, msgs, this); 804 pu.setAutoFixSliceNames(true); 805 pu.setThrowException(false); 806 if (xverManager == null) { 807 xverManager = new XVerExtensionManager(this); 808 } 809 pu.setXver(xverManager); 810 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 811 pu.sortDifferential(sd, p, p.getUrl(), errors, true); 812 } 813 pu.setDebug(false); 814 for (String err : errors) 815 msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); 816 pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName()); 817 for (ValidationMessage msg : msgs) { 818 if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) 819 throw new DefinitionException(formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage())); 820 } 821 if (!p.hasSnapshot()) 822 throw new FHIRException(formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl())); 823 pu = null; 824 } 825 } 826 827 // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind. 828 private boolean isProfileNeedsRegenerate(StructureDefinition p) { 829 boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse"); 830 if (needs) { 831 p.setUserData("hack.regnerated", "yes"); 832 } 833 return needs; 834 } 835 836 public boolean isIgnoreProfileErrors() { 837 return ignoreProfileErrors; 838 } 839 840 public void setIgnoreProfileErrors(boolean ignoreProfileErrors) { 841 this.ignoreProfileErrors = ignoreProfileErrors; 842 } 843 844 public String listMapUrls() { 845 return Utilities.listCanonicalUrls(transforms.keys()); 846 } 847 848 public boolean isProgress() { 849 return progress; 850 } 851 852 public void setProgress(boolean progress) { 853 this.progress = progress; 854 } 855 856 public void setClock(TimeTracker tt) { 857 clock = tt; 858 } 859 860 public boolean isCanNoTS() { 861 return canNoTS; 862 } 863 864 public void setCanNoTS(boolean canNoTS) { 865 this.canNoTS = canNoTS; 866 } 867 868 public XVerExtensionManager getXVer() { 869 if (xverManager == null) { 870 xverManager = new XVerExtensionManager(this); 871 } 872 return xverManager; 873 } 874 875 public void cachePackage(PackageVersion packageDetails, List<PackageVersion> dependencies) { 876 // nothing yet 877 } 878 879 @Override 880 public boolean hasPackage(String id, String ver) { 881 return loadedPackages.contains(id+"#"+ver); 882 } 883 884 public boolean hasPackage(String idAndver) { 885 return loadedPackages.contains(idAndver); 886 } 887 888 @Override 889 public void cachePackage(PackageDetails packageDetails, List<PackageVersion> dependencies) { 890 // TODO Auto-generated method stub 891 } 892 893 @Override 894 public boolean hasPackage(PackageVersion pack) { 895 return false; 896 } 897 898 @Override 899 public PackageDetails getPackage(PackageVersion pack) { 900 return null; 901 } 902 903} 904