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