001package ca.uhn.fhir.validation.schematron; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2018 University Health Network 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.rest.api.EncodingEnum; 025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 026import ca.uhn.fhir.util.BundleUtil; 027import ca.uhn.fhir.validation.*; 028import com.helger.commons.error.IError; 029import com.helger.commons.error.list.IErrorList; 030import com.helger.schematron.ISchematronResource; 031import com.helger.schematron.SchematronHelper; 032import com.helger.schematron.xslt.SchematronResourceSCH; 033import org.apache.commons.io.IOUtils; 034import org.hl7.fhir.instance.model.api.IBaseBundle; 035import org.hl7.fhir.instance.model.api.IBaseResource; 036import org.oclc.purl.dsdl.svrl.SchematronOutputType; 037 038import javax.xml.transform.stream.StreamSource; 039import java.io.InputStream; 040import java.io.StringReader; 041import java.util.HashMap; 042import java.util.List; 043import java.util.Locale; 044import java.util.Map; 045 046/** 047 * This class is only used using reflection from {@link SchematronProvider} in order 048 * to be truly optional. 049 */ 050public class SchematronBaseValidator implements IValidatorModule { 051 052 private Map<Class<? extends IBaseResource>, ISchematronResource> myClassToSchematron = new HashMap<Class<? extends IBaseResource>, ISchematronResource>(); 053 private FhirContext myCtx; 054 055 public SchematronBaseValidator(FhirContext theContext) { 056 myCtx = theContext; 057 } 058 059 @Override 060 public void validateResource(IValidationContext<IBaseResource> theCtx) { 061 062 if (theCtx.getResource() instanceof IBaseBundle) { 063 IBaseBundle bundle = (IBaseBundle) theCtx.getResource(); 064 List<IBaseResource> subResources = BundleUtil.toListOfResources(myCtx, bundle); 065 for (IBaseResource nextSubResource : subResources) { 066 validateResource(ValidationContext.subContext(theCtx, nextSubResource)); 067 } 068 } 069 070 ISchematronResource sch = getSchematron(theCtx); 071 String resourceAsString; 072 if (theCtx.getResourceAsStringEncoding() == EncodingEnum.XML) { 073 resourceAsString = theCtx.getResourceAsString(); 074 } else { 075 resourceAsString = theCtx.getFhirContext().newXmlParser().encodeResourceToString(theCtx.getResource()); 076 } 077 StreamSource source = new StreamSource(new StringReader(resourceAsString)); 078 079 SchematronOutputType results = SchematronHelper.applySchematron(sch, source); 080 if (results == null) { 081 return; 082 } 083 084 IErrorList errors = SchematronHelper.convertToErrorList(results, theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName()); 085 086 if (errors.getAllErrors().containsOnlySuccess()) { 087 return; 088 } 089 090 for (IError next : errors) { 091 ResultSeverityEnum severity; 092 if (next.isFailure()) { 093 severity = ResultSeverityEnum.ERROR; 094 } else if (next.isError()) { 095 severity = ResultSeverityEnum.FATAL; 096 } else if (next.isNoError()) { 097 severity = ResultSeverityEnum.WARNING; 098 } else { 099 continue; 100 } 101 102 String details = next.getAsString(Locale.getDefault()); 103 104 SingleValidationMessage message = new SingleValidationMessage(); 105 message.setMessage(details); 106 message.setLocationLine(next.getErrorLocation().getLineNumber()); 107 message.setLocationCol(next.getErrorLocation().getColumnNumber()); 108 message.setLocationString(next.getErrorLocation().getAsString()); 109 message.setSeverity(severity); 110 theCtx.addValidationMessage(message); 111 } 112 113 } 114 115 private ISchematronResource getSchematron(IValidationContext<IBaseResource> theCtx) { 116 Class<? extends IBaseResource> resource = theCtx.getResource().getClass(); 117 Class<? extends IBaseResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition().getImplementingClass(); 118 119 return getSchematronAndCache(theCtx, baseResourceClass); 120 } 121 122 private ISchematronResource getSchematronAndCache(IValidationContext<IBaseResource> theCtx, Class<? extends IBaseResource> theClass) { 123 synchronized (myClassToSchematron) { 124 ISchematronResource retVal = myClassToSchematron.get(theClass); 125 if (retVal != null) { 126 return retVal; 127 } 128 129 String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName().toLowerCase() 130 + ".sch"; 131 InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase); 132 try { 133 if (baseIs == null) { 134 throw new InternalErrorException("Failed to load schematron for resource '" + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName() + "'. " 135 + SchemaBaseValidator.RESOURCES_JAR_NOTE); 136 } 137 } finally { 138 IOUtils.closeQuietly(baseIs); 139 } 140 141 retVal = SchematronResourceSCH.fromClassPath(pathToBase); 142 myClassToSchematron.put(theClass, retVal); 143 return retVal; 144 } 145 } 146 147}