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