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 org.apache.commons.io.IOUtils;
051import org.hl7.fhir.exceptions.DefinitionException;
052import org.hl7.fhir.exceptions.FHIRException;
053import org.hl7.fhir.exceptions.FHIRFormatError;
054import org.hl7.fhir.exceptions.TerminologyServiceException;
055import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
056import org.hl7.fhir.r5.context.ILoggingService.LogCategory;
057import org.hl7.fhir.r5.formats.IParser;
058import org.hl7.fhir.r5.formats.JsonParser;
059import org.hl7.fhir.r5.formats.XmlParser;
060import org.hl7.fhir.r5.model.*;
061import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
062import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
063import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
064import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode;
065import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
066import org.hl7.fhir.r5.terminologies.JurisdictionUtilities;
067import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
068import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext;
069import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ITerminologyClientFactory;
070import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5;
071import org.hl7.fhir.r5.utils.validation.IResourceValidator;
072import org.hl7.fhir.r5.utils.validation.ValidatorSession;
073import org.hl7.fhir.r5.utils.R5Hacker;
074import org.hl7.fhir.r5.utils.UserDataNames;
075import org.hl7.fhir.r5.utils.XVerExtensionManager;
076import org.hl7.fhir.utilities.ByteProvider;
077import org.hl7.fhir.utilities.MagicResources;
078import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
079import org.hl7.fhir.utilities.FileUtilities;
080import org.hl7.fhir.utilities.TimeTracker;
081import org.hl7.fhir.utilities.Utilities;
082import org.hl7.fhir.utilities.VersionUtilities;
083import org.hl7.fhir.utilities.filesystem.CSFileInputStream;
084import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
085import org.hl7.fhir.utilities.i18n.I18nConstants;
086import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
087import org.hl7.fhir.utilities.npm.NpmPackage;
088import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
089
090import ca.uhn.fhir.parser.DataFormatException;
091
092/*
093 * This is a stand alone implementation of worker context for use inside a tool.
094 * It loads from the validation package (validation-min.xml.zip), and has a 
095 * very light client to connect to an open unauthenticated terminology service
096 */
097
098@MarkedToMoveToAdjunctPackage
099public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext {
100
101  public static class PackageResourceLoader extends CanonicalResourceProxy {
102
103    private final String filename;
104    private final IContextResourceLoader loader;
105    private final PackageInformation packageInformation;
106
107    public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader, PackageInformation pi) {
108      super(pri.getResourceType(), pri.getId(), loader == null ? pri.getUrl() :loader.patchUrl(pri.getUrl(), pri.getResourceType()), pri.getVersion(), pri.getSupplements(), pri.getDerivation(), pri.getContent());
109      this.filename = pri.getFilename();
110      this.loader = loader;
111      this.packageInformation = pi;
112    }
113
114    @Override
115    public CanonicalResource loadResource() {
116      try {
117        FileInputStream f = ManagedFileAccess.inStream(filename);
118        try  {
119          if (loader != null) {
120            return setPi(R5Hacker.fixR5BrokenResource((CanonicalResource) loader.loadResource(f, true)));
121          } else {
122            return setPi(R5Hacker.fixR5BrokenResource((CanonicalResource) new JsonParser().parse(f)));
123          }
124        } finally {
125          f.close();
126        }
127      } catch (Exception e) {
128        throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e);
129      }
130    }
131
132    private CanonicalResource setPi(CanonicalResource cr) {
133      cr.setSourcePackage(packageInformation);
134      return cr;
135    }
136  }
137
138  public interface ILoadFilter {
139    boolean isOkToLoad(Resource resource);
140    boolean isOkToLoad(String resourceType);
141  }
142
143  public interface IValidatorFactory {
144    IResourceValidator makeValidator(IWorkerContext ctxt, ValidatorSession session) throws FHIRException;
145    IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager, ValidatorSession session) throws FHIRException;
146  }
147
148        private Questionnaire questionnaire;
149  private String revision;
150  private String date;
151  private IValidatorFactory validatorFactory;
152  private boolean progress;
153  private final List<String> loadedPackages = new ArrayList<>();
154  private boolean canNoTS;
155  private XVerExtensionManager xverManager;
156  private boolean allowLazyLoading = true;
157
158  private SimpleWorkerContext() throws IOException, FHIRException {
159    super();
160  }
161
162  private SimpleWorkerContext(Locale locale) throws IOException, FHIRException {
163    super(locale);
164  }
165
166  public SimpleWorkerContext(SimpleWorkerContext other) throws IOException, FHIRException {
167    super();
168    copy(other);
169  }
170
171  private SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws IOException, FHIRException {
172    super(locale);
173    copy(other);
174  }
175  
176  protected void copy(SimpleWorkerContext other) {
177    super.copy(other);
178    binaries.putAll(other.binaries);
179    version = other.version;
180    revision = other.revision;
181    date = other.date;
182    validatorFactory = other.validatorFactory;
183    progress = other.progress;
184    loadedPackages.addAll(other.loadedPackages);
185    canNoTS = other.canNoTS;
186    xverManager = other.xverManager;
187    allowLazyLoading = other.allowLazyLoading;
188  }
189
190
191  public List<String> getLoadedPackages() {
192    return loadedPackages;
193  }
194
195  // -- Initializations
196  @AllArgsConstructor(access = AccessLevel.PRIVATE)
197  public static class SimpleWorkerContextBuilder {
198
199
200    @With
201    private final String terminologyCachePath;
202    @With
203    private final boolean cacheTerminologyClientErrors;
204    @With
205    private final boolean alwaysUseTerminologyServer;
206    @With
207    private final boolean readOnlyCache;
208
209    @With
210    private final Locale locale;
211
212    @With
213    private final String userAgent;
214
215    @With
216    private final boolean allowLoadingDuplicates;
217
218    @With
219    private final org.hl7.fhir.r5.context.ILoggingService loggingService;
220    private boolean defaultExpParams;
221
222    public SimpleWorkerContextBuilder() {
223      cacheTerminologyClientErrors = false;
224      alwaysUseTerminologyServer = false;
225      readOnlyCache = false;
226      terminologyCachePath = null;
227      locale = null;
228      userAgent = null;
229      allowLoadingDuplicates = false;
230      loggingService = new SystemOutLoggingService();
231    }
232
233    private SimpleWorkerContext getSimpleWorkerContextInstance() throws IOException {
234      if (locale != null) {
235        return new SimpleWorkerContext(locale);
236      } else {
237        return new SimpleWorkerContext();
238      }
239    }
240
241    public SimpleWorkerContext build() throws IOException {
242      SimpleWorkerContext context = getSimpleWorkerContextInstance();
243      return build(context);
244    }
245
246    private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException {
247      if (VersionUtilities.isR2Ver(context.getVersion()) || VersionUtilities.isR2Ver(context.getVersion())) {
248        System.out.println("As of end 2024, FHIR R2 (version "+context.getVersion()+") is no longer officially supported.");
249      }
250      context.initTxCache(terminologyCachePath);
251      context.setUserAgent(userAgent);
252      context.setLogger(loggingService);
253      context.cacheResource(new org.hl7.fhir.r5.formats.JsonParser().parse(MagicResources.spdxCodesAsData()));
254      if (defaultExpParams) {
255        context.setExpansionParameters(makeExpProfile());
256      }
257      return context;
258    }
259
260    public SimpleWorkerContext fromPackage(NpmPackage pi) throws IOException, FHIRException {
261      SimpleWorkerContext context = getSimpleWorkerContextInstance();
262      context.setAllowLoadingDuplicates(allowLoadingDuplicates);
263      context.terminologyClientManager.setFactory(TerminologyClientR5.factory());
264      context.loadFromPackage(pi, null);
265      return build(context);
266    }
267    
268    private Parameters makeExpProfile() {
269      Parameters ep = new Parameters();
270      ep.addParameter("cache-id", UUID.randomUUID().toString().toLowerCase());
271      return ep;
272    }
273
274    public SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader, boolean genSnapshots) throws IOException, FHIRException {
275      SimpleWorkerContext context = getSimpleWorkerContextInstance();
276      context.setAllowLoadingDuplicates(allowLoadingDuplicates);      
277      context.version = pi.fhirVersion();
278      context.terminologyClientManager.setFactory(loader.txFactory());
279      context.loadFromPackage(pi, loader);
280      context.finishLoading(genSnapshots);
281      if (defaultExpParams) {
282        context.setExpansionParameters(makeExpProfile());
283      }
284      return build(context);
285    }
286
287    /**
288     * Load the working context from the validation pack
289     *
290     * @param path
291     *           filename of the validation pack
292     * @return
293     * @throws IOException
294     * @throws FileNotFoundException
295     * @throws FHIRException
296     * @throws Exception
297     */
298    public  SimpleWorkerContext fromPack(String path) throws IOException, FHIRException {
299      SimpleWorkerContext context = getSimpleWorkerContextInstance();
300      context.setAllowLoadingDuplicates(allowLoadingDuplicates);
301      context.loadFromPack(path, null);
302      return build(context);
303    }
304
305    public SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException {
306      SimpleWorkerContext context = getSimpleWorkerContextInstance();
307      context.loadFromPack(path, loader);
308      return build(context);
309    }
310
311    public SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
312      SimpleWorkerContext context = getSimpleWorkerContextInstance();
313      context.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null);
314      return build(context);
315    }
316
317    public SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
318      SimpleWorkerContext context = getSimpleWorkerContextInstance();
319      InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
320      context.setAllowLoadingDuplicates(allowLoadingDuplicates);
321      context.loadFromStream(s, null);
322      return build(context);
323    }
324
325    public SimpleWorkerContext fromDefinitions(Map<String, ByteProvider> source, IContextResourceLoader loader, PackageInformation pi) throws IOException, FHIRException  {
326      SimpleWorkerContext context = getSimpleWorkerContextInstance();
327      for (String name : source.keySet()) {
328        try {
329          context.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name).getBytes()), loader, null, pi);
330        } catch (Exception e) {
331          System.out.println("Error loading "+name+": "+e.getMessage());
332          throw new FHIRException("Error loading "+name+": "+e.getMessage(), e);
333        }
334      }
335      return build(context);
336    }
337    public SimpleWorkerContext fromNothing() throws FHIRException, IOException  {
338      return build();
339    }
340
341    public SimpleWorkerContextBuilder withDefaultParams() {
342      defaultExpParams = true;
343      return this;
344    }
345  }
346
347  private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageInformation pi) throws IOException, FHIRException {
348    if (name.endsWith(".xml"))
349      loadFromFile(stream, name, loader, filter);
350    else if (name.endsWith(".json"))
351      loadFromFileJson(stream, name, loader, filter, pi);
352    else if (name.equals("version.info"))
353      readVersionInfo(stream);
354    else
355      binaries.put(name, new BytesProvider(FileUtilities.streamToBytesNoClose(stream)));
356  }
357
358  public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client, boolean useEcosystem) {
359    terminologyClientManager.setFactory(factory);
360    if (txLog == null) {
361      txLog = client.getLogger();
362    }
363    TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client, useEcosystem);
364    txLog("Connect to "+client.getAddress());
365    try {
366      tcc.initialize();  
367    } catch (Exception e) {
368      if (canRunWithoutTerminology) {
369        noTerminologyServer = true;
370        logger.logMessage("==============!! Running without terminology server !! ==============");
371        if (terminologyClientManager.getMasterClient() != null) {
372          logger.logMessage("txServer = "+ terminologyClientManager.getMasterClient().getId());
373          logger.logMessage("Error = "+e.getMessage()+"");
374        }
375        logger.logMessage("=====================================================================");
376      } else {
377        e.printStackTrace();
378        throw new TerminologyServiceException(e);
379      }
380    }      
381  }
382  
383  public void connectToTSServer(ITerminologyClientFactory factory, String address, String software, String log, boolean useEcosystem) {
384    try {
385      terminologyClientManager.setFactory(factory);
386      if (log != null && (log.endsWith(".htm") || log.endsWith(".html"))) {
387        txLog = new HTMLClientLogger(log);
388      } else {
389        txLog = new TextClientLogger(log);
390      }      
391      ITerminologyClient client = factory.makeClient("tx-server", address, software, txLog);
392      // txFactory.makeClient("Tx-Server", txServer, "fhir/publisher", null)
393//      terminologyClientManager.setLogger(txLog);
394//      terminologyClientManager.setUserAgent(userAgent);
395      connectToTSServer(factory, client, useEcosystem);
396      
397    } catch (Exception e) {
398      e.printStackTrace();
399      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);
400    }
401  }
402
403  public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws FHIRException {
404    loadFromFile(stream, name, loader, null);
405  }
406  
407        public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws FHIRException {
408                Resource f;
409                try {
410                  if (loader != null)
411                    f = loader.loadBundle(stream, false);
412                  else {
413                    XmlParser xml = new XmlParser();
414                    f = xml.parse(stream);
415                  }
416    } catch (DataFormatException e1) {
417      throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
418    } catch (Exception e1) {
419                        throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
420                }
421                if (f instanceof Bundle) {
422                  Bundle bnd = (Bundle) f;
423                  for (BundleEntryComponent e : bnd.getEntry()) {
424                    if (e.getFullUrl() == null) {
425                      logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)");
426                    }
427              if (filter == null || filter.isOkToLoad(e.getResource())) {
428                String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
429                if (path != null) {
430                  e.getResource().setWebPath(path);
431                }
432                      cacheResource(e.getResource());
433              }
434                  }
435                } else if (f instanceof CanonicalResource) {
436                  if (filter == null || filter.isOkToLoad(f)) {
437        String path = loader != null ? loader.getResourcePath(f) : null;
438        if (path != null) {
439          f.setWebPath(path);
440        }
441                    cacheResource(f);
442                  }
443                }
444        }
445
446  private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageInformation pi) throws IOException, FHIRException {
447    Bundle f = null;
448    try {
449      if (loader != null)
450        f = loader.loadBundle(stream, true);
451      else {
452        JsonParser json = new JsonParser();
453        Resource r = json.parse(stream);
454        if (r instanceof Bundle)
455          f = (Bundle) r;
456        else if (filter == null || filter.isOkToLoad(f)) {
457          cacheResourceFromPackage(r, pi);
458        }
459      }
460    } catch (FHIRFormatError e1) {
461      throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1);
462    }
463    if (f != null)
464      for (BundleEntryComponent e : f.getEntry()) {
465        if (filter == null || filter.isOkToLoad(e.getResource())) {
466          String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
467          if (path != null) {
468            e.getResource().setWebPath(path);
469          }
470          cacheResourceFromPackage(e.getResource(), pi);
471        }
472    }
473  }
474
475        private void loadFromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException {
476                loadFromStream(new CSFileInputStream(path), loader);
477        }
478  
479
480  @Override
481  public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException {
482    return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes());
483  }
484  
485  public static List<String> defaultTypesToLoad() {
486    // there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions 
487    return Utilities.strings("CodeSystem", "ValueSet", "ConceptMap", "NamingSystem",
488                         "StructureDefinition", "StructureMap", 
489                         "SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance",
490                         "Questionnaire", "ImplementationGuide", "Measure" );
491  }
492
493  @Override
494  public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List<String> types) throws IOException, FHIRException {
495    return loadFromPackageInt(pi, loader, types);
496  }
497 
498  @Override
499  public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws IOException, FHIRException {
500    return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version());
501  }
502  public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws IOException, FHIRException {
503    int t = 0;
504
505    for (String e : pi.dependencies()) {
506      if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) {
507        NpmPackage npm = pcm.loadPackage(e);
508        if (!VersionUtilities.versionsMatch(version, npm.fhirVersion())) {
509          System.out.println(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path));  
510        }
511        t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version());
512      }
513    }
514    t = t + loadFromPackageInt(pi, loader, loader.getTypes());
515    return t;
516  }
517
518
519  public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, List<String> types) throws IOException, FHIRException {
520    int t = 0;
521    if (progress) {
522      System.out.println("Load Package "+pi.name()+"#"+pi.version());
523    }
524    if (loadedPackages.contains(pi.id()+"#"+pi.version())) {
525      return 0;
526    }
527    
528    loadedPackages.add(pi.id()+"#"+pi.version());
529    if (packageTracker != null) {
530      packageTracker.packageLoaded(pi.id(), pi.version());
531    }
532    
533    String of = pi.getFolders().get("package").getFolderPath();
534    if (of != null) {
535      oidSources.add(new OIDSource(of, pi.vid()));
536    }
537    
538    if ((types == null || types.size() == 0) &&  loader != null) {
539      types = loader.getTypes();
540    }
541    PackageInformation pii = new PackageInformation(pi);
542    if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad() || !allowLazyLoading) {
543      // can't lazy load R2 because of valueset/codesystem implementation
544      if (types == null || types.size() == 0) {
545        types = Utilities.strings("StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" );
546      }
547      for (String s : pi.listResources(types)) {
548        try {
549          loadDefinitionItem(s, pi.load("package", s), loader, null, pii);
550          t++;
551        } catch (Exception e) {
552          throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e);
553        }      
554      }
555    } else {
556      if (types == null || types.size() == 0) {
557        types = Utilities.strings("StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measures" );
558      }
559      for (PackageResourceInformation pri : pi.listIndexedResources(types)) {
560        if (!pri.getFilename().contains("ig-r4") && (loader == null || loader.wantLoad(pi, pri))) {
561          try {
562            if (!pri.hasId()) {
563              loadDefinitionItem(pri.getFilename(), ManagedFileAccess.inStream(pri.getFilename()), loader, null, pii);
564            } else {
565              registerResourceFromPackage(new PackageResourceLoader(pri, loader, pii), pii);
566            }
567            t++;
568          } catch (FHIRException e) {
569            throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e);
570          }
571        }
572      }
573    }
574          for (String s : pi.list("other")) {
575            binaries.put(s, new BytesFromPackageProvider(pi, s));
576          }
577          if (version == null) {
578            version = pi.version();
579            if (version.equals("current")) {
580              version = "5.0.0";
581            }
582          }
583          if (loader != null && terminologyClientManager.getFactory() == null) {
584            terminologyClientManager.setFactory(loader.txFactory());
585          }
586          return t;
587        }
588
589  public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException {
590    loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null);
591  }
592  
593        private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException {
594                ZipInputStream zip = new ZipInputStream(stream);
595    ZipEntry zipEntry;
596    while ((zipEntry = zip.getNextEntry()) != null) {
597      String entryName = zipEntry.getName();
598      if (entryName.contains("..")) {
599        throw new RuntimeException("Entry with an illegal path: " + entryName);
600      }
601      loadDefinitionItem(entryName, zip, loader, null, null);
602                        zip.closeEntry();
603                }
604                zip.close();
605        }
606
607  private void readVersionInfo(InputStream stream) throws IOException, DefinitionException {
608    byte[] bytes = IOUtils.toByteArray(stream);
609    binaries.put("version.info", new BytesProvider(bytes));
610
611    String[] vi = new String(bytes).split("\\r?\\n");
612    for (String s : vi) {
613      if (s.startsWith("version=")) {
614        if (version == null)
615        version = s.substring(8);
616        else if (!version.equals(s.substring(8))) {
617          throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8)));
618        }
619      }
620      if (s.startsWith("revision="))
621        revision = s.substring(9);
622      if (s.startsWith("date="))
623        date = s.substring(5);
624    }
625  }
626
627        @Override
628        public IResourceValidator newValidator() throws FHIRException {
629          if (validatorFactory == null)
630            throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED));
631          return validatorFactory.makeValidator(this, xverManager, null).setJurisdiction(JurisdictionUtilities.getJurisdictionCodingFromLocale(Locale.getDefault().getCountry()));
632        }
633
634  @Override
635  public List<String> getResourceNames() {
636    Set<String> result = new HashSet<String>();
637    for (StructureDefinition sd : listStructures()) {
638      if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.hasUserData(UserDataNames.loader_urls_patched))
639        result.add(sd.getName());
640    }
641    return Utilities.sorted(result);
642  }
643
644 
645  public Questionnaire getQuestionnaire() {
646    return questionnaire;
647  }
648
649  public void setQuestionnaire(Questionnaire questionnaire) {
650    this.questionnaire = questionnaire;
651  }
652
653 
654
655  public void loadBinariesFromFolder(String folder) throws IOException {
656    for (String n : ManagedFileAccess.file(folder).list()) {
657      binaries.put(n, new BytesFromFileProvider(Utilities.path(folder, n)));
658    }
659  }
660  
661  public void loadBinariesFromFolder(NpmPackage pi) throws IOException {
662    for (String n : pi.list("other")) {
663      binaries.put(n, new BytesFromPackageProvider(pi, n));
664    }
665  }
666  
667  public void loadFromFolder(String folder) throws IOException {
668    for (String n : ManagedFileAccess.file(folder).list()) {
669      if (n.endsWith(".json")) 
670        loadFromFile(Utilities.path(folder, n), new JsonParser());
671      else if (n.endsWith(".xml")) 
672        loadFromFile(Utilities.path(folder, n), new XmlParser());
673    }
674  }
675  
676  private void loadFromFile(String filename, IParser p) {
677        Resource r; 
678        try {
679                r = p.parse(ManagedFileAccess.inStream(filename));
680      if (r.getResourceType() == ResourceType.Bundle) {
681        for (BundleEntryComponent e : ((Bundle) r).getEntry()) {
682          cacheResource(e.getResource());
683        }
684     } else {
685       cacheResource(r);
686     }
687        } catch (Exception e) {
688        return;
689    }
690  }
691
692 
693
694  @Override
695  public String getVersion() {
696    return version;
697  }
698
699  
700  public List<StructureMap> findTransformsforSource(String url) {
701    List<StructureMap> res = new ArrayList<StructureMap>();
702    for (StructureMap map : fetchResourcesByType(StructureMap.class)) {
703      boolean match = false;
704      boolean ok = true;
705      for (StructureMapStructureComponent t : map.getStructure()) {
706        if (t.getMode() == StructureMapModelMode.SOURCE) {
707          match = match || t.getUrl().equals(url);
708          ok = ok && t.getUrl().equals(url);
709        }
710      }
711      if (match && ok)
712        res.add(map);
713    }
714    return res;
715  }
716
717  public IValidatorFactory getValidatorFactory() {
718    return validatorFactory;
719  }
720
721  public void setValidatorFactory(IValidatorFactory validatorFactory) {
722    this.validatorFactory = validatorFactory;
723  }
724
725  @Override
726  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
727    T r = super.fetchResource(class_, uri);
728    if (r instanceof StructureDefinition) {
729      StructureDefinition p = (StructureDefinition)r;
730      try {
731        new ContextUtilities(this).generateSnapshot(p);
732      } catch (Exception e) {
733        // not sure what to do in this case?
734        System.out.println("Unable to generate snapshot @3 for "+uri+": "+e.getMessage());
735        if (logger.isDebugLogging()) {
736          e.printStackTrace();
737        }
738      }
739    }
740    return r;
741  }
742
743  @Override
744  public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) {
745    T r = super.fetchResource(class_, uri);
746    return r;
747  }
748
749  @Override
750  public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source) {
751    T resource = super.fetchResource(class_, uri, source);
752    if (resource instanceof StructureDefinition) {
753      StructureDefinition structureDefinition = (StructureDefinition)resource;
754      generateSnapshot(structureDefinition, "4");
755    }
756    return resource;
757  }
758
759
760
761
762  public String listMapUrls() {
763    return Utilities.listCanonicalUrls(transforms.keys());
764  }
765
766  public boolean isProgress() {
767    return progress;
768  }
769
770  public void setProgress(boolean progress) {
771    this.progress = progress;
772  }
773
774  public void setClock(TimeTracker tt) {
775    clock = tt;
776  }
777
778  public boolean isCanNoTS() {
779    return canNoTS;
780  }
781
782  public void setCanNoTS(boolean canNoTS) {
783    this.canNoTS = canNoTS;
784  }
785
786  public XVerExtensionManager getXVer() {
787    if (xverManager == null) {
788      xverManager = new XVerExtensionManager(this);
789    }
790   return xverManager;
791  }
792  
793  public void cachePackage(PackageInformation packageInfo) {
794    // nothing yet
795  }
796
797  @Override
798  public boolean hasPackage(String id, String ver) {
799    return loadedPackages.contains(id+"#"+ver);
800  }
801
802  public boolean hasPackage(String idAndver) {
803    if (loadedPackages.contains(idAndver)) {
804      return true;
805    }
806    // not clear whether the same logic should apply to other cross-version packages?
807    if (idAndver.startsWith("hl7.fhir.uv.extensions")) {
808      String v = idAndver.substring(idAndver.lastIndexOf("#")+1);
809      for (String s : loadedPackages) {
810        String v2 = s.substring(s.lastIndexOf("#")+1);
811        if (s.startsWith("hl7.fhir.uv.extensions.") && VersionUtilities.versionsMatch(v, v2)) {
812          return true;
813        }
814      }
815    }
816    return false;
817    
818  }
819
820  @Override
821  public boolean hasPackage(PackageInformation pack) {
822    return false;
823  }
824
825  @Override
826  public PackageInformation getPackage(String id, String ver) {
827    return null;
828  }
829
830  public boolean isAllowLazyLoading() {
831    return allowLazyLoading;
832  }
833
834  public void setAllowLazyLoading(boolean allowLazyLoading) {
835    this.allowLazyLoading = allowLazyLoading;
836  }
837
838  public String loadedPackageSummary() {
839     return loadedPackages.toString();
840  }
841
842  @Override
843  public String getSpecUrl() {
844    return VersionUtilities.getSpecUrl(getVersion())+"/";
845  }
846
847
848
849}
850