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.apache.commons.io.IOUtils;
34  import org.hl7.fhir.instance.model.api.IBaseBundle;
35  import org.hl7.fhir.instance.model.api.IBaseResource;
36  import org.oclc.purl.dsdl.svrl.SchematronOutputType;
37  
38  import javax.xml.transform.stream.StreamSource;
39  import java.io.InputStream;
40  import java.io.StringReader;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Locale;
44  import java.util.Map;
45  
46  /**
47   * This class is only used using reflection from {@link SchematronProvider} in order
48   * to be truly optional.
49   */
50  public class SchematronBaseValidator implements IValidatorModule {
51  
52  	private Map<Class<? extends IBaseResource>, ISchematronResource> myClassToSchematron = new HashMap<Class<? extends IBaseResource>, ISchematronResource>();
53  	private FhirContext myCtx;
54  
55  	public SchematronBaseValidator(FhirContext theContext) {
56  		myCtx = theContext;
57  	}
58  
59  	@Override
60  	public void validateResource(IValidationContext<IBaseResource> theCtx) {
61  
62  		if (theCtx.getResource() instanceof IBaseBundle) {
63  			IBaseBundle bundle = (IBaseBundle) theCtx.getResource();
64  			List<IBaseResource> subResources = BundleUtil.toListOfResources(myCtx, bundle);
65  			for (IBaseResource nextSubResource : subResources) {
66  				validateResource(ValidationContext.subContext(theCtx, nextSubResource));
67  			}
68  		}
69  		
70  		ISchematronResource sch = getSchematron(theCtx);
71  		String resourceAsString;
72  		if (theCtx.getResourceAsStringEncoding() == EncodingEnum.XML) {
73  			resourceAsString = theCtx.getResourceAsString();
74  		} else {
75  			resourceAsString = theCtx.getFhirContext().newXmlParser().encodeResourceToString(theCtx.getResource());
76  		}
77  		StreamSource source = new StreamSource(new StringReader(resourceAsString));
78  
79  		SchematronOutputType results = SchematronHelper.applySchematron(sch, source);
80  		if (results == null) {
81  			return;
82  		}
83  
84  		IErrorList errors = SchematronHelper.convertToErrorList(results, theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName());
85  
86  		if (errors.getAllErrors().containsOnlySuccess()) {
87  			return;
88  		}
89  
90  		for (IError next : errors) {
91  			ResultSeverityEnum severity;
92  			if (next.isFailure()) {
93  				severity = ResultSeverityEnum.ERROR;
94  			} else if (next.isError()) {
95  				severity = ResultSeverityEnum.FATAL;
96  			} else if (next.isNoError()) {
97  				severity = ResultSeverityEnum.WARNING;
98  			} else {
99  				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 }