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 &amp; 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}