View Javadoc

1   /**
2   The contents of this file are subject to the Mozilla Public License Version 1.1 
3   (the "License"); you may not use this file except in compliance with the License. 
4   You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
5   Software distributed under the License is distributed on an "AS IS" basis, 
6   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
7   specific language governing rights and limitations under the License. 
8   
9   The Original Code is "Varies.java".  Description: 
10  "Varies is a Type used as a placeholder for another Type in cases where 
11    the appropriate Type is not known until run-time (e.g" 
12  
13  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
14  2001.  All Rights Reserved. 
15  
16  Contributor(s): ______________________________________. 
17  
18  Alternatively, the contents of this file may be used under the terms of the 
19  GNU General Public License (the "GPL"), in which case the provisions of the GPL are 
20  applicable instead of those above.  If you wish to allow use of your version of this 
21  file only under the terms of the GPL and not to allow others to use your version 
22  of this file under the MPL, indicate your decision by deleting  the provisions above 
23  and replace  them with the notice and other provisions required by the GPL License.  
24  If you do not delete the provisions above, a recipient may use your version of 
25  this file under either the MPL or the GPL. 
26  
27  */
28  
29  package ca.uhn.hl7v2.model;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import ca.uhn.hl7v2.ErrorCode;
35  import ca.uhn.hl7v2.HL7Exception;
36  import ca.uhn.hl7v2.parser.EncodingCharacters;
37  import ca.uhn.hl7v2.parser.ModelClassFactory;
38  import ca.uhn.hl7v2.parser.ParserConfiguration;
39  
40  /**
41   * <p>Varies is a Type used as a placeholder for another Type in cases where 
42   * the appropriate Type is not known until run-time (e.g. OBX-5).  
43   * Parsers and validators may have logic that enforces restrictions on the 
44   * Type based on other features of a segment.</p>  
45   * <p>If you want to set both the type and the values of a Varies object, you should
46   * set the type first by calling setData(Type t), keeping a reference to your Type, 
47   * and then set values by calling methods on the Type.  Here is an example:</p>
48   * <p><code>CN cn = new CN();<br>
49   * variesObject.setData(cn);<br>
50   * cn.getIDNumber().setValue("foo");</code></p>
51   * 
52   * @author Bryan Tripp (bryan_tripp@users.sourceforge.net)
53   * @author Andy Pardue 
54   * 
55   */
56  @SuppressWarnings("serial")
57  public class Varies implements Type {
58  
59  	/** 
60  	 * System property key: The value may be set to provide a default
61  	 * datatype ("ST", "NM", etc) for an OBX segment with a missing
62  	 * OBX-2 value.
63  	 */	
64  	public static final String DEFAULT_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_obx2_type";
65  
66      /** 
67       * System property key: The value may be set to provide a default
68       * datatype ("ST", "NM", etc) for an OBX segment with an invalid
69       * OBX-2 value type. In other words, if OBX-2 has a value of "ZYZYZ",
70       * which is not a valid value, but this property is set to "ST", then
71       * OBX-5 will be parsed as an ST.
72       */ 
73      public static final String INVALID_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_obx2_type";
74  
75      /** 
76       * <p>
77       * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the
78       * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal
79       * character instead of a subcomponent delimiter, and will therefore be escaped if the message is
80       * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly
81       * escape ampersands in OBX-5 values.
82       * </p>
83       * <p>
84       * For example, consider the following OBX-5 segment:
85       * <pre>
86       *    OBX||ST|||Apples, Pears &amp; Bananas|||
87       * </pre>
88       * In this example, the data type is a primitive ST and does not support subcomponents, and the
89       * ampersand is obviously not intended to represent a subcomponent delimiter. If this 
90       * property is set to <code>true</code>, the entire string will be treated as the
91       * value of OBX-5, and if the message is re-encoded the string will appear
92       * as "Apples, Pears \T\ Bananas".
93       * </p>
94       * <p>
95       * If this property is set to anything other than "true", the subcomponent delimiter is treated as a component delimiter, 
96       * so the value after the ampersand is placed into an {@link ExtraComponents extra component}.
97       * </p>
98       */ 
99      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 }