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