1 package ca.uhn.fhir.validation;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
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
131
132
133
134
135
136
137
138
139
140
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 }