
001package ca.uhn.fhir.validation.schematron; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.i18n.Msg; 025import ca.uhn.fhir.rest.api.EncodingEnum; 026import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 027import ca.uhn.fhir.util.BundleUtil; 028import ca.uhn.fhir.validation.FhirValidator; 029import ca.uhn.fhir.validation.IValidationContext; 030import ca.uhn.fhir.validation.IValidatorModule; 031import ca.uhn.fhir.validation.ResultSeverityEnum; 032import ca.uhn.fhir.validation.SchemaBaseValidator; 033import ca.uhn.fhir.validation.SingleValidationMessage; 034import ca.uhn.fhir.validation.ValidationContext; 035import com.helger.commons.error.IError; 036import com.helger.commons.error.list.IErrorList; 037import com.helger.schematron.ISchematronResource; 038import com.helger.schematron.SchematronHelper; 039import com.helger.schematron.svrl.jaxb.SchematronOutputType; 040import com.helger.schematron.xslt.SchematronResourceSCH; 041import org.hl7.fhir.instance.model.api.IBaseBundle; 042import org.hl7.fhir.instance.model.api.IBaseResource; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046import javax.xml.transform.stream.StreamSource; 047import java.io.IOException; 048import java.io.InputStream; 049import java.io.StringReader; 050import java.util.HashMap; 051import java.util.List; 052import java.util.Locale; 053import java.util.Map; 054 055/** 056 * This class is only used using reflection from {@link SchematronProvider} in order 057 * to be truly optional. 058 */ 059public class SchematronBaseValidator implements IValidatorModule { 060 061 private static final Logger ourLog = LoggerFactory.getLogger(SchematronBaseValidator.class); 062 private final Map<Class<? extends IBaseResource>, ISchematronResource> myClassToSchematron = new HashMap<>(); 063 private FhirContext myCtx; 064 065 /** 066 * Constructor 067 */ 068 public SchematronBaseValidator(FhirContext theContext) { 069 myCtx = theContext; 070 } 071 072 @Override 073 public void validateResource(IValidationContext<IBaseResource> theCtx) { 074 075 if (theCtx.getResource() instanceof IBaseBundle) { 076 IBaseBundle bundle = (IBaseBundle) theCtx.getResource(); 077 List<IBaseResource> subResources = BundleUtil.toListOfResources(myCtx, bundle); 078 for (IBaseResource nextSubResource : subResources) { 079 validateResource(ValidationContext.subContext(theCtx, nextSubResource, theCtx.getOptions())); 080 } 081 } 082 083 ISchematronResource sch = getSchematron(theCtx); 084 String resourceAsString; 085 if (theCtx.getResourceAsStringEncoding() == EncodingEnum.XML) { 086 resourceAsString = theCtx.getResourceAsString(); 087 } else { 088 resourceAsString = theCtx.getFhirContext().newXmlParser().encodeResourceToString(theCtx.getResource()); 089 } 090 StreamSource source = new StreamSource(new StringReader(resourceAsString)); 091 092 SchematronOutputType results = SchematronHelper.applySchematron(sch, source); 093 if (results == null) { 094 return; 095 } 096 097 IErrorList errors = SchematronHelper.convertToErrorList(results, theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName()); 098 099 if (errors.getAllErrors().containsOnlySuccess()) { 100 return; 101 } 102 103 for (IError next : errors) { 104 ResultSeverityEnum severity; 105 if (next.isFailure()) { 106 severity = ResultSeverityEnum.ERROR; 107 } else if (next.isError()) { 108 severity = ResultSeverityEnum.FATAL; 109 } else if (next.isNoError()) { 110 severity = ResultSeverityEnum.WARNING; 111 } else { 112 continue; 113 } 114 115 String details = next.getAsString(Locale.getDefault()); 116 117 SingleValidationMessage message = new SingleValidationMessage(); 118 message.setMessage(details); 119 message.setLocationLine(next.getErrorLocation().getLineNumber()); 120 message.setLocationCol(next.getErrorLocation().getColumnNumber()); 121 message.setLocationString(next.getErrorLocation().getAsString()); 122 message.setSeverity(severity); 123 theCtx.addValidationMessage(message); 124 } 125 126 } 127 128 private ISchematronResource getSchematron(IValidationContext<IBaseResource> theCtx) { 129 Class<? extends IBaseResource> resource = theCtx.getResource().getClass(); 130 Class<? extends IBaseResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition().getImplementingClass(); 131 132 return getSchematronAndCache(theCtx, baseResourceClass); 133 } 134 135 private ISchematronResource getSchematronAndCache(IValidationContext<IBaseResource> theCtx, Class<? extends IBaseResource> theClass) { 136 synchronized (myClassToSchematron) { 137 ISchematronResource retVal = myClassToSchematron.get(theClass); 138 if (retVal != null) { 139 return retVal; 140 } 141 142 String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName().toLowerCase() 143 + ".sch"; 144 try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) { 145 if (baseIs == null) { 146 throw new InternalErrorException(Msg.code(1972) + "Failed to load schematron for resource '" + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName() + "'. " 147 + SchemaBaseValidator.RESOURCES_JAR_NOTE); 148 } 149 } catch (IOException e) { 150 ourLog.error("Failed to close stream", e); 151 } 152 153 retVal = SchematronResourceSCH.fromClassPath(pathToBase); 154 myClassToSchematron.put(theClass, retVal); 155 return retVal; 156 } 157 } 158}