View Javadoc
1   /**
2    * The contents of this file are subject to the Mozilla Public License Version 1.1
3    * (the "License"); you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    * Software distributed under the License is distributed on an "AS IS" basis,
6    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    * specific language governing rights and limitations under the License.
8    *
9    * The Original Code is "ExampleUseTerser.java".  Description:
10   * "Example Code"
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2001.  All Rights Reserved.
14   *
15   * Contributor(s): James Agnew
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   *
26   */
27  
28  package ca.uhn.hl7v2.examples;
29  
30  import ca.uhn.hl7v2.DefaultHapiContext;
31  import ca.uhn.hl7v2.HapiContext;
32  import ca.uhn.hl7v2.model.GenericMessage;
33  import ca.uhn.hl7v2.model.Message;
34  import ca.uhn.hl7v2.parser.CanonicalModelClassFactory;
35  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
36  import ca.uhn.hl7v2.parser.GenericModelClassFactory;
37  import ca.uhn.hl7v2.parser.PipeParser;
38  import ca.uhn.hl7v2.util.Terser;
39  
40  /**
41   * Example code illustrating how to handle multiple versions of HL7 from one codebase
42   * 
43   * @author <a href="mailto:jamesagnew@sourceforge.net">James Agnew</a>
44   * @version $Revision: 1.1 $ updated on $Date: 2011-05-22 16:52:21 $ by $Author: jamesagnew $
45   */
46  public class HandlingMultipleVersions {
47  
48  	public static void main(String[] args) throws Exception {
49  
50  		/*
51  		 * Often, you will need to handle multiple versions of HL7 from a sending system
52  		 * from within the same code base. Because HAPI uses different model classes for
53  		 * each version, this can seem difficult.
54  		 * 
55  		 * Before the first example, a bit of background information that is useful.
56  		 * HL7 v2 is a backwards compatible standard, for the most part. New versions
57  		 * of the standard will deprocate old fields and segments and groups, but they never
58  		 * remove them entirely. They will also rename fields and groups, but this has
59  		 * no effect on encoded messages if they are encoded using ER7 (pipe and hat)
60  		 * encoding, only on the message structure objects themselves.
61  		 * 
62  		 * Unfortunately, because of this renaming, it is not possible for the
63  		 * HAPI library to create a single version of a structure JAR which covers
64  		 * all versions of HL7 v2 (v2.1, v2.2, v2.3, etc). That said, it is always
65  		 * possible to use a HAPI message structure object to parse or encode a
66  		 * message of the same type from an earlier version of the standard. In
67  		 * other words, if you have a v2.2 ADT^A01 message, you can use the v2.3
68  		 * ADT_A01 structure class to parse it, and you can also use the v2.3 ADT_A01
69  		 * structure class to create a new v2.2 message if you are not planning on
70  		 * XML encoding it.  
71  		 * 
72  		 * The following example shows two ways of dealing with this situation. First,
73  		 * for this example, consider the following messages. Each is identical, aside
74  		 * from the version string: "2.5" and "2.3".
75  		 */
76  		
77          String v25message = "MSH|^~\\&|ULTRA|TML|OLIS|OLIS|200905011130||ORU^R01|20169838-v25|T|2.5\r"
78  			+ "PID|||7005728^^^TML^MR||TEST^RACHEL^DIAMOND||19310313|F|||200 ANYWHERE ST^^TORONTO^ON^M6G 2T9||(416)888-8888||||||1014071185^KR\r"
79  			+ "PV1|1||OLIS||||OLIST^BLAKE^DONALD^THOR^^^^^921379^^^^OLIST\r"
80  			+ "ORC|RE||T09-100442-RET-0^^OLIS_Site_ID^ISO|||||||||OLIST^BLAKE^DONALD^THOR^^^^L^921379\r"
81  			+ "OBR|0||T09-100442-RET-0^^OLIS_Site_ID^ISO|RET^RETICULOCYTE COUNT^HL79901 literal|||200905011106|||||||200905011106||OLIST^BLAKE^DONALD^THOR^^^^L^921379||7870279|7870279|T09-100442|MOHLTC|200905011130||B7|F||1^^^200905011106^^R\r"
82  			+ "OBX|1|ST|||Test Value";
83  		
84          String v23message = "MSH|^~\\&|ULTRA|TML|OLIS|OLIS|200905011130||ORU^R01|20169838-v23|T|2.3\r"
85  			+ "PID|||7005728^^^TML^MR||TEST^RACHEL^DIAMOND||19310313|F|||200 ANYWHERE ST^^TORONTO^ON^M6G 2T9||(416)888-8888||||||1014071185^KR\r"
86  			+ "PV1|1||OLIS||||OLIST^BLAKE^DONALD^THOR^^^^^921379^^^^OLIST\r"
87  			+ "ORC|RE||T09-100442-RET-0^^OLIS_Site_ID^ISO|||||||||OLIST^BLAKE^DONALD^THOR^^^^L^921379\r"
88  			+ "OBR|0||T09-100442-RET-0^^OLIS_Site_ID^ISO|RET^RETICULOCYTE COUNT^HL79901 literal|||200905011106|||||||200905011106||OLIST^BLAKE^DONALD^THOR^^^^L^921379||7870279|7870279|T09-100442|MOHLTC|200905011130||B7|F||1^^^200905011106^^R\r"
89  			+ "OBX|1|ST|||Test Value";
90  		
91          /*
92           * The first (and probably better in most ways) technique is as follows. Use a model class
93           * factory called the CanonicalModelClassFactory. This class forces a specific version of
94           * HL7 to be used. Because HL7 v2.x is a backwards compatible standard, you can choose the
95           * highest version you need to support, and the model classes will be compatible with
96           * messages from previous versions.
97           */
98  		
99          HapiContext context = new DefaultHapiContext();
100         
101         // Create the MCF. We want all parsed messages to be for HL7 version 2.5,
102         // despite what MSH-12 says.
103         CanonicalModelClassFactoryy.html#CanonicalModelClassFactory">CanonicalModelClassFactory mcf = new CanonicalModelClassFactory("2.5");
104         context.setModelClassFactory(mcf);
105 
106         // Pass the MCF to the parser in its constructor
107         PipeParser parser = context.getPipeParser();
108         
109         // The parser parses the v2.3 message to a "v25" structure
110         ca.uhn.hl7v2.model.v25.message.ORU_R01 msg = (ca.uhn.hl7v2.model.v25.message.ORU_R01) parser.parse(v23message);
111         
112         // 20169838-v23
113         System.out.println(msg.getMSH().getMessageControlID().getValue());
114 
115         // The parser also parses the v2.5 message to a "v25" structure
116         msg = (ca.uhn.hl7v2.model.v25.message.ORU_R01) parser.parse(v25message);
117         
118         // 20169838-v25
119         System.out.println(msg.getMSH().getMessageControlID().getValue());
120         
121         /*
122          * The second technique is to use the Terser. The Terser allows you
123          * to access field values using a path-like notation. For more information
124          * on the Terser, see the example here:
125          * http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/ExampleUseTerser.html
126          */
127 
128         // This time we just use a normal ModelClassFactory, which means we will be
129         // using the standard version-specific model classes
130         context.setModelClassFactory(new DefaultModelClassFactory());
131 
132         // 20169838-v23
133         Message v23Message = parser.parse(v23message);
134         Terserhtml#Terser">Terser t23 = new Terser(v23Message);
135         System.out.println(t23.get("/MSH-10"));
136 
137         // 20169838-v25
138         Message v25Message = parser.parse(v25message);
139         Terserhtml#Terser">Terser t25 = new Terser(v25Message);
140         System.out.println(t25.get("/MSH-10"));
141         
142         /*
143          * Note that this second technique has one major drawback: Although 
144          * message definitions are backwards compatible, some group names
145          * change between versions. If you are accessing a group within
146          * a complex message structure, this can cause issues.
147          * 
148          * This is less of an issue for some message types where groups are
149          * not used much (e.g. ADT)
150          */
151 
152         // This works and prints "Test Value"
153         System.out.println(t23.get("/RESPONSE/ORDER_OBSERVATION/OBSERVATION(0)/OBX-5"));
154 
155         // This fails...
156         // System.out.println(t25.get("/RESPONSE/ORDER_OBSERVATION/OBSERVATION(0)/OBX-5"));
157         
158         // ...because this would be required to extract the OBX-5 value from a v2.5 message
159         System.out.println(t25.get("/PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION(0)/OBX-5"));
160         
161         /*
162          * A third technique which may occasionally be useful is to simply use
163          * a "Generic" message structure. Generic message structures can 
164          * represent anything within an HL7 message, but they don't actually
165          * model all of the intricacies of the structure within the message,
166          * but rather just model all of the data in an unstructured way.
167          */
168         
169         // Create a new context using a Generic Model Class Factory
170         context = new DefaultHapiContext();
171         context.setModelClassFactory(new GenericModelClassFactory());
172 
173         v25message = "MSH|^~\\&|ULTRA|TML|OLIS|OLIS|200905011130||ORU^R01|20169838-v25|T|2.5\r"
174 			+ "PID|||7005728^^^TML^MR||TEST^RACHEL^DIAMOND||19310313|F|||200 ANYWHERE ST^^TORONTO^ON^M6G 2T9||(416)888-8888||||||1014071185^KR\r"
175 			+ "PV1|1||OLIS||||OLIST^BLAKE^DONALD^THOR^^^^^921379^^^^OLIST\r"
176 			+ "ORC|RE||T09-100442-RET-0^^OLIS_Site_ID^ISO|||||||||OLIST^BLAKE^DONALD^THOR^^^^L^921379\r"
177 			+ "OBR|0||T09-100442-RET-0^^OLIS_Site_ID^ISO|RET^RETICULOCYTE COUNT^HL79901 literal|||200905011106|||||||200905011106||OLIST^BLAKE^DONALD^THOR^^^^L^921379||7870279|7870279|T09-100442|MOHLTC|200905011130||B7|F||1^^^200905011106^^R\r"
178 			+ "OBX|1|ST|||Test Value\r"
179 			+ "NTE||Note for OBX(1)\r"
180 			+ "OBX|2|ST|||Value number 2";
181 
182         // The parser will always parse this as a "GenericMessage"
183         GenericMessage../ca/uhn/hl7v2/model/GenericMessage.html#GenericMessage">GenericMessage message = (GenericMessage) context.getPipeParser().parse(v25message);
184         
185         /* 
186          * A generic message has a flat structure, so you can ask for any
187          * field by only its segment name, not a complex path 
188          */
189         Terserr.html#Terser">Terser t = new Terser(message);
190         System.out.println(t.get("/OBX-5"));
191         // Prints: Test Value
192         
193         /*
194          * This technique isn't great for messages with complex structures. For
195          * example, the second OBX in the message above is a part of the base structure
196          * because GenericMessage has no groups.
197          * 
198          * It can be accessed using a new segment name (OBX2 instead of OBX)
199          * but this is error prone, so use with caution.
200          */
201         System.out.println(t.get("/OBX2-5"));
202         // Prints: Value number 2
203         
204 	}
205 
206 }