
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 032import java.io.ByteArrayInputStream; 033import java.io.FileInputStream; 034import java.io.FileNotFoundException; 035import java.io.IOException; 036import java.io.InputStream; 037import java.util.ArrayList; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Locale; 041import java.util.Map; 042import java.util.Set; 043import java.util.UUID; 044import java.util.zip.ZipEntry; 045import java.util.zip.ZipInputStream; 046 047import lombok.AccessLevel; 048import lombok.AllArgsConstructor; 049import lombok.With; 050import lombok.extern.slf4j.Slf4j; 051import org.apache.commons.io.IOUtils; 052import org.apache.commons.lang3.exception.ExceptionUtils; 053import org.hl7.fhir.exceptions.DefinitionException; 054import org.hl7.fhir.exceptions.FHIRException; 055import org.hl7.fhir.exceptions.FHIRFormatError; 056import org.hl7.fhir.exceptions.TerminologyServiceException; 057import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy; 058import org.hl7.fhir.r5.context.ILoggingService.LogCategory; 059import org.hl7.fhir.r5.formats.IParser; 060import org.hl7.fhir.r5.formats.JsonParser; 061import org.hl7.fhir.r5.formats.XmlParser; 062import org.hl7.fhir.r5.model.Bundle; 063import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; 064import org.hl7.fhir.r5.model.CanonicalResource; 065import org.hl7.fhir.r5.model.ImplementationGuide; 066import org.hl7.fhir.r5.model.PackageInformation; 067import org.hl7.fhir.r5.model.Parameters; 068import org.hl7.fhir.r5.model.Questionnaire; 069import org.hl7.fhir.r5.model.Resource; 070import org.hl7.fhir.r5.model.ResourceType; 071import org.hl7.fhir.r5.model.StructureDefinition; 072import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 073import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 074import org.hl7.fhir.r5.model.StructureMap; 075import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode; 076import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent; 077import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; 078import org.hl7.fhir.r5.terminologies.client.ITerminologyClient; 079import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ITerminologyClientFactory; 080import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5; 081import org.hl7.fhir.r5.utils.R5Hacker; 082import org.hl7.fhir.r5.utils.UserDataNames; 083import org.hl7.fhir.r5.utils.xver.XVerExtensionManager; 084import org.hl7.fhir.r5.utils.validation.IResourceValidator; 085import org.hl7.fhir.r5.utils.validation.ValidatorSession; 086import org.hl7.fhir.r5.utils.xver.XVerExtensionManagerFactory; 087import org.hl7.fhir.utilities.ByteProvider; 088import org.hl7.fhir.utilities.FileUtilities; 089import org.hl7.fhir.utilities.MagicResources; 090import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 091import org.hl7.fhir.utilities.TimeTracker; 092import org.hl7.fhir.utilities.Utilities; 093import org.hl7.fhir.utilities.VersionUtilities; 094import org.hl7.fhir.utilities.filesystem.CSFileInputStream; 095import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 096import org.hl7.fhir.utilities.http.ManagedWebAccess; 097import org.hl7.fhir.utilities.i18n.I18nConstants; 098import org.hl7.fhir.utilities.npm.BasePackageCacheManager; 099import org.hl7.fhir.utilities.npm.NpmPackage; 100import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation; 101 102import ca.uhn.fhir.parser.DataFormatException; 103 104/* 105 * This is a stand alone implementation of worker context for use inside a tool. 106 * It loads from the validation package (validation-min.xml.zip), and has a 107 * very light client to connect to an open unauthenticated terminology service 108 */ 109 110@Slf4j 111@MarkedToMoveToAdjunctPackage 112public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext { 113 114 public class InternalCanonicalResourceProxy extends CanonicalResourceProxy { 115 116 public InternalCanonicalResourceProxy(String type, String id, String url, String version) { 117 super(type, id, url, version, null, null, null); 118 } 119 120 @Override 121 public CanonicalResource loadResource() throws FHIRException { 122 throw new Error("not done yet"); 123 } 124 125 } 126 127 public static class PackageResourceLoader extends CanonicalResourceProxy { 128 129 private final String filename; 130 private final IContextResourceLoader loader; 131 private final PackageInformation packageInformation; 132 133 public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader, PackageInformation pi) { 134 super(pri.getResourceType(), pri.getId(), loader == null ? pri.getUrl() :loader.patchUrl(pri.getUrl(), pri.getResourceType()), pri.getVersion(), pri.getSupplements(), pri.getDerivation(), pri.getContent()); 135 this.filename = pri.getFilename(); 136 this.loader = loader; 137 this.packageInformation = pi; 138 } 139 140 @Override 141 public CanonicalResource loadResource() { 142 try { 143 FileInputStream f = ManagedFileAccess.inStream(filename); 144 try { 145 if (loader != null) { 146 return setPi(R5Hacker.fixR5BrokenResource((CanonicalResource) loader.loadResource(f, true))); 147 } else { 148 return setPi(R5Hacker.fixR5BrokenResource((CanonicalResource) new JsonParser().parse(f))); 149 } 150 } finally { 151 f.close(); 152 } 153 } catch (Exception e) { 154 throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e); 155 } 156 } 157 158 private CanonicalResource setPi(CanonicalResource cr) { 159 cr.setSourcePackage(packageInformation); 160 return cr; 161 } 162 163 /** 164 * This is not intended for use outside the package loaders 165 * 166 * @return 167 * @throws IOException 168 */ 169 public InputStream getStream() throws IOException { 170 return ManagedFileAccess.inStream(filename); 171 } 172 173 } 174 175 public interface ILoadFilter { 176 boolean isOkToLoad(Resource resource); 177 boolean isOkToLoad(String resourceType); 178 } 179 180 public interface IValidatorFactory { 181 IResourceValidator makeValidator(IWorkerContext ctxt, ValidatorSession session) throws FHIRException; 182 IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager, ValidatorSession session) throws FHIRException; 183 } 184 185 private Questionnaire questionnaire; 186 private String revision; 187 private String date; 188 private IValidatorFactory validatorFactory; 189 private boolean progress; 190 private final List<String> loadedPackages = new ArrayList<>(); 191 private boolean canNoTS; 192 private XVerExtensionManager xverManager; 193 private boolean allowLazyLoading = true; 194 195 private SimpleWorkerContext() throws IOException, FHIRException { 196 super(); 197 } 198 199 private SimpleWorkerContext(Locale locale) throws IOException, FHIRException { 200 super(locale); 201 } 202 203 public SimpleWorkerContext(SimpleWorkerContext other) throws IOException, FHIRException { 204 super(); 205 copy(other); 206 } 207 208 private SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws IOException, FHIRException { 209 super(locale); 210 copy(other); 211 } 212 213 protected void copy(SimpleWorkerContext other) { 214 super.copy(other); 215 binaries.putAll(other.binaries); 216 version = other.version; 217 revision = other.revision; 218 date = other.date; 219 validatorFactory = other.validatorFactory; 220 progress = other.progress; 221 loadedPackages.addAll(other.loadedPackages); 222 canNoTS = other.canNoTS; 223 xverManager = other.xverManager; 224 allowLazyLoading = other.allowLazyLoading; 225 questionnaire = other.questionnaire; 226 } 227 228 229 public List<String> getLoadedPackages() { 230 return loadedPackages; 231 } 232 233 // -- Initializations 234 @AllArgsConstructor(access = AccessLevel.PRIVATE) 235 public static class SimpleWorkerContextBuilder { 236 237 238 @With 239 private final String terminologyCachePath; 240 @With 241 private final boolean cacheTerminologyClientErrors; 242 @With 243 private final boolean alwaysUseTerminologyServer; 244 @With 245 private final boolean readOnlyCache; 246 247 @With 248 private final Locale locale; 249 250 @With 251 private final String userAgent; 252 253 @With 254 private final boolean allowLoadingDuplicates; 255 256 @With 257 private final org.hl7.fhir.r5.context.ILoggingService loggingService; 258 private boolean defaultExpParams; 259 260 public SimpleWorkerContextBuilder() { 261 cacheTerminologyClientErrors = false; 262 alwaysUseTerminologyServer = false; 263 readOnlyCache = false; 264 terminologyCachePath = null; 265 locale = null; 266 userAgent = null; 267 allowLoadingDuplicates = false; 268 loggingService = new Slf4JLoggingService(log); 269 } 270 271 private SimpleWorkerContext getSimpleWorkerContextInstance() throws IOException { 272 if (locale != null) { 273 return new SimpleWorkerContext(locale); 274 } else { 275 return new SimpleWorkerContext(); 276 } 277 } 278 279 public SimpleWorkerContext build() throws IOException { 280 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 281 return build(context); 282 } 283 284 private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException { 285 if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion())) { 286 log.warn("As of end 2024, FHIR R2 (version "+context.getVersion()+") is no longer officially supported."); 287 } 288 context.initTxCache(terminologyCachePath); 289 context.setUserAgent(userAgent); 290 context.setLogger(loggingService); 291 context.cacheResource(new org.hl7.fhir.r5.formats.JsonParser().parse(MagicResources.spdxCodesAsData())); 292 if (defaultExpParams) { 293 context.setExpansionParameters(makeExpProfile()); 294 } 295 return context; 296 } 297 298 public SimpleWorkerContext fromPackage(NpmPackage pi) throws IOException, FHIRException { 299 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 300 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 301 context.terminologyClientManager.setFactory(TerminologyClientR5.factory()); 302 context.loadFromPackage(pi, null); 303 return build(context); 304 } 305 306 private Parameters makeExpProfile() { 307 Parameters ep = new Parameters(); 308 ep.addParameter("cache-id", UUID.randomUUID().toString().toLowerCase()); 309 return ep; 310 } 311 312 public SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader, boolean genSnapshots) throws IOException, FHIRException { 313 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 314 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 315 context.version = pi.fhirVersion(); 316 context.terminologyClientManager.setFactory(loader.txFactory()); 317 context.loadFromPackage(pi, loader); 318 context.finishLoading(genSnapshots); 319 if (defaultExpParams) { 320 context.setExpansionParameters(makeExpProfile()); 321 } 322 return build(context); 323 } 324 325 /** 326 * Load the working context from the validation pack 327 * 328 * @param path 329 * filename of the validation pack 330 * @return 331 * @throws IOException 332 * @throws FileNotFoundException 333 * @throws FHIRException 334 * @throws Exception 335 */ 336 public SimpleWorkerContext fromPack(String path) throws IOException, FHIRException { 337 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 338 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 339 context.loadFromPack(path, null); 340 return build(context); 341 } 342 343 public SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException { 344 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 345 context.loadFromPack(path, loader); 346 return build(context); 347 } 348 349 public SimpleWorkerContext fromClassPath() throws IOException, FHIRException { 350 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 351 context.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null); 352 return build(context); 353 } 354 355 public SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException { 356 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 357 InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name); 358 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 359 context.loadFromStream(s, null); 360 return build(context); 361 } 362 363 public SimpleWorkerContext fromDefinitions(Map<String, ByteProvider> source, IContextResourceLoader loader, PackageInformation pi) throws IOException, FHIRException { 364 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 365 for (String name : source.keySet()) { 366 try { 367 context.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name).getBytes()), loader, null, pi); 368 } catch (Exception e) { 369 log.error("Error loading "+name+": "+e.getMessage()); 370 throw new FHIRException("Error loading "+name+": "+e.getMessage(), e); 371 } 372 } 373 return build(context); 374 } 375 public SimpleWorkerContext fromNothing() throws FHIRException, IOException { 376 return build(); 377 } 378 379 public SimpleWorkerContextBuilder withDefaultParams() { 380 defaultExpParams = true; 381 return this; 382 } 383 } 384 385 private Resource loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageInformation pi) throws IOException, FHIRException { 386 if (name.endsWith(".xml")) 387 return loadFromFile(stream, name, loader, filter); 388 else if (name.endsWith(".json")) 389 return loadFromFileJson(stream, name, loader, filter, pi); 390 else if (name.equals("version.info")) 391 readVersionInfo(stream); 392 else 393 binaries.put(name, new BytesProvider(FileUtilities.streamToBytesNoClose(stream))); 394 return null; 395 } 396 397 public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client, boolean useEcosystem) { 398 terminologyClientManager.setFactory(factory); 399 if (txLog == null) { 400 txLog = client.getLogger(); 401 } 402 try { 403 terminologyClientManager.setMasterClient(client, useEcosystem); 404 txLog("Connect to "+client.getAddress()); 405 } catch (Exception e) { 406 if (canRunWithoutTerminology) { 407 noTerminologyServer = true; 408 logger.logMessage("==============!! Running without terminology server !! =============="); 409 if (terminologyClientManager.getMasterClient() != null) { 410 logger.logMessage("txServer = "+ terminologyClientManager.getMasterClient().getId()); 411 logger.logMessage("Error = "+e.getMessage()+""); 412 } 413 logger.logMessage("====================================================================="); 414 } else { 415 e.printStackTrace(); 416 throw new TerminologyServiceException(e); 417 } 418 } 419 } 420 421 public void connectToTSServer(ITerminologyClientFactory factory, String address, String software, String log, boolean useEcosystem) { 422 try { 423 terminologyClientManager.setFactory(factory); 424 if (log != null) { 425 if (log.endsWith(".htm") || log.endsWith(".html")) { 426 txLog = new HTMLClientLogger(log); 427 } else if (log.endsWith(".txt") || log.endsWith(".log")) { 428 txLog = new TextClientLogger(log); 429 } else { 430 throw new IllegalArgumentException("Unknown extension for text file logging: \"" + log + "\" expected: .html, .htm, .txt or .log"); 431 } 432 } 433 ITerminologyClient client = factory.makeClient("tx-server", ManagedWebAccess.makeSecureRef(address), software, txLog); 434 // txFactory.makeClient("Tx-Server", txServer, "fhir/publisher", null) 435// terminologyClientManager.setLogger(txLog); 436// terminologyClientManager.setUserAgent(userAgent); 437 connectToTSServer(factory, client, useEcosystem); 438 439 } catch (Exception e) { 440 e.printStackTrace(); 441 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(), address), e); 442 } 443 } 444 445 public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws FHIRException { 446 loadFromFile(stream, name, loader, null); 447 } 448 449 public Resource loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws FHIRException { 450 Resource f; 451 try { 452 if (loader != null) 453 f = loader.loadBundle(stream, false); 454 else { 455 XmlParser xml = new XmlParser(); 456 f = xml.parse(stream); 457 } 458 } catch (DataFormatException e1) { 459 throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1); 460 } catch (Exception e1) { 461 throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1); 462 } 463 if (f instanceof Bundle) { 464 Bundle bnd = (Bundle) f; 465 for (BundleEntryComponent e : bnd.getEntry()) { 466 if (e.getFullUrl() == null) { 467 logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)"); 468 } 469 if (filter == null || filter.isOkToLoad(e.getResource())) { 470 String path = loader != null ? loader.getResourcePath(e.getResource()) : null; 471 if (path != null) { 472 e.getResource().setWebPath(path); 473 } 474 cacheResource(e.getResource()); 475 } 476 } 477 } else if (f instanceof CanonicalResource) { 478 if (filter == null || filter.isOkToLoad(f)) { 479 String path = loader != null ? loader.getResourcePath(f) : null; 480 if (path != null) { 481 f.setWebPath(path); 482 } 483 cacheResource(f); 484 } 485 } 486 return f; 487 } 488 489 private Resource loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageInformation pi) throws IOException, FHIRException { 490 Bundle f = null; 491 try { 492 if (loader != null) 493 f = loader.loadBundle(stream, true); 494 else { 495 JsonParser json = new JsonParser(); 496 Resource r = json.parse(stream); 497 if (r instanceof Bundle) 498 f = (Bundle) r; 499 else if (filter == null || filter.isOkToLoad(f)) { 500 cacheResourceFromPackage(r, pi); 501 } 502 } 503 } catch (FHIRFormatError e1) { 504 throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1); 505 } 506 if (f != null) 507 for (BundleEntryComponent e : f.getEntry()) { 508 if (filter == null || filter.isOkToLoad(e.getResource())) { 509 String path = loader != null ? loader.getResourcePath(e.getResource()) : null; 510 if (path != null) { 511 e.getResource().setWebPath(path); 512 } 513 cacheResourceFromPackage(e.getResource(), pi); 514 } 515 } 516 return f; 517 } 518 519 private void loadFromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException { 520 loadFromStream(new CSFileInputStream(path), loader); 521 } 522 523 524 @Override 525 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException { 526 return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes()); 527 } 528 529 public static Set<String> defaultTypesToLoad() { 530 // there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions 531 return Utilities.stringSet("CodeSystem", "ValueSet", "ConceptMap", "NamingSystem", 532 "StructureDefinition", "StructureMap", 533 "SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance", 534 "Questionnaire", "ImplementationGuide", "Measure" ); 535 536 537 } 538 539 @Override 540 public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws IOException, FHIRException { 541 return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version()); 542 } 543 public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws IOException, FHIRException { 544 int t = 0; 545 546 for (String e : pi.dependencies()) { 547 if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) { 548 NpmPackage npm = pcm.loadPackage(e); 549 if (!VersionUtilities.versionMatches(version, npm.fhirVersion())) { 550 log.info(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path)); 551 } 552 t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version()); 553 } 554 } 555 t = t + loadFromPackageInt(pi, loader, loader.getTypes()); 556 return t; 557 } 558 559 560 public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, Set<String> types) throws IOException, FHIRException { 561 int t = 0; 562 if (progress) { 563 log.info("Load Package "+pi.name()+"#"+pi.version()); 564 } 565 if (loadedPackages.contains(pi.id()+"#"+pi.version())) { 566 return 0; 567 } 568 569 loadedPackages.add(pi.id()+"#"+pi.version()); 570 if (packageTracker != null) { 571 packageTracker.packageLoaded(pi.id(), pi.version()); 572 } 573 574 String of = pi.getFolders().get("package").getFolderPath(); 575 if (of != null) { 576 oidSources.add(new OIDSource(of, pi.vid())); 577 } 578 579 if ((types == null || types.size() == 0) && loader != null) { 580 types = loader.getTypes(); 581 } 582 boolean hasIG = false; 583 PackageInformation pii = new PackageInformation(pi); 584 if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad() || !allowLazyLoading) { 585 // can't lazy load R2 because of valueset/codesystem implementation 586 if (types == null || types.size() == 0) { 587 types = Utilities.stringSet("ImplementationGuide", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" ); 588 } 589 for (String s : pi.listResources(types)) { 590 try { 591 Resource r = loadDefinitionItem(s, pi.load("package", s), loader, null, pii); 592 if (r != null) { 593 hasIG = "ImplementationGuide".equals(r.fhirType()) || hasIG; 594 } 595 t++; 596 } catch (Exception e) { 597 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e); 598 } 599 } 600 } else { 601 if (types == null || types.size() == 0) { 602 types = Utilities.stringSet("ImplementationGuide", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measure" ); 603 } 604 types.add("ImplementationGuide"); 605 if (loader != null) { 606 types = loader.reviewActualTypes(types); 607 } 608 for (PackageResourceInformation pri : pi.listIndexedResources(types)) { 609 if (!pri.getFilename().contains("ig-r4") && (loader == null || loader.wantLoad(pi, pri))) { 610 try { 611 612 hasIG = "ImplementationGuide".equals(pri.getResourceType()) || hasIG; 613 if (!pri.hasId()) { 614 loadDefinitionItem(pri.getFilename(), ManagedFileAccess.inStream(pri.getFilename()), loader, null, pii); 615 } else { 616 PackageResourceLoader pl = new PackageResourceLoader(pri, loader, pii); 617 if (loader != null) { 618 pl = loader.editInfo(pl); 619 } 620 if (pl != null) { 621 registerResourceFromPackage(pl, pii); 622 } 623 } 624 t++; 625 } catch (FHIRException e) { 626 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e); 627 } 628 } 629 } 630 } 631 if (!hasIG && !pi.isCore()) { 632 try { 633 registerResourceFromPackage(makeIgResource(pi), pii); 634 } catch (Exception e) { 635 log.error("Problem constructing IG for "+pi.vid()+": "+e.getMessage()); 636 } 637 } 638 for (String s : pi.list("other")) { 639 binaries.put(s, new BytesFromPackageProvider(pi, s)); 640 } 641 if (version == null) { 642 version = pi.version(); 643 if (version.equals("current")) { 644 version = "5.0.0"; 645 } 646 } 647 if (loader != null && terminologyClientManager.getFactory() == null) { 648 terminologyClientManager.setFactory(loader.txFactory()); 649 } 650 return t; 651 } 652 653 private CanonicalResourceProxy makeIgResource(NpmPackage pi) { 654 ImplementationGuide ig = new ImplementationGuide(); 655 ig.setId(pi.name()); 656 ig.setVersion(pi.version()); 657 ig.setUrl(makeIgUrl(pi)); 658 ig.setUserData(UserDataNames.IG_FAKE, true); 659 660 var res = new InternalCanonicalResourceProxy(ig.fhirType(), ig.getId(), ig.getUrl(), ig.getVersion()); 661 res.setResource(ig); 662 return res; 663 } 664 665 private String makeIgUrl(NpmPackage pi) { 666 switch (pi.name()) { 667 case "hl7.fhir.pubpack": return "http://hl7.org/fhir/pubpack/ImplementationGuide/hl7.fhir.pubpack"; 668 case "hl7.fhir.xver-extensions": return "http://hl7.org/fhir/xver-extensions/ImplementationGuide/hl7.fhir.xver-extensions"; 669 case "us.nlm.vsac": return "http://fhir.org/packages/us.nlm.vsac/ImplementationGuide/us.nlm.vsac"; 670 case "us.cdc.phinvads": return "https://phinvads.cdc.gov/vads/fhir/ImplementationGuide/us.cdc.phinvads"; 671 case "hl7.fhir.us.core.v610": return "http://hl7.org/fhir/us/core/v610/ImplementationGuide/hl7.fhir.us.core.v610"; 672 case "hl7.fhir.us.core.v311": return "http://hl7.org/fhir/us/core/v311/ImplementationGuide/hl7.fhir.us.core.v311"; 673 case "fhir.dicom": return "http://fhir.org/packages/fhir.dicom/ImplementationGuide/fhir.dicom"; 674 default: 675 if (pi.name() != null && pi.canonical() != null) { 676 return Utilities.pathURL(pi.canonical(), "ImplementationGuide", pi.name()); 677 } else { 678 throw new FHIRException("No IG canonical can be determined for package: "+pi.name()+"#"+pi.version()); 679 } 680 } 681 } 682 683 public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException { 684 loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null); 685 } 686 687 private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException { 688 ZipInputStream zip = new ZipInputStream(stream); 689 ZipEntry zipEntry; 690 while ((zipEntry = zip.getNextEntry()) != null) { 691 String entryName = zipEntry.getName(); 692 if (entryName.contains("..")) { 693 throw new RuntimeException("Entry with an illegal path: " + entryName); 694 } 695 loadDefinitionItem(entryName, zip, loader, null, null); 696 zip.closeEntry(); 697 } 698 zip.close(); 699 } 700 701 private void readVersionInfo(InputStream stream) throws IOException, DefinitionException { 702 byte[] bytes = IOUtils.toByteArray(stream); 703 binaries.put("version.info", new BytesProvider(bytes)); 704 705 String[] vi = new String(bytes).split("\\r?\\n"); 706 for (String s : vi) { 707 if (s.startsWith("version=")) { 708 if (version == null) 709 version = s.substring(8); 710 else if (!version.equals(s.substring(8))) { 711 throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8))); 712 } 713 } 714 if (s.startsWith("revision=")) 715 revision = s.substring(9); 716 if (s.startsWith("date=")) 717 date = s.substring(5); 718 } 719 } 720 721 @Override 722 public IResourceValidator newValidator() throws FHIRException { 723 if (validatorFactory == null) 724 throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED)); 725 return validatorFactory.makeValidator(this, xverManager, null).setJurisdiction(JurisdictionUtilities.getJurisdictionCodingFromLocale(Locale.getDefault().getCountry())); 726 } 727 728 @Override 729 public List<String> getResourceNames() { 730 Set<String> result = new HashSet<String>(); 731 for (StructureDefinition sd : listStructures()) { 732 if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.hasUserData(UserDataNames.loader_urls_patched)) 733 result.add(sd.getName()); 734 } 735 return Utilities.sorted(result); 736 } 737 738 739 public Questionnaire getQuestionnaire() { 740 return questionnaire; 741 } 742 743 public void setQuestionnaire(Questionnaire questionnaire) { 744 this.questionnaire = questionnaire; 745 } 746 747 748 749 public void loadBinariesFromFolder(String folder) throws IOException { 750 for (String n : ManagedFileAccess.file(folder).list()) { 751 binaries.put(n, new BytesFromFileProvider(Utilities.path(folder, n))); 752 } 753 } 754 755 public void loadBinariesFromFolder(NpmPackage pi) throws IOException { 756 for (String n : pi.list("other")) { 757 binaries.put(n, new BytesFromPackageProvider(pi, n)); 758 } 759 } 760 761 public void loadFromFolder(String folder) throws IOException { 762 for (String n : ManagedFileAccess.file(folder).list()) { 763 if (n.endsWith(".json")) 764 loadFromFile(Utilities.path(folder, n), new JsonParser()); 765 else if (n.endsWith(".xml")) 766 loadFromFile(Utilities.path(folder, n), new XmlParser()); 767 } 768 } 769 770 private void loadFromFile(String filename, IParser p) { 771 Resource r; 772 try { 773 r = p.parse(ManagedFileAccess.inStream(filename)); 774 if (r.getResourceType() == ResourceType.Bundle) { 775 for (BundleEntryComponent e : ((Bundle) r).getEntry()) { 776 cacheResource(e.getResource()); 777 } 778 } else { 779 cacheResource(r); 780 } 781 } catch (Exception e) { 782 return; 783 } 784 } 785 786 787 788 @Override 789 public String getVersion() { 790 return version; 791 } 792 793 794 public List<StructureMap> findTransformsforSource(String url) { 795 List<StructureMap> res = new ArrayList<StructureMap>(); 796 for (StructureMap map : fetchResourcesByType(StructureMap.class)) { 797 boolean match = false; 798 boolean ok = true; 799 for (StructureMapStructureComponent t : map.getStructure()) { 800 if (t.getMode() == StructureMapModelMode.SOURCE) { 801 match = match || t.getUrl().equals(url); 802 ok = ok && t.getUrl().equals(url); 803 } 804 } 805 if (match && ok) 806 res.add(map); 807 } 808 return res; 809 } 810 811 public IValidatorFactory getValidatorFactory() { 812 return validatorFactory; 813 } 814 815 public void setValidatorFactory(IValidatorFactory validatorFactory) { 816 this.validatorFactory = validatorFactory; 817 } 818 819 @Override 820 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 821 T r = super.fetchResource(class_, uri); 822 if (r instanceof StructureDefinition) { 823 StructureDefinition p = (StructureDefinition)r; 824 try { 825 cutils.generateSnapshot(p); 826 } catch (Exception e) { 827 // not sure what to do in this case? 828 log.error("Unable to generate snapshot @3 for "+uri+": "+e.getMessage()); 829 logger.logDebugMessage(org.hl7.fhir.r5.context.ILoggingService.LogCategory.GENERATE, ExceptionUtils.getStackTrace(e)); 830 } 831 } 832 return r; 833 } 834 835 @Override 836 public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) { 837 T r = super.fetchResource(class_, uri); 838 return r; 839 } 840 841 @Override 842 public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source) { 843 T resource = super.fetchResource(class_, uri, source); 844 if (resource instanceof StructureDefinition) { 845 StructureDefinition structureDefinition = (StructureDefinition)resource; 846 generateSnapshot(structureDefinition, "4"); 847 } 848 return resource; 849 } 850 851 852 853 854 public String listMapUrls() { 855 return Utilities.listCanonicalUrls(transforms.keys()); 856 } 857 858 public boolean isProgress() { 859 return progress; 860 } 861 862 public void setProgress(boolean progress) { 863 this.progress = progress; 864 } 865 866 public void setClock(TimeTracker tt) { 867 clock = tt; 868 } 869 870 public boolean isCanNoTS() { 871 return canNoTS; 872 } 873 874 public void setCanNoTS(boolean canNoTS) { 875 this.canNoTS = canNoTS; 876 } 877 878 public XVerExtensionManager getXVer() { 879 if (xverManager == null) { 880 xverManager = XVerExtensionManagerFactory.createExtensionManager(this); 881 } 882 return xverManager; 883 } 884 885 public void cachePackage(PackageInformation packageInfo) { 886 // nothing yet 887 } 888 889 @Override 890 public boolean hasPackage(String id, String ver) { 891 if (ver == null) { 892 for (String p : loadedPackages) { 893 if (p.startsWith(id+"#")) { 894 return true; 895 } 896 } 897 return false; 898 } else { 899 return loadedPackages.contains(id+"#"+ver); 900 } 901 } 902 903 public boolean hasPackage(String idAndver) { 904 if (loadedPackages.contains(idAndver)) { 905 return true; 906 } 907 // not clear whether the same logic should apply to other cross-version packages? 908 if (idAndver.startsWith("hl7.fhir.uv.extensions")) { 909 String v = idAndver.substring(idAndver.lastIndexOf("#")+1); 910 for (String s : loadedPackages) { 911 String v2 = s.substring(s.lastIndexOf("#")+1); 912 if (s.startsWith("hl7.fhir.uv.extensions.") && VersionUtilities.versionMatches(v, v2)) { 913 return true; 914 } 915 } 916 } 917 return false; 918 919 } 920 921 @Override 922 public boolean hasPackage(PackageInformation pack) { 923 return false; 924 } 925 926 @Override 927 public PackageInformation getPackage(String id, String ver) { 928 return null; 929 } 930 931 public boolean isAllowLazyLoading() { 932 return allowLazyLoading; 933 } 934 935 public void setAllowLazyLoading(boolean allowLazyLoading) { 936 this.allowLazyLoading = allowLazyLoading; 937 } 938 939 public String loadedPackageSummary() { 940 return loadedPackages.toString(); 941 } 942 943 @Override 944 public String getSpecUrl() { 945 return VersionUtilities.getSpecUrl(getVersion())+"/"; 946 } 947 948 949 // only for testing (and transient) 950 public void setXVerManager(XVerExtensionManager value) { 951 xverManager = value; 952 } 953 954} 955