001package org.hl7.fhir.validation;
002
003import java.io.ByteArrayInputStream;
004import java.io.File;
005import java.io.IOException;
006import java.util.ArrayList;
007import java.util.List;
008import java.util.Map;
009
010import javax.xml.parsers.DocumentBuilder;
011import javax.xml.parsers.DocumentBuilderFactory;
012import javax.xml.parsers.ParserConfigurationException;
013
014import org.hl7.fhir.convertors.loaders.loaderR5.NullLoaderKnowledgeProviderR5;
015import org.hl7.fhir.convertors.loaders.loaderR5.R2016MayToR5Loader;
016import org.hl7.fhir.convertors.loaders.loaderR5.R2ToR5Loader;
017import org.hl7.fhir.convertors.loaders.loaderR5.R3ToR5Loader;
018import org.hl7.fhir.convertors.loaders.loaderR5.R4BToR5Loader;
019import org.hl7.fhir.convertors.loaders.loaderR5.R4ToR5Loader;
020import org.hl7.fhir.convertors.loaders.loaderR5.R5ToR5Loader;
021import org.hl7.fhir.exceptions.FHIRException;
022import org.hl7.fhir.r5.context.IWorkerContext;
023import org.hl7.fhir.r5.context.SimpleWorkerContext;
024import org.hl7.fhir.r5.model.OperationOutcome;
025import org.hl7.fhir.r5.renderers.RendererFactory;
026import org.hl7.fhir.r5.renderers.utils.RenderingContext;
027import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
028import org.hl7.fhir.r5.utils.EOperationOutcome;
029import org.hl7.fhir.r5.utils.FHIRPathEngine;
030import org.hl7.fhir.r5.utils.OperationOutcomeUtilities;
031import org.hl7.fhir.utilities.Utilities;
032import org.hl7.fhir.utilities.VersionUtilities;
033import org.hl7.fhir.utilities.i18n.I18nConstants;
034import org.hl7.fhir.utilities.validation.ValidationMessage;
035import org.hl7.fhir.validation.cli.utils.AsteriskFilter;
036import org.hl7.fhir.validation.cli.utils.Common;
037import org.w3c.dom.Document;
038import org.xml.sax.SAXException;
039
040//TODO find a home for these and clean it up
041public class ValidatorUtils {
042
043  protected static void grabNatives(Map<String, byte[]> source, Map<String, byte[]> binaries, String prefix) {
044    for (Map.Entry<String, byte[]> e : source.entrySet()) {
045      if (e.getKey().endsWith(".zip"))
046        binaries.put(prefix + "#" + e.getKey(), e.getValue());
047    }
048  }
049
050  protected static IWorkerContext.IContextResourceLoader loaderForVersion(String version) {
051    if (Utilities.noString(version)) {
052      return null;
053    }
054    if (VersionUtilities.isR2Ver(version)) {
055      return new R2ToR5Loader(new String[]{"Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5());
056    } 
057    if (VersionUtilities.isR2BVer(version)) {
058      return new R2016MayToR5Loader(new String[]{"Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5()); // special case
059    }
060    if (VersionUtilities.isR3Ver(version)) {
061      return new R3ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5());
062    }
063    if (VersionUtilities.isR4Ver(version)) {
064      return new R4ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5(), version);
065    }
066    if (VersionUtilities.isR4BVer(version)) {
067      return new R4BToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5(), version);
068    }
069    if (VersionUtilities.isR5Ver(version)) {
070      return new R5ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5());
071    }
072    return null;
073  }
074
075  protected static Document parseXml(byte[] cnt) throws ParserConfigurationException, SAXException, IOException {
076    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
077    // xxe protection
078    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
079    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
080    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
081    factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
082    factory.setXIncludeAware(false);
083    factory.setExpandEntityReferences(false);
084    factory.setNamespaceAware(true);
085    DocumentBuilder builder = factory.newDocumentBuilder();
086    return builder.parse(new ByteArrayInputStream(cnt));
087  }
088
089  protected static List<ValidationMessage> filterMessages(List<ValidationMessage> messages) {
090    List<ValidationMessage> filteredValidation = new ArrayList<ValidationMessage>();
091    for (ValidationMessage e : messages) {
092      if (!filteredValidation.contains(e))
093        filteredValidation.add(e);
094    }
095    filteredValidation.sort(null);
096    return filteredValidation;
097  }
098
099  protected static OperationOutcome messagesToOutcome(List<ValidationMessage> messages, SimpleWorkerContext context, FHIRPathEngine fpe) throws IOException, FHIRException, EOperationOutcome {
100    OperationOutcome op = new OperationOutcome();
101    for (ValidationMessage vm : filterMessages(messages)) {
102      try {
103        fpe.parse(vm.getLocation());
104      } catch (Exception e) {
105        System.out.println("Internal error in location for message: '" + e.getMessage() + "', loc = '" + vm.getLocation() + "', err = '" + vm.getMessage() + "'");
106      }
107      op.getIssue().add(OperationOutcomeUtilities.convertToIssue(vm, op));
108    }
109    if (!op.hasIssue()) {
110      op.addIssue().setSeverity(OperationOutcome.IssueSeverity.INFORMATION).setCode(OperationOutcome.IssueType.INFORMATIONAL).getDetails().setText(context.formatMessage(I18nConstants.ALL_OK));
111    }
112    RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE);
113    RendererFactory.factory(op, rc).render(op);
114    return op;
115  }
116
117  /**
118   * Parses passed in resource path, adding any found references to the passed in list.
119   *
120   * @return {@link Boolean#TRUE} if more than one reference is found.
121   */
122  static boolean extractReferences(String name, List<String> refs, SimpleWorkerContext context) throws IOException {
123    if (Common.isNetworkPath(name)) {
124      refs.add(name);
125    } else if (Common.isWildcardPath(name)) {
126      AsteriskFilter filter = new AsteriskFilter(name);
127      File[] files = new File(filter.getDir()).listFiles(filter);
128      for (File file : files) {
129        refs.add(file.getPath());
130      }
131    } else {
132      File file = new File(name);
133      if (!file.exists()) {
134        if (System.console() != null) {
135          System.console().printf(context.formatMessage(I18nConstants.BAD_FILE_PATH_ERROR, name));
136        } else {
137          System.out.println(context.formatMessage(I18nConstants.BAD_FILE_PATH_ERROR, name));
138        }
139        throw new IOException("File " + name + " does not exist");
140      }
141
142      if (file.isFile()) {
143        refs.add(name);
144      } else {
145        for (int i = 0; i < file.listFiles().length; i++) {
146          File[] fileList = file.listFiles();
147          if (fileList[i].isFile())
148            refs.add(fileList[i].getPath());
149        }
150      }
151    }
152    return refs.size() > 1;
153  }
154
155  /**
156   * Iterates through the list of passed in sources, extracting all references and populated them in the passed in list.
157   *
158   * @return {@link Boolean#TRUE} if more than one reference is found.
159   */
160  public static boolean parseSources(List<String> sources, List<String> refs, SimpleWorkerContext context) throws IOException {
161    boolean multipleRefsFound = sources.size() > 1;
162    for (String source : sources) {
163      multipleRefsFound |= extractReferences(source, refs, context);
164    }
165    return multipleRefsFound;
166  }
167}