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.parser.EncodingCharacters;
037import ca.uhn.hl7v2.parser.ModelClassFactory;
038import ca.uhn.hl7v2.parser.ParserConfiguration;
039
040/**
041 * <p>Varies is a Type used as a placeholder for another Type in cases where 
042 * the appropriate Type is not known until run-time (e.g. OBX-5).  
043 * Parsers and validators may have logic that enforces restrictions on the 
044 * Type based on other features of a segment.</p>  
045 * <p>If you want to set both the type and the values of a Varies object, you should
046 * set the type first by calling setData(Type t), keeping a reference to your Type, 
047 * and then set values by calling methods on the Type.  Here is an example:</p>
048 * <p><code>CN cn = new CN();<br>
049 * variesObject.setData(cn);<br>
050 * cn.getIDNumber().setValue("foo");</code></p>
051 * 
052 * @author Bryan Tripp (bryan_tripp@users.sourceforge.net)
053 * @author Andy Pardue 
054 * 
055 */
056@SuppressWarnings("serial")
057public class Varies implements Type {
058
059        /** 
060         * System property key: The value may be set to provide a default
061         * datatype ("ST", "NM", etc) for an OBX segment with a missing
062         * OBX-2 value.
063         */     
064        public static final String DEFAULT_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.default_obx2_type";
065
066    /** 
067     * System property key: The value may be set to provide a default
068     * datatype ("ST", "NM", etc) for an OBX segment with an invalid
069     * OBX-2 value type. In other words, if OBX-2 has a value of "ZYZYZ",
070     * which is not a valid value, but this property is set to "ST", then
071     * OBX-5 will be parsed as an ST.
072     */ 
073    public static final String INVALID_OBX2_TYPE_PROP = "ca.uhn.hl7v2.model.varies.invalid_obx2_type";
074
075    /** 
076     * <p>
077     * System property key: If this is not set, or set to "true", and a subcomponent delimiter is found within the
078     * value of a Varies of a primitive type, this subcomponent delimiter will be treated as a literal
079     * character instead of a subcomponent delimiter, and will therefore be escaped if the message is
080     * re-encoded. This is handy when dealing with non-conformant sending systems which do not correctly
081     * escape ampersands in OBX-5 values.
082     * </p>
083     * <p>
084     * For example, consider the following OBX-5 segment:
085     * <pre>
086     *    OBX||ST|||Apples, Pears &amp; Bananas|||
087     * </pre>
088     * In this example, the data type is a primitive ST and does not support subcomponents, and the
089     * ampersand is obviously not intended to represent a subcomponent delimiter. If this 
090     * property is set to <code>true</code>, the entire string will be treated as the
091     * value of OBX-5, and if the message is re-encoded the string will appear
092     * as "Apples, Pears \T\ Bananas".
093     * </p>
094     * <p>
095     * If this property is set to anything other than "true", the subcomponent delimiter is treated as a component delimiter, 
096     * so the value after the ampersand is placed into an {@link ExtraComponents extra component}.
097     * </p>
098     */ 
099    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}