Package ca.uhn.hl7v2.parser

Source Code of ca.uhn.hl7v2.parser.PipeParser$MessageStructure

/**
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
* specific language governing rights and limitations under the License.
*
* The Original Code is "PipeParser.java".  Description:
* "An implementation of Parser that supports traditionally encoded (i.e"
*
* The Initial Developer of the Original Code is University Health Network. Copyright (C)
* 2001.  All Rights Reserved.
*
* Contributor(s): Kenneth Beaton.
*
* Alternatively, the contents of this file may be used under the terms of the
* GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
* applicable instead of those above.  If you wish to allow use of your version of this
* file only under the terms of the GPL and not to allow others to use your version
* of this file under the MPL, indicate your decision by deleting  the provisions above
* and replace  them with the notice and other provisions required by the GPL License.
* If you do not delete the provisions above, a recipient may use your version of
* this file under either the MPL or the GPL.
*
*/

package ca.uhn.hl7v2.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.model.DoNotCacheStructure;
import ca.uhn.hl7v2.model.Group;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.Primitive;
import ca.uhn.hl7v2.model.Segment;
import ca.uhn.hl7v2.model.Structure;
import ca.uhn.hl7v2.model.Type;
import ca.uhn.hl7v2.model.Varies;
import ca.uhn.hl7v2.util.ReflectionUtil;
import ca.uhn.hl7v2.util.Terser;
import ca.uhn.hl7v2.validation.impl.NoValidation;

/**
* An implementation of Parser that supports traditionally encoded (ie delimited
* with characters like |, ^, and ~) HL7 messages. Unexpected segments and
* fields are parsed into generic elements that are added to the message.
*
* @author Bryan Tripp (bryan_tripp@sourceforge.net)
*/
public class PipeParser extends Parser {

    private static final Logger log = LoggerFactory.getLogger(PipeParser.class);

    private final static String segDelim = "\r"; // see section 2.8 of spec

    private final HashMap<Class<? extends Message>, StructureDefinition> myStructureDefinitions = new HashMap<Class<? extends Message>, StructureDefinition>();

    /**
     * System property key. If value is "true", legacy mode will default to true
     *
     * @see #isLegacyMode()
     */
    public static final String DEFAULT_LEGACY_MODE_PROPERTY = "ca.uhn.hl7v2.parser.PipeParser.default_legacy_mode";

    private Boolean myLegacyMode = null;

   
  /** Creates a new PipeParser */
    public PipeParser() {
    }


    /**
     * Creates a new PipeParser
     *
     * @param theFactory
     *            custom factory to use for model class lookup
     */
    public PipeParser(ModelClassFactory theFactory) {
        super(theFactory);
    }


    /**
     * Returns a String representing the encoding of the given message, if the
     * encoding is recognized. For example if the given message appears to be
     * encoded using HL7 2.x XML rules then "XML" would be returned. If the
     * encoding is not recognized then null is returned. That this method
     * returns a specific encoding does not guarantee that the message is
     * correctly encoded (e.g. well formed XML) - just that it is not encoded
     * using any other encoding than the one returned.
     */
    public String getEncoding(String message) {
        String encoding = null;

        // quit if the string is too short
        if (message.length() < 4)
            return null;

        // see if it looks like this message is | encoded ...
        boolean ok = true;

        // string should start with "MSH"
        if (!message.startsWith("MSH"))
            return null;

        // 4th character of each segment should be field delimiter
        char fourthChar = message.charAt(3);
        StringTokenizer st = new StringTokenizer(message, String.valueOf(segDelim), false);
        while (st.hasMoreTokens()) {
            String x = st.nextToken();
            if (x.length() > 0) {
                if (Character.isWhitespace(x.charAt(0)))
                    x = stripLeadingWhitespace(x);
                if (x.length() >= 4 && x.charAt(3) != fourthChar)
                    return null;
            }
        }

        // should be at least 11 field delimiters (because MSH-12 is required)
        int nextFieldDelimLoc = 0;
        for (int i = 0; i < 11; i++) {
            nextFieldDelimLoc = message.indexOf(fourthChar, nextFieldDelimLoc + 1);
            if (nextFieldDelimLoc < 0)
                return null;
        }

        if (ok)
            encoding = "VB";

        return encoding;
    }


    /**
     * @return the preferred encoding of this Parser
     */
    public String getDefaultEncoding() {
        return "VB";
    }


    /**
     * Returns true if and only if the given encoding is supported by this
     * Parser.
     */
    public boolean supportsEncoding(String encoding) {
        boolean supports = false;
        if (encoding != null && encoding.equals("VB"))
            supports = true;
        return supports;
    }


