1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package ca.uhn.hl7v2.validation.impl;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.w3c.dom.DOMError;
38 import org.w3c.dom.DOMErrorHandler;
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43
44 import ca.uhn.hl7v2.Version;
45 import ca.uhn.hl7v2.util.XMLUtils;
46 import ca.uhn.hl7v2.validation.ValidationException;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 @SuppressWarnings("serial")
70 public class XMLSchemaRule extends AbstractEncodingRule {
71
72 private static final String SECTION_REFERENCE = "http://www.hl7.org/Special/committees/xml/drafts/v2xml.html";
73 private static final String DESCRIPTION = "Checks that an encoded XML message validates against a declared or default schema "
74 + "(it is recommended to use the standard HL7 schema, but this is not enforced here).";
75 private static final Logger log = LoggerFactory.getLogger(XMLSchemaRule.class);
76 private static final String DEFAULT_NS = "urn:hl7-org:v2xml";
77
78 private Map<String, String> locations;
79
80 private static class ErrorHandler implements DOMErrorHandler {
81 private final List<ValidationException> validationErrors;
82
83 public ErrorHandler(List<ValidationException> validationErrors) {
84 super();
85 this.validationErrors = validationErrors;
86 }
87
88 public boolean handleError(DOMError error) {
89 validationErrors.add(new ValidationException(getSeverity(error) + error.getMessage()));
90 return true;
91 }
92
93 private String getSeverity(DOMError error) {
94 switch (error.getSeverity()) {
95 case DOMError.SEVERITY_WARNING:
96 return "WARNING: ";
97 case DOMError.SEVERITY_ERROR:
98 return "ERROR: ";
99 default:
100 return "FATAL ERROR: ";
101 }
102 }
103
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public ValidationException[] apply(String msg) {
121 List<ValidationException> validationErrors = new ArrayList<>();
122 try {
123
124 Document doc = XMLUtils.parse(msg);
125 if (hasCorrectNamespace(doc, validationErrors)) {
126 XMLUtils.validate(doc, getSchemaLocation(doc), new ErrorHandler(validationErrors));
127 }
128 } catch (Exception e) {
129 log.error("Unable to validate message: {}", e.getMessage(), e);
130 validationErrors.add(new ValidationException("Unable to validate message "
131 + e.getMessage(), e));
132 }
133
134 return validationErrors.toArray(new ValidationException[0]);
135
136 }
137
138
139
140
141
142
143
144
145
146
147
148 private String getSchemaLocation(Document doc) throws IOException {
149 String schemaFilename = extractSchemaLocation(doc);
150 if (schemaFilename == null) {
151 if ((schemaFilename = staticSchema(doc)) == null) {
152 throw new IOException(
153 "Unable to retrieve a valid schema to use for message validation");
154 }
155 }
156 return schemaFilename;
157
158 }
159
160 private String extractSchemaLocation(Document doc) {
161 String schemaFileName = null;
162 log.debug("Trying to retrieve the schema defined in the xml document");
163 Element element = doc.getDocumentElement();
164 String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
165 "schemaLocation");
166 if (schemaLocation.length() > 0) {
167 log.debug("Schema defined in document: {}", schemaLocation);
168 String[] schemaItems = schemaLocation.split(" ");
169 if (schemaItems.length == 2) {
170 File f = new File(schemaItems[1]);
171 if (f.exists()) {
172 schemaFileName = schemaItems[1];
173 log.debug("Schema defined in document points to a valid file");
174 } else {
175 log.warn("Schema file defined in xml document not found on disk: {}",
176 schemaItems[1]);
177 }
178 }
179 } else {
180 log.debug("No schema location defined in the xml document");
181 }
182
183 return schemaFileName;
184 }
185
186 private String staticSchema(Document doc) {
187 String schemaFilename = null;
188 log.debug("Lookup HL7 version in MSH-12 to know which default schema to use");
189 NodeList nodeList = doc.getElementsByTagNameNS(DEFAULT_NS, "VID.1");
190 if (nodeList.getLength() == 1) {
191 Node versionNode = nodeList.item(0);
192 Version version = Version.versionOf(versionNode.getFirstChild().getNodeValue());
193 String schemaLocation = locations.get(version.getVersion());
194
195
196 schemaFilename = schemaLocation + "/" + doc.getDocumentElement().getNodeName() + ".xsd";
197 File myFile = new File(schemaFilename);
198 if (myFile.exists()) {
199 log.debug("Valid schema file present: {}", schemaFilename);
200 } else {
201 log.warn("Schema file not found on disk: {}", schemaFilename);
202 schemaFilename = null;
203 }
204 } else {
205 log.error("HL7 version node MSH-12 not present - unable to determine default schema");
206 }
207 return schemaFilename;
208 }
209
210
211
212
213 private boolean hasCorrectNamespace(Document domDocumentToValidate,
214 List<ValidationException> validationErrors) {
215 String nsUri = domDocumentToValidate.getDocumentElement().getNamespaceURI();
216 boolean ok = DEFAULT_NS.equals(nsUri);
217 if (!ok) {
218 ValidationException e = new ValidationException(
219 "The default namespace of the XML document is incorrect - should be "
220 + DEFAULT_NS + " but was " + nsUri);
221 validationErrors.add(e);
222 log.error(e.getMessage());
223 }
224 return ok;
225 }
226
227 public void setSchemaLocations(Map<String, String> locations) {
228 this.locations = locations;
229 }
230
231 Map<String, String> getSchemaLocations() {
232 return locations;
233 }
234
235
236
237
238 public String getDescription() {
239 return DESCRIPTION;
240 }
241
242
243
244
245 public String getSectionReference() {
246 return SECTION_REFERENCE;
247 }
248
249 }