001/** 002The 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. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Original Code is "Varies.java". Description: 010"Varies is a Type used as a placeholder for another Type in cases where 011 the appropriate Type is not known until run-time (e.g" 012 013The Initial Developer of the Original Code is University Health Network. Copyright (C) 0142001. All Rights Reserved. 015 016Contributor(s): ______________________________________. 017 018Alternatively, the contents of this file may be used under the terms of the 019GNU General Public License (the "GPL"), in which case the provisions of the GPL are 020applicable instead of those above. If you wish to allow use of your version of this 021file only under the terms of the GPL and not to allow others to use your version 022of this file under the MPL, indicate your decision by deleting the provisions above 023and replace them with the notice and other provisions required by the GPL License. 024If you do not delete the provisions above, a recipient may use your version of 025this file under either the MPL or the GPL. 026 027*/ 028 029package ca.uhn.hl7v2.model; 030 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import ca.uhn.hl7v2.ErrorCode; 035import ca.uhn.hl7v2.HL7Exception; 036import ca.uhn.hl7v2.parser.EncodingCharacters; 037import ca.uhn.hl7v2.parser.ModelClassFactory; 038import ca.uhn.hl7v2.parser.ParserConfiguration; 039 040/** 041 * <p>Varies is a Type used as a placeholder for another Type in cases where 042 * the appropriate Type is not known until run-time (e.g. OBX-5). 043 * Parsers and validators may have logic that enforces restrictions on the 044 * Type based on other features of a segment.</p> 045 * <p>If you want to set both the type and the values of a Varies object, you should 046 * set the type first by calling setData(Type t), keeping a reference to your Type, 047 * and then set values by calling methods on the Type. Here is an example:</p> 048 * <p><code>CN cn = new CN();<br> 049 * variesObject.setData(cn);<br> 050 * cn.getIDNumber().setValue("foo");</code></p> 051 * 052 * @author Bryan Tripp (bryan_tripp@users.sourceforge.net) 053 * @author Andy Pardue 054 * 055 */ 056@SuppressWarnings("serial") 057public class Varies implements Type { 058 059 /** 060 * System property key: The value may be set to provide a default 061 * datatype ("ST", "NM", etc) for an OBX segment with a missing 062 * OBX-2 value. 063 */ 064 public static final String DEFAULT_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_obx2_type"; 065 066 /** 067 * System property key: The value may be set to provide a default 068 * datatype ("ST", "NM", etc) for an OBX segment with an invalid 069 * OBX-2 value type. In other words, if OBX-2 has a value of "ZYZYZ", 070 * which is not a valid value, but this property is set to "ST", then 071 * OBX-5 will be parsed as an ST. 072 */ 073 public static final String INVALID_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_obx2_type"; 074 075 /** 076 * <p> 077 * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the 078 * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal 079 * character instead of a subcomponent delimiter, and will therefore be escaped if the message is 080 * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly 081 * escape ampersands in OBX-5 values. 082 * </p> 083 * <p> 084 * For example, consider the following OBX-5 segment: 085 * <pre> 086 * OBX||ST|||Apples, Pears & Bananas||| 087 * </pre> 088 * In this example, the data type is a primitive ST and does not support subcomponents, and the 089 * ampersand is obviously not intended to represent a subcomponent delimiter. If this 090 * property is set to <code>true</code>, the entire string will be treated as the 091 * value of OBX-5, and if the message is re-encoded the string will appear 092 * as "Apples, Pears \T\ Bananas". 093 * </p> 094 * <p> 095 * If this property is set to anything other than "true", the subcomponent delimiter is treated as a component delimiter, 096 * so the value after the ampersand is placed into an {@link ExtraComponents extra component}. 097 * </p> 098 */ 099 public static final String ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE = "ca.uhn.hl7v2.model.varies.escape_subcomponent_delim_in_primitive"; 100 101 private static final Logger log = LoggerFactory.getLogger(Varies.class); 102 103 private Type data; 104 private Message message; 105 106 /** 107 * Creates new Varies. 108 * 109 * @param message message to which this type belongs 110 */ 111 public Varies(Message message) { 112 data = new GenericPrimitive(message); 113 this.message = message; 114 } 115 116 /** 117 * Returns the data contained by this instance of Varies. Returns a GenericPrimitive unless 118 * setData() has been called. 119 */ 120 public Type getData() { 121 return this.data; 122 } 123 124 /** @see Type#getName */ 125 public String getName() { 126 String name = "*"; 127 if (this.data != null) { 128 name = this.data.getName(); 129 } 130 return name; 131 } 132 133 /** 134 * Sets the data contained by this instance of Varies. If a data object already exists, 135 * then its values are copied to the incoming data object before the old one is replaced. 136 * For example, if getData() returns an ST with the value "19901012" and you call 137 * setData(new DT()), then subsequent calls to getData() will return the same DT, with the value 138 * set to "19901012". 139 */ 140 public void setData(Type data) throws DataTypeException { 141 if (this.data != null) { 142 if (!(this.data instanceof Primitive) || ((Primitive) this.data).getValue() != null) { 143 ca.uhn.hl7v2.util.DeepCopy.copy(this.data, data); 144 } 145 } 146 this.data = data; 147 } 148 149 /** Returns extra components from the underlying Type */ 150 public ExtraComponents getExtraComponents() { 151 return this.data.getExtraComponents(); 152 } 153 154 /** 155 * @return the message to which this Type belongs 156 */ 157 public Message getMessage() { 158 return message; 159 } 160 161 /** 162 * <p> 163 * Sets the data type of field 5 in the given OBX segment to the value of OBX-2. The argument 164 * is a Segment as opposed to a particular OBX because it is meant to work with any version. 165 * </p> 166 * <p> 167 * Note that if no value is present in OBX-2, or an invalid value is present in 168 * OBX-2, this method will throw an error. This behaviour can be corrected by using the 169 * following system properties: {@link #DEFAULT_OBX2_TYPE_PROP} and {@link #INVALID_OBX2_TYPE_PROP}, 170 * or by using configuration in {@link ParserConfiguration} 171 * </p> 172 */ 173 public static void fixOBX5(Segment segment, ModelClassFactory factory) throws HL7Exception { 174 fixOBX5(segment, factory, segment.getMessage().getParser().getParserConfiguration()); 175 } 176 177 /** 178 * <p> 179 * Sets the data type of field 5 in the given OBX segment to the value of OBX-2. The argument 180 * is a Segment as opposed to a particular OBX because it is meant to work with any version. 181 * </p> 182 * <p> 183 * Note that if no value is present in OBX-2, or an invalid value is present in 184 * OBX-2, this method will throw an error. This behaviour can be corrected by using the 185 * following system properties: {@link #DEFAULT_OBX2_TYPE_PROP} and {@link #INVALID_OBX2_TYPE_PROP} 186 * or by using configuration in {@link ParserConfiguration} 187 * </p> 188 */ 189 public static void fixOBX5(Segment segment, ModelClassFactory factory, ParserConfiguration parserConfiguration) throws HL7Exception { 190 try { 191 //get unqualified class name 192 Primitive obx2 = (Primitive) segment.getField(2, 0); 193 Type[] reps = segment.getField(5); 194 for (int i = 0; i < reps.length; i++) { 195 Varies v = (Varies)reps[i]; 196 197 // If we don't have a value for OBX-2, a default 198 // can be supplied via a System property 199 if (obx2.getValue() == null) { 200 String defaultOBX2Type = parserConfiguration.getDefaultObx2Type(); 201 if (defaultOBX2Type == null) { 202 defaultOBX2Type = System.getProperty(DEFAULT_OBX2_TYPE_PROP); 203 } 204 if (defaultOBX2Type != null) { 205 log.debug("setting default obx2 type to {}", defaultOBX2Type); 206 obx2.setValue(defaultOBX2Type); 207 } 208 } // if 209 210 if (obx2.getValue() == null) { 211 if (v.getData() != null) { 212 if (!(v.getData() instanceof Primitive) || ((Primitive) v.getData()).getValue() != null) { 213 throw new HL7Exception( 214 "OBX-5 is valued, but OBX-2 is not. A datatype for OBX-5 must be specified using OBX-2. See JavaDoc for Varies#fixOBX5(Segment, ModelClassFactory)", 215 ErrorCode.REQUIRED_FIELD_MISSING); 216 } 217 } 218 } 219 else { 220 //set class 221 String version = segment.getMessage().getVersion(); 222 String obx2Value = obx2.getValue(); 223 Class<? extends Type> c = factory.getTypeClass(obx2Value, version); 224// Class c = ca.uhn.hl7v2.parser.Parser.findClass(obx2.getValue(), 225// segment.getMessage().getVersion(), 226// "datatype"); 227 if (c == null) { 228 229 String defaultOBX2Type = parserConfiguration.getInvalidObx2Type(); 230 if (defaultOBX2Type == null) { 231 defaultOBX2Type = System.getProperty(INVALID_OBX2_TYPE_PROP); 232 } 233 if (defaultOBX2Type != null) { 234 c = factory.getTypeClass(defaultOBX2Type, version); 235 } 236 237 if (c == null) { 238 Primitive obx1 = (Primitive) segment.getField(1, 0); 239 HL7Exception h = new HL7Exception("\'" + 240 obx2.getValue() + "\' in record " + 241 obx1.getValue() + " is invalid for version " + version + 242 ". See JavaDoc for Varies#fixOBX5(Segment, ModelClassFactory)", 243 ErrorCode.DATA_TYPE_ERROR); 244 h.setSegmentName("OBX"); 245 h.setFieldPosition(2); 246 throw h; 247 } 248 } 249 250 Type newTypeInstance; 251 try { 252 newTypeInstance = (Type) c.getConstructor(new Class[]{Message.class}).newInstance(new Object[]{v.getMessage()}); 253 } catch (NoSuchMethodException e) { 254 newTypeInstance = (Type) c.getConstructor(new Class[]{Message.class, Integer.class}).newInstance(new Object[]{v.getMessage(), 0}); 255 } 256 257 boolean escapeSubcomponentDelimInPrimitive = 258 parserConfiguration.isEscapeSubcomponentDelimiterInPrimitive() || 259 escapeSubcomponentDelimInPrimitive(); 260 261 262 if (newTypeInstance instanceof Primitive) { 263 Type[] subComponentsInFirstField = v.getFirstComponentSubcomponentsOnlyIfMoreThanOne(); 264 if (subComponentsInFirstField != null) { 265 266 if (escapeSubcomponentDelimInPrimitive) { 267 268 StringBuilder firstComponentValue = new StringBuilder(); 269 for (Type type : subComponentsInFirstField) { 270 if (firstComponentValue.length() != 0) { 271 char subComponentSeparator = EncodingCharacters.getInstance(segment.getMessage()).getSubcomponentSeparator(); 272 firstComponentValue.append(subComponentSeparator); 273 } 274 firstComponentValue.append(type.encode()); 275 } 276 277 v.setFirstComponentPrimitiveValue(firstComponentValue.toString()); 278 279 } 280 281 } 282 } 283 284 v.setData(newTypeInstance); 285 } 286 287 } // for reps 288 289 } 290 catch (HL7Exception e) { 291 throw e; 292 } 293 catch (Exception e) { 294 throw new HL7Exception( 295 e.getClass().getName() + " trying to set data type of OBX-5", e); 296 } 297 } 298 299 300 private static boolean escapeSubcomponentDelimInPrimitive() { 301 String property = System.getProperty(ESCAPE_SUBCOMPONENT_DELIM_IN_PRIMITIVE); 302 return property == null || "true".equalsIgnoreCase(property); 303 } 304 305 private void setFirstComponentPrimitiveValue(String theValue) throws DataTypeException { 306 Composite c = (Composite) data; 307 Type firstComponent = c.getComponent(0); 308 setFirstComponentPrimitiveValue(firstComponent, theValue); 309 } 310 311 312 private void setFirstComponentPrimitiveValue(Type theFirstComponent, String theValue) 313 throws DataTypeException { 314 315 if (theFirstComponent instanceof Varies) { 316 Varies firstComponentVaries = (Varies)theFirstComponent; 317 if (((Varies) theFirstComponent).getData() instanceof Composite) { 318 Type[] subComponents = ((Composite)firstComponentVaries.getData()).getComponents(); 319 setFirstComponentPrimitiveValue(subComponents[0], theValue); 320 for (int i = 1; i < subComponents.length; i++) { 321 setFirstComponentPrimitiveValue(subComponents[i], ""); 322 } 323 } else { 324 Primitive p = (Primitive) firstComponentVaries.getData(); 325 p.setValue(theValue); 326 } 327 } else if (theFirstComponent instanceof Composite) { 328 Type[] subComponents = ((Composite)theFirstComponent).getComponents(); 329 setFirstComponentPrimitiveValue(subComponents[0], theValue); 330 for (int i = 1; i < subComponents.length; i++) { 331 setFirstComponentPrimitiveValue(subComponents[i], ""); 332 } 333 } else { 334 ((Primitive)theFirstComponent).setValue(theValue); 335 } 336 } 337 338 /** 339 * Returns an array containing the subcomponents within the first component of this Varies 340 * object only if there are more than one of them. Otherwise, returns null. 341 */ 342 private Type[] getFirstComponentSubcomponentsOnlyIfMoreThanOne() throws DataTypeException { 343 if (data instanceof Composite) { 344 Composite c = (Composite) data; 345 Type firstComponent = c.getComponent(0); 346 if (firstComponent instanceof Varies) { 347 Varies firstComponentVaries = (Varies) firstComponent; 348 if (firstComponentVaries.getData() instanceof Composite) { 349 return ((Composite)firstComponentVaries.getData()).getComponents(); 350 } 351 } 352 } 353 return null; 354 } 355 356 /** 357 * {@inheritDoc } 358 */ 359 public void parse(String string) throws HL7Exception { 360 if (data != null) { 361 data.clear(); 362 } 363 getMessage().getParser().parse(this, string, EncodingCharacters.getInstance(getMessage())); 364 } 365 366 /** 367 * {@inheritDoc } 368 */ 369 public String encode() throws HL7Exception { 370 return getMessage().getParser().doEncode(this, EncodingCharacters.getInstance(getMessage())); 371 } 372 373 /** 374 * {@inheritDoc } 375 */ 376 public void clear() { 377 data.clear(); 378 } 379 380 /** 381 * {@inheritDoc } 382 */ 383 public boolean isEmpty() { 384 return data.isEmpty(); 385 } 386 387 /** 388 * {@inheritDoc } 389 */ 390 public String toString() { 391 return AbstractType.toString(this); 392 } 393 394}