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