    /**
     * @deprecated this method should not be public
     * @param message
     * @return
     * @throws HL7Exception
     * @throws EncodingNotSupportedException
     */
    public String getMessageStructure(String message) throws HL7Exception, EncodingNotSupportedException {
        return getStructure(message).messageStructure;
    }


    /**
     * @returns the message structure from MSH-9-3
     */
    private MessageStructure getStructure(String message) throws HL7Exception, EncodingNotSupportedException {
        EncodingCharacters ec = getEncodingChars(message);
        String messageStructure = null;
        boolean explicityDefined = true;
        String wholeFieldNine;
        try {
            String[] fields = split(message.substring(0, Math.max(message.indexOf(segDelim), message.length())), String.valueOf(ec.getFieldSeparator()));
            wholeFieldNine = fields[8];

            // message structure is component 3 but we'll accept a composite of
            // 1 and 2 if there is no component 3 ...
            // if component 1 is ACK, then the structure is ACK regardless of
            // component 2
            String[] comps = split(wholeFieldNine, String.valueOf(ec.getComponentSeparator()));
            if (comps.length >= 3) {
                messageStructure = comps[2];
            } else if (comps.length > 0 && comps[0] != null && comps[0].equals("ACK")) {
                messageStructure = "ACK";
            } else if (comps.length == 2) {
                explicityDefined = false;
                messageStructure = comps[0] + "_" + comps[1];
            }
            /*
             * else if (comps.length == 1 && comps[0] != null &&
             * comps[0].equals("ACK")) { messageStructure = "ACK"; //it's common
             * for people to only populate component 1 in an ACK msg }
             */
            else {
                StringBuilder buf = new StringBuilder("Can't determine message structure from MSH-9: ");
                buf.append(wholeFieldNine);
                if (comps.length < 3) {
                    buf.append(" HINT: there are only ");
                    buf.append(comps.length);
                    buf.append(" of 3 components present");
                }
                throw new HL7Exception(buf.toString(), HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
            }
        } catch (IndexOutOfBoundsException e) {
            throw new HL7Exception("Can't find message structure (MSH-9-3): " + e.getMessage(), HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
        }

        return new MessageStructure(messageStructure, explicityDefined);
    }


    /**
     * Returns object that contains the field separator and encoding characters
     * for this message.
     * @throws HL7Exception
     */
    private static EncodingCharacters getEncodingChars(String message) throws HL7Exception {
      if (message.length() < 8) {
          throw new HL7Exception("Invalid message content: \"" + message + "\"");
      }
        return new EncodingCharacters(message.charAt(3), message.substring(4, 8));
    }


    /**
     * Parses a message string and returns the corresponding Message object.
     * Unexpected segments added at the end of their group.
     *
     * @throws HL7Exception
     *             if the message is not correctly formatted.
     * @throws EncodingNotSupportedException
     *             if the message encoded is not supported by this parser.
     */
    protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException {

        // try to instantiate a message object of the right class
        MessageStructure structure = getStructure(message);
        Message m = instantiateMessage(structure.messageStructure, version, structure.explicitlyDefined);

        parse(m, message);

        return m;
    }

   
    /**
     * {@inheritDoc}
     */
    protected Message doParseForSpecificPackage(String message, String version, String packageName) throws HL7Exception, EncodingNotSupportedException {

        // try to instantiate a message object of the right class
        MessageStructure structure = getStructure(message);
        Message m = instantiateMessageInASpecificPackage(structure.messageStructure, version, structure.explicitlyDefined, packageName);

        parse(m, message);

        return m;
    }
   

    /**
     * Generates (or returns the cached value of) the message
     */
    private IStructureDefinition getStructureDefinition(Message theMessage) throws HL7Exception {

      Class<? extends Message> clazz = theMessage.getClass();
        StructureDefinition retVal = myStructureDefinitions.get(clazz);
        if (retVal != null) {
            return retVal;
        }
       
        if (clazz.isAnnotationPresent(DoNotCacheStructure.class)) {
            Holder<StructureDefinition> previousLeaf = new Holder<StructureDefinition>();
            retVal = createStructureDefinition(theMessage, previousLeaf);
        } else {
          Message message = ReflectionUtil.instantiateMessage(clazz, getFactory());
          Holder<StructureDefinition> previousLeaf = new Holder<StructureDefinition>();
          retVal = createStructureDefinition(message, previousLeaf);
          myStructureDefinitions.put(clazz, retVal);
        }
       
        return retVal;
    }


    private StructureDefinition createStructureDefinition(Structure theStructure, Holder<StructureDefinition> thePreviousLeaf) throws HL7Exception {

        StructureDefinition retVal = new StructureDefinition();
        retVal.setName(theStructure.getName());

        if (theStructure instanceof Group) {
            retVal.setSegment(false);
            Group group = (Group) theStructure;
            int index = 0;
            for (String nextName : group.getNames()) {
                Structure nextChild = group.get(nextName);
                StructureDefinition structureDefinition = createStructureDefinition(nextChild, thePreviousLeaf);
                structureDefinition.setNameAsItAppearsInParent(nextName);
                structureDefinition.setRepeating(group.isRepeating(nextName));
                structureDefinition.setRequired(group.isRequired(nextName));
                structureDefinition.setPosition(index++);
                structureDefinition.setParent(retVal);
                retVal.addChild(structureDefinition);
            }
        } else {
            if (thePreviousLeaf.getObject() != null) {
                thePreviousLeaf.getObject().setNextLeaf(retVal);
            }
            thePreviousLeaf.setObject(retVal);
            retVal.setSegment(true);
        }

        return retVal;
    }


    /**
     * Parses a segment string and populates the given Segment object.
     * Unexpected fields are added as Varies' at the end of the segment.
     *
     * @param theRepetition
     *            The repetition number of this segment within its group
     * @throws HL7Exception
     *             if the given string does not contain the given segment or if
     *             the string is not encoded properly
     */
    public void parse(Segment destination, String segment, EncodingCharacters encodingChars) throws HL7Exception {
        parse(destination, segment, encodingChars, null);
    }


    /**
     * Parses a segment string and populates the given Segment object.
     * Unexpected fields are added as Varies' at the end of the segment.
     *
     * @param theRepetition
     *            The repetition number of this segment within its group
     * @throws HL7Exception
     *             if the given string does not contain the given segment or if
     *             the string is not encoded properly
     */
    public void parse(Segment destination, String segment, EncodingCharacters encodingChars, Integer theRepetition) throws HL7Exception {
        int fieldOffset = 0;
        if (isDelimDefSegment(destination.getName())) {
            fieldOffset = 1;
            // set field 1 to fourth character of string
            Terser.set(destination, 1, 0, 1, 1, String.valueOf(encodingChars.getFieldSeparator()));
        }

        String[] fields = split(segment, String.valueOf(encodingChars.getFieldSeparator()));
        // destination.setName(fields[0]);
        for (int i = 1; i < fields.length; i++) {
            String[] reps = split(fields[i], String.valueOf(encodingChars.getRepetitionSeparator()));

            // MSH-2 will get split incorrectly so we have to fudge it ...
            boolean isMSH2 = isDelimDefSegment(destination.getName()) && i + fieldOffset == 2;
            if (isMSH2) {
                reps = new String[1];
                reps[0] = fields[i];
            }

            for (int j = 0; j < reps.length; j++) {
                try {
                  log.debug("Parsing field {} repetition {}", i + fieldOffset, j);
                    Type field = destination.getField(i + fieldOffset, j);
                    if (isMSH2) {
                        Terser.getPrimitive(field, 1, 1).setValue(reps[j]);
                    } else {
                        parse(field, reps[j], encodingChars);
                    }
                } catch (HL7Exception e) {
                    // set the field location and throw again ...
                    e.setFieldPosition(i);
                    if (theRepetition != null) {
                        e.setSegmentRepetition(theRepetition);
                    }
                    e.setSegmentName(destination.getName());
                    throw e;
                }
            }
        }

        // set data type of OBX-5
        if (destination.getClass().getName().indexOf("OBX") >= 0) {
            Varies.fixOBX5(destination, getFactory());
        }

    }


    /**
     * @return true if the segment is MSH, FHS, or BHS. These need special
     *         treatment because they define delimiters.
     * @param theSegmentName
     */
    private static boolean isDelimDefSegment(String theSegmentName) {
        boolean is = false;
        if (theSegmentName.equals("MSH") || theSegmentName.equals("FHS") || theSegmentName.equals("BHS")) {
            is = true;
        }
        return is;
    }


    /**
     * Fills a field with values from an unparsed string representing the field.
     *
     * @param destinationField
     *            the field Type
     * @param data
     *            the field string (including all components and subcomponents;
     *            not including field delimiters)
     * @param encodingCharacters
     *            the encoding characters used in the message
     */
    @Override
    public void parse(Type destinationField, String data, EncodingCharacters encodingCharacters) throws HL7Exception {
        String[] components = split(data, String.valueOf(encodingCharacters.getComponentSeparator()));
        for (int i = 0; i < components.length; i++) {
            String[] subcomponents = split(components[i], String.valueOf(encodingCharacters.getSubcomponentSeparator()));
            for (int j = 0; j < subcomponents.length; j++) {
                String val = subcomponents[j];
                if (val != null) {
                    val = Escape.unescape(val, encodingCharacters);
                }
                Terser.getPrimitive(destinationField, i + 1, j + 1).setValue(val);
            }
        }
    }


    /**
     * Splits the given composite string into an array of components using the
     * given delimiter.
     */
    public static String[] split(String composite, String delim) {
        ArrayList<String> components = new ArrayList<String>();

        // defend against evil nulls
        if (composite == null)
            composite = "";
        if (delim == null)
            delim = "";

        StringTokenizer tok = new StringTokenizer(composite, delim, true);
        boolean previousTokenWasDelim = true;
        while (tok.hasMoreTokens()) {
            String thisTok = tok.nextToken();
            if (thisTok.equals(delim)) {
                if (previousTokenWasDelim)
                    components.add(null);
                previousTokenWasDelim = true;
            } else {
                components.add(thisTok);
                previousTokenWasDelim = false;
            }
        }

        String[] ret = new String[components.size()];
        for (int i = 0; i < components.size(); i++) {
            ret[i] = (String) components.get(i);
        }

        return ret;
    }


    /**
     * {@inheritDoc }
     */
    @Override
    public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
        return encode(structure, encodingCharacters);
    }


    /**
     * {@inheritDoc }
     */
    @Override
    public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
        return encode(type, encodingCharacters);
    }


