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