View Javadoc
1   package ca.uhn.fhir.validation;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 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 java.io.ByteArrayInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.StringReader;
28  import java.io.UnsupportedEncodingException;
29  import java.nio.charset.Charset;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.Map;
34  import java.util.Set;
35  
36  import javax.xml.XMLConstants;
37  import javax.xml.transform.Source;
38  import javax.xml.transform.stream.StreamSource;
39  import javax.xml.validation.Schema;
40  import javax.xml.validation.SchemaFactory;
41  import javax.xml.validation.Validator;
42  
43  import org.apache.commons.io.IOUtils;
44  import org.apache.commons.io.input.BOMInputStream;
45  import org.w3c.dom.ls.LSInput;
46  import org.w3c.dom.ls.LSResourceResolver;
47  import org.xml.sax.SAXException;
48  import org.xml.sax.SAXParseException;
49  
50  import ca.uhn.fhir.context.ConfigurationException;
51  import ca.uhn.fhir.model.api.Bundle;
52  import ca.uhn.fhir.model.api.IResource;
53  import ca.uhn.fhir.model.dstu.resource.OperationOutcome.Issue;
54  import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
55  import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
56  
57  class SchemaBaseValidator implements IValidator {
58  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaBaseValidator.class);
59  	private static final Set<String> SCHEMA_NAMES;
60  
61  	static {
62  		HashSet<String> sn = new HashSet<String>();
63  		sn.add("xml.xsd");
64  		sn.add("xhtml1-strict.xsd");
65  		sn.add("fhir-single.xsd");
66  		sn.add("tombstone.xsd");
67  		sn.add("opensearch.xsd");
68  		sn.add("opensearchscore.xsd");
69  		sn.add("xmldsig-core-schema.xsd");
70  		SCHEMA_NAMES = Collections.unmodifiableSet(sn);
71  	}
72  
73  	private Map<String, Schema> myKeyToSchema = new HashMap<String, Schema>();
74  
75  	private void doValidate(ValidationContext<?> theContext, String schemaName) {
76  		Schema schema = loadSchema("dstu", schemaName);
77  
78  		try {
79  			Validator validator = schema.newValidator();
80  			MyErrorHandler handler = new MyErrorHandler(theContext);
81  			validator.setErrorHandler(handler);
82  			String encodedResource = theContext.getXmlEncodedResource();
83  
84  			// ourLog.info(new FhirContext().newXmlParser().setPrettyPrint(true).encodeBundleToString((Bundle) theContext.getResource()));
85  
86  			validator.validate(new StreamSource(new StringReader(encodedResource)));
87  		} catch (SAXException e) {
88  			throw new ConfigurationException("Could not apply schema file", e);
89  		} catch (IOException e) {
90  			// This shouldn't happen since we're using a string source
91  			throw new ConfigurationException("Could not load/parse schema file", e);
92  		}
93  	}
94  
95  	private Schema loadSchema(String theVersion, String theSchemaName) {
96  		String key = theVersion + "-" + theSchemaName;
97  
98  		synchronized (myKeyToSchema) {
99  			Schema schema = myKeyToSchema.get(key);
100 			if (schema != null) {
101 				return schema;
102 			}
103 
104 			Source baseSource = loadXml("dstu", null, theSchemaName);
105 
106 			SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
107 			schemaFactory.setResourceResolver(new MyResourceResolver("dstu"));
108 
109 			try {
110 				schema = schemaFactory.newSchema(new Source[] { baseSource });
111 			} catch (SAXException e) {
112 				throw new ConfigurationException("Could not load/parse schema file", e);
113 			}
114 			myKeyToSchema.put(key, schema);
115 			return schema;
116 		}
117 	}
118 
119 	private Source loadXml(String theVersion, String theSystemId, String theSchemaName) {
120 		String pathToBase = "ca/uhn/fhir/model/" + theVersion + "/schema/" + theSchemaName;
121 		ourLog.debug("Going to load resource: {}", pathToBase);
122 		InputStream baseIs = FhirValidator.class.getClassLoader().getResourceAsStream(pathToBase);
123 		if (baseIs == null) {
124 			throw new ValidationFailureException("No FHIR-BASE schema found");
125 		}
126 		 baseIs = new BOMInputStream(baseIs, false);
127 		 InputStreamReader baseReader = new InputStreamReader(baseIs, Charset.forName("UTF-8"));
128 		 Source baseSource = new StreamSource(baseReader, theSystemId);
129 
130 //		String schema;
131 //		try {
132 //			schema = IOUtils.toString(baseIs, Charset.forName("UTF-8"));
133 //		} catch (IOException e) {
134 //			throw new InternalErrorException(e);
135 //		}
136 //
137 //		ourLog.info("Schema is:\n{}", schema);
138 //		
139 //		Source baseSource = new StreamSource(new StringReader(schema), theSystemId);
140 //		Source baseSource = new StreamSource(baseIs, theSystemId);
141 		return baseSource;
142 	}
143 
144 	@Override
145 	public void validateBundle(ValidationContext<Bundle> theContext) {
146 		doValidate(theContext, "fhir-atom-single.xsd");
147 	}
148 
149 	@Override
150 	public void validateResource(ValidationContext<IResource> theContext) {
151 		doValidate(theContext, "fhir-single.xsd");
152 	}
153 
154 	private static class MyErrorHandler implements org.xml.sax.ErrorHandler {
155 
156 		private ValidationContext<?> myContext;
157 
158 		public MyErrorHandler(ValidationContext<?> theContext) {
159 			myContext = theContext;
160 		}
161 
162 		private void addIssue(SAXParseException theException, IssueSeverityEnum severity) {
163 			Issue issue = myContext.getOperationOutcome().addIssue();
164 			issue.setSeverity(severity);
165 			issue.setDetails(theException.getLocalizedMessage());
166 			issue.addLocation().setValue("Line[" + theException.getLineNumber() + "] Col[" + theException.getColumnNumber() + "]");
167 		}
168 
169 		@Override
170 		public void error(SAXParseException theException) throws SAXException {
171 			addIssue(theException, IssueSeverityEnum.ERROR);
172 		}
173 
174 		@Override
175 		public void fatalError(SAXParseException theException) throws SAXException {
176 			addIssue(theException, IssueSeverityEnum.FATAL);
177 		}
178 
179 		@Override
180 		public void warning(SAXParseException theException) throws SAXException {
181 			addIssue(theException, IssueSeverityEnum.WARNING);
182 		}
183 
184 	}
185 
186 	private static final class MyResourceResolver implements LSResourceResolver {
187 		private String myVersion;
188 
189 		private MyResourceResolver(String theVersion) {
190 			myVersion = theVersion;
191 		}
192 
193 		@Override
194 		public LSInput resolveResource(String theType, String theNamespaceURI, String thePublicId, String theSystemId, String theBaseURI) {
195 			if (theSystemId != null && SCHEMA_NAMES.contains(theSystemId)) {
196 				LSInputImpl input = new LSInputImpl();
197 				input.setPublicId(thePublicId);
198 				input.setSystemId(theSystemId);
199 				input.setBaseURI(theBaseURI);
200 				String pathToBase = "ca/uhn/fhir/model/" + myVersion + "/schema/" + theSystemId;
201 				
202 				ourLog.debug("Loading referenced schema file: " + pathToBase);
203 				
204 				InputStream baseIs = FhirValidator.class.getClassLoader().getResourceAsStream(pathToBase);
205 				if (baseIs == null) {
206 					throw new ValidationFailureException("No FHIR-BASE schema found");
207 				}
208 
209 				input.setByteStream(baseIs);
210 				
211 				return input;
212 
213 			}
214 
215 			throw new ConfigurationException("Unknown schema: " + theBaseURI);
216 		}
217 	}
218 
219 }