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