
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 try { 409 terminologyClientManager.setMasterClient(client, useEcosystem); 410 txLog("Connect to "+client.getAddress()); 411 } catch (Exception e) { 412 if (canRunWithoutTerminology) { 413 noTerminologyServer = true; 414 logger.logMessage("==============!! Running without terminology server !! =============="); 415 if (terminologyClientManager.getMasterClient() != null) { 416 logger.logMessage("txServer = "+ terminologyClientManager.getMasterClient().getId()); 417 logger.logMessage("Error = "+e.getMessage()+""); 418 } 419 logger.logMessage("====================================================================="); 420 } else { 421 e.printStackTrace(); 422 throw new TerminologyServiceException(e); 423 } 424 } 425 } 426 427 public void connectToTSServer(ITerminologyClientFactory factory, String address, String software, String log, boolean useEcosystem) { 428 try { 429 terminologyClientManager.setFactory(factory); 430 if (log != null) { 431 if (log.endsWith(".htm") || log.endsWith(".html")) { 432 txLog = new HTMLClientLogger(log); 433 } else if (log.endsWith(".txt") || log.endsWith(".log")) { 434 txLog = new TextClientLogger(log); 435 } else { 436 throw new IllegalArgumentException("Unknown extension for text file logging: \"" + log + "\" expected: .html, .htm, .txt or .log"); 437 } 438 } 439 ITerminologyClient client = factory.makeClient("tx-server", ManagedWebAccess.makeSecureRef(address), software, txLog); 440 // txFactory.makeClient("Tx-Server", txServer, "fhir/publisher", null) 441// terminologyClientManager.setLogger(txLog); 442// terminologyClientManager.setUserAgent(userAgent); 443 connectToTSServer(factory, client, useEcosystem); 444 445 } catch (Exception e) { 446 e.printStackTrace(); 447 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); 448 } 449 } 450 451 public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws FHIRException { 452 loadFromFile(stream, name, loader, null); 453 } 454 455 public Resource loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws FHIRException { 456 Resource f; 457 try { 458 if (loader != null) 459 f = loader.loadBundle(stream, false); 460 else { 461 XmlParser xml = new XmlParser(); 462 f = xml.parse(stream); 463 } 464 } catch (DataFormatException e1) { 465 throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1); 466 } catch (Exception e1) { 467 throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1); 468 } 469 if (f instanceof Bundle) { 470 Bundle bnd = (Bundle) f; 471 for (BundleEntryComponent e : bnd.getEntry()) { 472 if (e.getFullUrl() == null) { 473 logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)"); 474 } 475 if (filter == null || filter.isOkToLoad(e.getResource())) { 476 String path = loader != null ? loader.getResourcePath(e.getResource()) : null; 477 if (path != null) { 478 e.getResource().setWebPath(path); 479 } 480 cacheResource(e.getResource()); 481 } 482 } 483 } else if (f instanceof CanonicalResource) { 484 if (filter == null || filter.isOkToLoad(f)) { 485 String path = loader != null ? loader.getResourcePath(f) : null; 486 if (path != null) { 487 f.setWebPath(path); 488 } 489 cacheResource(f); 490 } 491 } 492 return f; 493 } 494 495 private Resource loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageInformation pi) throws IOException, FHIRException { 496 Bundle f = null; 497 try { 498 if (loader != null) 499 f = loader.loadBundle(stream, true); 500 else { 501 JsonParser json = new JsonParser(); 502 Resource r = json.parse(stream); 503 if (r instanceof Bundle) 504 f = (Bundle) r; 505 else if (filter == null || filter.isOkToLoad(f)) { 506 cacheResourceFromPackage(r, pi); 507 } 508 } 509 } catch (FHIRFormatError e1) { 510 throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1); 511 } 512 if (f != null) 513 for (BundleEntryComponent e : f.getEntry()) { 514 if (filter == null || filter.isOkToLoad(e.getResource())) { 515 String path = loader != null ? loader.getResourcePath(e.getResource()) : null; 516 if (path != null) { 517 e.getResource().setWebPath(path); 518 } 519 cacheResourceFromPackage(e.getResource(), pi); 520 } 521 } 522 return f; 523 } 524 525 private void loadFromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException { 526 loadFromStream(new CSFileInputStream(path), loader); 527 } 528 529 530 @Override 531 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException { 532 return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes()); 533 } 534 535 public static Set<String> defaultTypesToLoad() { 536 // there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions 537 return Utilities.stringSet("CodeSystem", "ValueSet", "ConceptMap", "NamingSystem", 538 "StructureDefinition", "StructureMap", 539 "SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance", 540 "Questionnaire", "ImplementationGuide", "Measure" ); 541 542 543 } 544 545 @Override 546 public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws IOException, FHIRException { 547 return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version()); 548 } 549 public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws IOException, FHIRException { 550 int t = 0; 551 552 for (String e : pi.dependencies()) { 553 if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) { 554 NpmPackage npm = pcm.loadPackage(e); 555 if (!VersionUtilities.versionsMatch(version, npm.fhirVersion())) { 556 log.info(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path)); 557 } 558 t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version()); 559 } 560 } 561 t = t + loadFromPackageInt(pi, loader, loader.getTypes()); 562 return t; 563 } 564 565 566 public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, Set<String> types) throws IOException, FHIRException { 567 int t = 0; 568 if (progress) { 569 log.info("Load Package "+pi.name()+"#"+pi.version()); 570 } 571 if (loadedPackages.contains(pi.id()+"#"+pi.version())) { 572 return 0; 573 } 574 575 loadedPackages.add(pi.id()+"#"+pi.version()); 576 if (packageTracker != null) { 577 packageTracker.packageLoaded(pi.id(), pi.version()); 578 } 579 580 String of = pi.getFolders().get("package").getFolderPath(); 581 if (of != null) { 582 oidSources.add(new OIDSource(of, pi.vid())); 583 } 584 585 if ((types == null || types.size() == 0) && loader != null) { 586 types = loader.getTypes(); 587 } 588 boolean hasIG = false; 589 PackageInformation pii = new PackageInformation(pi); 590 if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad() || !allowLazyLoading) { 591 // can't lazy load R2 because of valueset/codesystem implementation 592 if (types == null || types.size() == 0) { 593 types = Utilities.stringSet("ImplementationGuide", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" ); 594 } 595 for (String s : pi.listResources(types)) { 596 try { 597 Resource r = loadDefinitionItem(s, pi.load("package", s), loader, null, pii); 598 if (r != null) { 599 hasIG = "ImplementationGuide".equals(r.fhirType()) || hasIG; 600 } 601 t++; 602 } catch (Exception e) { 603 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e); 604 } 605 } 606 } else { 607 if (types == null || types.size() == 0) { 608 types = Utilities.stringSet("ImplementationGuide", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measure" ); 609 } 610 types.add("ImplementationGuide"); 611 if (loader != null) { 612 types = loader.reviewActualTypes(types); 613 } 614 for (PackageResourceInformation pri : pi.listIndexedResources(types)) { 615 if (!pri.getFilename().contains("ig-r4") && (loader == null || loader.wantLoad(pi, pri))) { 616 try { 617 618 hasIG = "ImplementationGuide".equals(pri.getResourceType()) || hasIG; 619 if (!pri.hasId()) { 620 loadDefinitionItem(pri.getFilename(), ManagedFileAccess.inStream(pri.getFilename()), loader, null, pii); 621 } else { 622 PackageResourceLoader pl = new PackageResourceLoader(pri, loader, pii); 623 if (loader != null) { 624 pl = loader.editInfo(pl); 625 } 626 if (pl != null) { 627 registerResourceFromPackage(pl, pii); 628 } 629 } 630 t++; 631 } catch (FHIRException e) { 632 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e); 633 } 634 } 635 } 636 } 637 if (!hasIG && !pi.isCore()) { 638 try { 639 registerResourceFromPackage(makeIgResource(pi), pii); 640 } catch (Exception e) { 641 log.error("Problem constructing IG for "+pi.vid()+": "+e.getMessage()); 642 } 643 } 644 for (String s : pi.list("other")) { 645 binaries.put(s, new BytesFromPackageProvider(pi, s)); 646 } 647 if (version == null) { 648 version = pi.version(); 649 if (version.equals("current")) { 650 version = "5.0.0"; 651 } 652 } 653 if (loader != null && terminologyClientManager.getFactory() == null) { 654 terminologyClientManager.setFactory(loader.txFactory()); 655 } 656 return t; 657 } 658 659 private CanonicalResourceProxy makeIgResource(NpmPackage pi) { 660 ImplementationGuide ig = new ImplementationGuide(); 661 ig.setId(pi.name()); 662 ig.setVersion(pi.version()); 663 ig.setUrl(makeIgUrl(pi)); 664 ig.setUserData(UserDataNames.IG_FAKE, true); 665 666 var res = new InternalCanonicalResourceProxy(ig.fhirType(), ig.getId(), ig.getUrl(), ig.getVersion()); 667 res.setResource(ig); 668 return res; 669 } 670 671 private String makeIgUrl(NpmPackage pi) { 672 switch (pi.name()) { 673 case "hl7.fhir.pubpack": return "http://hl7.org/fhir/pubpack/ImplementationGuide/hl7.fhir.pubpack"; 674 case "hl7.fhir.xver-extensions": return "http://hl7.org/fhir/xver-extensions/ImplementationGuide/hl7.fhir.xver-extensions"; 675 case "us.nlm.vsac": return "http://fhir.org/packages/us.nlm.vsac/ImplementationGuide/us.nlm.vsac"; 676 case "us.cdc.phinvads": return "https://phinvads.cdc.gov/vads/fhir/ImplementationGuide/us.cdc.phinvads"; 677 case "hl7.fhir.us.core.v610": return "http://hl7.org/fhir/us/core/v610/ImplementationGuide/hl7.fhir.us.core.v610"; 678 case "hl7.fhir.us.core.v311": return "http://hl7.org/fhir/us/core/v311/ImplementationGuide/hl7.fhir.us.core.v311"; 679 case "fhir.dicom": return "http://fhir.org/packages/fhir.dicom/ImplementationGuide/fhir.dicom"; 680 default: 681 if (pi.name() != null && pi.canonical() != null) { 682 return Utilities.pathURL(pi.canonical(), "ImplementationGuide", pi.name()); 683 } else { 684 throw new FHIRException("No IG canonical can be determined for package: "+pi.name()+"#"+pi.version()); 685 } 686 } 687 } 688 689 public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException { 690 loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null); 691 } 692 693 private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException { 694 ZipInputStream zip = new ZipInputStream(stream); 695 ZipEntry zipEntry; 696 while ((zipEntry = zip.getNextEntry()) != null) { 697 String entryName = zipEntry.getName(); 698 if (entryName.contains("..")) { 699 throw new RuntimeException("Entry with an illegal path: " + entryName); 700 } 701 loadDefinitionItem(entryName, zip, loader, null, null); 702 zip.closeEntry(); 703 } 704 zip.close(); 705 } 706 707 private void readVersionInfo(InputStream stream) throws IOException, DefinitionException { 708 byte[] bytes = IOUtils.toByteArray(stream); 709 binaries.put("version.info", new BytesProvider(bytes)); 710 711 String[] vi = new String(bytes).split("\\r?\\n"); 712 for (String s : vi) { 713 if (s.startsWith("version=")) { 714 if (version == null) 715 version = s.substring(8); 716 else if (!version.equals(s.substring(8))) { 717 throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8))); 718 } 719 } 720 if (s.startsWith("revision=")) 721 revision = s.substring(9); 722 if (s.startsWith("date=")) 723 date = s.substring(5); 724 } 725 } 726 727 @Override 728 public IResourceValidator newValidator() throws FHIRException { 729 if (validatorFactory == null) 730 throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED)); 731 return validatorFactory.makeValidator(this, xverManager, null).setJurisdiction(JurisdictionUtilities.getJurisdictionCodingFromLocale(Locale.getDefault().getCountry())); 732 } 733 734 @Override 735 public List<String> getResourceNames() { 736 Set<String> result = new HashSet<String>(); 737 for (StructureDefinition sd : listStructures()) { 738 if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.hasUserData(UserDataNames.loader_urls_patched)) 739 result.add(sd.getName()); 740 } 741 return Utilities.sorted(result); 742 } 743 744 745 public Questionnaire getQuestionnaire() { 746 return questionnaire; 747 } 748 749 public void setQuestionnaire(Questionnaire questionnaire) { 750 this.questionnaire = questionnaire; 751 } 752 753 754 755 public void loadBinariesFromFolder(String folder) throws IOException { 756 for (String n : ManagedFileAccess.file(folder).list()) { 757 binaries.put(n, new BytesFromFileProvider(Utilities.path(folder, n))); 758 } 759 } 760 761 public void loadBinariesFromFolder(NpmPackage pi) throws IOException { 762 for (String n : pi.list("other")) { 763 binaries.put(n, new BytesFromPackageProvider(pi, n)); 764 } 765 } 766 767 public void loadFromFolder(String folder) throws IOException { 768 for (String n : ManagedFileAccess.file(folder).list()) { 769 if (n.endsWith(".json")) 770 loadFromFile(Utilities.path(folder, n), new JsonParser()); 771 else if (n.endsWith(".xml")) 772 loadFromFile(Utilities.path(folder, n), new XmlParser()); 773 } 774 } 775 776 private void loadFromFile(String filename, IParser p) { 777 Resource r; 778 try { 779 r = p.parse(ManagedFileAccess.inStream(filename)); 780 if (r.getResourceType() == ResourceType.Bundle) { 781 for (BundleEntryComponent e : ((Bundle) r).getEntry()) { 782 cacheResource(e.getResource()); 783 } 784 } else { 785 cacheResource(r); 786 } 787 } catch (Exception e) { 788 return; 789 } 790 } 791 792 793 794 @Override 795 public String getVersion() { 796 return version; 797 } 798 799 800 public List<StructureMap> findTransformsforSource(String url) { 801 List<StructureMap> res = new ArrayList<StructureMap>(); 802 for (StructureMap map : fetchResourcesByType(StructureMap.class)) { 803 boolean match = false; 804 boolean ok = true; 805 for (StructureMapStructureComponent t : map.getStructure()) { 806 if (t.getMode() == StructureMapModelMode.SOURCE) { 807 match = match || t.getUrl().equals(url); 808 ok = ok && t.getUrl().equals(url); 809 } 810 } 811 if (match && ok) 812 res.add(map); 813 } 814 return res; 815 } 816 817 public IValidatorFactory getValidatorFactory() { 818 return validatorFactory; 819 } 820 821 public void setValidatorFactory(IValidatorFactory validatorFactory) { 822 this.validatorFactory = validatorFactory; 823 } 824 825 @Override 826 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 827 T r = super.fetchResource(class_, uri); 828 if (r instanceof StructureDefinition) { 829 StructureDefinition p = (StructureDefinition)r; 830 try { 831 cutils.generateSnapshot(p); 832 } catch (Exception e) { 833 // not sure what to do in this case? 834 log.error("Unable to generate snapshot @3 for "+uri+": "+e.getMessage()); 835 logger.logDebugMessage(org.hl7.fhir.r5.context.ILoggingService.LogCategory.GENERATE, ExceptionUtils.getStackTrace(e)); 836 } 837 } 838 return r; 839 } 840 841 @Override 842 public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) { 843 T r = super.fetchResource(class_, uri); 844 return r; 845 } 846 847 @Override 848 public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source) { 849 T resource = super.fetchResource(class_, uri, source); 850 if (resource instanceof StructureDefinition) { 851 StructureDefinition structureDefinition = (StructureDefinition)resource; 852 generateSnapshot(structureDefinition, "4"); 853 } 854 return resource; 855 } 856 857 858 859 860 public String listMapUrls() { 861 return Utilities.listCanonicalUrls(transforms.keys()); 862 } 863 864 public boolean isProgress() { 865 return progress; 866 } 867 868 public void setProgress(boolean progress) { 869 this.progress = progress; 870 } 871 872 public void setClock(TimeTracker tt) { 873 clock = tt; 874 } 875 876 public boolean isCanNoTS() { 877 return canNoTS; 878 } 879 880 public void setCanNoTS(boolean canNoTS) { 881 this.canNoTS = canNoTS; 882 } 883 884 public XVerExtensionManager getXVer() { 885 if (xverManager == null) { 886 xverManager = new XVerExtensionManager(this); 887 } 888 return xverManager; 889 } 890 891 public void cachePackage(PackageInformation packageInfo) { 892 // nothing yet 893 } 894 895 @Override 896 public boolean hasPackage(String id, String ver) { 897 if (ver == null) { 898 for (String p : loadedPackages) { 899 if (p.startsWith(id+"#")) { 900 return true; 901 } 902 } 903 return false; 904 } else { 905 return loadedPackages.contains(id+"#"+ver); 906 } 907 } 908 909 public boolean hasPackage(String idAndver) { 910 if (loadedPackages.contains(idAndver)) { 911 return true; 912 } 913 // not clear whether the same logic should apply to other cross-version packages? 914 if (idAndver.startsWith("hl7.fhir.uv.extensions")) { 915 String v = idAndver.substring(idAndver.lastIndexOf("#")+1); 916 for (String s : loadedPackages) { 917 String v2 = s.substring(s.lastIndexOf("#")+1); 918 if (s.startsWith("hl7.fhir.uv.extensions.") && VersionUtilities.versionsMatch(v, v2)) { 919 return true; 920 } 921 } 922 } 923 return false; 924 925 } 926 927 @Override 928 public boolean hasPackage(PackageInformation pack) { 929 return false; 930 } 931 932 @Override 933 public PackageInformation getPackage(String id, String ver) { 934 return null; 935 } 936 937 public boolean isAllowLazyLoading() { 938 return allowLazyLoading; 939 } 940 941 public void setAllowLazyLoading(boolean allowLazyLoading) { 942 this.allowLazyLoading = allowLazyLoading; 943 } 944 945 public String loadedPackageSummary() { 946 return loadedPackages.toString(); 947 } 948 949 @Override 950 public String getSpecUrl() { 951 return VersionUtilities.getSpecUrl(getVersion())+"/"; 952 } 953 954 955 956} 957