    /**
     * Encodes the given Type, using the given encoding characters. It is
     * assumed that the Type represents a complete field rather than a
     * component.
     */
    public static String encode(Type source, EncodingCharacters encodingChars) {
      return encode(source, encodingChars, null, null);
    }


    private static String encode(Type source, EncodingCharacters encodingChars, ParserConfiguration parserConfig, String currentTerserPath) {
        if (source instanceof Varies) {
          Varies varies = (Varies) source;
          if (varies.getData() != null) {
            source = varies.getData();
          }
        }
     
        StringBuilder field = new StringBuilder();
        for (int i = 1; i <= Terser.numComponents(source); i++) {
            StringBuilder comp = new StringBuilder();
            for (int j = 1; j <= Terser.numSubComponents(source, i); j++) {
                Primitive p = Terser.getPrimitive(source, i, j);
                comp.append(encodePrimitive(p, encodingChars));
                comp.append(encodingChars.getSubcomponentSeparator());
            }
            field.append(stripExtraDelimiters(comp.toString(), encodingChars.getSubcomponentSeparator()));
            field.append(encodingChars.getComponentSeparator());
        }
       
        int forceUpToFieldNum = 0;
        if (parserConfig != null && currentTerserPath != null) {
          for (String nextPath : parserConfig.getForcedEncode()) {
            if (nextPath.startsWith(currentTerserPath) && nextPath.length() > currentTerserPath.length()) {
              int endOfFieldDef = nextPath.indexOf('-', currentTerserPath.length());
              if (endOfFieldDef == -1) {
                forceUpToFieldNum = 0;
                break;
              }
              String fieldNumString = nextPath.substring(endOfFieldDef + 1, nextPath.length());
              if (fieldNumString.length() > 0) {
                forceUpToFieldNum = Math.max(forceUpToFieldNum, Integer.parseInt(fieldNumString));
              }
            }
          }
        }
       
        char componentSeparator = encodingChars.getComponentSeparator();
    String retVal = stripExtraDelimiters(field.toString(), componentSeparator);
       
    while (forceUpToFieldNum > 0 && (countInstancesOf(retVal, componentSeparator) + 1) < forceUpToFieldNum) {
      retVal = retVal + componentSeparator;
    }
       
    return retVal;
  }


