Coverage Report - ca.uhn.hl7v2.conf.parser.ProfileParser
 
Classes in this File Line Coverage Branch Coverage Complexity
ProfileParser
85%
184/214
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  1
         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  14
         public ProfileParser(boolean alwaysValidate) {
 105  
 
 106  14
                 this.alwaysValidate = alwaysValidate;
 107  14
                 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  14
         }
 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  1
                 InputStream stream = Thread.currentThread().getContextClassLoader()
 136  
                                 .getResourceAsStream(classPath);
 137  1
                 if (stream == null) {
 138  0
                         throw new FileNotFoundException(classPath);
 139  
                 }
 140  
 
 141  1
                 StringBuffer profileString = new StringBuffer();
 142  1
                 byte[] buffer = new byte[1000];
 143  
                 int bytesRead;
 144  655
                 while ((bytesRead = stream.read(buffer)) > 0) {
 145  654
                         profileString.append(new String(buffer, 0, bytesRead));
 146  
                 }
 147  
 
 148  1
                 RuntimeProfile profile = new RuntimeProfile();
 149  1
                 Document doc = parseIntoDOM(profileString.toString());
 150  
 
 151  1
                 Element root = doc.getDocumentElement();
 152  1
                 profile.setHL7Version(root.getAttribute("HL7Version"));
 153  
 
 154  
                 // get static definition
 155  1
                 NodeList nl = root.getElementsByTagName("HL7v2xStaticDef");
 156  1
                 Element staticDef = (Element) nl.item(0);
 157  1
                 StaticDef sd = parseStaticProfile(staticDef);
 158  1
                 profile.setMessage(sd);
 159  1
                 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  18
                 RuntimeProfile profile = new RuntimeProfile();
 167  18
                 Document doc = parseIntoDOM(profileString);
 168  
 
 169  18
                 Element root = doc.getDocumentElement();
 170  18
                 profile.setHL7Version(root.getAttribute("HL7Version"));
 171  
 
 172  18
                 NodeList metadataList = root.getElementsByTagName("MetaData");
 173  18
                 if (metadataList.getLength() > 0) {
 174  18
                         Element metadata = (Element) metadataList.item(0);
 175  18
                         String name = metadata.getAttribute("Name");
 176  18
                         profile.setName(name);
 177  
                 }
 178  
 
 179  
                 // get static definition
 180  18
                 NodeList nl = root.getElementsByTagName("HL7v2xStaticDef");
 181  18
                 Element staticDef = (Element) nl.item(0);
 182  18
                 StaticDef sd = parseStaticProfile(staticDef);
 183  18
                 profile.setMessage(sd);
 184  18
                 return profile;
 185  
         }
 186  
 
 187  
         private StaticDef parseStaticProfile(Element elem) throws ProfileException {
 188  19
                 StaticDef message = new StaticDef();
 189  19
                 message.setMsgType(elem.getAttribute("MsgType"));
 190  19
                 message.setEventType(elem.getAttribute("EventType"));
 191  19
                 message.setMsgStructID(elem.getAttribute("MsgStructID"));
 192  19
                 message.setOrderControl(elem.getAttribute("OrderControl"));
 193  19
                 message.setEventDesc(elem.getAttribute("EventDesc"));
 194  19
                 message.setIdentifier(elem.getAttribute("identifier"));
 195  19
                 message.setRole(elem.getAttribute("role"));
 196  
 
 197  19
                 Element md = getFirstElementByTagName("MetaData", elem);
 198  19
                 if (md != null)
 199  19
                         message.setMetaData(parseMetaData(md));
 200  
 
 201  19
                 message.setImpNote(getValueOfFirstElement("ImpNote", elem));
 202  19
                 message.setDescription(getValueOfFirstElement("Description", elem));
 203  19
                 message.setReference(getValueOfFirstElement("Reference", elem));
 204  
 
 205  19
                 parseChildren(message, elem);
 206  19
                 return message;
 207  
         }
 208  
 
 209  
         /** Parses metadata element */
 210  
         private MetaData parseMetaData(Element elem) {
 211  19
                 log.debug("ProfileParser.parseMetaData() has been called ... note that this method does nothing.");
 212  19
                 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  37
                 int childIndex = 1;
 221  37
                 NodeList children = elem.getChildNodes();
 222  352
                 for (int i = 0; i < children.getLength(); i++) {
 223  315
                         Node n = children.item(i);
 224  315
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 225  139
                                 Element child = (Element) n;
 226  139
                                 if (child.getNodeName().equalsIgnoreCase("SegGroup")) {
 227  18
                                         SegGroup group = parseSegmentGroupProfile(child);
 228  18
                                         parent.setChild(childIndex++, group);
 229  18
                                 } else if (child.getNodeName().equalsIgnoreCase("Segment")) {
 230  102
                                         Seg segment = parseSegmentProfile(child);
 231  102
                                         parent.setChild(childIndex++, segment);
 232  
                                 }
 233  
                         }
 234  
                 }
 235  37
         }
 236  
 
 237  
         /** Parses a segment group profile */
 238  
         private SegGroup parseSegmentGroupProfile(Element elem) throws ProfileException {
 239  18
                 SegGroup group = new SegGroup();
 240  18
                 log.debug("Parsing segment group profile: " + elem.getAttribute("Name"));
 241  
 
 242  18
                 parseProfileStuctureData(group, elem);
 243  
 
 244  18
                 parseChildren(group, elem);
 245  18
                 return group;
 246  
         }
 247  
 
 248  
         /** Parses a segment profile */
 249  
         private Seg parseSegmentProfile(Element elem) throws ProfileException {
 250  102
                 Seg segment = new Seg();
 251  102
                 log.debug("Parsing segment profile: " + elem.getAttribute("Name"));
 252  
 
 253  102
                 parseProfileStuctureData(segment, elem);
 254  
 
 255  102
                 int childIndex = 1;
 256  102
                 NodeList children = elem.getChildNodes();
 257  3380
                 for (int i = 0; i < children.getLength(); i++) {
 258  3278
                         Node n = children.item(i);
 259  3278
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 260  1587
                                 Element child = (Element) n;
 261  1587
                                 if (child.getNodeName().equalsIgnoreCase("Field")) {
 262  1586
                                         Field field = parseFieldProfile(child);
 263  1586
                                         segment.setField(childIndex++, field);
 264  
                                 }
 265  
                         }
 266  
                 }
 267  
 
 268  102
                 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  120
                 struct.setName(elem.getAttribute("Name"));
 275  120
                 struct.setLongName(elem.getAttribute("LongName"));
 276  120
                 struct.setUsage(elem.getAttribute("Usage"));
 277  120
                 String min = elem.getAttribute("Min");
 278  120
                 String max = elem.getAttribute("Max");
 279  
                 try {
 280  120
                         struct.setMin(Short.parseShort(min));
 281  120
                         if (max.indexOf('*') >= 0) {
 282  23
                                 struct.setMax((short) -1);
 283  
                         } else {
 284  97
                                 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  120
                 }
 289  
 
 290  120
                 struct.setImpNote(getValueOfFirstElement("ImpNote", elem));
 291  120
                 struct.setDescription(getValueOfFirstElement("Description", elem));
 292  120
                 struct.setReference(getValueOfFirstElement("Reference", elem));
 293  120
                 struct.setPredicate(getValueOfFirstElement("Predicate", elem));
 294  120
         }
 295  
 
 296  
         /** Parses a field profile */
 297  
         private Field parseFieldProfile(Element elem) throws ProfileException {
 298  1586
                 Field field = new Field();
 299  1586
                 log.debug("  Parsing field profile: " + elem.getAttribute("Name"));
 300  
 
 301  1586
                 field.setUsage(elem.getAttribute("Usage"));
 302  1586
                 String itemNo = elem.getAttribute("ItemNo");
 303  1586
                 String min = elem.getAttribute("Min");
 304  1586
                 String max = elem.getAttribute("Max");
 305  
 
 306  
                 try {
 307  1586
                         if (itemNo.length() > 0) {
 308  1558
                                 field.setItemNo(Short.parseShort(itemNo));
 309  
                         }
 310  0
                 } catch (NumberFormatException e) {
 311  0
                         throw new ProfileException("Invalid ItemNo: " + itemNo + "( for name "
 312  
                                         + elem.getAttribute("Name") + ")", e);
 313  1586
                 } // try-catch
 314  
 
 315  
                 try {
 316  1586
                         field.setMin(Short.parseShort(min));
 317  1586
                         if (max.indexOf('*') >= 0) {
 318  362
                                 field.setMax((short) -1);
 319  
                         } else {
 320  1224
                                 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  1586
                 }
 325  
 
 326  1586
                 parseAbstractComponentData(field, elem);
 327  
 
 328  1586
                 int childIndex = 1;
 329  1586
                 NodeList children = elem.getChildNodes();
 330  17162
                 for (int i = 0; i < children.getLength(); i++) {
 331  15576
                         Node n = children.item(i);
 332  15576
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 333  6995
                                 Element child = (Element) n;
 334  6995
                                 if (child.getNodeName().equalsIgnoreCase("Component")) {
 335  5429
                                         Component comp = (Component) parseComponentProfile(child, false);
 336  5429
                                         field.setComponent(childIndex++, comp);
 337  
                                 }
 338  
                         }
 339  
                 }
 340  
 
 341  1586
                 return field;
 342  
         }
 343  
 
 344  
         /** Parses a component profile */
 345  
         private AbstractComponent<?> parseComponentProfile(Element elem, boolean isSubComponent)
 346  
                         throws ProfileException {
 347  9855
                 AbstractComponent<?> comp = null;
 348  9855
                 if (isSubComponent) {
 349  4426
                         log.debug("      Parsing subcomp profile: " + elem.getAttribute("Name"));
 350  4426
                         comp = new SubComponent();
 351  
                 } else {
 352  5429
                         log.debug("    Parsing comp profile: " + elem.getAttribute("Name"));
 353  5429
                         comp = new Component();
 354  
 
 355  5429
                         int childIndex = 1;
 356  5429
                         NodeList children = elem.getChildNodes();
 357  20876
                         for (int i = 0; i < children.getLength(); i++) {
 358  15447
                                 Node n = children.item(i);
 359  15447
                                 if (n.getNodeType() == Node.ELEMENT_NODE) {
 360  5009
                                         Element child = (Element) n;
 361  5009
                                         if (child.getNodeName().equalsIgnoreCase("SubComponent")) {
 362  4426
                                                 SubComponent subcomp = (SubComponent) parseComponentProfile(child, true);
 363  4426
                                                 ((Component) comp).setSubComponent(childIndex++, subcomp);
 364  
                                         }
 365  
                                 }
 366  
                         }
 367  
                 }
 368  
 
 369  9855
                 parseAbstractComponentData(comp, elem);
 370  
 
 371  9855
                 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  11441
                 comp.setName(elem.getAttribute("Name"));
 380  11441
                 comp.setUsage(elem.getAttribute("Usage"));
 381  11441
                 comp.setDatatype(elem.getAttribute("Datatype"));
 382  11441
                 String length = elem.getAttribute("Length");
 383  11441
                 if (length != null && length.length() > 0) {
 384  
                         try {
 385  11324
                                 comp.setLength(Long.parseLong(length));
 386  0
                         } catch (NumberFormatException e) {
 387  0
                                 throw new ProfileException("Length must be a long integer: " + length, e);
 388  11324
                         }
 389  
                 }
 390  11441
                 comp.setConstantValue(elem.getAttribute("ConstantValue"));
 391  11441
                 String table = elem.getAttribute("Table");
 392  11441
                 if (table != null && table.length() > 0) {
 393  
                         try {
 394  4212
                                 comp.setTable(table);
 395  0
                         } catch (NumberFormatException e) {
 396  0
                                 throw new ProfileException("Table must be a short integer: " + table, e);
 397  4212
                         }
 398  
                 }
 399  
 
 400  11441
                 comp.setImpNote(getValueOfFirstElement("ImpNote", elem));
 401  11441
                 comp.setDescription(getValueOfFirstElement("Description", elem));
 402  11441
                 comp.setReference(getValueOfFirstElement("Reference", elem));
 403  11441
                 comp.setPredicate(getValueOfFirstElement("Predicate", elem));
 404  
 
 405  11441
                 int dataValIndex = 0;
 406  11441
                 NodeList children = elem.getChildNodes();
 407  49176
                 for (int i = 0; i < children.getLength(); i++) {
 408  37735
                         Node n = children.item(i);
 409  37735
                         if (n.getNodeType() == Node.ELEMENT_NODE) {
 410  13147
                                 Element child = (Element) n;
 411  13147
                                 if (child.getNodeName().equalsIgnoreCase("DataValues")) {
 412  582
                                         DataValue val = new DataValue();
 413  582
                                         val.setExValue(child.getAttribute("ExValue"));
 414  582
                                         comp.setDataValues(dataValIndex++, val);
 415  
                                 }
 416  
                         }
 417  
                 }
 418  
 
 419  11441
         }
 420  
 
 421  
         /** Parses profile string into DOM document */
 422  
         private Document parseIntoDOM(String profileString) throws ProfileException {
 423  
                 try {
 424  19
                         Document doc = XMLUtils.parse(profileString, true);
 425  19
                         if (alwaysValidate)
 426  7
                                 XMLUtils.validate(doc, PROFILE_XSD, errorHandler);
 427  19
                         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  46320
                 NodeList nl = parent.getElementsByTagName(name);
 439  46320
                 Element ret = null;
 440  46320
                 if (nl.getLength() > 0) {
 441  3873
                         ret = (Element) nl.item(0);
 442  
                 }
 443  46320
                 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  46301
                 Element el = getFirstElementByTagName(name, parent);
 451  46301
                 String val = null;
 452  46301
                 if (el != null) {
 453  
                         try {
 454  3854
                                 Node n = el.getFirstChild();
 455  3854
                                 if (n.getNodeType() == Node.TEXT_NODE) {
 456  3854
                                         val = n.getNodeValue();
 457  
                                 }
 458  0
                         } catch (Exception e) {
 459  0
                                 throw new ProfileException("Unable to get value of node " + name, e);
 460  3854
                         }
 461  
                 }
 462  46301
                 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  
 }