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