  private static String encodePrimitive(Primitive p, EncodingCharacters encodingChars) {
        String val = (p).getValue();
        if (val == null) {
            val = "";
        } else {
            val = Escape.escape(val, encodingChars);
        }
        return val;
    }


    /**
     * Removes unecessary delimiters from the end of a field or segment. This
     * seems to be more convenient than checking to see if they are needed while
     * we are building the encoded string.
     */
    private static String stripExtraDelimiters(String in, char delim) {
        char[] chars = in.toCharArray();

        // search from back end for first occurance of non-delimiter ...
        int c = chars.length - 1;
        boolean found = false;
        while (c >= 0 && !found) {
            if (chars[c--] != delim)
                found = true;
        }

        String ret = "";
        if (found)
            ret = String.valueOf(chars, 0, c + 2);
        return ret;
    }


    /**
     * Formats a Message object into an HL7 message string using the given
     * encoding.
     *
     * @throws HL7Exception
     *             if the data fields in the message do not permit encoding
     *             (e.g. required fields are null)
     * @throws EncodingNotSupportedException
     *             if the requested encoding is not supported by this parser.
     */
    protected String doEncode(Message source, String encoding) throws HL7Exception, EncodingNotSupportedException {
        if (!this.supportsEncoding(encoding))
            throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding");

        return encode(source);
    }


