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