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