    /**
     * Formats a Message object into an HL7 message string using this parser's
     * default encoding ("VB").
     *
     * @throws HL7Exception
     *             if the data fields in the message do not permit encoding
     *             (e.g. required fields are null)
     */
    protected String doEncode(Message source) throws HL7Exception {
        // get encoding characters ...
        Segment msh = (Segment) source.get("MSH");
        String fieldSepString = Terser.get(msh, 1, 0, 1, 1);

        if (fieldSepString == null)
            throw new HL7Exception("Can't encode message: MSH-1 (field separator) is missing");

        char fieldSep = '|';
        if (fieldSepString != null && fieldSepString.length() > 0)
            fieldSep = fieldSepString.charAt(0);

        String encCharString = Terser.get(msh, 2, 0, 1, 1);

        if (encCharString == null)
            throw new HL7Exception("Can't encode message: MSH-2 (encoding characters) is missing");

        if (encCharString.length() != 4)
            throw new HL7Exception("Encoding characters (MSH-2) value '" + encCharString + "' invalid -- must be 4 characters", HL7Exception.DATA_TYPE_ERROR);
        EncodingCharacters en = new EncodingCharacters(fieldSep, encCharString);

        // pass down to group encoding method which will operate recursively on
        // children ...
        return encode((Group) source, en, getParserConfiguration(), "");
    }


    /**
     * Returns given group serialized as a pipe-encoded string - this method is
     * called by encode(Message source, String encoding).
     */
    public static String encode(Group source, EncodingCharacters encodingChars) throws HL7Exception {
        return encode(source, encodingChars, source.getMessage().getParser().getParserConfiguration(), "");
    }


    /**
     * Returns given group serialized as a pipe-encoded string - this method is
     * called by encode(Message source, String encoding).
     */
  private static String encode(Group source, EncodingCharacters encodingChars, ParserConfiguration parserConfiguration, String currentTerserPath) throws HL7Exception {
    StringBuilder result = new StringBuilder();

        String[] names = source.getNames();
       
        String firstMandatorySegmentName = null;
        boolean haveEncounteredMandatorySegment = false;
        boolean haveEncounteredContent = false;
        boolean haveHadMandatorySegment = false;
        boolean haveHadSegmentBeforeMandatorySegment = false;
               
        for (int i = 0; i < names.length; i++) {
         
            String nextName = names[i];
            source.get(nextName, 0);
      Structure[] reps = source.getAll(nextName);
            boolean nextNameIsRequired = source.isRequired(nextName);
       
            boolean havePreviouslyEncounteredMandatorySegment = haveEncounteredMandatorySegment;
            haveEncounteredMandatorySegment |= nextNameIsRequired;
            if (nextNameIsRequired && !haveHadMandatorySegment) {
                if (!source.isGroup(nextName)) {
                  firstMandatorySegmentName = nextName;
                }
            }
           
            String nextTerserPath = currentTerserPath.length() > 0 ? currentTerserPath + "/" + nextName : nextName;
           
            // Add all reps of the next segment/group
            for (int rep = 0; rep < reps.length; rep++) {
             
                if (reps[rep] instanceof Group) {
                   
                  String encodedGroup = encode((Group) reps[rep], encodingChars, parserConfiguration, nextTerserPath);
          result.append(encodedGroup);
         
          if (encodedGroup.length() > 0) {
                        if (!haveHadMandatorySegment && !haveEncounteredMandatorySegment) {
                          haveHadSegmentBeforeMandatorySegment = true;
                        }
                        if (nextNameIsRequired && !haveHadMandatorySegment && !havePreviouslyEncounteredMandatorySegment) {
                          haveHadMandatorySegment = true;
                        }
            haveEncounteredContent = true;
          }
         
                } else {

                  // Check if we are configured to force the encoding of this segment
                  boolean encodeEmptySegments = parserConfiguration.determineForcedEncodeIncludesTerserPath(nextTerserPath);
                  String segString = encode((Segment) reps[rep], encodingChars, parserConfiguration, nextTerserPath);
                    if (segString.length() >= 4 || encodeEmptySegments) {
                        result.append(segString);
                       
                        if (segString.length() == 3) {
                          result.append(encodingChars.getFieldSeparator());
                        }
                       
                        result.append(segDelim);
                       
                        haveEncounteredContent = true;
                       
                        if (nextNameIsRequired) {
                          haveHadMandatorySegment = true;
                        }
                       
                        if (!haveHadMandatorySegment && !haveEncounteredMandatorySegment) {
                          haveHadSegmentBeforeMandatorySegment = true;
                        }
                       
                    }
                   
                }
               
            }
                       
        }
       
        if (firstMandatorySegmentName != null && !haveHadMandatorySegment &&
            !haveHadSegmentBeforeMandatorySegment && haveEncounteredContent &&
            parserConfiguration.isEncodeEmptyMandatorySegments()) {
          return firstMandatorySegmentName.substring(0, 3) + encodingChars.getFieldSeparator() + segDelim + result;
        } else {
          return result.toString();
        }
  }

