001/** 002The 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. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Original Code is "GroupPointer.java". Description: 010"A GroupPointer is used when parsing traditionally encoded HL7 messages" 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132001. All Rights Reserved. 014 015Contributor(s): ______________________________________. 016 017Alternatively, the contents of this file may be used under the terms of the 018GNU General Public License (the �GPL�), in which case the provisions of the GPL are 019applicable instead of those above. If you wish to allow use of your version of this 020file only under the terms of the GPL and not to allow others to use your version 021of this file under the MPL, indicate your decision by deleting the provisions above 022and replace them with the notice and other provisions required by the GPL License. 023If you do not delete the provisions above, a recipient may use your version of 024this file under either the MPL or the GPL. 025 026*/ 027package ca.uhn.hl7v2.parser; 028 029import java.io.BufferedReader; 030import java.io.IOException; 031import java.io.InputStreamReader; 032import java.net.URL; 033import java.net.URLConnection; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.HashMap; 037import java.util.Iterator; 038import java.util.List; 039import java.util.Map; 040import java.util.Stack; 041import java.util.StringTokenizer; 042 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046import ca.uhn.hl7v2.HL7Exception; 047import ca.uhn.hl7v2.model.Message; 048import ca.uhn.hl7v2.model.Primitive; 049import ca.uhn.hl7v2.model.Segment; 050import ca.uhn.hl7v2.model.Type; 051import 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 */ 064public 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}