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
77%
7/9
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  30
 @SuppressWarnings("serial")
 70  30
 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  5
         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 List<ValidationException> validationErrors;
 82  
 
 83  
                 public ErrorHandler(List<ValidationException> validationErrors) {
 84  20
                         super();
 85  20
                         this.validationErrors = validationErrors;
 86  20
                 }
 87  
 
 88  
                 public boolean handleError(DOMError error) {
 89  15
                         validationErrors.add(new ValidationException(getSeverity(error) + error.getMessage()));
 90  15
                         return true;
 91  
                 }
 92  
 
 93  
                 private String getSeverity(DOMError error) {
 94  15
                         switch (error.getSeverity()) {
 95  
                         case DOMError.SEVERITY_WARNING:
 96  0
                                 return "WARNING: ";
 97  
                         case DOMError.SEVERITY_ERROR:
 98  15
                                 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  30
                 List<ValidationException> validationErrors = new ArrayList<ValidationException>();
 122  
                 try {
 123  
                         // parse the incoming string into a dom document - no schema validation yet
 124  30
                         Document doc = XMLUtils.parse(msg);
 125  30
                         if (hasCorrectNamespace(doc, validationErrors)) {
 126  30
                                 XMLUtils.validate(doc, getSchemaLocation(doc), new ErrorHandler(validationErrors));
 127  
                         }
 128  10
                 } catch (Exception e) {
 129  10
                         log.error("Unable to validate message: {}", e.getMessage(), e);
 130  20
                         validationErrors.add(new ValidationException("Unable to validate message "
 131  10
                                         + e.getMessage(), e));
 132  20
                 }
 133  
 
 134  30
                 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  30
                 String schemaFilename = extractSchemaLocation(doc);
 150  30
                 if (schemaFilename == null) {
 151  30
                         if ((schemaFilename = staticSchema(doc)) == null) {
 152  10
                                 throw new IOException(
 153  
                                                 "Unable to retrieve a valid schema to use for message validation");
 154  
                         }
 155  
                 }
 156  20
                 return schemaFilename;
 157  
 
 158  
         }
 159  
 
 160  
         private String extractSchemaLocation(Document doc) {
 161  30
                 String schemaFileName = null;
 162  30
                 log.debug("Trying to retrieve the schema defined in the xml document");
 163  30
                 Element element = doc.getDocumentElement();
 164  30
                 String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
 165  
                                 "schemaLocation");
 166  30
                 if (schemaLocation.length() > 0) {
 167  10
                         log.debug("Schema defined in document: {}", schemaLocation);
 168  10
                         String schemaItems[] = schemaLocation.split(" ");
 169  10
                         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  10
                 } else {
 180  20
                         log.debug("No schema location defined in the xml document");
 181  
                 }
 182  
 
 183  30
                 return schemaFileName;
 184  
         }
 185  
 
 186  
         private String staticSchema(Document doc) {
 187  30
                 String schemaFilename = null;
 188  30
                 log.debug("Lookup HL7 version in MSH-12 to know which default schema to use");
 189  30
                 NodeList nodeList = doc.getElementsByTagNameNS(DEFAULT_NS, "VID.1");
 190  30
                 if (nodeList.getLength() == 1) {
 191  30
                         Node versionNode = nodeList.item(0);
 192  30
                         Version version = Version.versionOf(versionNode.getFirstChild().getNodeValue());
 193  30
                         String schemaLocation = locations.get(version.getVersion());
 194  
 
 195  
                         // use the message structure as schema file name (root)
 196  30
                         schemaFilename = schemaLocation + "/" + doc.getDocumentElement().getNodeName() + ".xsd";
 197  30
                         File myFile = new File(schemaFilename);
 198  30
                         if (myFile.exists()) {
 199  20
                                 log.debug("Valid schema file present: {}", schemaFilename);
 200  
                         } else {
 201  10
                                 log.warn("Schema file not found on disk: {}", schemaFilename);
 202  10
                                 schemaFilename = null;
 203  
                         }
 204  30
                 } else {
 205  0
                         log.error("HL7 version node MSH-12 not present - unable to determine default schema");
 206  
                 }
 207  30
                 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  30
                 String nsUri = domDocumentToValidate.getDocumentElement().getNamespaceURI();
 216  30
                 boolean ok = DEFAULT_NS.equals(nsUri);
 217  30
                 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  30
                 return ok;
 225  
         }
 226  
 
 227  
         public void setSchemaLocations(Map<String, String> locations) {
 228  30
                 this.locations = locations;
 229  30
         }
 230  
 
 231  
         Map<String, String> getSchemaLocations() {
 232  10
                 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  
 }