    /**
     * Convenience factory method which returns an instance that has a
     * {@link NoValidation NoValidation validation context}.
     */
    public static PipeParser getInstanceWithNoValidation() {
        PipeParser retVal = new PipeParser();
        retVal.setValidationContext(new NoValidation());
        return retVal;
    }

    public static String encode(Segment source, EncodingCharacters encodingChars) {
      return encode(source, encodingChars, null, null);
    }


    private static String encode(Segment source, EncodingCharacters encodingChars, ParserConfiguration parserConfig, String currentTerserPath) {
        StringBuilder result = new StringBuilder();
        result.append(source.getName());
        result.append(encodingChars.getFieldSeparator());

        // start at field 2 for MSH segment because field 1 is the field
        // delimiter
        int startAt = 1;
        if (isDelimDefSegment(source.getName()))
            startAt = 2;

        // loop through fields; for every field delimit any repetitions and add
        // field delimiter after ...
        int numFields = source.numFields();
        for (int i = startAt; i <= numFields; i++) {
         
          String nextFieldTerserPath = currentTerserPath + "-" + i;
            if (parserConfig != null && currentTerserPath != null) {
              for (String nextPath : parserConfig.getForcedEncode()) {
                if (nextPath.startsWith(nextFieldTerserPath)) {
                  try {
              source.getField(i, 0);
            } catch (HL7Exception e) {
                      log.error("Error while encoding segment: ", e);
            }
                }
              }
            }
           
            try {
                Type[] reps = source.getField(i);
                for (int j = 0; j < reps.length; j++) {
                    String fieldText = encode(reps[j], encodingChars, parserConfig, nextFieldTerserPath);
                    // if this is MSH-2, then it shouldn't be escaped, so
                    // unescape it again
                    if (isDelimDefSegment(source.getName()) && i == 2)
                        fieldText = Escape.unescape(fieldText, encodingChars);
                    result.append(fieldText);
                    if (j < reps.length - 1)
                        result.append(encodingChars.getRepetitionSeparator());
                }
            } catch (HL7Exception e) {
                log.error("Error while encoding segment: ", e);
            }
            result.append(encodingChars.getFieldSeparator());
        }

        int forceUpToFieldNum = 0;
        if (parserConfig != null && currentTerserPath != null) {
          forceUpToFieldNum = parserConfig.determineForcedFieldNumForTerserPath(currentTerserPath);
        }
       
        // strip trailing delimiters ...
        char fieldSeparator = encodingChars.getFieldSeparator();
    String retVal = stripExtraDelimiters(result.toString(), fieldSeparator);
   
    int offset = isDelimDefSegment(source.getName()) ? 1 : 0;
    while (forceUpToFieldNum > 0 && (countInstancesOf(retVal, fieldSeparator) + offset) < forceUpToFieldNum) {
      retVal = retVal + fieldSeparator;
    }
   
    return retVal;
  }


  private static int countInstancesOf(String theString, char theCharToSearchFor) {
    int retVal = 0;
    for (int i = 0; i < theString.length(); i++) {
      if (theString.charAt(i) == theCharToSearchFor) {
        retVal++;
      }
    }
    return retVal;
  }


  /**
     * Removes leading whitespace from the given string. This method was created
     * to deal with frequent problems parsing messages that have been
     * hand-written in windows. The intuitive way to delimit segments is to hit
     * <ENTER> at the end of each segment, but this creates both a carriage
     * return and a line feed, so to the parser, the first character of the next
     * segment is the line feed.
     */
    public static String stripLeadingWhitespace(String in) {
        StringBuilder out = new StringBuilder();
        char[] chars = in.toCharArray();
        int c = 0;
        while (c < chars.length) {
            if (!Character.isWhitespace(chars[c]))
                break;
            c++;
        }
        for (int i = c; i < chars.length; i++) {
            out.append(chars[i]);
        }
        return out.toString();
    }


