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