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 "PipeParser.java". Description: 010 * "An implementation of Parser that supports traditionally encoded (i.e" 011 * 012 * The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 * 2001. All Rights Reserved. 014 * 015 * Contributor(s): Kenneth Beaton. 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 028 package ca.uhn.hl7v2.parser; 029 030 import java.util.ArrayList; 031 import java.util.StringTokenizer; 032 033 import ca.uhn.hl7v2.HL7Exception; 034 import ca.uhn.hl7v2.model.Group; 035 import ca.uhn.hl7v2.model.Message; 036 import ca.uhn.hl7v2.model.Primitive; 037 import ca.uhn.hl7v2.model.Segment; 038 import ca.uhn.hl7v2.model.Structure; 039 import ca.uhn.hl7v2.model.Type; 040 import ca.uhn.hl7v2.model.Varies; 041 import ca.uhn.hl7v2.util.Terser; 042 import ca.uhn.hl7v2.util.MessageIterator; 043 import ca.uhn.hl7v2.util.FilterIterator; 044 import ca.uhn.log.HapiLog; 045 import ca.uhn.log.HapiLogFactory; 046 047 /** 048 * This is a legacy implementation of the PipeParser and should not be used 049 * for new projects. 050 * 051 * In version 1.0 of HAPI, a behaviour was corrected where unexpected segments 052 * would be placed at the tail end of the first segment group encountered. Any 053 * legacy code which still depends on previous behaviour can use this 054 * implementation. 055 * 056 * @author Bryan Tripp (bryan_tripp@sourceforge.net) 057 * @deprecated 058 */ 059 public class OldPipeParser extends Parser { 060 061 private static final HapiLog log = HapiLogFactory.getHapiLog(PipeParser.class); 062 063 private final static String segDelim = "\r"; //see section 2.8 of spec 064 065 /** Creates a new PipeParser */ 066 public OldPipeParser() { 067 } 068 069 /** 070 * Creates a new PipeParser 071 * 072 * @param theFactory custom factory to use for model class lookup 073 */ 074 public OldPipeParser(ModelClassFactory theFactory) { 075 super(theFactory); 076 } 077 078 /** 079 * Returns a String representing the encoding of the given message, if 080 * the encoding is recognized. For example if the given message appears 081 * to be encoded using HL7 2.x XML rules then "XML" would be returned. 082 * If the encoding is not recognized then null is returned. That this 083 * method returns a specific encoding does not guarantee that the 084 * message is correctly encoded (e.g. well formed XML) - just that 085 * it is not encoded using any other encoding than the one returned. 086 */ 087 public String getEncoding(String message) { 088 String encoding = null; 089 090 //quit if the string is too short 091 if (message.length() < 4) 092 return null; 093 094 //see if it looks like this message is | encoded ... 095 boolean ok = true; 096 097 //string should start with "MSH" 098 if (!message.startsWith("MSH")) 099 return null; 100 101 //4th character of each segment should be field delimiter 102 char fourthChar = message.charAt(3); 103 StringTokenizer st = new StringTokenizer(message, String.valueOf(segDelim), false); 104 while (st.hasMoreTokens()) { 105 String x = st.nextToken(); 106 if (x.length() > 0) { 107 if (Character.isWhitespace(x.charAt(0))) 108 x = stripLeadingWhitespace(x); 109 if (x.length() >= 4 && x.charAt(3) != fourthChar) 110 return null; 111 } 112 } 113 114 //should be at least 11 field delimiters (because MSH-12 is required) 115 int nextFieldDelimLoc = 0; 116 for (int i = 0; i < 11; i++) { 117 nextFieldDelimLoc = message.indexOf(fourthChar, nextFieldDelimLoc + 1); 118 if (nextFieldDelimLoc < 0) 119 return null; 120 } 121 122 if (ok) 123 encoding = "VB"; 124 125 return encoding; 126 } 127 128 /** 129 * @return the preferred encoding of this Parser 130 */ 131 public String getDefaultEncoding() { 132 return "VB"; 133 } 134 135 /** 136 * Returns true if and only if the given encoding is supported 137 * by this Parser. 138 */ 139 public boolean supportsEncoding(String encoding) { 140 boolean supports = false; 141 if (encoding != null && encoding.equals("VB")) 142 supports = true; 143 return supports; 144 } 145 146 /** 147 * @deprecated this method should not be public 148 * @param message 149 * @return 150 * @throws HL7Exception 151 * @throws EncodingNotSupportedException 152 */ 153 public String getMessageStructure(String message) throws HL7Exception, EncodingNotSupportedException { 154 return getStructure(message).messageStructure; 155 } 156 157 /** 158 * @returns the message structure from MSH-9-3 159 */ 160 private MessageStructure getStructure(String message) throws HL7Exception, EncodingNotSupportedException { 161 EncodingCharacters ec = getEncodingChars(message); 162 String messageStructure = null; 163 boolean explicityDefined = true; 164 String wholeFieldNine; 165 try { 166 String[] fields = split(message.substring(0, Math.max(message.indexOf(segDelim), message.length())), 167 String.valueOf(ec.getFieldSeparator())); 168 wholeFieldNine = fields[8]; 169 170 //message structure is component 3 but we'll accept a composite of 1 and 2 if there is no component 3 ... 171 // if component 1 is ACK, then the structure is ACK regardless of component 2 172 String[] comps = split(wholeFieldNine, String.valueOf(ec.getComponentSeparator())); 173 if (comps.length >= 3) { 174 messageStructure = comps[2]; 175 } else if (comps.length > 0 && comps[0] != null && comps[0].equals("ACK")) { 176 messageStructure = "ACK"; 177 } else if (comps.length == 2) { 178 explicityDefined = false; 179 messageStructure = comps[0] + "_" + comps[1]; 180 } 181 /*else if (comps.length == 1 && comps[0] != null && comps[0].equals("ACK")) { 182 messageStructure = "ACK"; //it's common for people to only populate component 1 in an ACK msg 183 }*/ 184 else { 185 StringBuffer buf = new StringBuffer("Can't determine message structure from MSH-9: "); 186 buf.append(wholeFieldNine); 187 if (comps.length < 3) { 188 buf.append(" HINT: there are only "); 189 buf.append(comps.length); 190 buf.append(" of 3 components present"); 191 } 192 throw new HL7Exception(buf.toString(), HL7Exception.UNSUPPORTED_MESSAGE_TYPE); 193 } 194 } 195 catch (IndexOutOfBoundsException e) { 196 throw new HL7Exception( 197 "Can't find message structure (MSH-9-3): " + e.getMessage(), 198 HL7Exception.UNSUPPORTED_MESSAGE_TYPE); 199 } 200 201 return new MessageStructure(messageStructure, explicityDefined); 202 } 203 204 /** 205 * Returns object that contains the field separator and encoding characters 206 * for this message. 207 */ 208 private static EncodingCharacters getEncodingChars(String message) { 209 return new EncodingCharacters(message.charAt(3), message.substring(4, 8)); 210 } 211 212 /** 213 * Parses a message string and returns the corresponding Message 214 * object. Unexpected segments added at the end of their group. 215 * 216 * @throws HL7Exception if the message is not correctly formatted. 217 * @throws EncodingNotSupportedException if the message encoded 218 * is not supported by this parser. 219 */ 220 protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException { 221 222 //try to instantiate a message object of the right class 223 MessageStructure structure = getStructure(message); 224 Message m = instantiateMessage(structure.messageStructure, version, structure.explicitlyDefined); 225 226 parse(m, message); 227 228 return m; 229 } 230 231 /** 232 * Parses a segment string and populates the given Segment object. Unexpected fields are 233 * added as Varies' at the end of the segment. 234 * 235 * @throws HL7Exception if the given string does not contain the 236 * given segment or if the string is not encoded properly 237 */ 238 public void parse(Segment destination, String segment, EncodingCharacters encodingChars) throws HL7Exception { 239 int fieldOffset = 0; 240 if (isDelimDefSegment(destination.getName())) { 241 fieldOffset = 1; 242 //set field 1 to fourth character of string 243 Terser.set(destination, 1, 0, 1, 1, String.valueOf(encodingChars.getFieldSeparator())); 244 } 245 246 String[] fields = split(segment, String.valueOf(encodingChars.getFieldSeparator())); 247 //destination.setName(fields[0]); 248 for (int i = 1; i < fields.length; i++) { 249 String[] reps = split(fields[i], String.valueOf(encodingChars.getRepetitionSeparator())); 250 if (log.isDebugEnabled()) { 251 log.debug(reps.length + "reps delimited by: " + encodingChars.getRepetitionSeparator()); 252 } 253 254 //MSH-2 will get split incorrectly so we have to fudge it ... 255 boolean isMSH2 = isDelimDefSegment(destination.getName()) && i+fieldOffset == 2; 256 if (isMSH2) { 257 reps = new String[1]; 258 reps[0] = fields[i]; 259 } 260 261 for (int j = 0; j < reps.length; j++) { 262 try { 263 StringBuffer statusMessage = new StringBuffer("Parsing field "); 264 statusMessage.append(i+fieldOffset); 265 statusMessage.append(" repetition "); 266 statusMessage.append(j); 267 log.debug(statusMessage.toString()); 268 //parse(destination.getField(i + fieldOffset, j), reps[j], encodingChars, false); 269 270 Type field = destination.getField(i + fieldOffset, j); 271 if (isMSH2) { 272 Terser.getPrimitive(field, 1, 1).setValue(reps[j]); 273 } else { 274 parse(field, reps[j], encodingChars); 275 } 276 } 277 catch (HL7Exception e) { 278 //set the field location and throw again ... 279 e.setFieldPosition(i); 280 e.setSegmentRepetition(MessageIterator.getIndex(destination.getParent(), destination).rep); 281 e.setSegmentName(destination.getName()); 282 throw e; 283 } 284 } 285 } 286 287 //set data type of OBX-5 288 if (destination.getClass().getName().indexOf("OBX") >= 0) { 289 Varies.fixOBX5(destination, getFactory()); 290 } 291 292 } 293 294 /** 295 * @return true if the segment is MSH, FHS, or BHS. These need special treatment 296 * because they define delimiters. 297 * @param theSegmentName 298 */ 299 private static boolean isDelimDefSegment(String theSegmentName) { 300 boolean is = false; 301 if (theSegmentName.equals("MSH") 302 || theSegmentName.equals("FHS") 303 || theSegmentName.equals("BHS")) 304 { 305 is = true; 306 } 307 return is; 308 } 309 310 /** 311 * Fills a field with values from an unparsed string representing the field. 312 * @param destinationField the field Type 313 * @param data the field string (including all components and subcomponents; not including field delimiters) 314 * @param encodingCharacters the encoding characters used in the message 315 */ 316 public void parse(Type destinationField, String data, EncodingCharacters encodingCharacters) throws HL7Exception { 317 String[] components = split(data, String.valueOf(encodingCharacters.getComponentSeparator())); 318 for (int i = 0; i < components.length; i++) { 319 String[] subcomponents = split(components[i], String.valueOf(encodingCharacters.getSubcomponentSeparator())); 320 for (int j = 0; j < subcomponents.length; j++) { 321 String val = subcomponents[j]; 322 if (val != null) { 323 val = Escape.unescape(val, encodingCharacters); 324 } 325 Terser.getPrimitive(destinationField, i+1, j+1).setValue(val); 326 } 327 } 328 } 329 330 /** Returns the component or subcomponent separator from the given encoding characters. */ 331 private static char getSeparator(boolean subComponents, EncodingCharacters encodingChars) { 332 char separator; 333 if (subComponents) { 334 separator = encodingChars.getSubcomponentSeparator(); 335 } 336 else { 337 separator = encodingChars.getComponentSeparator(); 338 } 339 return separator; 340 } 341 342 /** 343 * Splits the given composite string into an array of components using the given 344 * delimiter. 345 */ 346 public static String[] split(String composite, String delim) { 347 ArrayList components = new ArrayList(); 348 349 //defend against evil nulls 350 if (composite == null) 351 composite = ""; 352 if (delim == null) 353 delim = ""; 354 355 StringTokenizer tok = new StringTokenizer(composite, delim, true); 356 boolean previousTokenWasDelim = true; 357 while (tok.hasMoreTokens()) { 358 String thisTok = tok.nextToken(); 359 if (thisTok.equals(delim)) { 360 if (previousTokenWasDelim) 361 components.add(null); 362 previousTokenWasDelim = true; 363 } 364 else { 365 components.add(thisTok); 366 previousTokenWasDelim = false; 367 } 368 } 369 370 String[] ret = new String[components.size()]; 371 for (int i = 0; i < components.size(); i++) { 372 ret[i] = (String) components.get(i); 373 } 374 375 return ret; 376 } 377 378 /** 379 * Encodes the given Type, using the given encoding characters. 380 * It is assumed that the Type represents a complete field rather than a component. 381 */ 382 public static String encode(Type source, EncodingCharacters encodingChars) { 383 StringBuffer field = new StringBuffer(); 384 for (int i = 1; i <= Terser.numComponents(source); i++) { 385 StringBuffer comp = new StringBuffer(); 386 for (int j = 1; j <= Terser.numSubComponents(source, i); j++) { 387 Primitive p = Terser.getPrimitive(source, i, j); 388 comp.append(encodePrimitive(p, encodingChars)); 389 comp.append(encodingChars.getSubcomponentSeparator()); 390 } 391 field.append(stripExtraDelimiters(comp.toString(), encodingChars.getSubcomponentSeparator())); 392 field.append(encodingChars.getComponentSeparator()); 393 } 394 return stripExtraDelimiters(field.toString(), encodingChars.getComponentSeparator()); 395 //return encode(source, encodingChars, false); 396 } 397 398 private static String encodePrimitive(Primitive p, EncodingCharacters encodingChars) { 399 String val = ((Primitive) p).getValue(); 400 if (val == null) { 401 val = ""; 402 } else { 403 val = Escape.escape(val, encodingChars); 404 } 405 return val; 406 } 407 408 /** 409 * Removes unecessary delimiters from the end of a field or segment. 410 * This seems to be more convenient than checking to see if they are needed 411 * while we are building the encoded string. 412 */ 413 private static String stripExtraDelimiters(String in, char delim) { 414 char[] chars = in.toCharArray(); 415 416 //search from back end for first occurance of non-delimiter ... 417 int c = chars.length - 1; 418 boolean found = false; 419 while (c >= 0 && !found) { 420 if (chars[c--] != delim) 421 found = true; 422 } 423 424 String ret = ""; 425 if (found) 426 ret = String.valueOf(chars, 0, c + 2); 427 return ret; 428 } 429 430 /** 431 * Formats a Message object into an HL7 message string using the given 432 * encoding. 433 * @throws HL7Exception if the data fields in the message do not permit encoding 434 * (e.g. required fields are null) 435 * @throws EncodingNotSupportedException if the requested encoding is not 436 * supported by this parser. 437 */ 438 protected String doEncode(Message source, String encoding) throws HL7Exception, EncodingNotSupportedException { 439 if (!this.supportsEncoding(encoding)) 440 throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding"); 441 442 return encode(source); 443 } 444 445 /** 446 * Formats a Message object into an HL7 message string using this parser's 447 * default encoding ("VB"). 448 * @throws HL7Exception if the data fields in the message do not permit encoding 449 * (e.g. required fields are null) 450 */ 451 protected String doEncode(Message source) throws HL7Exception { 452 //get encoding characters ... 453 Segment msh = (Segment) source.get("MSH"); 454 String fieldSepString = Terser.get(msh, 1, 0, 1, 1); 455 456 if (fieldSepString == null) 457 throw new HL7Exception("Can't encode message: MSH-1 (field separator) is missing"); 458 459 char fieldSep = '|'; 460 if (fieldSepString != null && fieldSepString.length() > 0) 461 fieldSep = fieldSepString.charAt(0); 462 463 String encCharString = Terser.get(msh, 2, 0, 1, 1); 464 465 if (encCharString == null) 466 throw new HL7Exception("Can't encode message: MSH-2 (encoding characters) is missing"); 467 468 if (encCharString.length() != 4) 469 throw new HL7Exception( 470 "Encoding characters '" + encCharString + "' invalid -- must be 4 characters", 471 HL7Exception.DATA_TYPE_ERROR); 472 EncodingCharacters en = new EncodingCharacters(fieldSep, encCharString); 473 474 //pass down to group encoding method which will operate recursively on children ... 475 return encode((Group) source, en); 476 } 477 478 /** 479 * Returns given group serialized as a pipe-encoded string - this method is called 480 * by encode(Message source, String encoding). 481 */ 482 public static String encode(Group source, EncodingCharacters encodingChars) throws HL7Exception { 483 StringBuffer result = new StringBuffer(); 484 485 String[] names = source.getNames(); 486 for (int i = 0; i < names.length; i++) { 487 Structure[] reps = source.getAll(names[i]); 488 for (int rep = 0; rep < reps.length; rep++) { 489 if (reps[rep] instanceof Group) { 490 result.append(encode((Group) reps[rep], encodingChars)); 491 } 492 else { 493 String segString = encode((Segment) reps[rep], encodingChars); 494 if (segString.length() >= 4) { 495 result.append(segString); 496 result.append('\r'); 497 } 498 } 499 } 500 } 501 return result.toString(); 502 } 503 504 public static String encode(Segment source, EncodingCharacters encodingChars) { 505 StringBuffer result = new StringBuffer(); 506 result.append(source.getName()); 507 result.append(encodingChars.getFieldSeparator()); 508 509 //start at field 2 for MSH segment because field 1 is the field delimiter 510 int startAt = 1; 511 if (isDelimDefSegment(source.getName())) 512 startAt = 2; 513 514 //loop through fields; for every field delimit any repetitions and add field delimiter after ... 515 int numFields = source.numFields(); 516 for (int i = startAt; i <= numFields; i++) { 517 try { 518 Type[] reps = source.getField(i); 519 for (int j = 0; j < reps.length; j++) { 520 String fieldText = encode(reps[j], encodingChars); 521 //if this is MSH-2, then it shouldn't be escaped, so unescape it again 522 if (isDelimDefSegment(source.getName()) && i == 2) 523 fieldText = Escape.unescape(fieldText, encodingChars); 524 result.append(fieldText); 525 if (j < reps.length - 1) 526 result.append(encodingChars.getRepetitionSeparator()); 527 } 528 } 529 catch (HL7Exception e) { 530 log.error("Error while encoding segment: ", e); 531 } 532 result.append(encodingChars.getFieldSeparator()); 533 } 534 535 //strip trailing delimiters ... 536 return stripExtraDelimiters(result.toString(), encodingChars.getFieldSeparator()); 537 } 538 539 /** 540 * Removes leading whitespace from the given string. This method was created to deal with frequent 541 * problems parsing messages that have been hand-written in windows. The intuitive way to delimit 542 * segments is to hit <ENTER> at the end of each segment, but this creates both a carriage return 543 * and a line feed, so to the parser, the first character of the next segment is the line feed. 544 */ 545 public static String stripLeadingWhitespace(String in) { 546 StringBuffer out = new StringBuffer(); 547 char[] chars = in.toCharArray(); 548 int c = 0; 549 while (c < chars.length) { 550 if (!Character.isWhitespace(chars[c])) 551 break; 552 c++; 553 } 554 for (int i = c; i < chars.length; i++) { 555 out.append(chars[i]); 556 } 557 return out.toString(); 558 } 559 560 /** 561 * <p>Returns a minimal amount of data from a message string, including only the 562 * data needed to send a response to the remote system. This includes the 563 * following fields: 564 * <ul><li>field separator</li> 565 * <li>encoding characters</li> 566 * <li>processing ID</li> 567 * <li>message control ID</li></ul> 568 * This method is intended for use when there is an error parsing a message, 569 * (so the Message object is unavailable) but an error message must be sent 570 * back to the remote system including some of the information in the inbound 571 * message. This method parses only that required information, hopefully 572 * avoiding the condition that caused the original error. The other 573 * fields in the returned MSH segment are empty.</p> 574 */ 575 public Segment getCriticalResponseData(String message) throws HL7Exception { 576 //try to get MSH segment 577 int locStartMSH = message.indexOf("MSH"); 578 if (locStartMSH < 0) 579 throw new HL7Exception( 580 "Couldn't find MSH segment in message: " + message, 581 HL7Exception.SEGMENT_SEQUENCE_ERROR); 582 int locEndMSH = message.indexOf('\r', locStartMSH + 1); 583 if (locEndMSH < 0) 584 locEndMSH = message.length(); 585 String mshString = message.substring(locStartMSH, locEndMSH); 586 587 //find out what the field separator is 588 char fieldSep = mshString.charAt(3); 589 590 //get field array 591 String[] fields = split(mshString, String.valueOf(fieldSep)); 592 593 Segment msh = null; 594 try { 595 //parse required fields 596 String encChars = fields[1]; 597 char compSep = encChars.charAt(0); 598 String messControlID = fields[9]; 599 String[] procIDComps = split(fields[10], String.valueOf(compSep)); 600 601 //fill MSH segment 602 String version = "2.4"; //default 603 try { 604 version = this.getVersion(message); 605 } 606 catch (Exception e) { /* use the default */ 607 } 608 609 msh = Parser.makeControlMSH(version, getFactory()); 610 Terser.set(msh, 1, 0, 1, 1, String.valueOf(fieldSep)); 611 Terser.set(msh, 2, 0, 1, 1, encChars); 612 Terser.set(msh, 10, 0, 1, 1, messControlID); 613 Terser.set(msh, 11, 0, 1, 1, procIDComps[0]); 614 Terser.set(msh, 12, 0, 1, 1, version); 615 616 } 617 catch (Exception e) { 618 throw new HL7Exception( 619 "Can't parse critical fields from MSH segment (" 620 + e.getClass().getName() 621 + ": " 622 + e.getMessage() 623 + "): " 624 + mshString, 625 HL7Exception.REQUIRED_FIELD_MISSING, e); 626 } 627 628 return msh; 629 } 630 631 /** 632 * For response messages, returns the value of MSA-2 (the message ID of the message 633 * sent by the sending system). This value may be needed prior to main message parsing, 634 * so that (particularly in a multi-threaded scenario) the message can be routed to 635 * the thread that sent the request. We need this information first so that any 636 * parse exceptions are thrown to the correct thread. 637 * Returns null if MSA-2 can not be found (e.g. if the message is not a 638 * response message). 639 */ 640 public String getAckID(String message) { 641 String ackID = null; 642 int startMSA = message.indexOf("\rMSA"); 643 if (startMSA >= 0) { 644 int startFieldOne = startMSA + 5; 645 char fieldDelim = message.charAt(startFieldOne - 1); 646 int start = message.indexOf(fieldDelim, startFieldOne) + 1; 647 int end = message.indexOf(fieldDelim, start); 648 int segEnd = message.indexOf(String.valueOf(segDelim), start); 649 if (segEnd > start && segEnd < end) 650 end = segEnd; 651 652 //if there is no field delim after MSH-2, need to go to end of message, but not including end seg delim if it exists 653 if (end < 0) { 654 if (message.charAt(message.length() - 1) == '\r') { 655 end = message.length() - 1; 656 } 657 else { 658 end = message.length(); 659 } 660 } 661 if (start > 0 && end > start) { 662 ackID = message.substring(start, end); 663 } 664 } 665 log.debug("ACK ID: " + ackID); 666 return ackID; 667 } 668 669 /** 670 * Returns the version ID (MSH-12) from the given message, without fully parsing the message. 671 * The version is needed prior to parsing in order to determine the message class 672 * into which the text of the message should be parsed. 673 * @throws HL7Exception if the version field can not be found. 674 */ 675 public String getVersion(String message) throws HL7Exception { 676 int startMSH = message.indexOf("MSH"); 677 int endMSH = message.indexOf(OldPipeParser.segDelim, startMSH); 678 if (endMSH < 0) 679 endMSH = message.length(); 680 String msh = message.substring(startMSH, endMSH); 681 String fieldSep = null; 682 if (msh.length() > 3) { 683 fieldSep = String.valueOf(msh.charAt(3)); 684 } 685 else { 686 throw new HL7Exception("Can't find field separator in MSH: " + msh, HL7Exception.UNSUPPORTED_VERSION_ID); 687 } 688 689 String[] fields = split(msh, fieldSep); 690 691 String compSep = null; 692 if (fields.length >= 2 && fields[1] != null && fields[1].length() == 4) { 693 compSep = String.valueOf(fields[1].charAt(0)); //get component separator as 1st encoding char 694 } 695 else { 696 throw new HL7Exception("Invalid or incomplete encoding characters - MSH-2 is " + fields[1], 697 HL7Exception.REQUIRED_FIELD_MISSING); 698 } 699 700 String version = null; 701 if (fields.length >= 12) { 702 String[] comp = split(fields[11], compSep); 703 if (comp.length >= 1) { 704 version = comp[0]; 705 } else { 706 throw new HL7Exception("Can't find version ID - MSH.12 is " + fields[11], 707 HL7Exception.REQUIRED_FIELD_MISSING); 708 } 709 } 710 else { 711 throw new HL7Exception( 712 "Can't find version ID - MSH has only " + fields.length + " fields.", 713 HL7Exception.REQUIRED_FIELD_MISSING); 714 } 715 return version; 716 } 717 718 /** 719 * {@inheritDoc } 720 */ 721 public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception { 722 return encode(structure, encodingCharacters); 723 } 724 725 /** 726 * {@inheritDoc } 727 */ 728 public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception { 729 return encode(type, encodingCharacters); 730 } 731 732 public void parse(Message message, String string) throws HL7Exception { 733 //MessagePointer ptr = new MessagePointer(this, m, getEncodingChars(message)); 734 MessageIterator messageIter = new MessageIterator(message, "MSH", true); 735 FilterIterator.Predicate segmentsOnly = new FilterIterator.Predicate() { 736 public boolean evaluate(Object obj) { 737 if (Segment.class.isAssignableFrom(obj.getClass())) { 738 return true; 739 } else { 740 return false; 741 } 742 } 743 }; 744 FilterIterator segmentIter = new FilterIterator(messageIter, segmentsOnly); 745 746 String[] segments = split(string, segDelim); 747 748 char delim = '|'; 749 for (int i = 0; i < segments.length; i++) { 750 751 //get rid of any leading whitespace characters ... 752 if (segments[i] != null && segments[i].length() > 0 && Character.isWhitespace(segments[i].charAt(0))) 753 segments[i] = stripLeadingWhitespace(segments[i]); 754 755 //sometimes people put extra segment delimiters at end of msg ... 756 if (segments[i] != null && segments[i].length() >= 3) { 757 final String name; 758 if (i == 0) { 759 name = segments[i].substring(0, 3); 760 delim = segments[i].charAt(3); 761 } else { 762 if (segments[i].indexOf(delim) >= 0 ) { 763 name = segments[i].substring(0, segments[i].indexOf(delim)); 764 } else { 765 name = segments[i]; 766 } 767 } 768 769 log.debug("Parsing segment " + name); 770 771 messageIter.setDirection(name); 772 FilterIterator.Predicate byDirection = new FilterIterator.Predicate() { 773 public boolean evaluate(Object obj) { 774 Structure s = (Structure) obj; 775 log.debug("PipeParser iterating message in direction " + name + " at " + s.getName()); 776 if (s.getName().matches(name + "\\d*")) { 777 return true; 778 } else { 779 return false; 780 } 781 } 782 }; 783 FilterIterator dirIter = new FilterIterator(segmentIter, byDirection); 784 if (dirIter.hasNext()) { 785 parse((Segment) dirIter.next(), segments[i], getEncodingChars(string)); 786 } 787 } 788 } 789 } 790 791 792 /** 793 * A struct for holding a message class string and a boolean indicating whether it 794 * was defined explicitly. 795 */ 796 private static class MessageStructure { 797 public String messageStructure; 798 public boolean explicitlyDefined; 799 800 public MessageStructure(String theMessageStructure, boolean isExplicitlyDefined) { 801 messageStructure = theMessageStructure; 802 explicitlyDefined = isExplicitlyDefined; 803 } 804 } 805 806 }