    /**
     * <p>
     * Returns a minimal amount of data from a message string, including only
     * the data needed to send a response to the remote system. This includes
     * the following fields:
     * <ul>
     * <li>field separator</li>
     * <li>encoding characters</li>
     * <li>processing ID</li>
     * <li>message control ID</li>
     * </ul>
     * This method is intended for use when there is an error parsing a message,
     * (so the Message object is unavailable) but an error message must be sent
     * back to the remote system including some of the information in the
     * inbound message. This method parses only that required information,
     * hopefully avoiding the condition that caused the original error. The
     * other fields in the returned MSH segment are empty.
     * </p>
     */
    public Segment getCriticalResponseData(String message) throws HL7Exception {
        // try to get MSH segment
        int locStartMSH = message.indexOf("MSH");
        if (locStartMSH < 0)
            throw new HL7Exception("Couldn't find MSH segment in message: " + message, HL7Exception.SEGMENT_SEQUENCE_ERROR);
        int locEndMSH = message.indexOf('\r', locStartMSH + 1);
        if (locEndMSH < 0)
            locEndMSH = message.length();
        String mshString = message.substring(locStartMSH, locEndMSH);

        // find out what the field separator is
        char fieldSep = mshString.charAt(3);

        // get field array
        String[] fields = split(mshString, String.valueOf(fieldSep));

        Segment msh = null;
        try {
            // parse required fields
            String encChars = fields[1];
            char compSep = encChars.charAt(0);
            String messControlID = fields[9];
            String[] procIDComps = split(fields[10], String.valueOf(compSep));

            // fill MSH segment
            String version = "2.4"; // default
            try {
                version = this.getVersion(message);
            } catch (Exception e) { /* use the default */
            }

            msh = Parser.makeControlMSH(version, getFactory());
            Terser.set(msh, 1, 0, 1, 1, String.valueOf(fieldSep));
            Terser.set(msh, 2, 0, 1, 1, encChars);
            Terser.set(msh, 10, 0, 1, 1, messControlID);
            Terser.set(msh, 11, 0, 1, 1, procIDComps[0]);
            Terser.set(msh, 12, 0, 1, 1, version);

        } catch (Exception e) {
            throw new HL7Exception("Can't parse critical fields from MSH segment (" + e.getClass().getName() + ": " + e.getMessage() + "): " + mshString, HL7Exception.REQUIRED_FIELD_MISSING, e);
        }

        return msh;
    }


    /**
     * For response messages, returns the value of MSA-2 (the message ID of the
     * message sent by the sending system). This value may be needed prior to
     * main message parsing, so that (particularly in a multi-threaded scenario)
     * the message can be routed to the thread that sent the request. We need
     * this information first so that any parse exceptions are thrown to the
     * correct thread. Returns null if MSA-2 can not be found (e.g. if the
     * message is not a response message).
     */
    public String getAckID(String message) {
        String ackID = null;
        int startMSA = message.indexOf("\rMSA");
        if (startMSA >= 0) {
            int startFieldOne = startMSA + 5;
            char fieldDelim = message.charAt(startFieldOne - 1);
            int start = message.indexOf(fieldDelim, startFieldOne) + 1;
            int end = message.indexOf(fieldDelim, start);
            int segEnd = message.indexOf(String.valueOf(segDelim), start);
            if (segEnd > start && segEnd < end)
                end = segEnd;

            // 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
            if (end < 0) {
                if (message.charAt(message.length() - 1) == '\r') {
                    end = message.length() - 1;
                } else {
                    end = message.length();
                }
            }
            if (start > 0 && end > start) {
                ackID = message.substring(start, end);
            }
        }
        log.debug("ACK ID: {}", ackID);
        return ackID;
    }


    /**
     * Defaults to <code>false</code>
     *
     * @see #isLegacyMode()
     */
    public void setLegacyMode(boolean legacyMode) {
        this.myLegacyMode = legacyMode;
    }


    /**
     * {@inheritDoc }
     */
    @Override
    public String encode(Message source) throws HL7Exception {
        if (myLegacyMode != null && myLegacyMode) {
           
            @SuppressWarnings("deprecation")
            OldPipeParser oldPipeParser = new OldPipeParser(getFactory());
           
            return oldPipeParser.encode(source);
        }
        return super.encode(source);
    }


    /**
     * {@inheritDoc }
     */
    @Override
    public Message parse(String message) throws HL7Exception, EncodingNotSupportedException {
        if (myLegacyMode != null && myLegacyMode) {
           
            @SuppressWarnings("deprecation")
            OldPipeParser oldPipeParser = new OldPipeParser(getFactory());
           
            return oldPipeParser.parse(message);
        }
        return super.parse(message);
    }


    /**
     * <p>
     * Returns <code>true</code> if legacy mode is on.
     * </p>
     * <p>
     * Prior to release 1.0, when an unexpected segment was encountered in a
     * message, HAPI would recurse to the deepest nesting in the last group it
     * encountered after the current position in the message, and deposit the
     * segment there. This could lead to unusual behaviour where all segments
     * afterward would not be in an expected spot within the message.
     * </p>
     * <p>
     * This should normally be set to false, but any code written before the
     * release of HAPI 1.0 which depended on this behaviour might need legacy
     * mode to be set to true.
     * </p>
     * <p>
     * Defaults to <code>false</code>. Note that this method only overrides
     * behaviour of the {@link #parse(java.lang.String)} and
     * {@link #encode(ca.uhn.hl7v2.model.Message) } methods
     * </p>
     */
    public boolean isLegacyMode() {
        if (myLegacyMode == null) {
            if (Boolean.parseBoolean(System.getProperty(DEFAULT_LEGACY_MODE_PROPERTY))) {
                return true;
            } else {
                return false;
            }
        }
        return this.myLegacyMode;
    }


