001 /**
002 The contents of this file are subject to the Mozilla Public License Version 1.1
003 (the "License"); you may not use this file except in compliance with the License.
004 You may obtain a copy of the License at http://www.mozilla.org/MPL/
005 Software distributed under the License is distributed on an "AS IS" basis,
006 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007 specific language governing rights and limitations under the License.
008
009 The Original Code is "GroupPointer.java". Description:
010 "A GroupPointer is used when parsing traditionally encoded HL7 messages"
011
012 The Initial Developer of the Original Code is University Health Network. Copyright (C)
013 2001. All Rights Reserved.
014
015 Contributor(s): ______________________________________.
016
017 Alternatively, the contents of this file may be used under the terms of the
018 GNU General Public License (the �GPL�), in which case the provisions of the GPL are
019 applicable instead of those above. If you wish to allow use of your version of this
020 file only under the terms of the GPL and not to allow others to use your version
021 of this file under the MPL, indicate your decision by deleting the provisions above
022 and replace them with the notice and other provisions required by the GPL License.
023 If you do not delete the provisions above, a recipient may use your version of
024 this file under either the MPL or the GPL.
025
026 */
027 package ca.uhn.hl7v2.parser;
028
029 import java.io.BufferedReader;
030 import java.io.IOException;
031 import java.io.InputStreamReader;
032 import java.net.URL;
033 import java.net.URLConnection;
034 import java.util.ArrayList;
035 import java.util.Arrays;
036 import java.util.HashMap;
037 import java.util.Iterator;
038 import java.util.List;
039 import java.util.Map;
040 import java.util.Stack;
041 import java.util.StringTokenizer;
042
043 import org.slf4j.Logger;
044 import org.slf4j.LoggerFactory;
045
046 import ca.uhn.hl7v2.HL7Exception;
047 import ca.uhn.hl7v2.model.Message;
048 import ca.uhn.hl7v2.model.Primitive;
049 import ca.uhn.hl7v2.model.Segment;
050 import ca.uhn.hl7v2.model.Type;
051 import ca.uhn.hl7v2.util.Terser;
052
053 /**
054 * This class has been deprecated and should not be used.
055 *
056 * PipeParser has been optimized and is now roughly 20% faster than FastParser (see
057 * FastParserTest for a test of this)
058 *
059 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
060 * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
061 *
062 * @deprecated
063 */
064 public class FastParser extends Parser {
065
066 private static final Logger ourLog = LoggerFactory.getLogger(FastParser.class);
067
068 private static char ourSegmentSeparator = '\r';
069 private Map<Object, StructRef> myEventGuideMap;
070 private PipeParser myPipeParser;
071
072 /**
073 * @param theEventGuideMap a map with keys in the form "type^event" (like MSH-9
074 * components 1 and 2). Values are corresponding parsing guides for those events.
075 * A parsing guide is a group of StructRef that identify which segments to parse,
076 * the relationships between them, and where to find them in a message hierarchy.
077 * The value in the map is the RootRef of the message root. It must return the
078 * StructRef for the MSH segment from getSuccessor("MSH"). References to other
079 * segments can be included as needed.
080 */
081 public FastParser(Map<Object, StructRef> theEventGuideMap) {
082 this(null, theEventGuideMap);
083 }
084
085 /**
086 * @param theFactory custom factory to use for model class lookup
087 * @param theEventGuideMap a map with keys in the form "type^event" (like MSH-9
088 * components 1 and 2). Values are corresponding parsing guides for those events.
089 * A parsing guide is a group of StructRef that identify which segments to parse,
090 * the relationships between them, and where to find them in a message hierarchy.
091 * The value in the map is the RootRef of the message root. It must return the
092 * StructRef for the MSH segment from getSuccessor("MSH"). References to other
093 * segments can be included as needed.
094 */
095 public FastParser(ModelClassFactory theFactory, Map<Object, StructRef> theEventGuideMap) {
096 super(theFactory);
097 myEventGuideMap = theEventGuideMap;
098 myPipeParser = new PipeParser();
099 }
100
101 /**
102 * Loads a parsing guide map (as required for FastParser instantiation). The URL should
103 * point to a file with one or more guides in sections delimited by blank lines. Within
104 * a section, the first line must contain an event name of the for "type^event". Subsequent
105 * lines define the parsed parts of messages with that event. Each line begins with either
106 * a segment name or "{" (indicating group start) or "}" (indicating group end). Group
107 * start lines then have whitespace and a Terser path to the group (relative to the closest
108 * ancestor group listed in the parsin guide). Segment lines then have whitespace and a
109 * relative Terser path to the segment, followed by a colon and a comma-delimited list of field
110 * numbers, which indicates which fields for that segment are to be parsed. Within Terser
111 * paths, repetition numbers must be replaced with asterisks. An example follows:
112 *
113 * ORU^R01
114 * MSH MSH:9,12
115 * { ORU_R01_PIDNTEPV1ORCOBRNTEOBXNTE(*)
116 * { ORU_R01_PIDNTEPV1
117 * PID PID:3-5
118 * }
119 * { ORU_R01_ORCOBRNTEOBXNTE(*)
120 * { ORU_R01_OBXNTE(*)
121 * OBX OBX:2,5
122 * }
123 * }
124 * }
125 *
126 * ADT^A01
127 * MSH MSH:9,12
128 * PID PID:3
129 * PV1 PV1:7-9
130 *
131 * @param theMapURL an URL to a file of the form desribed above
132 * @return the corresponding Map
133 */
134 public static Map<Object, StructRef> loadEventGuideMap(URL theMapURL) throws HL7Exception {
135 Map<Object, StructRef> result = new HashMap<Object, StructRef>();
136
137 try {
138 URLConnection conn = theMapURL.openConnection();
139 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
140
141 String eventName = null;
142 StringBuffer spec = new StringBuffer();
143 String line = null;
144 while ((line = reader.readLine()) != null) {
145 if (line.length() == 0) {
146 finish(eventName, spec, result);
147 eventName = null;
148 spec = new StringBuffer();
149 } else {
150 if (eventName == null) {
151 eventName = line;
152 } else {
153 spec.append(line + "\r");
154 }
155 }
156 }
157 reader.close();
158 finish(eventName, spec, result);
159 } catch (IOException e) {
160 throw new HL7Exception(e);
161 }
162
163 return result;
164 }
165
166 private static void finish(String theEventName, StringBuffer theSpec, Map<Object, StructRef> theMap) {
167 if (theEventName != null) {
168 RootRef root = parseGuide(theSpec.toString());
169 theMap.put(theEventName, root);
170 }
171 }
172
173 private static RootRef parseGuide(String theSpec) {
174 StringTokenizer lines = new StringTokenizer(theSpec, "\r", false);
175 RootRef result = new RootRef();
176 Stack<StructRef> ancestry = new Stack<StructRef>();
177 ancestry.push(result);
178 Map<Object, StructRef> successors = new HashMap<Object, StructRef>();
179
180 StructRef previous = result;
181 while (lines.hasMoreTokens()) {
182 String line = lines.nextToken();
183 StringTokenizer parts = new StringTokenizer(line, "\t ", false);
184 String segName = parts.nextToken();
185 String path = parts.hasMoreTokens() ? parts.nextToken() : "";
186 parts = new StringTokenizer(path, ":", false);
187 path = parts.hasMoreTokens() ? parts.nextToken() : null;
188
189 int[] fields = getFieldList(parts.hasMoreTokens() ? parts.nextToken() : "");
190
191 if (segName.equals("}")) {
192 StructRef parent = (StructRef) ancestry.pop();
193 if (parent.getChildName() != null && parent.getRelativePath().indexOf('*') >= 0) { //repeating group
194 previous.setSuccessor(parent.getChildName(), parent);
195 }
196 } else {
197 boolean isSegment = !(segName.equals("{"));
198 StructRef ref = new StructRef((StructRef) ancestry.peek(), path, isSegment, fields);
199 if (isSegment) {
200 previous.setSuccessor(segName, ref);
201 if (path.indexOf('*') >= 0) ref.setSuccessor(segName, ref);
202 setGroupSuccessors(successors, segName);
203 } else {
204 successors.put(previous, ref);
205 }
206 if (!isSegment) ancestry.push(ref);
207 previous = ref;
208 }
209 }
210
211 return result;
212 }
213
214 private static void setGroupSuccessors(Map<Object, StructRef> theSuccessors, String theSegName) {
215 for (Iterator<Object> it = theSuccessors.keySet().iterator(); it.hasNext(); ) {
216 StructRef from = (StructRef) it.next();
217 StructRef to = (StructRef) theSuccessors.get(from);
218 from.setSuccessor(theSegName, to);
219 }
220 theSuccessors.clear();
221 }
222
223 private static int[] getFieldList(String theSpec) {
224 StringTokenizer tok = new StringTokenizer(theSpec, ",", false);
225 List<Integer> fieldList = new ArrayList<Integer>(30);
226 while (tok.hasMoreTokens()) {
227 String token = tok.nextToken();
228 int index = token.indexOf('-');
229 if (index >= 0) { //it's a range
230 int start = Integer.parseInt(token.substring(0, index));
231 int end = Integer.parseInt(token.substring(index+1));
232 for (int i = start; i <= end; i++) {
233 fieldList.add(new Integer(i));
234 }
235 } else {
236 fieldList.add(Integer.valueOf(token));
237 }
238 }
239
240 int[] result = new int[fieldList.size()];
241 for (int i = 0; i < result.length; i++) {
242 result[i] = ((Integer) fieldList.get(i)).intValue();
243 }
244
245 return result;
246 }
247
248 /**
249 * @see ca.uhn.hl7v2.parser.Parser#getEncoding(java.lang.String)
250 */
251 public String getEncoding(String message) {
252 return myPipeParser.getEncoding(message);
253 }
254
255 /**
256 * @see ca.uhn.hl7v2.parser.Parser#supportsEncoding(java.lang.String)
257 */
258 public boolean supportsEncoding(String encoding) {
259 return myPipeParser.supportsEncoding(encoding);
260 }
261
262 /**
263 * @return the preferred encoding of this Parser
264 */
265 public String getDefaultEncoding() {
266 return "VB";
267 }
268
269 /**
270 * @see ca.uhn.hl7v2.parser.Parser#doParse(java.lang.String, java.lang.String)
271 */
272 protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException {
273 Message result = null;
274
275 char fieldSep = message.charAt(3);
276 EncodingCharacters ec = new EncodingCharacters(fieldSep, message.substring(4, 8));
277
278 StringTokenizer tok = new StringTokenizer(message.substring(4),
279 String.valueOf(new char[]{fieldSep, ourSegmentSeparator}), true);
280
281 String[] mshFields = getMSHFields(tok, fieldSep);
282 Object[] structure = getStructure(mshFields[8], ec.getComponentSeparator());
283
284 StructRef root = (StructRef) myEventGuideMap.get(structure[0]);
285 if (root == null) {
286 ourLog.debug("FastParser delegating to PipeParser because no metadata available for event {}",
287 structure[0]);
288 result = myPipeParser.parse(message);
289 } else {
290 // int csIndex = mshFields[11].indexOf(ec.getComponentSeparator());
291 result = instantiateMessage((String) structure[1], version, ((Boolean) structure[2]).booleanValue());
292
293 StructRef mshRef = null;
294 synchronized (root) {
295 mshRef = root.getSuccessor("MSH");
296 root.reset();
297 }
298 Segment msh = (Segment) result.get("MSH");
299 for (int i = 0; i < mshRef.getFields().length; i++) {
300 int fieldNum = mshRef.getFields()[i];
301 parse(mshFields[fieldNum-1], msh, fieldNum, ec);
302 }
303
304 parse(tok, result, root, ec);
305 }
306
307 return result;
308 }
309
310 private String[] getMSHFields(StringTokenizer tok, char fieldSep) {
311 String[] result = new String[21];
312 result[0] = String.valueOf(fieldSep);
313 String token = null;
314 int field = 1;
315 while (tok.hasMoreTokens() && (token = tok.nextToken()).charAt(0) != ourSegmentSeparator) {
316 if (token.charAt(0) == fieldSep) {
317 field++;
318 } else {
319 result[field] = token;
320 }
321 }
322 return result;
323 }
324
325 private void parse(StringTokenizer tok, Message message, StructRef root, EncodingCharacters ec)
326 throws HL7Exception {
327
328 Terser t = new Terser(message);
329
330 synchronized (root) {
331 StructRef ref = root.getSuccessor("MSH");
332
333 int field = 0;
334 Segment segment = null;
335 int[] fields = new int[0];
336
337 while (tok.hasMoreTokens()) {
338 String token = tok.nextToken();
339 if (token.charAt(0) == ec.getFieldSeparator()) {
340 field++;
341 } else if (token.charAt(0) == ourSegmentSeparator) {
342 field = 0;
343 } else if (field == 0) {
344 StructRef newref = drill(ref, token);
345 if (newref == null) {
346 segment = null;
347 fields = new int[0];
348 } else {
349 ref = newref;
350 ourLog.debug("Parsing into segment {}", ref.getFullPath());
351 segment = t.getSegment(ref.getFullPath());
352 fields = ref.getFields();
353 }
354 } else if (segment != null && Arrays.binarySearch(fields, field) >= 0) {
355 parse(token, segment, field, ec);
356 }
357 }
358 root.reset();
359 }
360 }
361
362 //drill through groups to a segment
363 private StructRef drill(StructRef ref, String name) {
364 ref = ref.getSuccessor(name);
365 while (ref != null && !ref.isSegment()) {
366 ref = ref.getSuccessor(name);
367 }
368 return ref;
369 }
370
371 private void parse(String field, Segment segment, int num, EncodingCharacters ec) throws HL7Exception {
372 if (field != null) {
373 int rep = 0;
374 int component = 1;
375 int subcomponent = 1;
376 Type type = segment.getField(num, rep);
377
378 String delim = String.valueOf(new char[]{ec.getRepetitionSeparator(),
379 ec.getComponentSeparator(), ec.getSubcomponentSeparator()});
380 for (StringTokenizer tok = new StringTokenizer(field, delim, true); tok.hasMoreTokens(); ) {
381 String token = tok.nextToken();
382 char c = token.charAt(0);
383 if (c == ec.getRepetitionSeparator()) {
384 rep++;
385 component = 1;
386 subcomponent = 1;
387 type = segment.getField(num, rep);
388 } else if (c == ec.getComponentSeparator()) {
389 component++;
390 subcomponent = 1;
391 } else if (c == ec.getSubcomponentSeparator()) {
392 subcomponent++;
393 } else {
394 Primitive p = Terser.getPrimitive(type, component, subcomponent);
395 p.setValue(token);
396 }
397 }
398 }
399 }
400
401 /**
402 * @returns the message structure from MSH-9-3
403 */
404 private Object[] getStructure(String msh9, char compSep) throws HL7Exception {
405 String structure = null;
406 String event = null;
407
408 String[] components = new String[3];
409 StringTokenizer tok = new StringTokenizer(msh9, String.valueOf(compSep), true);
410 for (int i = 0; tok.hasMoreTokens() && i < components.length; ) {
411 String token = tok.nextToken();
412 if (token.charAt(0) == compSep) {
413 i++;
414 } else {
415 components[i] = token;
416 }
417 }
418
419 boolean explicitlyDefined = (components[2] == null) ? false : true;
420
421 if (explicitlyDefined) {
422 structure = components[2];
423 } else if (components[0] != null && components[0].equals("ACK")) {
424 structure = "ACK";
425 } else if (components[0] != null && components[1] != null) {
426 structure = components[0] + "_" + components[1];
427 } else {
428 throw new HL7Exception("Can't determine message structure from MSH-9: " + msh9,
429 HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
430 }
431
432 if (components[1] == null) {
433 event = components[0];
434 } else {
435 event = components[0] + "^" + components[1];
436 }
437
438 return new Object[] {event, structure, Boolean.valueOf(explicitlyDefined)};
439 }
440
441
442 /**
443 * @see ca.uhn.hl7v2.parser.Parser#encode(ca.uhn.hl7v2.model.Message, java.lang.String)
444 */
445 protected String doEncode(Message source, String encoding) throws HL7Exception,
446 EncodingNotSupportedException {
447 return myPipeParser.doEncode(source, encoding);
448 }
449
450 /**
451 * @see ca.uhn.hl7v2.parser.Parser#encode(ca.uhn.hl7v2.model.Message)
452 */
453 protected String doEncode(Message source) throws HL7Exception {
454 return myPipeParser.doEncode(source);
455 }
456
457 /**
458 * @see ca.uhn.hl7v2.parser.Parser#getCriticalResponseData(java.lang.String)
459 */
460 public Segment getCriticalResponseData(String message) throws HL7Exception {
461 return myPipeParser.getCriticalResponseData(message);
462 }
463
464 /**
465 * @see ca.uhn.hl7v2.parser.Parser#getAckID(java.lang.String)
466 */
467 public String getAckID(String message) {
468 return myPipeParser.getAckID(message);
469 }
470
471 /**
472 * @see ca.uhn.hl7v2.parser.Parser#getVersion(java.lang.String)
473 */
474 public String getVersion(String message) throws HL7Exception {
475 return myPipeParser.getVersion(message);
476 }
477
478 /**
479 * Not supported, throws UnsupportedOperationException
480 *
481 * @throws UnsupportedOperationException
482 */
483 @Override
484 public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
485 throw new UnsupportedOperationException("Not supported yet.");
486 }
487
488 /**
489 * Not supported, throws UnsupportedOperationException
490 *
491 * @throws UnsupportedOperationException
492 */
493 @Override
494 public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
495 throw new UnsupportedOperationException("Not supported yet.");
496 }
497
498 /**
499 * Not supported, throws UnsupportedOperationException
500 *
501 * @throws UnsupportedOperationException
502 */
503 @Override
504 public void parse(Type type, String string, EncodingCharacters encodingCharacters) throws HL7Exception {
505 throw new UnsupportedOperationException("Not supported yet.");
506 }
507
508 /**
509 * Not supported, throws UnsupportedOperationException
510 *
511 * @throws UnsupportedOperationException
512 */
513 @Override
514 public void parse(Segment segment, String string, EncodingCharacters encodingCharacters) throws HL7Exception {
515 throw new UnsupportedOperationException("Not supported yet.");
516 }
517
518
519 /**
520 * Not supported, throws UnsupportedOperationException
521 *
522 * @throws UnsupportedOperationException
523 */
524 @Override
525 public void parse(Message message, String string) throws HL7Exception {
526 throw new UnsupportedOperationException("Not supported yet.");
527 }
528
529 /**
530 * Throws unsupported operation exception
531 *
532 * @throws Unsupported operation exception
533 */
534 @Override
535 protected Message doParseForSpecificPackage(String theMessage, String theVersion, String thePackageName) throws HL7Exception, EncodingNotSupportedException {
536 throw new UnsupportedOperationException("Not supported yet.");
537 }
538
539 /**
540 * A pointer to a distinct segment or group position in a message.
541 *
542 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
543 * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
544 */
545 public static class StructRef {
546
547 private StructRef myParent;
548 private String myRelativePath;
549 private Map<Object, StructRef> mySuccessors;
550 private int myRep;
551 private boolean mySegmentFlag;
552 //private boolean myResettableFlag;
553 private int[] myFields;
554 private List<StructRef> myChildren;
555
556 /**
557 * @param theParent a StructRef for the parent Group of the referenced Structure
558 * @param theRelativePath the relative (from the parent) Terser path to the referenced
559 * structure. If the structure repeats, the rep number should be replaced with "*"
560 * (it will be incremented as needed).
561 * @param isSegment true iff the referenced Structure is a Segment (rather than a Group)
562 * @param theFields a list of fields to be parsed for this segment (null or empty for groups)
563 */
564 public StructRef(StructRef theParent, String theRelativePath, boolean isSegment, int[] theFields) {
565 myParent = theParent;
566 myChildren = new ArrayList<StructRef>();
567 if (myParent != null) myParent.addChild(this);
568
569 myRelativePath = theRelativePath;
570 if (!myRelativePath.startsWith("/")) {
571 myRelativePath = "/" + myRelativePath;
572 }
573 mySegmentFlag = isSegment;
574 mySuccessors = new HashMap<Object, StructRef>();
575 myRep = -1;
576 if (mySegmentFlag) {
577 myFields = theFields;
578 Arrays.sort(myFields);
579 } else {
580 myFields = new int[0];
581 }
582 //myResettableFlag = (myParent == null) ? true : false;
583 }
584
585 /**
586 * Indicates an immediately subsequent structure in parsing order. A Structure in a list
587 * should point to the next Structure in the list. A Structure that repeats should point to
588 * itself. A Structure at the end of a repeating Group should point to the Group.
589 * A Group should point to its first child.
590 *
591 * @param theName name of the next Segment in this direction (ie if the next structure is a group,
592 * not that one)
593 * @param theSuccessor the immediately next StructRef in that direction
594 */
595 public void setSuccessor(String theName, StructRef theSuccessor) {
596 mySuccessors.put(theName, theSuccessor);
597 }
598
599 /**
600 * @return full Terser path, including parent and repetition information.
601 */
602 public String getFullPath() {
603 return myParent.getFullPath() + myRelativePath.replaceAll("\\*", String.valueOf(myRep));
604 }
605
606 /**
607 * @return relative Terser path as defined in constructor
608 */
609 public String getRelativePath() {
610 return myRelativePath;
611 }
612
613 /**
614 * @param theName name of a successor in parse order, as set in setSuccessor()
615 * @return the StructRef under that name
616 */
617 public StructRef getSuccessor(String theName) {
618 StructRef ref = (StructRef) mySuccessors.get(theName);
619 if (ref != null) {
620 ref.next();
621 }
622 return ref;
623 }
624
625 /**
626 * @return name of first successor, if available and if this is not a segment reference,
627 * otherwise null
628 */
629 public String getChildName() {
630 String result = null;
631 if (!mySegmentFlag && !mySuccessors.isEmpty()) {
632 result = (String) mySuccessors.keySet().iterator().next();
633 }
634 return result;
635 }
636
637 /**
638 * @return true iff referenced Structure is a Segment
639 */
640 public boolean isSegment() {
641 return mySegmentFlag;
642 }
643
644 /**
645 * Increments the repetition number of the underlying Structure, which is used in getFullPath()
646 */
647 private void next() {
648 myRep++;
649 resetChildren();
650 }
651
652 private void addChild(StructRef theChild) {
653 if (!isSegment()) {
654 myChildren.add(theChild);
655 }
656 }
657
658 /**
659 * Resets the StructRef to its starting state, before its first iteration, and resets
660 * its children as well.
661 */
662 public void reset() {
663 myRep = -1;
664 resetChildren();
665 }
666
667 private void resetChildren() {
668 for (int i = 0; i < myChildren.size(); i++) {
669 StructRef child = (StructRef) myChildren.get(i);
670 child.reset();
671 }
672 }
673
674 /**
675 * @return an ordered list of fields to be parsed for this segment (empty if not a segment)
676 */
677 public int[] getFields() {
678 return myFields;
679 }
680
681 }
682
683 /**
684 * A convenience StructRef that points to a message root.
685 *
686 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
687 * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
688 */
689 public static class RootRef extends StructRef {
690 public RootRef() {
691 super(null, "", false, null);
692 }
693
694 public String getFullPath() {
695 return "";
696 }
697 }
698
699 }