Coverage Report - ca.uhn.hl7v2.conf.parser.ProfileParser
 
Classes in this File Line Coverage Branch Coverage Complexity
ProfileParser
85%
185/216
87%
58/66
4.059
ProfileParser$1
20%
1/5
0%
0/2
4.059
 
 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 "ProfileParser.java".  Description: 
 10  
 "Parses a Message Profile XML document into a RuntimeProfile object." 
 11  
 
 12  
 The Initial Developer of the Original Code is University Health Network. Copyright (C) 
 13  
 2003.  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  
 
 28  
 package ca.uhn.hl7v2.conf.parser;
 29  
 
 30  
 import java.io.BufferedReader;
 31  
 import java.io.File;
 32  
 import java.io.FileNotFoundException;
 33  
 import java.io.FileReader;
 34  
 import java.io.IOException;
 35  
 import java.io.InputStream;
 36  
 
 37  
 import org.slf4j.Logger;
 38  
 import org.slf4j.LoggerFactory;
 39  
 import org.w3c.dom.DOMError;
 40  
 import org.w3c.dom.DOMErrorHandler;
 41  
 import org.w3c.dom.Document;
 42  
 import org.w3c.dom.Element;
 43  
 import org.w3c.dom.Node;
 44  
 import org.w3c.dom.NodeList;
 45  
 
 46  
 import ca.uhn.hl7v2.conf.ProfileException;
 47  
 import ca.uhn.hl7v2.conf.spec.MetaData;
 48  
 import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
 49  
 import ca.uhn.hl7v2.conf.spec.message.AbstractComponent;
 50  
 import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
 51  
 import ca.uhn.hl7v2.conf.spec.message.Component;
 52  
 import ca.uhn.hl7v2.conf.spec.message.DataValue;
 53  
 import ca.uhn.hl7v2.conf.spec.message.Field;
 54  
 import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
 55  
 import ca.uhn.hl7v2.conf.spec.message.Seg;
 56  
 import ca.uhn.hl7v2.conf.spec.message.SegGroup;
 57  
 import ca.uhn.hl7v2.conf.spec.message.StaticDef;
 58  
 import ca.uhn.hl7v2.conf.spec.message.SubComponent;
 59  
 import ca.uhn.hl7v2.util.XMLUtils;
 60  
 
 61  
 /**
 62  
  * <p>
 63  
  * Parses a Message Profile XML document into a RuntimeProfile object. A Message Profile is a formal
 64  
  * description of additional constraints on a message (beyond what is specified in the HL7
 65  
  * specification), usually for a particular system, region, etc. Message profiles are introduced in
 66  
  * HL7 version 2.5 section 2.12. The RuntimeProfile object is simply an object representation of the
 67  
  * profile, which may be used for validating messages or editing the profile.
 68  
  * </p>
 69  
  * <p>
 70  
  * Example usage: <code><pre>
 71  
  *                 // Load the profile from the classpath
 72  
  *      ProfileParser parser = new ProfileParser(false);
 73  
  *      RuntimeProfile profile = parser.parseClasspath("ca/uhn/hl7v2/conf/parser/example_ack.xml");
 74  
  * 
 75  
  *      // Create a message to validate
 76  
  *      String message = "MSH|^~\\&|||||||ACK^A01|1|D|2.4|||||CAN|wrong|F^^HL70001^x^^HL78888|\r"; //note HL7888 doesn't exist
 77  
  *      ACK msg = (ACK) (new PipeParser()).parse(message);
 78  
  *                 
 79  
  *      // Validate
 80  
  *                 HL7Exception[] errors = new DefaultValidator().validate(msg, profile.getMessage());
 81  
  *                 
 82  
  *                 // Each exception is a validation error
 83  
  *                 System.out.println("Validation errors: " + Arrays.asList(errors));
 84  
  * </pre></code>
 85  
  * </p>
 86  
  * 
 87  
  * @author Bryan Tripp
 88  
  */
 89  0
 public class ProfileParser {
 90  
 
 91  
         private static final String PROFILE_XSD = "ca/uhn/hl7v2/conf/parser/message_profile.xsd";
 92  
 
 93  5
         private static final Logger log = LoggerFactory.getLogger(ProfileParser.class);
 94  
 
 95  
         private boolean alwaysValidate;
 96  
         private DOMErrorHandler errorHandler;
 97  
 
 98  
         /**
 99  
          * Creates a new instance of ProfileParser
 100  
          * 
 101  
          * @param alwaysValidate if true, validates all profiles against a local copy of the
 102  
          *            profile XSD; if false, validates against declared grammar (if any)
 103  
          */
 104  75
         public ProfileParser(boolean alwaysValidate) {
 105  
 
 106  75
                 this.alwaysValidate = alwaysValidate;
 107  75
                 this.errorHandler = new DOMErrorHandler() {
 108  
 
 109  
                         public boolean handleError(DOMError error) {
 110  0
                                 if (error.getSeverity() == DOMError.SEVERITY_WARNING) {
 111  0
                                         log.warn("Warning: {}", error.getMessage());
 112  
                                 } else {
 113  0
                                         throw new RuntimeException((Exception) error.getRelatedException());
 114  
                                 }
 115  0
                                 return true;
 116  
                         }
 117  
 
 118  
                 };
 119  75
         }
 120  
 
 121  
 
 122  
         /**
 123  
          * Parses an XML profile string into a RuntimeProfile object.
 124  
          * 
 125  
          * Input is a path pointing to a textual file on the classpath. Note that the file will be read
 126  
          * using the thread context class loader.
 127  
          * 
 128  
          * For example, if you had a file called PROFILE.TXT in package com.foo.stuff, you would pass in
 129  
          * "com/foo/stuff/PROFILE.TXT"
 130  
          * 
 131  
          * @throws IOException If the resource can't be read
 132  
          */
 133  
         public RuntimeProfile parseClasspath(String classPath) throws ProfileException, IOException {
 134  
 
 135  5
                 InputStream stream = Thread.currentThread().getContextClassLoader()
 136  5
                                 .getResourceAsStream(classPath);
 137  5
                 if (stream == null) {
 138  0
                         throw new FileNotFoundException(classPath);
 139  
                 }
 140  
 
 141  5
                 StringBuffer profileString = new StringBuffer();
 142  5
                 byte[] buffer = new byte[1000];
 143  
                 int bytesRead;
 144  3280
                 while ((bytesRead = stream.read(buffer)) > 0) {
 145  3275
                         profileString.append(new String(buffer, 0, bytesRead));
 146  
                 }
 147  
 
 148  5
                 RuntimeProfile profile = new RuntimeProfile();
 149  5
                 Document doc = parseIntoDOM(profileString.toString());
 150  
 
 151  5
                 Element root = doc.getDocumentElement();
 152  5
                 profile.setHL7Version(root.getAttribute("HL7Version"));
 153  
 
 154  
                 // get static definition
 155  5
                 NodeList nl = root.getElementsByTagName("HL7v2xStaticDef");
 156  5
                 Element staticDef = (Element) nl.item(0);
 157  5
                 StaticDef sd = parseStaticProfile(staticDef);
 158  5
                 profile.setMessage(sd);
 159  5
                 return profile;
 160  
         }
 161  
 
 162  
         /**
 163  
          * Parses an XML profile string into a RuntimeProfile object.
 164  
          */
 165  
         public RuntimeProfile parse(String profileString) throws ProfileException {
 166  70
                 RuntimeProfile profile = new RuntimeProfile();
 167  70
                 Document doc = parseIntoDOM(profileString);
 168  
 
 169  70
                 Element root = doc.getDocumentElement();
 170  70
                 profile.setHL7Version(root.getAttribute("HL7Version"));
 171  
 
 172  70
                 NodeList metadataList = root.getElementsByTagName("MetaData");
 173  70
                 if (metadataList.getLength() > 0) {
 174  70
                         Element metadata = (Element) metadataList.item(0);
 175  70
                         String name = metadata.getAttribute("Name");
 176  70
                         profile.setName(name);
 177  
                 }
 178  
 
 179  
                 // get static definition
 180  70
                 NodeList nl = root.getElementsByTagName("HL7v2xStaticDef");
 181  70
                 Element staticDef = (Element) nl.item(0);
 182  70
                 StaticDef sd = parseStaticProfile(staticDef);
 183  70
                 profile.setMessage(sd);
 184  70
                 return profile;
 185  
         }
 186  
 
 187  
         private StaticDef parseStaticProfile(Element elem) throws ProfileException {
 188  75
                 StaticDef message = new StaticDef();
 189  75
                 message.setMsgType(elem.getAttribute("MsgType"));
 190  75
                 message.setEventType(elem.getAttribute("EventType"));
 191  75
                 message.setMsgStructID(elem.getAttribute("MsgStructID"));
 192  75
                 message.setOrderControl(elem.getAttribute("OrderControl"));
 193  75
                 message.setEventDesc(elem.getAttribute("EventDesc"));
 194  75
                 message.setIdentifier(elem.getAttribute("Identifier"));
 195  75
                 message.setRole(elem.getAttribute("Role"));
 196  
 
 197  75
                 Element md = getFirstElementByTagName("MetaData", elem);
 198  75
                 if (md != null)
 199  75
                         message.setMetaData(parseMetaData(md));
 200  
 
 201  75
                 message.setImpNote(getValueOfFirstElement("ImpNote", elem));
 202  75
                 message.setDescription(getValueOfFirstElement("Description", elem));
 203  75
                 message.setReference(getValueOfFirstElement("Reference", elem));
 204  
 
 205  75
                 parseChildren(message, elem);
 206  75
                 return message;
 207  
         }
 208  
 
 209  
         /** Parses metadata element */
 210  
         private MetaData parseMetaData(Element elem) {
 211  75
                 log.debug("ProfileParser.parseMetaData() has been called ... note that this method does nothing.");
 212  75
                 return null;
 213  
         }
 214  
 
 215  
         /**
 216  
          * Parses children (i.e. segment groups, segments) of a segment group or message profile
 217  
          */
 218  
         private void parseChildren(AbstractSegmentContainer parent, Element elem)
 219  
                         throws ProfileException {
 220  165
                 int childIndex = 1;
 221  165
                 NodeList children = elem.getChildNodes();
 222  1560
                 for (int i = 0; i < children.getLength(); i++) {
 223  1395
                         Node n = children.item(i);
 224  1395
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 225  615
                                 Element child = (Element) n;
 226  615
                                 if (child.getNodeName().equalsIgnoreCase("SegGroup")) {
 227  90
                                         SegGroup group = parseSegmentGroupProfile(child);
 228  90
                                         parent.setChild(childIndex++, group);
 229  90
                                 } else if (child.getNodeName().equalsIgnoreCase("Segment")) {
 230  450
                                         Seg segment = parseSegmentProfile(child);
 231  450
                                         parent.setChild(childIndex++, segment);
 232  
                                 }
 233  
                         }
 234  
                 }
 235  165
         }
 236  
 
 237  
         /** Parses a segment group profile */
 238  
         private SegGroup parseSegmentGroupProfile(Element elem) throws ProfileException {
 239  90
                 SegGroup group = new SegGroup();
 240  90
                 log.debug("Parsing segment group profile: " + elem.getAttribute("Name"));
 241  
 
 242  90
                 parseProfileStuctureData(group, elem);
 243  
 
 244  90
                 parseChildren(group, elem);
 245  90
                 return group;
 246  
         }
 247  
 
 248  
         /** Parses a segment profile */
 249  
         private Seg parseSegmentProfile(Element elem) throws ProfileException {
 250  450
                 Seg segment = new Seg();
 251  450
                 log.debug("Parsing segment profile: " + elem.getAttribute("Name"));
 252  
 
 253  450
                 parseProfileStuctureData(segment, elem);
 254  
 
 255  450
                 int childIndex = 1;
 256  450
                 NodeList children = elem.getChildNodes();
 257  15660
                 for (int i = 0; i < children.getLength(); i++) {
 258  15210
                         Node n = children.item(i);
 259  15210
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 260  7375
                                 Element child = (Element) n;
 261  7375
                                 if (child.getNodeName().equalsIgnoreCase("Field")) {
 262  7370
                                         Field field = parseFieldProfile(child);
 263  7370
                                         segment.setField(childIndex++, field);
 264  
                                 }
 265  
                         }
 266  
                 }
 267  
 
 268  450
                 return segment;
 269  
         }
 270  
 
 271  
         /** Parse common data in profile structure (eg SegGroup, Segment) */
 272  
         private void parseProfileStuctureData(ProfileStructure struct, Element elem)
 273  
                         throws ProfileException {
 274  540
                 struct.setName(elem.getAttribute("Name"));
 275  540
                 struct.setLongName(elem.getAttribute("LongName"));
 276  540
                 struct.setUsage(elem.getAttribute("Usage"));
 277  540
                 String min = elem.getAttribute("Min");
 278  540
                 String max = elem.getAttribute("Max");
 279  
                 try {
 280  540
                         struct.setMin(Short.parseShort(min));
 281  540
                         if (max.indexOf('*') >= 0) {
 282  115
                                 struct.setMax((short) -1);
 283  
                         } else {
 284  425
                                 struct.setMax(Short.parseShort(max));
 285  
                         }
 286  0
                 } catch (NumberFormatException e) {
 287  0
                         throw new ProfileException("Min and max must be short integers: " + min + ", " + max, e);
 288  540
                 }
 289  
 
 290  540
                 struct.setImpNote(getValueOfFirstElement("ImpNote", elem));
 291  540
                 struct.setDescription(getValueOfFirstElement("Description", elem));
 292  540
                 struct.setReference(getValueOfFirstElement("Reference", elem));
 293  540
                 struct.setPredicate(getValueOfFirstElement("Predicate", elem));
 294  540
         }
 295  
 
 296  
         /** Parses a field profile */
 297  
         private Field parseFieldProfile(Element elem) throws ProfileException {
 298  7370
                 Field field = new Field();
 299  7370
                 log.debug("  Parsing field profile: " + elem.getAttribute("Name"));
 300  
 
 301  7370
                 field.setUsage(elem.getAttribute("Usage"));
 302  7370
                 String itemNo = elem.getAttribute("ItemNo");
 303  7370
                 String min = elem.getAttribute("Min");
 304  7370
                 String max = elem.getAttribute("Max");
 305  
 
 306  
                 try {
 307  7370
                         if (itemNo.length() > 0) {
 308  7230
                                 field.setItemNo(Short.parseShort(itemNo));
 309  
                         }
 310  0
                 } catch (NumberFormatException e) {
 311  0
                         throw new ProfileException("Invalid ItemNo: " + itemNo + "( for name "
 312  0
                                         + elem.getAttribute("Name") + ")", e);
 313  7370
                 } // try-catch
 314  
 
 315  
                 try {
 316  7370
                         field.setMin(Short.parseShort(min));
 317  7370
                         if (max.indexOf('*') >= 0) {
 318  1770
                                 field.setMax((short) -1);
 319  
                         } else {
 320  5600
                                 field.setMax(Short.parseShort(max));
 321  
                         }
 322  0
                 } catch (NumberFormatException e) {
 323  0
                         throw new ProfileException("Min and max must be short integers: " + min + ", " + max, e);
 324  7370
                 }
 325  
 
 326  7370
                 parseAbstractComponentData(field, elem);
 327  
 
 328  7370
                 int childIndex = 1;
 329  7370
                 NodeList children = elem.getChildNodes();
 330  82050
                 for (int i = 0; i < children.getLength(); i++) {
 331  74680
                         Node n = children.item(i);
 332  74680
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 333  33655
                                 Element child = (Element) n;
 334  33655
                                 if (child.getNodeName().equalsIgnoreCase("Component")) {
 335  26385
                                         Component comp = (Component) parseComponentProfile(child, false);
 336  26385
                                         field.setComponent(childIndex++, comp);
 337  
                                 }
 338  
                         }
 339  
                 }
 340  
 
 341  7370
                 return field;
 342  
         }
 343  
 
 344  
         /** Parses a component profile */
 345  
         private AbstractComponent<?> parseComponentProfile(Element elem, boolean isSubComponent)
 346  
                         throws ProfileException {
 347  48155
                 AbstractComponent<?> comp = null;
 348  48155
                 if (isSubComponent) {
 349  21770
                         log.debug("      Parsing subcomp profile: " + elem.getAttribute("Name"));
 350  21770
                         comp = new SubComponent();
 351  
                 } else {
 352  26385
                         log.debug("    Parsing comp profile: " + elem.getAttribute("Name"));
 353  26385
                         comp = new Component();
 354  
 
 355  26385
                         int childIndex = 1;
 356  26385
                         NodeList children = elem.getChildNodes();
 357  102140
                         for (int i = 0; i < children.getLength(); i++) {
 358  75755
                                 Node n = children.item(i);
 359  75755
                                 if (n.getNodeType() == Node.ELEMENT_NODE) {
 360  24685
                                         Element child = (Element) n;
 361  24685
                                         if (child.getNodeName().equalsIgnoreCase("SubComponent")) {
 362  21770
                                                 SubComponent subcomp = (SubComponent) parseComponentProfile(child, true);
 363  21770
                                                 ((Component) comp).setSubComponent(childIndex++, subcomp);
 364  
                                         }
 365  
                                 }
 366  
                         }
 367  
                 }
 368  
 
 369  48155
                 parseAbstractComponentData(comp, elem);
 370  
 
 371  48155
                 return comp;
 372  
         }
 373  
 
 374  
         /**
 375  
          * Parses common features of AbstractComponents (ie field, component, subcomponent)
 376  
          */
 377  
         private void parseAbstractComponentData(AbstractComponent<?> comp, Element elem)
 378  
                         throws ProfileException {
 379  55525
                 comp.setName(elem.getAttribute("Name"));
 380  55525
                 comp.setUsage(elem.getAttribute("Usage"));
 381  55525
                 comp.setDatatype(elem.getAttribute("Datatype"));
 382  55525
                 String length = elem.getAttribute("Length");
 383  55525
                 if (length != null && length.length() > 0) {
 384  
                         try {
 385  55120
                                 comp.setLength(Long.parseLong(length));
 386  0
                         } catch (NumberFormatException e) {
 387  0
                                 throw new ProfileException("Length must be a long integer: " + length, e);
 388  55120
                         }
 389  
                 }
 390  55525
                 comp.setConstantValue(elem.getAttribute("ConstantValue"));
 391  55525
                 String table = elem.getAttribute("Table");
 392  55525
                 if (table != null && table.length() > 0) {
 393  
                         try {
 394  20280
                                 comp.setTable(table);
 395  0
                         } catch (NumberFormatException e) {
 396  0
                                 throw new ProfileException("Table must be a short integer: " + table, e);
 397  20280
                         }
 398  
                 }
 399  
 
 400  55525
                 comp.setImpNote(getValueOfFirstElement("ImpNote", elem));
 401  55525
                 comp.setDescription(getValueOfFirstElement("Description", elem));
 402  55525
                 comp.setReference(getValueOfFirstElement("Reference", elem));
 403  55525
                 comp.setPredicate(getValueOfFirstElement("Predicate", elem));
 404  
 
 405  55525
                 int dataValIndex = 0;
 406  55525
                 NodeList children = elem.getChildNodes();
 407  239160
                 for (int i = 0; i < children.getLength(); i++) {
 408  183635
                         Node n = children.item(i);
 409  183635
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 410  64055
                                 Element child = (Element) n;
 411  64055
                                 if (child.getNodeName().equalsIgnoreCase("DataValues")) {
 412  2910
                                         DataValue val = new DataValue();
 413  2910
                                         val.setExValue(child.getAttribute("ExValue"));
 414  2910
                                         comp.setDataValues(dataValIndex++, val);
 415  
                                 }
 416  
                         }
 417  
                 }
 418  
 
 419  55525
         }
 420  
 
 421  
         /** Parses profile string into DOM document */
 422  
         private Document parseIntoDOM(String profileString) throws ProfileException {
 423  
                 try {
 424  75
                         Document doc = XMLUtils.parse(profileString, true);
 425  75
                         if (alwaysValidate)
 426  10
                                 XMLUtils.validate(doc, PROFILE_XSD, errorHandler);
 427  75
                         return doc;
 428  0
                 } catch (Exception e) {
 429  0
                         throw new ProfileException("Exception parsing message profile: " + e.getMessage(), e);
 430  
                 }
 431  
         }
 432  
 
 433  
         /**
 434  
          * Returns the first child element of the given parent that matches the given tag name. Returns
 435  
          * null if no instance of the expected element is present.
 436  
          */
 437  
         private Element getFirstElementByTagName(String name, Element parent) {
 438  224560
                 NodeList nl = parent.getElementsByTagName(name);
 439  224560
                 Element ret = null;
 440  224560
                 if (nl.getLength() > 0) {
 441  18705
                         ret = (Element) nl.item(0);
 442  
                 }
 443  224560
                 return ret;
 444  
         }
 445  
 
 446  
         /**
 447  
          * Gets the result of getFirstElementByTagName() and returns the value of that element.
 448  
          */
 449  
         private String getValueOfFirstElement(String name, Element parent) throws ProfileException {
 450  224485
                 Element el = getFirstElementByTagName(name, parent);
 451  224485
                 String val = null;
 452  224485
                 if (el != null) {
 453  
                         try {
 454  18630
                                 Node n = el.getFirstChild();
 455  18630
                                 if (n.getNodeType() == Node.TEXT_NODE) {
 456  18630
                                         val = n.getNodeValue();
 457  
                                 }
 458  0
                         } catch (Exception e) {
 459  0
                                 throw new ProfileException("Unable to get value of node " + name, e);
 460  18630
                         }
 461  
                 }
 462  224485
                 return val;
 463  
         }
 464  
 
 465  
         public static void main(String args[]) {
 466  
 
 467  0
                 if (args.length != 1) {
 468  0
                         System.out.println("Usage: ProfileParser profile_file");
 469  0
                         System.exit(1);
 470  
                 }
 471  
 
 472  
                 try {
 473  
                         // File f = new
 474  
                         // File("C:\\Documents and Settings\\bryan\\hapilocal\\hapi\\ca\\uhn\\hl7v2\\conf\\parser\\example_ack.xml");
 475  0
                         File f = new File(args[0]);
 476  
                         @SuppressWarnings("resource")
 477  0
                         BufferedReader in = new BufferedReader(new FileReader(f));
 478  0
                         char[] cbuf = new char[(int) f.length()];
 479  0
                         in.read(cbuf, 0, (int) f.length());
 480  0
                         String xml = String.valueOf(cbuf);
 481  
                         // System.out.println(xml);
 482  
 
 483  0
                         ProfileParser pp = new ProfileParser(true);
 484  0
                         pp.parse(xml);
 485  0
                 } catch (Exception e) {
 486  0
                         e.printStackTrace();
 487  0
                 }
 488  0
         }
 489  
 
 490  
 }