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}