
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.PackageInformation; 068import org.hl7.fhir.r5.model.Parameters; 069import org.hl7.fhir.r5.model.Questionnaire; 070import org.hl7.fhir.r5.model.Resource; 071import org.hl7.fhir.r5.model.ResourceType; 072import org.hl7.fhir.r5.model.StructureDefinition; 073import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 074import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 075import org.hl7.fhir.r5.model.StructureMap; 076import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode; 077import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent; 078import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; 079import org.hl7.fhir.r5.terminologies.client.ITerminologyClient; 080import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext; 081import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ITerminologyClientFactory; 082import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5; 083import org.hl7.fhir.r5.utils.R5Hacker; 084import org.hl7.fhir.r5.utils.UserDataNames; 085import org.hl7.fhir.r5.utils.XVerExtensionManager; 086import org.hl7.fhir.r5.utils.validation.IResourceValidator; 087import org.hl7.fhir.r5.utils.validation.ValidatorSession; 088import org.hl7.fhir.utilities.ByteProvider; 089import org.hl7.fhir.utilities.FileUtilities; 090import org.hl7.fhir.utilities.MagicResources; 091import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 092import org.hl7.fhir.utilities.TimeTracker; 093import org.hl7.fhir.utilities.Utilities; 094import org.hl7.fhir.utilities.VersionUtilities; 095import org.hl7.fhir.utilities.filesystem.CSFileInputStream; 096import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 097import org.hl7.fhir.utilities.http.ManagedWebAccess; 098import org.hl7.fhir.utilities.i18n.I18nConstants; 099import org.hl7.fhir.utilities.npm.BasePackageCacheManager; 100import org.hl7.fhir.utilities.npm.NpmPackage; 101import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation; 102 103import ca.uhn.fhir.parser.DataFormatException; 104import lombok.AccessLevel; 105import lombok.AllArgsConstructor; 106import lombok.With; 107 108/* 109 * This is a stand alone implementation of worker context for use inside a tool. 110 * It loads from the validation package (validation-min.xml.zip), and has a 111 * very light client to connect to an open unauthenticated terminology service 112 */ 113 114@Slf4j 115@MarkedToMoveToAdjunctPackage 116public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext { 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 List<String> suppressedMappings; 199 200 private SimpleWorkerContext() throws IOException, FHIRException { 201 super(); 202 } 203 204 private SimpleWorkerContext(Locale locale) throws IOException, FHIRException { 205 super(locale); 206 } 207 208 public SimpleWorkerContext(SimpleWorkerContext other) throws IOException, FHIRException { 209 super(); 210 copy(other); 211 } 212 213 private SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws IOException, FHIRException { 214 super(locale); 215 copy(other); 216 } 217 218 protected void copy(SimpleWorkerContext other) { 219 super.copy(other); 220 binaries.putAll(other.binaries); 221 version = other.version; 222 revision = other.revision; 223 date = other.date; 224 validatorFactory = other.validatorFactory; 225 progress = other.progress; 226 loadedPackages.addAll(other.loadedPackages); 227 canNoTS = other.canNoTS; 228 xverManager = other.xverManager; 229 allowLazyLoading = other.allowLazyLoading; 230 questionnaire = other.questionnaire; 231 } 232 233 234 public List<String> getLoadedPackages() { 235 return loadedPackages; 236 } 237 238 // -- Initializations 239 @AllArgsConstructor(access = AccessLevel.PRIVATE) 240 public static class SimpleWorkerContextBuilder { 241 242 243 @With 244 private final String terminologyCachePath; 245 @With 246 private final boolean cacheTerminologyClientErrors; 247 @With 248 private final boolean alwaysUseTerminologyServer; 249 @With 250 private final boolean readOnlyCache; 251 252 @With 253 private final Locale locale; 254 255 @With 256 private final String userAgent; 257 258 @With 259 private final boolean allowLoadingDuplicates; 260 261 @With 262 private final org.hl7.fhir.r5.context.ILoggingService loggingService; 263 private boolean defaultExpParams; 264 265 public SimpleWorkerContextBuilder() { 266 cacheTerminologyClientErrors = false; 267 alwaysUseTerminologyServer = false; 268 readOnlyCache = false; 269 terminologyCachePath = null; 270 locale = null; 271 userAgent = null; 272 allowLoadingDuplicates = false; 273 loggingService = new Slf4JLoggingService(log); 274 } 275 276 private SimpleWorkerContext getSimpleWorkerContextInstance() throws IOException { 277 if (locale != null) { 278 return new SimpleWorkerContext(locale); 279 } else { 280 return new SimpleWorkerContext(); 281 } 282 } 283 284 public SimpleWorkerContext build() throws IOException { 285 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 286 return build(context); 287 } 288 289 private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException { 290 if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion())) { 291 log.warn("As of end 2024, FHIR R2 (version "+context.getVersion()+") is no longer officially supported."); 292 } 293 context.initTxCache(terminologyCachePath); 294 context.setUserAgent(userAgent); 295 context.setLogger(loggingService); 296 context.cacheResource(new org.hl7.fhir.r5.formats.JsonParser().parse(MagicResources.spdxCodesAsData())); 297 if (defaultExpParams) { 298 context.setExpansionParameters(makeExpProfile()); 299 } 300 return context; 301 } 302 303 public SimpleWorkerContext fromPackage(NpmPackage pi) throws IOException, FHIRException { 304 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 305 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 306 context.terminologyClientManager.setFactory(TerminologyClientR5.factory()); 307 context.loadFromPackage(pi, null); 308 return build(context); 309 } 310 311 private Parameters makeExpProfile() { 312 Parameters ep = new Parameters(); 313 ep.addParameter("cache-id", UUID.randomUUID().toString().toLowerCase()); 314 return ep; 315 } 316 317 public SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader, boolean genSnapshots) throws IOException, FHIRException { 318 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 319 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 320 context.version = pi.fhirVersion(); 321 context.terminologyClientManager.setFactory(loader.txFactory()); 322 context.loadFromPackage(pi, loader); 323 context.finishLoading(genSnapshots); 324 if (defaultExpParams) { 325 context.setExpansionParameters(makeExpProfile()); 326 } 327 return build(context); 328 } 329 330 /** 331 * Load the working context from the validation pack 332 * 333 * @param path 334 * filename of the validation pack 335 * @return 336 * @throws IOException 337 * @throws FileNotFoundException 338 * @throws FHIRException 339 * @throws Exception 340 */ 341 public SimpleWorkerContext fromPack(String path) throws IOException, FHIRException { 342 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 343 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 344 context.loadFromPack(path, null); 345 return build(context); 346 } 347 348 public SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException { 349 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 350 context.loadFromPack(path, loader); 351 return build(context); 352 } 353 354 public SimpleWorkerContext fromClassPath() throws IOException, FHIRException { 355 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 356 context.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null); 357 return build(context); 358 } 359 360 public SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException { 361 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 362 InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name); 363 context.setAllowLoadingDuplicates(allowLoadingDuplicates); 364 context.loadFromStream(s, null); 365 return build(context); 366 } 367 368 public SimpleWorkerContext fromDefinitions(Map<String, ByteProvider> source, IContextResourceLoader loader, PackageInformation pi) throws IOException, FHIRException { 369 SimpleWorkerContext context = getSimpleWorkerContextInstance(); 370 for (String name : source.keySet()) { 371 try { 372 context.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name).getBytes()), loader, null, pi); 373 } catch (Exception e) { 374 log.error("Error loading "+name+": "+e.getMessage()); 375 throw new FHIRException("Error loading "+name+": "+e.getMessage(), e); 376 } 377 } 378 return build(context); 379 } 380 public SimpleWorkerContext fromNothing() throws FHIRException, IOException { 381 return build(); 382 } 383 384 public SimpleWorkerContextBuilder withDefaultParams() { 385 defaultExpParams = true; 386 return this; 387 } 388 } 389 390 private Resource loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageInformation pi) throws IOException, FHIRException { 391 if (name.endsWith(".xml")) 392 return loadFromFile(stream, name, loader, filter); 393 else if (name.endsWith(".json")) 394 return loadFromFileJson(stream, name, loader, filter, pi); 395 else if (name.equals("version.info")) 396 readVersionInfo(stream); 397 else 398 binaries.put(name, new BytesProvider(FileUtilities.streamToBytesNoClose(stream))); 399 return null; 400 } 401 402 public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client, boolean useEcosystem) { 403 terminologyClientManager.setFactory(factory); 404 if (txLog == null) { 405 txLog = client.getLogger(); 406 } 407 TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client, useEcosystem); 408 txLog("Connect to "+client.getAddress()); 409 try { 410 tcc.initialize(); 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 loadFromPackage(NpmPackage pi, IContextResourceLoader loader, Set<String> types) throws IOException, FHIRException { 547 return loadFromPackageInt(pi, loader, types); 548 } 549 550 @Override 551 public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws IOException, FHIRException { 552 return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version()); 553 } 554 public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws IOException, FHIRException { 555 int t = 0; 556 557 for (String e : pi.dependencies()) { 558 if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) { 559 NpmPackage npm = pcm.loadPackage(e); 560 if (!VersionUtilities.versionsMatch(version, npm.fhirVersion())) { 561 log.info(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path)); 562 } 563 t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version()); 564 } 565 } 566 t = t + loadFromPackageInt(pi, loader, loader.getTypes()); 567 return t; 568 } 569 570 571 public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, Set<String> types) throws IOException, FHIRException { 572 int t = 0; 573 if (progress) { 574 log.info("Load Package "+pi.name()+"#"+pi.version()); 575 } 576 if (loadedPackages.contains(pi.id()+"#"+pi.version())) { 577 return 0; 578 } 579 580 loadedPackages.add(pi.id()+"#"+pi.version()); 581 if (packageTracker != null) { 582 packageTracker.packageLoaded(pi.id(), pi.version()); 583 } 584 585 String of = pi.getFolders().get("package").getFolderPath(); 586 if (of != null) { 587 oidSources.add(new OIDSource(of, pi.vid())); 588 } 589 590 if ((types == null || types.size() == 0) && loader != null) { 591 types = loader.getTypes(); 592 } 593 boolean hasIG = false; 594 PackageInformation pii = new PackageInformation(pi); 595 if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad() || !allowLazyLoading) { 596 // can't lazy load R2 because of valueset/codesystem implementation 597 if (types == null || types.size() == 0) { 598 types = Utilities.stringSet("ImplementationGuide", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" ); 599 } 600 for (String s : pi.listResources(types)) { 601 try { 602 Resource r = loadDefinitionItem(s, pi.load("package", s), loader, null, pii); 603 if (r != null) { 604 hasIG = "ImplementationGuide".equals(r.fhirType()) || hasIG; 605 } 606 t++; 607 } catch (Exception e) { 608 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e); 609 } 610 } 611 } else { 612 if (types == null || types.size() == 0) { 613 types = Utilities.stringSet("ImplementationGuide", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measure" ); 614 } 615 types.add("ImplementationGuide"); 616 if (loader != null) { 617 types = loader.reviewActualTypes(types); 618 } 619 for (PackageResourceInformation pri : pi.listIndexedResources(types)) { 620 if (!pri.getFilename().contains("ig-r4") && (loader == null || loader.wantLoad(pi, pri))) { 621 try { 622 623 hasIG = "ImplementationGuide".equals(pri.getResourceType()) || hasIG; 624 if (!pri.hasId()) { 625 loadDefinitionItem(pri.getFilename(), ManagedFileAccess.inStream(pri.getFilename()), loader, null, pii); 626 } else { 627 PackageResourceLoader pl = new PackageResourceLoader(pri, loader, pii); 628 if (loader != null) { 629 pl = loader.editInfo(pl); 630 } 631 if (pl != null) { 632 registerResourceFromPackage(pl, pii); 633 } 634 } 635 t++; 636 } catch (FHIRException e) { 637 throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e); 638 } 639 } 640 } 641 } 642 if (!hasIG && !pi.isCore()) { 643 try { 644 registerResourceFromPackage(makeIgResource(pi), pii); 645 } catch (Exception e) { 646 log.error("Problem constructing IG for "+pi.vid()+": "+e.getMessage()); 647 } 648 } 649 for (String s : pi.list("other")) { 650 binaries.put(s, new BytesFromPackageProvider(pi, s)); 651 } 652 if (version == null) { 653 version = pi.version(); 654 if (version.equals("current")) { 655 version = "5.0.0"; 656 } 657 } 658 if (loader != null && terminologyClientManager.getFactory() == null) { 659 terminologyClientManager.setFactory(loader.txFactory()); 660 } 661 return t; 662 } 663 664 private CanonicalResourceProxy makeIgResource(NpmPackage pi) { 665 ImplementationGuide ig = new ImplementationGuide(); 666 ig.setId(pi.name()); 667 ig.setVersion(pi.version()); 668 ig.setUrl(makeIgUrl(pi)); 669 ig.setUserData(UserDataNames.IG_FAKE, true); 670 671 var res = new InternalCanonicalResourceProxy(ig.fhirType(), ig.getId(), ig.getUrl(), ig.getVersion()); 672 res.setResource(ig); 673 return res; 674 } 675 676 private String makeIgUrl(NpmPackage pi) { 677 switch (pi.name()) { 678 case "hl7.fhir.pubpack": return "http://hl7.org/fhir/pubpack/ImplementationGuide/hl7.fhir.pubpack"; 679 case "hl7.fhir.xver-extensions": return "http://hl7.org/fhir/xver-extensions/ImplementationGuide/hl7.fhir.xver-extensions"; 680 case "us.nlm.vsac": return "http://fhir.org/packages/us.nlm.vsac/ImplementationGuide/us.nlm.vsac"; 681 case "us.cdc.phinvads": return "https://phinvads.cdc.gov/vads/fhir/ImplementationGuide/us.cdc.phinvads"; 682 case "hl7.fhir.us.core.v610": return "http://hl7.org/fhir/us/core/v610/ImplementationGuide/hl7.fhir.us.core.v610"; 683 case "hl7.fhir.us.core.v311": return "http://hl7.org/fhir/us/core/v311/ImplementationGuide/hl7.fhir.us.core.v311"; 684 case "fhir.dicom": return "http://fhir.org/packages/fhir.dicom/ImplementationGuide/fhir.dicom"; 685 default: 686 if (pi.name() != null && pi.canonical() != null) { 687 return Utilities.pathURL(pi.canonical(), "ImplementationGuide", pi.name()); 688 } else { 689 throw new FHIRException("No IG canonical can be determined for package: "+pi.name()+"#"+pi.version()); 690 } 691 } 692 } 693 694 public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException { 695 loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null); 696 } 697 698 private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException { 699 ZipInputStream zip = new ZipInputStream(stream); 700 ZipEntry zipEntry; 701 while ((zipEntry = zip.getNextEntry()) != null) { 702 String entryName = zipEntry.getName(); 703 if (entryName.contains("..")) { 704 throw new RuntimeException("Entry with an illegal path: " + entryName); 705 } 706 loadDefinitionItem(entryName, zip, loader, null, null); 707 zip.closeEntry(); 708 } 709 zip.close(); 710 } 711 712 private void readVersionInfo(InputStream stream) throws IOException, DefinitionException { 713 byte[] bytes = IOUtils.toByteArray(stream); 714 binaries.put("version.info", new BytesProvider(bytes)); 715 716 String[] vi = new String(bytes).split("\\r?\\n"); 717 for (String s : vi) { 718 if (s.startsWith("version=")) { 719 if (version == null) 720 version = s.substring(8); 721 else if (!version.equals(s.substring(8))) { 722 throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8))); 723 } 724 } 725 if (s.startsWith("revision=")) 726 revision = s.substring(9); 727 if (s.startsWith("date=")) 728 date = s.substring(5); 729 } 730 } 731 732 @Override 733 public IResourceValidator newValidator() throws FHIRException { 734 if (validatorFactory == null) 735 throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED)); 736 return validatorFactory.makeValidator(this, xverManager, null).setJurisdiction(JurisdictionUtilities.getJurisdictionCodingFromLocale(Locale.getDefault().getCountry())); 737 } 738 739 @Override 740 public List<String> getResourceNames() { 741 Set<String> result = new HashSet<String>(); 742 for (StructureDefinition sd : listStructures()) { 743 if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.hasUserData(UserDataNames.loader_urls_patched)) 744 result.add(sd.getName()); 745 } 746 return Utilities.sorted(result); 747 } 748 749 750 public Questionnaire getQuestionnaire() { 751 return questionnaire; 752 } 753 754 public void setQuestionnaire(Questionnaire questionnaire) { 755 this.questionnaire = questionnaire; 756 } 757 758 759 760 public void loadBinariesFromFolder(String folder) throws IOException { 761 for (String n : ManagedFileAccess.file(folder).list()) { 762 binaries.put(n, new BytesFromFileProvider(Utilities.path(folder, n))); 763 } 764 } 765 766 public void loadBinariesFromFolder(NpmPackage pi) throws IOException { 767 for (String n : pi.list("other")) { 768 binaries.put(n, new BytesFromPackageProvider(pi, n)); 769 } 770 } 771 772 public void loadFromFolder(String folder) throws IOException { 773 for (String n : ManagedFileAccess.file(folder).list()) { 774 if (n.endsWith(".json")) 775 loadFromFile(Utilities.path(folder, n), new JsonParser()); 776 else if (n.endsWith(".xml")) 777 loadFromFile(Utilities.path(folder, n), new XmlParser()); 778 } 779 } 780 781 private void loadFromFile(String filename, IParser p) { 782 Resource r; 783 try { 784 r = p.parse(ManagedFileAccess.inStream(filename)); 785 if (r.getResourceType() == ResourceType.Bundle) { 786 for (BundleEntryComponent e : ((Bundle) r).getEntry()) { 787 cacheResource(e.getResource()); 788 } 789 } else { 790 cacheResource(r); 791 } 792 } catch (Exception e) { 793 return; 794 } 795 } 796 797 798 799 @Override 800 public String getVersion() { 801 return version; 802 } 803 804 805 public List<StructureMap> findTransformsforSource(String url) { 806 List<StructureMap> res = new ArrayList<StructureMap>(); 807 for (StructureMap map : fetchResourcesByType(StructureMap.class)) { 808 boolean match = false; 809 boolean ok = true; 810 for (StructureMapStructureComponent t : map.getStructure()) { 811 if (t.getMode() == StructureMapModelMode.SOURCE) { 812 match = match || t.getUrl().equals(url); 813 ok = ok && t.getUrl().equals(url); 814 } 815 } 816 if (match && ok) 817 res.add(map); 818 } 819 return res; 820 } 821 822 public IValidatorFactory getValidatorFactory() { 823 return validatorFactory; 824 } 825 826 public void setValidatorFactory(IValidatorFactory validatorFactory) { 827 this.validatorFactory = validatorFactory; 828 } 829 830 @Override 831 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 832 T r = super.fetchResource(class_, uri); 833 if (r instanceof StructureDefinition) { 834 StructureDefinition p = (StructureDefinition)r; 835 try { 836 new ContextUtilities(this, suppressedMappings).generateSnapshot(p); 837 } catch (Exception e) { 838 // not sure what to do in this case? 839 log.error("Unable to generate snapshot @3 for "+uri+": "+e.getMessage()); 840 logger.logDebugMessage(org.hl7.fhir.r5.context.ILoggingService.LogCategory.GENERATE, ExceptionUtils.getStackTrace(e)); 841 } 842 } 843 return r; 844 } 845 846 @Override 847 public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) { 848 T r = super.fetchResource(class_, uri); 849 return r; 850 } 851 852 @Override 853 public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source) { 854 T resource = super.fetchResource(class_, uri, source); 855 if (resource instanceof StructureDefinition) { 856 StructureDefinition structureDefinition = (StructureDefinition)resource; 857 generateSnapshot(structureDefinition, "4"); 858 } 859 return resource; 860 } 861 862 863 864 865 public String listMapUrls() { 866 return Utilities.listCanonicalUrls(transforms.keys()); 867 } 868 869 public boolean isProgress() { 870 return progress; 871 } 872 873 public void setProgress(boolean progress) { 874 this.progress = progress; 875 } 876 877 public void setClock(TimeTracker tt) { 878 clock = tt; 879 } 880 881 public boolean isCanNoTS() { 882 return canNoTS; 883 } 884 885 public void setCanNoTS(boolean canNoTS) { 886 this.canNoTS = canNoTS; 887 } 888 889 public XVerExtensionManager getXVer() { 890 if (xverManager == null) { 891 xverManager = new XVerExtensionManager(this); 892 } 893 return xverManager; 894 } 895 896 public void cachePackage(PackageInformation packageInfo) { 897 // nothing yet 898 } 899 900 @Override 901 public boolean hasPackage(String id, String ver) { 902 if (ver == null) { 903 for (String p : loadedPackages) { 904 if (p.startsWith(id+"#")) { 905 return true; 906 } 907 } 908 return false; 909 } else { 910 return loadedPackages.contains(id+"#"+ver); 911 } 912 } 913 914 public boolean hasPackage(String idAndver) { 915 if (loadedPackages.contains(idAndver)) { 916 return true; 917 } 918 // not clear whether the same logic should apply to other cross-version packages? 919 if (idAndver.startsWith("hl7.fhir.uv.extensions")) { 920 String v = idAndver.substring(idAndver.lastIndexOf("#")+1); 921 for (String s : loadedPackages) { 922 String v2 = s.substring(s.lastIndexOf("#")+1); 923 if (s.startsWith("hl7.fhir.uv.extensions.") && VersionUtilities.versionsMatch(v, v2)) { 924 return true; 925 } 926 } 927 } 928 return false; 929 930 } 931 932 @Override 933 public boolean hasPackage(PackageInformation pack) { 934 return false; 935 } 936 937 @Override 938 public PackageInformation getPackage(String id, String ver) { 939 return null; 940 } 941 942 public boolean isAllowLazyLoading() { 943 return allowLazyLoading; 944 } 945 946 public void setAllowLazyLoading(boolean allowLazyLoading) { 947 this.allowLazyLoading = allowLazyLoading; 948 } 949 950 public String loadedPackageSummary() { 951 return loadedPackages.toString(); 952 } 953 954 @Override 955 public String getSpecUrl() { 956 return VersionUtilities.getSpecUrl(getVersion())+"/"; 957 } 958 959 public List<String> getSuppressedMappings() { 960 return suppressedMappings; 961 } 962 963 public void setSuppressedMappings(List<String> suppressedMappings) { 964 this.suppressedMappings = suppressedMappings; 965 } 966 967 968 969} 970