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 & 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 }