View Javadoc
1   package ca.uhn.fhir.validation.schematron;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.FhirContext;
24  import ca.uhn.fhir.rest.api.EncodingEnum;
25  import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
26  import ca.uhn.fhir.util.BundleUtil;
27  import ca.uhn.fhir.validation.*;
28  import com.helger.commons.error.IError;
29  import com.helger.commons.error.list.IErrorList;
30  import com.helger.schematron.ISchematronResource;
31  import com.helger.schematron.SchematronHelper;
32  import com.helger.schematron.xslt.SchematronResourceSCH;
33  import org.hl7.fhir.instance.model.api.IBaseBundle;
34  import org.hl7.fhir.instance.model.api.IBaseResource;
35  import org.oclc.purl.dsdl.svrl.SchematronOutputType;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  import javax.xml.transform.stream.StreamSource;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.StringReader;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.Locale;
46  import java.util.Map;
47  
48  /**
49   * This class is only used using reflection from {@link SchematronProvider} in order
50   * to be truly optional.
51   */
52  public class SchematronBaseValidator implements IValidatorModule {
53  
54  	private static final Logger ourLog = LoggerFactory.getLogger(SchematronBaseValidator.class);
55  	private final Map<Class<? extends IBaseResource>, ISchematronResource> myClassToSchematron = new HashMap<>();
56  	private FhirContext myCtx;
57  
58  	/**
59  	 * Constructor
60  	 */
61  	public SchematronBaseValidator(FhirContext theContext) {
62  		myCtx = theContext;
63  	}
64  
65  	@Override
66  	public void validateResource(IValidationContext<IBaseResource> theCtx) {
67  
68  		if (theCtx.getResource() instanceof IBaseBundle) {
69  			IBaseBundle bundle = (IBaseBundle) theCtx.getResource();
70  			List<IBaseResource> subResources = BundleUtil.toListOfResources(myCtx, bundle);
71  			for (IBaseResource nextSubResource : subResources) {
72  				validateResource(ValidationContext.subContext(theCtx, nextSubResource));
73  			}
74  		}
75  
76  		ISchematronResource sch = getSchematron(theCtx);
77  		String resourceAsString;
78  		if (theCtx.getResourceAsStringEncoding() == EncodingEnum.XML) {
79  			resourceAsString = theCtx.getResourceAsString();
80  		} else {
81  			resourceAsString = theCtx.getFhirContext().newXmlParser().encodeResourceToString(theCtx.getResource());
82  		}
83  		StreamSource source = new StreamSource(new StringReader(resourceAsString));
84  
85  		SchematronOutputType results = SchematronHelper.applySchematron(sch, source);
86  		if (results == null) {
87  			return;
88  		}
89  
90  		IErrorList errors = SchematronHelper.convertToErrorList(results, theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName());
91  
92  		if (errors.getAllErrors().containsOnlySuccess()) {
93  			return;
94  		}
95  
96  		for (IError next : errors) {
97  			ResultSeverityEnum severity;
98  			if (next.isFailure()) {
99  				severity = ResultSeverityEnum.ERROR;
100 			} else if (next.isError()) {
101 				severity = ResultSeverityEnum.FATAL;
102 			} else if (next.isNoError()) {
103 				severity = ResultSeverityEnum.WARNING;
104 			} else {
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.getErrorLocation().getLineNumber());
113 			message.setLocationCol(next.getErrorLocation().getColumnNumber());
114 			message.setLocationString(next.getErrorLocation().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 			try (InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase)) {
138 				if (baseIs == null) {
139 					throw new InternalErrorException("Failed to load schematron for resource '" + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName() + "'. "
140 						+ SchemaBaseValidator.RESOURCES_JAR_NOTE);
141 				}
142 			} catch (IOException e) {
143 				ourLog.error("Failed to close stream", e);
144 			}
145 
146 			retVal = SchematronResourceSCH.fromClassPath(pathToBase);
147 			myClassToSchematron.put(theClass, retVal);
148 			return retVal;
149 		}
150 	}
151 }