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.HL7Exception;
035import ca.uhn.hl7v2.parser.EncodingCharacters;
036import ca.uhn.hl7v2.parser.ModelClassFactory;
037import 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 */
055public 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 &amp; 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}