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}