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 * System property key: The value may be set to provide a default 064 * datatype ("ST", "NM", etc) for an MFE segment with a missing 065 * MFE-5 value. 066 */ 067 public static final String DEFAULT_MFE5_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_mfe5_type"; 068 069 /** 070 * System property key: The value may be set to provide a default 071 * datatype ("ST", "NM", etc) for an MFE segment with an invalid 072 * MFE-5 value type. In other words, if MFE-5 has a value of "ZYZYZ", 073 * which is not a valid value, but this property is set to "ST", then 074 * MFE-4 will be parsed as an ST. 075 */ 076 public static final String INVALID_MFE5_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_mfe5_type"; 077 078 079 /** 080 * <p> 081 * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the 082 * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal 083 * character instead of a subcomponent delimiter, and will therefore be escaped if the message is 084 * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly 085 * escape ampersands in OBX-5 values. 086 * </p> 087 * <p> 088 * For example, consider the following OBX-5 segment: 089 * <pre> 090 * OBX||ST|||Apples, Pears & Bananas||| 091 * </pre> 092 * In this example, the data type is a primitive ST and does not support subcomponents, and the 093 * ampersand is obviously not intended to represent a subcomponent delimiter. If this 094 * property is set to <code>true</code>, the entire string will be treated as the 095 * value of OBX-5, and if the message is re-encoded the string will appear 096 * as "Apples, Pears \T\ Bananas". 097 * </p> 098 * <p> 099 * 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 type = (Primitive) segment.getField(typeField, 0); 197 Type[] reps = segment.getField(dataField); 198 for (Type rep : reps) { 199 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) || ((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 obx1 = (Primitive) segment.getField(1, 0); 228 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 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 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 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 c = (Composite) v.getData(); 334 Type firstComponent = c.getComponent(0); 335 if (firstComponent instanceof Varies) { 336 Varies firstComponentVaries = (Varies) firstComponent; 337 if (firstComponentVaries.getData() instanceof Composite) { 338 return ((Composite)firstComponentVaries.getData()).getComponents(); 339 } 340 } 341 } 342 return null; 343 } 344}