Coverage Report - ca.uhn.hl7v2.parser.FixFieldDataType
 
Classes in this File Line Coverage Branch Coverage Complexity
FixFieldDataType
75%
78/104
62%
40/64
6
 
 1  
 /*
 2  
  The contents of this file are subject to the Mozilla Public License Version 1.1
 3  
  (the "License"); you may not use this file except in compliance with the License.
 4  
  You may obtain a copy of the License at http://www.mozilla.org/MPL/
 5  
  Software distributed under the License is distributed on an "AS IS" basis,
 6  
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
 7  
  specific language governing rights and limitations under the License.
 8  
 
 9  
  The Original Code is "FixOBX5.java".  Description:
 10  
  ""
 11  
 
 12  
  The Initial Developer of the Original Code is University Health Network. Copyright (C)
 13  
  2015.  All Rights Reserved.
 14  
 
 15  
  Contributor(s): ______________________________________.
 16  
 
 17  
  Alternatively, the contents of this file may be used under the terms of the
 18  
  GNU General Public License (the "GPL"), in which case the provisions of the GPL are
 19  
  applicable instead of those above.  If you wish to allow use of your version of this
 20  
  file only under the terms of the GPL and not to allow others to use your version
 21  
  of this file under the MPL, indicate your decision by deleting  the provisions above
 22  
  and replace  them with the notice and other provisions required by the GPL License.
 23  
  If you do not delete the provisions above, a recipient may use your version of
 24  
  this file under either the MPL or the GPL.
 25  
  */
 26  
 
 27  
 package ca.uhn.hl7v2.parser;
 28  
 
 29  
 import java.lang.reflect.Constructor;
 30  
 
 31  
 import ca.uhn.hl7v2.ErrorCode;
 32  
 import ca.uhn.hl7v2.HL7Exception;
 33  
 import ca.uhn.hl7v2.Version;
 34  
 import ca.uhn.hl7v2.model.*;
 35  
 import org.slf4j.Logger;
 36  
 import org.slf4j.LoggerFactory;
 37  
 
 38  
 /**
 39  
  * Utility class that provides methods for fixing OBX-5 data type. This has been refactored out
 40  
  * of {@link ca.uhn.hl7v2.model.Varies}.
 41  
  */
 42  
 public final class FixFieldDataType {
 43  
 
 44  0
     private FixFieldDataType() {}
 45  
 
 46  
     /**
 47  
      * System property key: The value may be set to provide a default
 48  
      * datatype ("ST", "NM", etc) for an OBX segment with a missing
 49  
      * OBX-2 value.
 50  
      */
 51  
     public static final String DEFAULT_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_obx2_type";
 52  
 
 53  
     /**
 54  
      * System property key: The value may be set to provide a default
 55  
      * datatype ("ST", "NM", etc) for an OBX segment with an invalid
 56  
      * OBX-2 value type. In other words, if OBX-2 has a value of "ZYZYZ",
 57  
      * which is not a valid value, but this property is set to "ST", then
 58  
      * OBX-5 will be parsed as an ST.
 59  
      */
 60  
     public static final String INVALID_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_obx2_type";
 61  
 
 62  
     /**
 63  
      * <p>
 64  
      * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the
 65  
      * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal
 66  
      * character instead of a subcomponent delimiter, and will therefore be escaped if the message is
 67  
      * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly
 68  
      * escape ampersands in OBX-5 values.
 69  
      * </p>
 70  
      * <p>
 71  
      * For example, consider the following OBX-5 segment:
 72  
      * <pre>
 73  
      *    OBX||ST|||Apples, Pears &amp; Bananas|||
 74  
      * </pre>
 75  
      * In this example, the data type is a primitive ST and does not support subcomponents, and the
 76  
      * ampersand is obviously not intended to represent a subcomponent delimiter. If this
 77  
      * property is set to <code>true</code>, the entire string will be treated as the
 78  
      * value of OBX-5, and if the message is re-encoded the string will appear
 79  
      * as "Apples, Pears \T\ Bananas".
 80  
      * </p>
 81  
      * <p>
 82  
      * If this property is set to anything other than "true", the subcomponent delimiter is treated as a component delimiter,
 83  
      * so the value after the ampersand is placed into an {@link ExtraComponents extra component}.
 84  
      * </p>
 85  
      */
 86  
     public static final String ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE = "ca.uhn.hl7v2.model.varies.escape_subcomponent_delim_in_primitive";
 87  
 
 88  
 
 89  5
     private static final Logger LOG = LoggerFactory.getLogger(Varies.class);
 90  
 
 91  
 
 92  
     /**
 93  
      * <p>
 94  
      * Sets the data type of field 5 in the given OBX segment to the value of OBX-2.  The argument
 95  
      * is a Segment as opposed to a particular OBX because it is meant to work with any version.
 96  
      * </p>
 97  
      * <p>
 98  
      * Note that if no value is present in OBX-2, or an invalid value is present in
 99  
      * OBX-2, this method will throw an error. This behaviour can be corrected by using the
 100  
      * following system properties: {@link #DEFAULT_OBX2_TYPE_PROP} and {@link #INVALID_OBX2_TYPE_PROP}
 101  
      * or by using configuration in {@link ParserConfiguration}
 102  
      * </p>
 103  
      *
 104  
      * @param segment OBX segment instance to be modified
 105  
      * @param factory ModelClassFactory to be used
 106  
      * @param parserConfiguration configuration that influences setting OBX-5
 107  
      * @throws ca.uhn.hl7v2.HL7Exception if the operation fails
 108  
      */
 109  
     public static void fixOBX5(Segment segment, ModelClassFactory factory, ParserConfiguration parserConfiguration)
 110  
             throws HL7Exception {
 111  900
         if (!segment.getName().contains("OBX")) {
 112  0
             throw new IllegalArgumentException("Expected OBX segment, but was: " + segment.getName());
 113  
         }
 114  900
         String defaultOBX2Type = parserConfiguration.getDefaultObx2Type();
 115  900
         if (defaultOBX2Type == null) {
 116  900
             defaultOBX2Type = System.getProperty(DEFAULT_OBX2_TYPE_PROP);
 117  
         }
 118  900
         String invalidOBX2Type = parserConfiguration.getInvalidObx2Type();
 119  900
         if (invalidOBX2Type == null) {
 120  900
             invalidOBX2Type = System.getProperty(INVALID_OBX2_TYPE_PROP);
 121  
         }
 122  
 
 123  900
         fix(segment, 2, 5, defaultOBX2Type, invalidOBX2Type, factory, parserConfiguration);
 124  895
     }
 125  
 
 126  
     public static void fixMFE4(Segment segment, ModelClassFactory factory, ParserConfiguration parserConfiguration)
 127  
             throws HL7Exception {
 128  0
         if (!(segment.getName().contains("MFE")) &&
 129  0
                 Version.versionOf(segment.getMessage().getVersion()).isGreaterThan(Version.V23)) {
 130  0
             throw new IllegalArgumentException("Expected MFE segment, but was: " + segment.getName());
 131  
         }
 132  0
         fix(segment, 5, 4, null, null, factory, parserConfiguration);
 133  0
     }
 134  
 
 135  
     /**
 136  
      * A more generic version of the task of adapting a varies field to a given type
 137  
      *
 138  
      * @param segment segment instance
 139  
      * @param typeField field number of the specified data type
 140  
      * @param dataField field number of the varies data field
 141  
      * @param defaultType default type if the typeField is empty
 142  
      * @param invalidType default type if the typeField is invalid
 143  
      * @param factory ModelClassFactory to be used
 144  
      * @param parserConfiguration parser config
 145  
      * @throws HL7Exception if the operation fails
 146  
      */
 147  
     public static void fix(Segment segment, int typeField, int dataField, String defaultType, String invalidType, ModelClassFactory factory, ParserConfiguration parserConfiguration)
 148  
         throws HL7Exception {
 149  
         try {
 150  
             //get unqualified class name
 151  900
             Primitive type = (Primitive) segment.getField(typeField, 0);
 152  900
             Type[] reps = segment.getField(dataField);
 153  1675
             for (Type rep : reps) {
 154  780
                 Varies v = (Varies)rep;
 155  780
                 if (type.getValue() == null) {
 156  0
                     if (defaultType != null) {
 157  0
                         LOG.debug("setting default {}-{} type to {}", new Object[] {segment.getName(), typeField, defaultType});
 158  0
                         type.setValue(defaultType);
 159  
                     }
 160  
                 } // if
 161  
 
 162  780
                 if (type.getValue() == null) {
 163  0
                     if (v.getData() != null) {
 164  0
                         if (!(v.getData() instanceof Primitive) || ((Primitive) v.getData()).getValue() != null) {
 165  0
                             throw new HL7Exception(String.format(
 166  0
                                     "A datatype for %s-%n must be specified in %s-%n.", segment.getName(), dataField, segment.getName(), typeField),
 167  
                                     ErrorCode.REQUIRED_FIELD_MISSING);
 168  
                         }
 169  
                     }
 170  
                 }
 171  
                 else {
 172  
                     //set class
 173  780
                     String version = segment.getMessage().getVersion();
 174  780
                     String typeValue = type.getValue();
 175  780
                     Class<? extends Type> c = factory.getTypeClass(typeValue, version);
 176  780
                     if (c == null) {
 177  10
                         if (invalidType != null) {
 178  5
                             c = factory.getTypeClass(invalidType, version);
 179  
                         }
 180  
 
 181  10
                         if (c == null) {
 182  5
                             Primitive obx1 = (Primitive) segment.getField(1, 0);
 183  5
                             HL7Exception h = new HL7Exception("\'" +
 184  5
                                     type.getValue() + "\' in record " +
 185  5
                                     obx1.getValue() + " is invalid for version " + version,
 186  
                                     ErrorCode.DATA_TYPE_ERROR);
 187  5
                             h.setSegmentName(segment.getName());
 188  5
                             h.setFieldPosition(typeField);
 189  5
                             throw h;
 190  
                         }
 191  
                     }
 192  
 
 193  
                     Type newTypeInstance;
 194  
                     try {
 195  775
                         Constructor<? extends Type> constr = c.getConstructor(new Class[]{Message.class});
 196  775
                         newTypeInstance = constr.newInstance(v.getMessage());
 197  0
                     } catch (NoSuchMethodException e) {
 198  0
                         Constructor<? extends Type> constr = c.getConstructor(new Class[]{Message.class, Integer.class});
 199  0
                         newTypeInstance = constr.newInstance(v.getMessage(), 0);
 200  775
                     }
 201  
 
 202  775
                     boolean escapeSubcomponentDelimInPrimitive =
 203  775
                             parserConfiguration.isEscapeSubcomponentDelimiterInPrimitive() ||
 204  770
                                     escapeSubcomponentDelimInPrimitive();
 205  
 
 206  
 
 207  775
                     if (newTypeInstance instanceof Primitive) {
 208  710
                         Type[] subComponentsInFirstField = getFirstComponentSubcomponentsOnlyIfMoreThanOne(v);
 209  710
                         if (subComponentsInFirstField != null) {
 210  
 
 211  25
                             if (escapeSubcomponentDelimInPrimitive) {
 212  
 
 213  20
                                 StringBuilder firstComponentValue = new StringBuilder();
 214  65
                                 for (Type stype : subComponentsInFirstField) {
 215  45
                                     if (firstComponentValue.length() != 0) {
 216  25
                                         char subComponentSeparator = EncodingCharacters.getInstance(segment.getMessage()).getSubcomponentSeparator();
 217  25
                                         firstComponentValue.append(subComponentSeparator);
 218  
                                     }
 219  45
                                     firstComponentValue.append(stype.encode());
 220  
                                 }
 221  
 
 222  20
                                 setFirstComponentPrimitiveValue(v, firstComponentValue.toString());
 223  
 
 224  
                             }
 225  
 
 226  
                         }
 227  
                     }
 228  
 
 229  775
                     v.setData(newTypeInstance);
 230  
                 }
 231  
 
 232  
             } // for reps
 233  
 
 234  
         }
 235  5
         catch (HL7Exception e) {
 236  5
             throw e;
 237  
         }
 238  0
         catch (Exception e) {
 239  0
             throw new HL7Exception(
 240  0
                     e.getClass().getName() + " trying to set data type of " + segment.getName() + "-" + dataField, e);
 241  895
         }
 242  895
     }
 243  
 
 244  
     private static boolean escapeSubcomponentDelimInPrimitive() {
 245  770
         String property = System.getProperty(ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE);
 246  770
         return property == null || "true".equalsIgnoreCase(property);
 247  
     }
 248  
 
 249  
     private static void setFirstComponentPrimitiveValue(Varies v, String theValue) throws DataTypeException {
 250  20
         Composite c = (Composite) v.getData();
 251  20
         Type firstComponent = c.getComponent(0);
 252  20
         setFirstComponentPrimitiveValue(firstComponent, theValue);
 253  20
     }
 254  
 
 255  
 
 256  
     private static void setFirstComponentPrimitiveValue(Type theFirstComponent, String theValue)
 257  
             throws DataTypeException {
 258  
 
 259  65
         if (theFirstComponent instanceof Varies) {
 260  65
             Varies firstComponentVaries = (Varies)theFirstComponent;
 261  65
             if (((Varies) theFirstComponent).getData() instanceof Composite) {
 262  20
                 Type[] subComponents = ((Composite)firstComponentVaries.getData()).getComponents();
 263  20
                 setFirstComponentPrimitiveValue(subComponents[0], theValue);
 264  45
                 for (int i = 1; i < subComponents.length; i++) {
 265  25
                     setFirstComponentPrimitiveValue(subComponents[i], "");
 266  
                 }
 267  20
             } else {
 268  45
                 Primitive p = (Primitive) firstComponentVaries.getData();
 269  45
                 p.setValue(theValue);
 270  
             }
 271  65
         } else if (theFirstComponent instanceof Composite) {
 272  0
             Type[] subComponents = ((Composite)theFirstComponent).getComponents();
 273  0
             setFirstComponentPrimitiveValue(subComponents[0], theValue);
 274  0
             for (int i = 1; i < subComponents.length; i++) {
 275  0
                 setFirstComponentPrimitiveValue(subComponents[i], "");
 276  
             }
 277  0
         } else {
 278  0
             ((Primitive)theFirstComponent).setValue(theValue);
 279  
         }
 280  65
     }
 281  
 
 282  
     /**
 283  
      * Returns an array containing the subcomponents within the first component of this Varies
 284  
      * object only if there are more than one of them. Otherwise, returns null.
 285  
      */
 286  
     private static Type[] getFirstComponentSubcomponentsOnlyIfMoreThanOne(Varies v) throws DataTypeException {
 287  710
         if (v.getData() instanceof Composite) {
 288  35
             Composite c = (Composite) v.getData();
 289  35
             Type firstComponent = c.getComponent(0);
 290  35
             if (firstComponent instanceof Varies) {
 291  35
                 Varies firstComponentVaries = (Varies) firstComponent;
 292  35
                 if (firstComponentVaries.getData() instanceof Composite) {
 293  25
                     return ((Composite)firstComponentVaries.getData()).getComponents();
 294  
                 }
 295  
             }
 296  
         }
 297  685
         return null;
 298  
     }
 299  
 }