001/*
002 The contents of this file are subject to the Mozilla Public License Version 1.1
003 (the "License"); you may not use this file except in compliance with the License.
004 You may obtain a copy of the License at http://www.mozilla.org/MPL/
005 Software distributed under the License is distributed on an "AS IS" basis,
006 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007 specific language governing rights and limitations under the License.
008
009 The Original Code is "FixOBX5.java".  Description:
010 ""
011
012 The Initial Developer of the Original Code is University Health Network. Copyright (C)
013 2015.  All Rights Reserved.
014
015 Contributor(s): ______________________________________.
016
017 Alternatively, the contents of this file may be used under the terms of the
018 GNU General Public License (the "GPL"), in which case the provisions of the GPL are
019 applicable instead of those above.  If you wish to allow use of your version of this
020 file only under the terms of the GPL and not to allow others to use your version
021 of this file under the MPL, indicate your decision by deleting  the provisions above
022 and replace  them with the notice and other provisions required by the GPL License.
023 If you do not delete the provisions above, a recipient may use your version of
024 this file under either the MPL or the GPL.
025 */
026
027package ca.uhn.hl7v2.parser;
028
029import java.lang.reflect.Constructor;
030
031import ca.uhn.hl7v2.ErrorCode;
032import ca.uhn.hl7v2.HL7Exception;
033import ca.uhn.hl7v2.Version;
034import ca.uhn.hl7v2.model.*;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Utility class that provides methods for fixing OBX-5 data type. This has been refactored out
040 * of {@link ca.uhn.hl7v2.model.Varies}.
041 */
042public final class FixFieldDataType {
043
044    private FixFieldDataType() {}
045
046    /**
047     * System property key: The value may be set to provide a default
048     * datatype ("ST", "NM", etc) for an OBX segment with a missing
049     * OBX-2 value.
050     */
051    public static final String DEFAULT_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_obx2_type";
052
053    /**
054     * System property key: The value may be set to provide a default
055     * datatype ("ST", "NM", etc) for an OBX segment with an invalid
056     * OBX-2 value type. In other words, if OBX-2 has a value of "ZYZYZ",
057     * which is not a valid value, but this property is set to "ST", then
058     * OBX-5 will be parsed as an ST.
059     */
060    public static final String INVALID_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_obx2_type";
061
062    /**
063     * <p>
064     * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the
065     * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal
066     * character instead of a subcomponent delimiter, and will therefore be escaped if the message is
067     * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly
068     * escape ampersands in OBX-5 values.
069     * </p>
070     * <p>
071     * For example, consider the following OBX-5 segment:
072     * <pre>
073     *    OBX||ST|||Apples, Pears &amp; Bananas|||
074     * </pre>
075     * In this example, the data type is a primitive ST and does not support subcomponents, and the
076     * ampersand is obviously not intended to represent a subcomponent delimiter. If this
077     * property is set to <code>true</code>, the entire string will be treated as the
078     * value of OBX-5, and if the message is re-encoded the string will appear
079     * as "Apples, Pears \T\ Bananas".
080     * </p>
081     * <p>
082     * If this property is set to anything other than "true", the subcomponent delimiter is treated as a component delimiter,
083     * so the value after the ampersand is placed into an {@link ExtraComponents extra component}.
084     * </p>
085     */
086    public static final String ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE = "ca.uhn.hl7v2.model.varies.escape_subcomponent_delim_in_primitive";
087
088
089    private static final Logger LOG = LoggerFactory.getLogger(Varies.class);
090
091
092    /**
093     * <p>
094     * Sets the data type of field 5 in the given OBX segment to the value of OBX-2.  The argument
095     * is a Segment as opposed to a particular OBX because it is meant to work with any version.
096     * </p>
097     * <p>
098     * Note that if no value is present in OBX-2, or an invalid value is present in
099     * 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        if (!segment.getName().contains("OBX")) {
112            throw new IllegalArgumentException("Expected OBX segment, but was: " + segment.getName());
113        }
114        String defaultOBX2Type = parserConfiguration.getDefaultObx2Type();
115        if (defaultOBX2Type == null) {
116            defaultOBX2Type = System.getProperty(DEFAULT_OBX2_TYPE_PROP);
117        }
118        String invalidOBX2Type = parserConfiguration.getInvalidObx2Type();
119        if (invalidOBX2Type == null) {
120            invalidOBX2Type = System.getProperty(INVALID_OBX2_TYPE_PROP);
121        }
122
123        fix(segment, 2, 5, defaultOBX2Type, invalidOBX2Type, factory, parserConfiguration);
124    }
125
126    public static void fixMFE4(Segment segment, ModelClassFactory factory, ParserConfiguration parserConfiguration)
127            throws HL7Exception {
128        if (!(segment.getName().contains("MFE")) &&
129                Version.versionOf(segment.getMessage().getVersion()).isGreaterThan(Version.V23)) {
130            throw new IllegalArgumentException("Expected MFE segment, but was: " + segment.getName());
131        }
132        fix(segment, 5, 4, null, null, factory, parserConfiguration);
133    }
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            Primitive type = (Primitive) segment.getField(typeField, 0);
152            Type[] reps = segment.getField(dataField);
153            for (Type rep : reps) {
154                Varies v = (Varies)rep;
155                if (type.getValue() == null) {
156                    if (defaultType != null) {
157                        LOG.debug("setting default {}-{} type to {}", new Object[] {segment.getName(), typeField, defaultType});
158                        type.setValue(defaultType);
159                    }
160                } // if
161
162                if (type.getValue() == null) {
163                    if (v.getData() != null) {
164                        if (!(v.getData() instanceof Primitive) || ((Primitive) v.getData()).getValue() != null) {
165                            throw new HL7Exception(String.format(
166                                    "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                    String version = segment.getMessage().getVersion();
174                    String typeValue = type.getValue();
175                    Class<? extends Type> c = factory.getTypeClass(typeValue, version);
176                    if (c == null) {
177                        if (invalidType != null) {
178                            c = factory.getTypeClass(invalidType, version);
179                        }
180
181                        if (c == null) {
182                            Primitive obx1 = (Primitive) segment.getField(1, 0);
183                            HL7Exception h = new HL7Exception("\'" +
184                                    type.getValue() + "\' in record " +
185                                    obx1.getValue() + " is invalid for version " + version,
186                                    ErrorCode.DATA_TYPE_ERROR);
187                            h.setSegmentName(segment.getName());
188                            h.setFieldPosition(typeField);
189                            throw h;
190                        }
191                    }
192
193                    Type newTypeInstance;
194                    try {
195                        Constructor<? extends Type> constr = c.getConstructor(new Class[]{Message.class});
196                        newTypeInstance = constr.newInstance(v.getMessage());
197                    } catch (NoSuchMethodException e) {
198                        Constructor<? extends Type> constr = c.getConstructor(new Class[]{Message.class, Integer.class});
199                        newTypeInstance = constr.newInstance(v.getMessage(), 0);
200                    }
201
202                    boolean escapeSubcomponentDelimInPrimitive =
203                            parserConfiguration.isEscapeSubcomponentDelimiterInPrimitive() ||
204                                    escapeSubcomponentDelimInPrimitive();
205
206
207                    if (newTypeInstance instanceof Primitive) {
208                        Type[] subComponentsInFirstField = getFirstComponentSubcomponentsOnlyIfMoreThanOne(v);
209                        if (subComponentsInFirstField != null) {
210
211                            if (escapeSubcomponentDelimInPrimitive) {
212
213                                StringBuilder firstComponentValue = new StringBuilder();
214                                for (Type stype : subComponentsInFirstField) {
215                                    if (firstComponentValue.length() != 0) {
216                                        char subComponentSeparator = EncodingCharacters.getInstance(segment.getMessage()).getSubcomponentSeparator();
217                                        firstComponentValue.append(subComponentSeparator);
218                                    }
219                                    firstComponentValue.append(stype.encode());
220                                }
221
222                                setFirstComponentPrimitiveValue(v, firstComponentValue.toString());
223
224                            }
225
226                        }
227                    }
228
229                    v.setData(newTypeInstance);
230                }
231
232            } // for reps
233
234        }
235        catch (HL7Exception e) {
236            throw e;
237        }
238        catch (Exception e) {
239            throw new HL7Exception(
240                    e.getClass().getName() + " trying to set data type of " + segment.getName() + "-" + dataField, e);
241        }
242    }
243
244    private static boolean escapeSubcomponentDelimInPrimitive() {
245        String property = System.getProperty(ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE);
246        return property == null || "true".equalsIgnoreCase(property);
247    }
248
249    private static void setFirstComponentPrimitiveValue(Varies v, String theValue) throws DataTypeException {
250        Composite c = (Composite) v.getData();
251        Type firstComponent = c.getComponent(0);
252        setFirstComponentPrimitiveValue(firstComponent, theValue);
253    }
254
255
256    private static void setFirstComponentPrimitiveValue(Type theFirstComponent, String theValue)
257            throws DataTypeException {
258
259        if (theFirstComponent instanceof Varies) {
260            Varies firstComponentVaries = (Varies)theFirstComponent;
261            if (((Varies) theFirstComponent).getData() instanceof Composite) {
262                Type[] subComponents = ((Composite)firstComponentVaries.getData()).getComponents();
263                setFirstComponentPrimitiveValue(subComponents[0], theValue);
264                for (int i = 1; i < subComponents.length; i++) {
265                    setFirstComponentPrimitiveValue(subComponents[i], "");
266                }
267            } else {
268                Primitive p = (Primitive) firstComponentVaries.getData();
269                p.setValue(theValue);
270            }
271        } else if (theFirstComponent instanceof Composite) {
272            Type[] subComponents = ((Composite)theFirstComponent).getComponents();
273            setFirstComponentPrimitiveValue(subComponents[0], theValue);
274            for (int i = 1; i < subComponents.length; i++) {
275                setFirstComponentPrimitiveValue(subComponents[i], "");
276            }
277        } else {
278            ((Primitive)theFirstComponent).setValue(theValue);
279        }
280    }
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        if (v.getData() instanceof Composite) {
288            Composite c = (Composite) v.getData();
289            Type firstComponent = c.getComponent(0);
290            if (firstComponent instanceof Varies) {
291                Varies firstComponentVaries = (Varies) firstComponent;
292                if (firstComponentVaries.getData() instanceof Composite) {
293                    return ((Composite)firstComponentVaries.getData()).getComponents();
294                }
295            }
296        }
297        return null;
298    }
299}