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
032
033
034import java.io.ByteArrayInputStream;
035import java.io.File;
036import java.io.FileInputStream;
037import java.io.FileNotFoundException;
038import java.io.IOException;
039import java.io.InputStream;
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.HashSet;
043import java.util.List;
044import java.util.Locale;
045import java.util.Map;
046import java.util.Set;
047import java.util.zip.ZipEntry;
048import java.util.zip.ZipInputStream;
049
050import lombok.AccessLevel;
051import lombok.AllArgsConstructor;
052import lombok.With;
053import org.apache.commons.io.IOUtils;
054import org.hl7.fhir.exceptions.DefinitionException;
055import org.hl7.fhir.exceptions.FHIRException;
056import org.hl7.fhir.exceptions.FHIRFormatError;
057import org.hl7.fhir.r5.conformance.ProfileUtilities;
058import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider;
059import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
060import org.hl7.fhir.r5.context.IWorkerContext.ILoggingService.LogCategory;
061import org.hl7.fhir.r5.formats.IParser;
062import org.hl7.fhir.r5.formats.JsonParser;
063import org.hl7.fhir.r5.formats.ParserType;
064import org.hl7.fhir.r5.formats.XmlParser;
065import org.hl7.fhir.r5.model.*;
066import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
067import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
068import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
069import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
070import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode;
071import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
072import org.hl7.fhir.r5.terminologies.TerminologyClient;
073import org.hl7.fhir.r5.utils.validation.IResourceValidator;
074import org.hl7.fhir.r5.utils.XVerExtensionManager;
075import org.hl7.fhir.utilities.CSFileInputStream;
076import org.hl7.fhir.utilities.TextFile;
077import org.hl7.fhir.utilities.TimeTracker;
078import org.hl7.fhir.utilities.Utilities;
079import org.hl7.fhir.utilities.VersionUtilities;
080import org.hl7.fhir.utilities.i18n.I18nConstants;
081import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
082import org.hl7.fhir.utilities.npm.NpmPackage;
083import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
084import org.hl7.fhir.utilities.validation.ValidationMessage;
085import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
086import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
087
088import ca.uhn.fhir.parser.DataFormatException;
089
090/*
091 * This is a stand alone implementation of worker context for use inside a tool.
092 * It loads from the validation package (validation-min.xml.zip), and has a 
093 * very light client to connect to an open unauthenticated terminology service
094 */
095
096public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider {
097
098  public static class PackageResourceLoader extends CanonicalResourceProxy {
099
100    private final String filename;
101    private final IContextResourceLoader loader;
102
103    public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) {
104      super(pri.getType(), pri.getId(), pri.getUrl(),pri.getVersion());
105      this.filename = pri.getFilename();
106      this.loader = loader;
107    }
108
109    @Override
110    public CanonicalResource loadResource() {
111      try {
112        FileInputStream f = new FileInputStream(filename);
113        try  {
114          if (loader != null) {
115            return (CanonicalResource) loader.loadResource(f, true);
116          } else {
117            return (CanonicalResource) new JsonParser().parse(f);
118          }
119        } finally {
120          f.close();
121        }
122      } catch (Exception e) {
123        throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e);
124      }
125    }
126  }
127
128  public interface ILoadFilter {
129    boolean isOkToLoad(Resource resource);
130    boolean isOkToLoad(String resourceType);
131  }
132
133  public interface IValidatorFactory {
134    IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException;
135    IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager) throws FHIRException;
136  }
137
138        private Questionnaire questionnaire;
139  private String revision;
140  private String date;
141  private IValidatorFactory validatorFactory;
142  private boolean ignoreProfileErrors;
143  private boolean progress;
144  private final List<String> loadedPackages = new ArrayList<>();
145  private boolean canNoTS;
146  private XVerExtensionManager xverManager;
147
148  private SimpleWorkerContext() throws IOException, FHIRException {
149    super();
150  }
151
152  private SimpleWorkerContext(Locale locale) throws IOException, FHIRException {
153    super(locale);
154  }
155
156  private SimpleWorkerContext(SimpleWorkerContext other) throws IOException, FHIRException {
157    super();
158    copy(other);
159  }
160
161  private SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws IOException, FHIRException {
162    super(locale);
163    copy(other);
164  }
165  
166  protected void copy(SimpleWorkerContext other) {
167    super.copy(other);
168    questionnaire = other.questionnaire;
169    binaries.putAll(other.binaries);
170    version = other.version;
171    revision = other.revision;
172    date = other.date;
173    validatorFactory = other.validatorFactory;
174  }
175
176
177  public List<String> getLoadedPackages() {
178    return loadedPackages;
179  }
180
181  // -- Initializations
182  @AllArgsConstructor(access = AccessLevel.PRIVATE)
183  public static class SimpleWorkerContextBuilder {
184
185
186    @With
187    private final String terminologyCachePath;
188    @With
189    private final boolean cacheTerminologyClientErrors;
190    @With
191    private final boolean alwaysUseTerminologyServer;
192    @With
193    private final boolean readOnlyCache;
194
195    @With
196    private final Locale locale;
197
198    @With
199    private final String userAgent;
200
201    @With
202    private final boolean allowLoadingDuplicates;
203
204    public SimpleWorkerContextBuilder() {
205      cacheTerminologyClientErrors = false;
206      alwaysUseTerminologyServer = false;
207      readOnlyCache = false;
208      terminologyCachePath = null;
209      locale = null;
210      userAgent = null;
211      allowLoadingDuplicates = false;
212    }
213
214    private SimpleWorkerContext getSimpleWorkerContextInstance() throws IOException {
215      if (locale != null) {
216        return new SimpleWorkerContext(locale);
217      } else {
218        return new SimpleWorkerContext();
219      }
220    }
221
222    public SimpleWorkerContext build() throws IOException {
223      SimpleWorkerContext context = getSimpleWorkerContextInstance();
224      return build(context);
225    }
226
227    private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException {
228      context.initTS(terminologyCachePath);
229      context.setUserAgent(userAgent);
230      return context;
231    }
232
233    public SimpleWorkerContext fromPackage(NpmPackage pi) throws IOException, FHIRException {
234      SimpleWorkerContext context = getSimpleWorkerContextInstance();
235      context.setAllowLoadingDuplicates(allowLoadingDuplicates);
236      context.loadFromPackage(pi, null);
237      return build(context);
238    }
239
240    public SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException {
241      SimpleWorkerContext context = getSimpleWorkerContextInstance();
242      context.setAllowLoadingDuplicates(true);
243      context.version = pi.getNpm().get("version").getAsString();
244      context.loadFromPackage(pi, loader);
245      context.finishLoading();
246      return build(context);
247    }
248
249    /**
250     * Load the working context from the validation pack
251     *
252     * @param path
253     *           filename of the validation pack
254     * @return
255     * @throws IOException
256     * @throws FileNotFoundException
257     * @throws FHIRException
258     * @throws Exception
259     */
260    public  SimpleWorkerContext fromPack(String path) throws IOException, FHIRException {
261      SimpleWorkerContext context = getSimpleWorkerContextInstance();
262      context.setAllowLoadingDuplicates(allowLoadingDuplicates);
263      context.loadFromPack(path, null);
264      return build(context);
265    }
266
267    public SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException {
268      SimpleWorkerContext context = getSimpleWorkerContextInstance();
269      context.loadFromPack(path, loader);
270      return build(context);
271    }
272
273    public SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
274      SimpleWorkerContext context = getSimpleWorkerContextInstance();
275      context.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null);
276      return build(context);
277    }
278
279    public SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
280      SimpleWorkerContext context = getSimpleWorkerContextInstance();
281      InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
282      context.setAllowLoadingDuplicates(allowLoadingDuplicates);
283      context.loadFromStream(s, null);
284      return build(context);
285    }
286
287    public SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader, PackageVersion pi) throws IOException, FHIRException  {
288      SimpleWorkerContext context = getSimpleWorkerContextInstance();
289      for (String name : source.keySet()) {
290        try {
291          context.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), loader, null, pi);
292        } catch (Exception e) {
293          System.out.println("Error loading "+name+": "+e.getMessage());
294          throw new FHIRException("Error loading "+name+": "+e.getMessage(), e);
295        }
296      }
297      return build(context);
298    }
299    public SimpleWorkerContext fromNothing() throws FHIRException, IOException  {
300      return build();
301    }
302  }
303
304  private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
305    if (name.endsWith(".xml"))
306      loadFromFile(stream, name, loader, filter);
307    else if (name.endsWith(".json"))
308      loadFromFileJson(stream, name, loader, filter, pi);
309    else if (name.equals("version.info"))
310      readVersionInfo(stream);
311    else
312      loadBytes(name, stream);
313  }
314
315  public String connectToTSServer(TerminologyClient client, String log) {
316    try {
317      tlog("Connect to "+client.getAddress());
318      txClient = client;
319      if (log != null && log.endsWith(".txt")) {
320        txLog = new TextClientLogger(log);
321      } else {
322        txLog = new HTMLClientLogger(log);
323      }
324      txClient.setLogger(txLog);
325      txClient.setUserAgent(userAgent);
326
327      final CapabilityStatement capabilitiesStatementQuick = txCache.hasCapabilityStatement() ? txCache.getCapabilityStatement() : txClient.getCapabilitiesStatementQuick();
328      txCache.cacheCapabilityStatement(capabilitiesStatementQuick);
329
330      final TerminologyCapabilities capabilityStatement = txCache.hasTerminologyCapabilities() ? txCache.getTerminologyCapabilities() : txClient.getTerminologyCapabilities();
331      txCache.cacheTerminologyCapabilities(capabilityStatement);
332
333      setTxCaps(capabilityStatement);
334      return capabilitiesStatementQuick.getSoftware().getVersion();
335    } catch (Exception e) {
336      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()), e);
337    }
338  }
339
340  public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws FHIRException {
341    loadFromFile(stream, name, loader, null);
342  }
343  
344        public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws FHIRException {
345                Resource f;
346                try {
347                  if (loader != null)
348                    f = loader.loadBundle(stream, false);
349                  else {
350                    XmlParser xml = new XmlParser();
351                    f = xml.parse(stream);
352                  }
353    } catch (DataFormatException e1) {
354      throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
355    } catch (Exception e1) {
356                        throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
357                }
358                if (f instanceof Bundle) {
359                  Bundle bnd = (Bundle) f;
360                  for (BundleEntryComponent e : bnd.getEntry()) {
361                    if (e.getFullUrl() == null) {
362                      logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)");
363                    }
364              if (filter == null || filter.isOkToLoad(e.getResource())) {
365                String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
366                if (path != null) {
367                  e.getResource().setUserData("path", path);
368                }
369                      cacheResource(e.getResource());
370              }
371                  }
372                } else if (f instanceof CanonicalResource) {
373                  if (filter == null || filter.isOkToLoad(f)) {
374        String path = loader != null ? loader.getResourcePath(f) : null;
375        if (path != null) {
376          f.setUserData("path", path);
377        }
378                    cacheResource(f);
379                  }
380                }
381        }
382
383  private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
384    Bundle f = null;
385    try {
386      if (loader != null)
387        f = loader.loadBundle(stream, true);
388      else {
389        JsonParser json = new JsonParser();
390        Resource r = json.parse(stream);
391        if (r instanceof Bundle)
392          f = (Bundle) r;
393        else if (filter == null || filter.isOkToLoad(f)) {
394          cacheResourceFromPackage(r, pi);
395        }
396      }
397    } catch (FHIRFormatError e1) {
398      throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1);
399    }
400    if (f != null)
401      for (BundleEntryComponent e : f.getEntry()) {
402        if (filter == null || filter.isOkToLoad(e.getResource())) {
403          String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
404          if (path != null) {
405            e.getResource().setUserData("path", path);
406          }
407          cacheResourceFromPackage(e.getResource(), pi);
408        }
409    }
410  }
411
412        private void loadFromPack(String path, IContextResourceLoader loader) throws IOException, FHIRException {
413                loadFromStream(new CSFileInputStream(path), loader);
414        }
415  
416
417  @Override
418  public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws IOException, FHIRException {
419    return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes());
420  }
421  
422  public static String[] defaultTypesToLoad() {
423    // there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions 
424    return new String[] {"CodeSystem", "ValueSet", "ConceptMap", "NamingSystem",
425                         "StructureDefinition", "StructureMap", 
426                         "SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance",
427                         "Questionnaire", "ImplementationGuide", "Measure" };
428  }
429
430  @Override
431  public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws IOException, FHIRException {
432    return loadFromPackageInt(pi, loader, types);
433  }
434 
435  @Override
436  public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws IOException, FHIRException {
437    return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version());
438  }
439  public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws IOException, FHIRException {
440    int t = 0;
441
442    for (String e : pi.dependencies()) {
443      if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) {
444        NpmPackage npm = pcm.loadPackage(e);
445        if (!VersionUtilities.versionsMatch(version, npm.fhirVersion())) {
446          System.out.println(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path));  
447        }
448        t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version());
449      }
450    }
451    t = t + loadFromPackageInt(pi, loader, loader.getTypes());
452    return t;
453  }
454
455
456  public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, String... types) throws IOException, FHIRException {
457    int t = 0;
458    if (progress) {
459      System.out.println("Load Package "+pi.name()+"#"+pi.version());
460    }
461    if (loadedPackages.contains(pi.id()+"#"+pi.version())) {
462      return 0;
463    }
464    loadedPackages.add(pi.id()+"#"+pi.version());
465
466    
467    if ((types == null || types.length == 0) &&  loader != null) {
468      types = loader.getTypes();
469    }
470    if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad()) {
471      // can't lazy load R2 because of valueset/codesystem implementation
472      if (types.length == 0) {
473        types = new String[] { "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" };
474      }
475      for (String s : pi.listResources(types)) {
476        try {
477          loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version()));
478          t++;
479        } catch (Exception e) {
480          throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e);
481        }
482      }
483    } else {
484      if (types.length == 0) {
485        types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measures" };
486      }
487      for (PackageResourceInformation pri : pi.listIndexedResources(types)) {
488        try {
489          registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version()));
490          t++;
491        } catch (FHIRException e) {
492          throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e);
493        }
494      }
495    }
496          for (String s : pi.list("other")) {
497            binaries.put(s, TextFile.streamToBytes(pi.load("other", s)));
498          }
499          if (version == null) {
500            version = pi.version();
501          }
502          return t;
503        }
504
505  public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException {
506    loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null);
507  }
508  
509        private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException {
510                ZipInputStream zip = new ZipInputStream(stream);
511                ZipEntry ze;
512                while ((ze = zip.getNextEntry()) != null) {
513      loadDefinitionItem(ze.getName(), zip, loader, null, null);
514                        zip.closeEntry();
515                }
516                zip.close();
517        }
518
519  private void readVersionInfo(InputStream stream) throws IOException, DefinitionException {
520    byte[] bytes = IOUtils.toByteArray(stream);
521    binaries.put("version.info", bytes);
522
523    String[] vi = new String(bytes).split("\\r?\\n");
524    for (String s : vi) {
525      if (s.startsWith("version=")) {
526        if (version == null)
527        version = s.substring(8);
528        else if (!version.equals(s.substring(8))) 
529          throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8)));
530      }
531      if (s.startsWith("revision="))
532        revision = s.substring(9);
533      if (s.startsWith("date="))
534        date = s.substring(5);
535    }
536  }
537
538        private void loadBytes(String name, InputStream stream) throws IOException {
539    byte[] bytes = IOUtils.toByteArray(stream);
540          binaries.put(name, bytes);
541  }
542
543        @Override
544        public IParser getParser(ParserType type) {
545                switch (type) {
546                case JSON: return newJsonParser();
547                case XML: return newXmlParser();
548                default:
549                        throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
550                }
551        }
552
553        @Override
554        public IParser getParser(String type) {
555                if (type.equalsIgnoreCase("JSON"))
556                        return new JsonParser();
557                if (type.equalsIgnoreCase("XML"))
558                        return new XmlParser();
559                throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
560        }
561
562        @Override
563        public IParser newJsonParser() {
564                return new JsonParser();
565        }
566        @Override
567        public IParser newXmlParser() {
568                return new XmlParser();
569        }
570
571        @Override
572        public IResourceValidator newValidator() throws FHIRException {
573          if (validatorFactory == null)
574            throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED));
575          return validatorFactory.makeValidator(this, xverManager);
576        }
577
578
579
580
581  @Override
582  public List<String> getResourceNames() {
583    List<String> result = new ArrayList<String>();
584    for (StructureDefinition sd : listStructures()) {
585      if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
586        result.add(sd.getName());
587    }
588    Collections.sort(result);
589    return result;
590  }
591
592  @Override
593  public List<String> getTypeNames() {
594    List<String> result = new ArrayList<String>();
595    for (StructureDefinition sd : listStructures()) {
596      if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
597        result.add(sd.getName());
598    }
599    Collections.sort(result);
600    return result;
601  }
602
603  @Override
604  public String getAbbreviation(String name) {
605    return "xxx";
606  }
607
608  @Override
609  public boolean isDatatype(String typeSimple) {
610    // TODO Auto-generated method stub
611    return false;
612  }
613
614  @Override
615  public boolean isResource(String t) {
616    StructureDefinition sd;
617    try {
618      sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
619    } catch (Exception e) {
620      return false;
621    }
622    if (sd == null)
623      return false;
624    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
625      return false;
626    return sd.getKind() == StructureDefinitionKind.RESOURCE;
627  }
628
629  @Override
630  public boolean hasLinkFor(String typeSimple) {
631    return false;
632  }
633
634  @Override
635  public String getLinkFor(String corePath, String typeSimple) {
636    return null;
637  }
638
639  @Override
640  public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
641    return null;
642  }
643
644  @Override
645  public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
646    return null;
647  }
648
649  @Override
650  public String getLinkForProfile(StructureDefinition profile, String url) {
651    return null;
652  }
653
654  public Questionnaire getQuestionnaire() {
655    return questionnaire;
656  }
657
658  public void setQuestionnaire(Questionnaire questionnaire) {
659    this.questionnaire = questionnaire;
660  }
661
662  @Override
663  public List<StructureDefinition> allStructures() {
664    List<StructureDefinition> result = new ArrayList<StructureDefinition>();
665    Set<StructureDefinition> set = new HashSet<StructureDefinition>();
666    for (StructureDefinition sd : listStructures()) {
667      if (!set.contains(sd)) {
668        try {
669          generateSnapshot(sd);
670          // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
671        } catch (Exception e) {
672          System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
673          if (true) {
674            e.printStackTrace();
675          }
676        }
677        result.add(sd);
678        set.add(sd);
679      }
680    }
681    return result;
682  }
683
684
685  public void loadBinariesFromFolder(String folder) throws IOException {
686    for (String n : new File(folder).list()) {
687      loadBytes(n, new FileInputStream(Utilities.path(folder, n)));
688    }
689  }
690  
691  public void loadBinariesFromFolder(NpmPackage pi) throws IOException {
692    for (String n : pi.list("other")) {
693      loadBytes(n, pi.load("other", n));
694    }
695  }
696  
697  public void loadFromFolder(String folder) throws IOException {
698    for (String n : new File(folder).list()) {
699      if (n.endsWith(".json")) 
700        loadFromFile(Utilities.path(folder, n), new JsonParser());
701      else if (n.endsWith(".xml")) 
702        loadFromFile(Utilities.path(folder, n), new XmlParser());
703    }
704  }
705  
706  private void loadFromFile(String filename, IParser p) {
707        Resource r; 
708        try {
709                r = p.parse(new FileInputStream(filename));
710      if (r.getResourceType() == ResourceType.Bundle) {
711        for (BundleEntryComponent e : ((Bundle) r).getEntry()) {
712          cacheResource(e.getResource());
713        }
714     } else {
715       cacheResource(r);
716     }
717        } catch (Exception e) {
718        return;
719    }
720  }
721
722  @Override
723  public boolean prependLinks() {
724    return false;
725  }
726
727  @Override
728  public boolean hasCache() {
729    return true;
730  }
731
732  @Override
733  public String getVersion() {
734    return version;
735  }
736
737  
738  public List<StructureMap> findTransformsforSource(String url) {
739    List<StructureMap> res = new ArrayList<StructureMap>();
740    for (StructureMap map : listTransforms()) {
741      boolean match = false;
742      boolean ok = true;
743      for (StructureMapStructureComponent t : map.getStructure()) {
744        if (t.getMode() == StructureMapModelMode.SOURCE) {
745          match = match || t.getUrl().equals(url);
746          ok = ok && t.getUrl().equals(url);
747        }
748      }
749      if (match && ok)
750        res.add(map);
751    }
752    return res;
753  }
754
755  public IValidatorFactory getValidatorFactory() {
756    return validatorFactory;
757  }
758
759  public void setValidatorFactory(IValidatorFactory validatorFactory) {
760    this.validatorFactory = validatorFactory;
761  }
762
763  @Override
764  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
765    T r = super.fetchResource(class_, uri);
766    if (r instanceof StructureDefinition) {
767      StructureDefinition p = (StructureDefinition)r;
768      try {
769        generateSnapshot(p);
770      } catch (Exception e) {
771        // not sure what to do in this case?
772        System.out.println("Unable to generate snapshot for "+uri+": "+e.getMessage());
773      }
774    }
775    return r;
776  }
777  
778  @Override
779  public StructureDefinition fetchRawProfile(String uri) {
780    StructureDefinition r = super.fetchResource(StructureDefinition.class, uri);
781    return r;
782  }
783  
784  @Override
785  public void generateSnapshot(StructureDefinition p) throws FHIRException {
786    generateSnapshot(p, false);
787  }
788  
789  @Override
790  public void generateSnapshot(StructureDefinition p, boolean logical) throws FHIRException {
791    if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p) ) && (logical || p.getKind() != StructureDefinitionKind.LOGICAL)) {
792      if (!p.hasBaseDefinition())
793        throw new DefinitionException(formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
794      StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition());
795      if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
796        sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
797      }
798      if (sd == null) {
799        throw new DefinitionException(formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
800      }
801      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
802      List<String> errors = new ArrayList<String>();
803      ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
804      pu.setAutoFixSliceNames(true);
805      pu.setThrowException(false);
806      if (xverManager == null) {
807        xverManager = new XVerExtensionManager(this);
808      }
809      pu.setXver(xverManager);
810      if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
811        pu.sortDifferential(sd, p, p.getUrl(), errors, true);
812      }
813      pu.setDebug(false);
814      for (String err : errors)
815        msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
816      pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName());
817      for (ValidationMessage msg : msgs) {
818        if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)
819          throw new DefinitionException(formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
820      }
821      if (!p.hasSnapshot())
822        throw new FHIRException(formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
823      pu = null;
824    }
825  }
826
827  // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind.
828  private boolean isProfileNeedsRegenerate(StructureDefinition p) {
829    boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse");
830    if (needs) {
831      p.setUserData("hack.regnerated", "yes");
832    }
833    return needs;
834  }
835
836  public boolean isIgnoreProfileErrors() {
837    return ignoreProfileErrors;
838  }
839
840  public void setIgnoreProfileErrors(boolean ignoreProfileErrors) {
841    this.ignoreProfileErrors = ignoreProfileErrors;
842  }
843
844  public String listMapUrls() {
845    return Utilities.listCanonicalUrls(transforms.keys());
846  }
847
848  public boolean isProgress() {
849    return progress;
850  }
851
852  public void setProgress(boolean progress) {
853    this.progress = progress;
854  }
855
856  public void setClock(TimeTracker tt) {
857    clock = tt;
858  }
859
860  public boolean isCanNoTS() {
861    return canNoTS;
862  }
863
864  public void setCanNoTS(boolean canNoTS) {
865    this.canNoTS = canNoTS;
866  }
867
868  public XVerExtensionManager getXVer() {
869    if (xverManager == null) {
870      xverManager = new XVerExtensionManager(this);
871    }
872   return xverManager;
873  }
874  
875  public void cachePackage(PackageVersion packageDetails, List<PackageVersion> dependencies) {
876    // nothing yet
877  }
878
879  @Override
880  public boolean hasPackage(String id, String ver) {
881    return loadedPackages.contains(id+"#"+ver);
882  }
883
884  public boolean hasPackage(String idAndver) {
885    return loadedPackages.contains(idAndver);
886  }
887
888  @Override
889  public void cachePackage(PackageDetails packageDetails, List<PackageVersion> dependencies) {
890    // TODO Auto-generated method stub    
891  }
892
893  @Override
894  public boolean hasPackage(PackageVersion pack) {
895    return false;
896  }
897
898  @Override
899  public PackageDetails getPackage(PackageVersion pack) {
900    return null;
901  }
902
903}
904