View Javadoc

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