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