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