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