    /**
     * Returns the version ID (MSH-12) from the given message, without fully
     * parsing the message. The version is needed prior to parsing in order to
     * determine the message class into which the text of the message should be
     * parsed.
     *
     * @throws HL7Exception
     *             if the version field can not be found.
     */
    public String getVersion(String message) throws HL7Exception {
        int startMSH = message.indexOf("MSH");
        int endMSH = message.indexOf(PipeParser.segDelim, startMSH);
        if (endMSH < 0)
            endMSH = message.length();
        String msh = message.substring(startMSH, endMSH);
        String fieldSep = null;
        if (msh.length() > 3) {
            fieldSep = String.valueOf(msh.charAt(3));
        } else {
            throw new HL7Exception("Can't find field separator in MSH: " + msh, HL7Exception.UNSUPPORTED_VERSION_ID);
        }

        String[] fields = split(msh, fieldSep);

        String compSep = null;
        if (fields.length >= 2 && fields[1] != null && fields[1].length() == 4) {
            compSep = String.valueOf(fields[1].charAt(0)); // get component
                                                           // separator as 1st
                                                           // encoding char
        } else {
            throw new HL7Exception("Invalid or incomplete encoding characters - MSH-2 is " + fields[1], HL7Exception.REQUIRED_FIELD_MISSING);
        }

        String version = null;
        if (fields.length >= 12) {
            String[] comp = split(fields[11], compSep);
            if (comp.length >= 1) {
                version = comp[0];
            } else {
                throw new HL7Exception("Can't find version ID - MSH.12 is " + fields[11], HL7Exception.REQUIRED_FIELD_MISSING);
            }
        } else {
            throw new HL7Exception("Can't find version ID - MSH has only " + fields.length + " fields.", HL7Exception.REQUIRED_FIELD_MISSING);
        }
        return version;
    }


    @Override
    public void parse(Message message, String string) throws HL7Exception {
        IStructureDefinition structureDef = getStructureDefinition(message);

        // MessagePointer ptr = new MessagePointer(this, m,
        // getEncodingChars(message));
        MessageIterator messageIter = new MessageIterator(message, structureDef, "MSH", true);

        String[] segments = split(string, segDelim);

        if (segments.length == 0) {
          throw new HL7Exception("Invalid message content: \"" + string + "\"");
        }
       
        if (segments[0] == null || segments[0].length() < 4) {
          throw new HL7Exception("Invalid message content: \"" + string + "\"");
        }

        char delim = '|';
        for (int i = 0; i < segments.length; i++) {

            // get rid of any leading whitespace characters ...
            if (segments[i] != null && segments[i].length() > 0 && Character.isWhitespace(segments[i].charAt(0)))
                segments[i] = stripLeadingWhitespace(segments[i]);

            // sometimes people put extra segment delimiters at end of msg ...
            if (segments[i] != null && segments[i].length() >= 3) {
             
                final String name;
                if (i == 0) {
                    if (segments[i].length() < 4) {
                      throw new HL7Exception("Invalid message content: \"" + string + "\"");
                    }
                    name = segments[i].substring(0, 3);
                    delim = segments[i].charAt(3);
                } else {
                    if (segments[i].indexOf(delim) >= 0) {
                        name = segments[i].substring(0, segments[i].indexOf(delim));
                    } else {
                        name = segments[i];
                    }
                }

                log.debug("Parsing segment {}", name);

                messageIter.setDirection(name);

                if (messageIter.hasNext()) {
                    Segment next = (Segment) messageIter.next();
                    int nextIndexWithinParent = messageIter.getNextIndexWithinParent();
                    parse(next, segments[i], getEncodingChars(string), nextIndexWithinParent);
                }
            }
        }
    }
   
   
    /**
     * A struct for holding a message class string and a boolean indicating
     * whether it was defined explicitly.
     */
    private static class MessageStructure {
        public String messageStructure;
        public boolean explicitlyDefined;


        public MessageStructure(String theMessageStructure, boolean isExplicitlyDefined) {
            messageStructure = theMessageStructure;
            explicitlyDefined = isExplicitlyDefined;
        }
    }

    private static class Holder<T> {
        private T myObject;


        public T getObject() {
            return myObject;
        }


        public void setObject(T theObject) {
            myObject = theObject;
        }
    }

}
TOP

Related Classes of ca.uhn.hl7v2.parser.PipeParser$MessageStructure

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.