View Javadoc
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      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       * System property key: The value may be set to provide a default
64       * datatype ("ST", "NM", etc) for an MFE segment with a missing
65       * MFE-5 value.
66       */
67      public static final String DEFAULT_MFE5_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_mfe5_type";
68  
69      /**
70       * System property key: The value may be set to provide a default
71       * datatype ("ST", "NM", etc) for an MFE segment with an invalid
72       * MFE-5 value type. In other words, if MFE-5 has a value of "ZYZYZ",
73       * which is not a valid value, but this property is set to "ST", then
74       * MFE-4 will be parsed as an ST.
75       */
76      public static final String INVALID_MFE5_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_mfe5_type";
77  
78  
79      /**
80       * <p>
81       * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the
82       * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal
83       * character instead of a subcomponent delimiter, and will therefore be escaped if the message is
84       * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly
85       * escape ampersands in OBX-5 values.
86       * </p>
87       * <p>
88       * For example, consider the following OBX-5 segment:
89       * <pre>
90       *    OBX||ST|||Apples, Pears &amp; Bananas|||
91       * </pre>
92       * In this example, the data type is a primitive ST and does not support subcomponents, and the
93       * ampersand is obviously not intended to represent a subcomponent delimiter. If this
94       * property is set to <code>true</code>, the entire string will be treated as the
95       * value of OBX-5, and if the message is re-encoded the string will appear
96       * as "Apples, Pears \T\ Bananas".
97       * </p>
98       * <p>
99       * If this property is set to anything other than "true", the subcomponent delimiter is treated as a component delimiter,
100      * so the value after the ampersand is placed into an {@link ExtraComponents extra component}.
101      * </p>
102      */
103     public static final String ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE = "ca.uhn.hl7v2.model.varies.escape_subcomponent_delim_in_primitive";
104 
105 
106     private static final Logger LOG = LoggerFactory.getLogger(Varies.class);
107 
108 
109     /**
110      * <p>
111      * Sets the data type of field 5 in the given OBX segment to the value of OBX-2.  The argument
112      * is a Segment as opposed to a particular OBX because it is meant to work with any version.
113      * </p>
114      * <p>
115      * Note that if no value is present in OBX-2, or an invalid value is present in
116      * OBX-2, this method will throw an error. This behaviour can be corrected by using the
117      * following system properties: {@link #DEFAULT_OBX2_TYPE_PROP} and {@link #INVALID_OBX2_TYPE_PROP}
118      * or by using configuration in {@link ParserConfiguration}
119      * </p>
120      *
121      * @param segment OBX segment instance to be modified
122      * @param factory ModelClassFactory to be used
123      * @param parserConfiguration configuration that influences setting OBX-5
124      * @throws ca.uhn.hl7v2.HL7Exception if the operation fails
125      */
126     public static void fixOBX5(Segment segment, ModelClassFactory factory, ParserConfiguration parserConfiguration)
127             throws HL7Exception {
128         if (!segment.getName().contains("OBX")) {
129             throw new IllegalArgumentException("Expected OBX segment, but was: " + segment.getName());
130         }
131         String defaultOBX2Type = parserConfiguration.getDefaultObx2Type();
132         if (defaultOBX2Type == null) {
133             defaultOBX2Type = System.getProperty(DEFAULT_OBX2_TYPE_PROP);
134         }
135         String invalidOBX2Type = parserConfiguration.getInvalidObx2Type();
136         if (invalidOBX2Type == null) {
137             invalidOBX2Type = System.getProperty(INVALID_OBX2_TYPE_PROP);
138         }
139 
140         fix(segment, 2, 5, defaultOBX2Type, invalidOBX2Type, factory, parserConfiguration);
141     }
142 
143     /**
144      * <p>
145      * Sets the data type of field 4 in the given MFE segment to the value of MFE-5.  The argument
146      * is a Segment as opposed to a particular MFE because it is meant to work with any version.
147      * </p>
148      * <p>
149      * Note that if no value is present in MFE-5, or an invalid value is present in
150      * MFE-5, this method will throw an error. This behaviour can be corrected by using the
151      * following system properties: {@link #DEFAULT_MFE5_TYPE_PROP} and {@link #INVALID_MFE5_TYPE_PROP}
152      * or by using configuration in {@link ParserConfiguration}
153      * </p>
154      *
155      * @param segment MFE segment instance to be modified
156      * @param factory ModelClassFactory to be used
157      * @param parserConfiguration configuration that influences setting MFE-5
158      * @throws ca.uhn.hl7v2.HL7Exception if the operation fails
159      */
160     public static void fixMFE4(Segment segment, ModelClassFactory factory, ParserConfiguration parserConfiguration)
161             throws HL7Exception {
162         if (!(segment.getName().contains("MFE")) &&
163                 Version.versionOf(segment.getMessage().getVersion()).isGreaterThan(Version.V23)) {
164             throw new IllegalArgumentException("Expected MFE segment, but was: " + segment.getName());
165         }
166 
167         String defaultMFE5Type = parserConfiguration.getDefaultMfe5Type();
168         if (defaultMFE5Type == null) {
169             defaultMFE5Type = System.getProperty(DEFAULT_MFE5_TYPE_PROP);
170         }
171 
172         String invalidMFE5Type = parserConfiguration.getInvalidMfe5Type();
173         if (invalidMFE5Type == null) {
174             invalidMFE5Type = System.getProperty(INVALID_MFE5_TYPE_PROP);
175         }
176 
177         fix(segment, 5, 4, defaultMFE5Type, invalidMFE5Type, factory, parserConfiguration);
178     }
179 
180     /**
181      * A more generic version of the task of adapting a varies field to a given type
182      *
183      * @param segment segment instance
184      * @param typeField field number of the specified data type
185      * @param dataField field number of the varies data field
186      * @param defaultType default type if the typeField is empty
187      * @param invalidType default type if the typeField is invalid
188      * @param factory ModelClassFactory to be used
189      * @param parserConfiguration parser config
190      * @throws HL7Exception if the operation fails
191      */
192     public static void fix(Segment segment, int typeField, int dataField, String defaultType, String invalidType, ModelClassFactory factory, ParserConfiguration parserConfiguration)
193         throws HL7Exception {
194         try {
195             //get unqualified class name
196             Primitive./../../../ca/uhn/hl7v2/model/Primitive.html#Primitive">Primitive type = (Primitive) segment.getField(typeField, 0);
197             Type[] reps = segment.getField(dataField);
198             for (Type rep : reps) {
199                 Variesref="../../../../ca/uhn/hl7v2/model/Varies.html#Varies">Varies v = (Varies)rep;
200                 if (type.getValue() == null) {
201                     if (defaultType != null) {
202                         LOG.debug("setting default {}-{} type to {}", segment.getName(), typeField, defaultType);
203                         type.setValue(defaultType);
204                     }
205                 } // if
206 
207                 if (type.getValue() == null) {
208                     if (v.getData() != null) {
209                         if (!(v.getData() instanceof Primitive"../../../../ca/uhn/hl7v2/model/Primitive.html#Primitive">Primitive) || ((Primitive) v.getData()).getValue() != null) {
210                             throw new HL7Exception(String.format(
211                                     "A datatype for %s-%d must be specified in %s-%d.", segment.getName(), dataField, segment.getName(), typeField),
212                                     ErrorCode.REQUIRED_FIELD_MISSING);
213                         }
214                     }
215                 }
216                 else {
217                     //set class
218                     String version = segment.getMessage().getVersion();
219                     String typeValue = type.getValue();
220                     Class<? extends Type> c = factory.getTypeClass(typeValue, version);
221                     if (c == null) {
222                         if (invalidType != null) {
223                             c = factory.getTypeClass(invalidType, version);
224                         }
225 
226                         if (c == null) {
227                             Primitive./../../../ca/uhn/hl7v2/model/Primitive.html#Primitive">Primitive obx1 = (Primitive) segment.getField(1, 0);
228                             HL7Exceptionl#HL7Exception">HL7Exception h = new HL7Exception("'" +
229                                     type.getValue() + "' in record " +
230                                     obx1.getValue() + " is invalid for version " + version,
231                                     ErrorCode.DATA_TYPE_ERROR);
232                             h.setSegmentName(segment.getName());
233                             h.setFieldPosition(typeField);
234                             throw h;
235                         }
236                     }
237 
238                     Type newTypeInstance;
239                     try {
240                         Constructor<? extends Type> constr = c.getConstructor(Message.class);
241                         newTypeInstance = constr.newInstance(v.getMessage());
242                     } catch (NoSuchMethodException e) {
243                         Constructor<? extends Type> constr = c.getConstructor(Message.class, Integer.class);
244                         newTypeInstance = constr.newInstance(v.getMessage(), 0);
245                     }
246 
247                     boolean escapeSubcomponentDelimInPrimitive =
248                             parserConfiguration.isEscapeSubcomponentDelimiterInPrimitive() ||
249                                     escapeSubcomponentDelimInPrimitive();
250 
251 
252                     if (newTypeInstance instanceof Primitive) {
253                         Type[] subComponentsInFirstField = getFirstComponentSubcomponentsOnlyIfMoreThanOne(v);
254                         if (subComponentsInFirstField != null) {
255 
256                             if (escapeSubcomponentDelimInPrimitive) {
257 
258                                 StringBuilder firstComponentValue = new StringBuilder();
259                                 for (Type stype : subComponentsInFirstField) {
260                                     if (firstComponentValue.length() != 0) {
261                                         char subComponentSeparator = EncodingCharacters.getInstance(segment.getMessage()).getSubcomponentSeparator();
262                                         firstComponentValue.append(subComponentSeparator);
263                                     }
264                                     firstComponentValue.append(stype.encode());
265                                 }
266 
267                                 setFirstComponentPrimitiveValue(v, firstComponentValue.toString());
268 
269                             }
270 
271                         }
272                     }
273 
274                     v.setData(newTypeInstance);
275                 }
276 
277             } // for reps
278 
279         }
280         catch (HL7Exception e) {
281             throw e;
282         }
283         catch (Exception e) {
284             throw new HL7Exception(
285                     e.getClass().getName() + " trying to set data type of " + segment.getName() + "-" + dataField, e);
286         }
287     }
288 
289     private static boolean escapeSubcomponentDelimInPrimitive() {
290         String property = System.getProperty(ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE);
291         return property == null || "true".equalsIgnoreCase(property);
292     }
293 
294     private static void setFirstComponentPrimitiveValue(Varies v, String theValue) throws DataTypeException {
295         Composite="../../../../ca/uhn/hl7v2/model/Composite.html#Composite">Composite c = (Composite) v.getData();
296         Type firstComponent = c.getComponent(0);
297         setFirstComponentPrimitiveValue(firstComponent, theValue);
298     }
299 
300 
301     private static void setFirstComponentPrimitiveValue(Type theFirstComponent, String theValue)
302             throws DataTypeException {
303 
304         if (theFirstComponent instanceof Varies) {
305             Varies/uhn/hl7v2/model/Varies.html#Varies">Varies firstComponentVaries = (Varies)theFirstComponent;
306             if (((Varies) theFirstComponent).getData() instanceof Composite) {
307                 Type[] subComponents = ((Composite)firstComponentVaries.getData()).getComponents();
308                 setFirstComponentPrimitiveValue(subComponents[0], theValue);
309                 for (int i = 1; i < subComponents.length; i++) {
310                     setFirstComponentPrimitiveValue(subComponents[i], "");
311                 }
312             } else {
313                 Primitive="../../../../ca/uhn/hl7v2/model/Primitive.html#Primitive">Primitive p = (Primitive) firstComponentVaries.getData();
314                 p.setValue(theValue);
315             }
316         } else if (theFirstComponent instanceof Composite) {
317             Type[] subComponents = ((Composite)theFirstComponent).getComponents();
318             setFirstComponentPrimitiveValue(subComponents[0], theValue);
319             for (int i = 1; i < subComponents.length; i++) {
320                 setFirstComponentPrimitiveValue(subComponents[i], "");
321             }
322         } else {
323             ((Primitive)theFirstComponent).setValue(theValue);
324         }
325     }
326 
327     /**
328      * Returns an array containing the subcomponents within the first component of this Varies
329      * object only if there are more than one of them. Otherwise, returns null.
330      */
331     private static Type[] getFirstComponentSubcomponentsOnlyIfMoreThanOne(Varies v) throws DataTypeException {
332         if (v.getData() instanceof Composite) {
333             Composite="../../../../ca/uhn/hl7v2/model/Composite.html#Composite">Composite c = (Composite) v.getData();
334             Type firstComponent = c.getComponent(0);
335             if (firstComponent instanceof Varies) {
336                 Varies/uhn/hl7v2/model/Varies.html#Varies">Varies firstComponentVaries = (Varies) firstComponent;
337                 if (firstComponentVaries.getData() instanceof Composite) {
338                     return ((Composite)firstComponentVaries.getData()).getComponents();
339                 }
340             }
341         }
342         return null;
343     }
344 }