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