Coverage Report - ca.uhn.hl7v2.validation.impl.XMLSchemaRule
 
Classes in this File Line Coverage Branch Coverage Complexity
XMLSchemaRule
82%
51/62
61%
11/18
2.25
XMLSchemaRule$ErrorHandler
80%
8/10
33%
1/3
2.25
 
 1  
 /**
 2  
 The contents of this file are subject to the Mozilla Public License Version 1.1 
 3  
 (the "License"); you may not use this file except in compliance with the License. 
 4  
 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
 5  
 Software distributed under the License is distributed on an "AS IS" basis, 
 6  
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
 7  
 specific language governing rights and limitations under the License. 
 8  
 
 9  
 The Original Code is "XMLSchemaRule.java".  Description: 
 10  
 "Validate hl7 v2.xml messages against a given xml-schema." 
 11  
 
 12  
 The Initial Developer of the Original Code is University Health Network. Copyright (C) 
 13  
 2004.  All Rights Reserved. 
 14  
 
 15  
 Contributor(s): ______________________________________. 
 16  
 
 17  
 Alternatively, the contents of this file may be used under the terms of the 
 18  
 GNU General Public License (the "GPL"), in which case the provisions of the GPL are 
 19  
 applicable instead of those above.  If you wish to allow use of your version of this 
 20  
 file only under the terms of the GPL and not to allow others to use your version 
 21  
 of this file under the MPL, indicate your decision by deleting  the provisions above 
 22  
 and replace  them with the notice and other provisions required by the GPL License.  
 23  
 If you do not delete the provisions above, a recipient may use your version of 
 24  
 this file under either the MPL or the GPL. 
 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  
  * Validates HL7 version 2 messages encoded according to the HL7 XML Encoding Syntax against XML
 50  
  * schemas provided by hl7.org.
 51  
  * <p>
 52  
  * The XML schema to validate against is determined as follows:
 53  
  * <ul>
 54  
  * <li>if the XML document contains a schemaLocation that points to a valid file, this file is
 55  
  * assumed to contain the schema definition
 56  
  * <li>the location configured using {@link #setSchemaLocations(Map)}
 57  
  * </ul>
 58  
  * <p>
 59  
  * The validation fails, if
 60  
  * <ul>
 61  
  * <li>no valid schema file could be found
 62  
  * <li>the default namespace of the XML document is not <code>urn:hl7-org:v2xml</code>
 63  
  * <li>the document does not validate against the XML schema file foudn as described above
 64  
  * </ul>
 65  
  * 
 66  
  * @author Nico Vannieuwenhuyze
 67  
  * @author Christian Ohr
 68  
  */
 69  6
 @SuppressWarnings("serial")
 70  6
 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  1
         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  6
         private class ErrorHandler implements DOMErrorHandler {
 81  
                 private List<ValidationException> validationErrors;
 82  
 
 83  4
                 public ErrorHandler(List<ValidationException> validationErrors) {
 84  4
                         super();
 85  4
                         this.validationErrors = validationErrors;
 86  4
                 }
 87  
 
 88  
                 public boolean handleError(DOMError error) {
 89  3
                         validationErrors.add(new ValidationException(getSeverity(error) + error.getMessage()));
 90  3
                         return true;
 91  
                 }
 92  
 
 93  
                 private String getSeverity(DOMError error) {
 94  3
                         switch (error.getSeverity()) {
 95  
                         case DOMError.SEVERITY_WARNING:
 96  0
                                 return "WARNING: ";
 97  
                         case DOMError.SEVERITY_ERROR:
 98  3
                                 return "ERROR: ";
 99  
                         default:
 100  0
                                 return "FATAL ERROR: ";
 101  
                         }
 102  
                 }
 103  
 
 104  
         }
 105  
 
 106  
         /**
 107  
          * Test/validate a given xml document against a hl7 v2.xml schema.
 108  
          * <p>
 109  
          * Before the schema is applied, the namespace is verified because otherwise schema validation
 110  
          * fails anyway.
 111  
          * <p>
 112  
          * If a schema file is specified in the xml message and the file can be located on the disk this
 113  
          * one is used. If no schema has been specified, or the file can't be located, the locations
 114  
          * property is used.
 115  
          * 
 116  
          * @param msg the xml message (as string) to be validated.
 117  
          * @return ValidationException[] an array of validation exceptions, which is zero-sized when no
 118  
          *         validation errors occured.
 119  
          */
 120  
         public ValidationException[] apply(String msg) {
 121  6
                 List<ValidationException> validationErrors = new ArrayList<ValidationException>();
 122  
                 try {
 123  
                         // parse the incoming string into a dom document - no schema validation yet
 124  6
                         Document doc = XMLUtils.parse(msg);
 125  6
                         if (hasCorrectNamespace(doc, validationErrors)) {
 126  6
                                 XMLUtils.validate(doc, getSchemaLocation(doc), new ErrorHandler(validationErrors));
 127  
                         }
 128  2
                 } catch (Exception e) {
 129  2
                         log.error("Unable to validate message: {}", e.getMessage(), e);
 130  2
                         validationErrors.add(new ValidationException("Unable to validate message "
 131  
                                         + e.getMessage(), e));
 132  4
                 }
 133  
 
 134  6
                 return validationErrors.toArray(new ValidationException[validationErrors.size()]);
 135  
 
 136  
         }
 137  
 
 138  
         /**
 139  
          * 
 140  
          * Try to obtain the XML schema file (depending on message version), either as provided in
 141  
          * xsi:schemaLocation, or as provided in the locations property or in a subdirectory of the
 142  
          * current dir.
 143  
          * 
 144  
          * @param doc the DOM document
 145  
          * @return the file name of the schema
 146  
          * @throws IOException
 147  
          */
 148  
         private String getSchemaLocation(Document doc) throws IOException {
 149  6
                 String schemaFilename = extractSchemaLocation(doc);
 150  6
                 if (schemaFilename == null) {
 151  6
                         if ((schemaFilename = staticSchema(doc)) == null) {
 152  2
                                 throw new IOException(
 153  
                                                 "Unable to retrieve a valid schema to use for message validation");
 154  
                         }
 155  
                 }
 156  4
                 return schemaFilename;
 157  
 
 158  
         }
 159  
 
 160  
         private String extractSchemaLocation(Document doc) {
 161  6
                 String schemaFileName = null;
 162  6
                 log.debug("Trying to retrieve the schema defined in the xml document");
 163  6
                 Element element = doc.getDocumentElement();
 164  6
                 String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
 165  
                                 "schemaLocation");
 166  6
                 if (schemaLocation.length() > 0) {
 167  2
                         log.debug("Schema defined in document: {}", schemaLocation);
 168  2
                         String schemaItems[] = schemaLocation.split(" ");
 169  2
                         if (schemaItems.length == 2) {
 170  0
                                 File f = new File(schemaItems[1]);
 171  0
                                 if (f.exists()) {
 172  0
                                         schemaFileName = schemaItems[1];
 173  0
                                         log.debug("Schema defined in document points to a valid file");
 174  
                                 } else {
 175  0
                                         log.warn("Schema file defined in xml document not found on disk: {}",
 176  
                                                         schemaItems[1]);
 177  
                                 }
 178  
                         }
 179  2
                 } else {
 180  4
                         log.debug("No schema location defined in the xml document");
 181  
                 }
 182  
 
 183  6
                 return schemaFileName;
 184  
         }
 185  
 
 186  
         private String staticSchema(Document doc) {
 187  6
                 String schemaFilename = null;
 188  6
                 log.debug("Lookup HL7 version in MSH-12 to know which default schema to use");
 189  6
                 NodeList nodeList = doc.getElementsByTagNameNS(DEFAULT_NS, "VID.1");
 190  6
                 if (nodeList.getLength() == 1) {
 191  6
                         Node versionNode = nodeList.item(0);
 192  6
                         Version version = Version.versionOf(versionNode.getFirstChild().getNodeValue());
 193  6
                         String schemaLocation = locations.get(version.getVersion());
 194  
 
 195  
                         // use the message structure as schema file name (root)
 196  6
                         schemaFilename = schemaLocation + "/" + doc.getDocumentElement().getNodeName() + ".xsd";
 197  6
                         File myFile = new File(schemaFilename);
 198  6
                         if (myFile.exists()) {
 199  4
                                 log.debug("Valid schema file present: {}", schemaFilename);
 200  
                         } else {
 201  2
                                 log.warn("Schema file not found on disk: {}", schemaFilename);
 202  2
                                 schemaFilename = null;
 203  
                         }
 204  6
                 } else {
 205  0
                         log.error("HL7 version node MSH-12 not present - unable to determine default schema");
 206  
                 }
 207  6
                 return schemaFilename;
 208  
         }
 209  
 
 210  
         /**
 211  
          * @return <code>true</code> if default namespace is set properly
 212  
          */
 213  
         private boolean hasCorrectNamespace(Document domDocumentToValidate,
 214  
                         List<ValidationException> validationErrors) {
 215  6
                 String nsUri = domDocumentToValidate.getDocumentElement().getNamespaceURI();
 216  6
                 boolean ok = DEFAULT_NS.equals(nsUri);
 217  6
                 if (!ok) {
 218  0
                         ValidationException e = new ValidationException(
 219  
                                         "The default namespace of the XML document is incorrect - should be "
 220  
                                                         + DEFAULT_NS + " but was " + nsUri);
 221  0
                         validationErrors.add(e);
 222  0
                         log.error(e.getMessage());
 223  
                 }
 224  6
                 return ok;
 225  
         }
 226  
 
 227  
         public void setSchemaLocations(Map<String, String> locations) {
 228  6
                 this.locations = locations;
 229  6
         }
 230  
 
 231  
         Map<String, String> getSchemaLocations() {
 232  2
                 return locations;
 233  
         }
 234  
 
 235  
         /**
 236  
          * @see ca.uhn.hl7v2.validation.Rule#getDescription()
 237  
          */
 238  
         public String getDescription() {
 239  0
                 return DESCRIPTION;
 240  
         }
 241  
 
 242  
         /**
 243  
          * @see ca.uhn.hl7v2.validation.Rule#getSectionReference()
 244  
          */
 245  
         public String getSectionReference() {
 246  0
                 return SECTION_REFERENCE;
 247  
         }
 248  
 
 249  
 }