001package ca.uhn.fhir.validation.schematron; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2017 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 java.io.InputStream; 024import java.io.StringReader; 025import java.util.*; 026 027import javax.xml.transform.stream.StreamSource; 028 029import org.apache.commons.io.IOUtils; 030import org.hl7.fhir.instance.model.api.IBaseBundle; 031import org.hl7.fhir.instance.model.api.IBaseResource; 032import org.oclc.purl.dsdl.svrl.SchematronOutputType; 033 034import com.phloc.commons.error.IResourceError; 035import com.phloc.commons.error.IResourceErrorGroup; 036import com.phloc.schematron.ISchematronResource; 037import com.phloc.schematron.SchematronHelper; 038import com.phloc.schematron.xslt.SchematronResourceSCH; 039 040import ca.uhn.fhir.context.FhirContext; 041import ca.uhn.fhir.rest.api.EncodingEnum; 042import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 043import ca.uhn.fhir.util.BundleUtil; 044import ca.uhn.fhir.validation.*; 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 IResourceErrorGroup errors = SchematronHelper.convertToResourceErrorGroup(results, theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName()); 085 086 if (errors.getAllErrors().containsOnlySuccess()) { 087 return; 088 } 089 090 for (IResourceError next : errors.getAllErrors().getAllResourceErrors()) { 091 ResultSeverityEnum severity; 092 switch (next.getErrorLevel()) { 093 case ERROR: 094 severity = ResultSeverityEnum.ERROR; 095 break; 096 case FATAL_ERROR: 097 severity = ResultSeverityEnum.FATAL; 098 break; 099 case WARN: 100 severity = ResultSeverityEnum.WARNING; 101 break; 102 case INFO: 103 case SUCCESS: 104 default: 105 continue; 106 } 107 108 String details = next.getAsString(Locale.getDefault()); 109 110 SingleValidationMessage message = new SingleValidationMessage(); 111 message.setMessage(details); 112 message.setLocationLine(next.getLocation().getLineNumber()); 113 message.setLocationCol(next.getLocation().getColumnNumber()); 114 message.setLocationString(next.getLocation().getAsString()); 115 message.setSeverity(severity); 116 theCtx.addValidationMessage(message); 117 } 118 119 } 120 121 private ISchematronResource getSchematron(IValidationContext<IBaseResource> theCtx) { 122 Class<? extends IBaseResource> resource = theCtx.getResource().getClass(); 123 Class<? extends IBaseResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition().getImplementingClass(); 124 125 return getSchematronAndCache(theCtx, baseResourceClass); 126 } 127 128 private ISchematronResource getSchematronAndCache(IValidationContext<IBaseResource> theCtx, Class<? extends IBaseResource> theClass) { 129 synchronized (myClassToSchematron) { 130 ISchematronResource retVal = myClassToSchematron.get(theClass); 131 if (retVal != null) { 132 return retVal; 133 } 134 135 String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName().toLowerCase() 136 + ".sch"; 137 InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase); 138 try { 139 if (baseIs == null) { 140 throw new InternalErrorException("Failed to load schematron for resource '" + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName() + "'. " 141 + SchemaBaseValidator.RESOURCES_JAR_NOTE); 142 } 143 } finally { 144 IOUtils.closeQuietly(baseIs); 145 } 146 147 retVal = SchematronResourceSCH.fromClassPath(pathToBase); 148 myClassToSchematron.put(theClass, retVal); 149 return retVal; 150 } 151 } 152 153}