Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DefaultModelClassFactory |
|
| 3.5714285714285716;3.571 |
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 Initial Developer of the Original Code is University Health Network. Copyright (C) | |
10 | 2001. All Rights Reserved. | |
11 | ||
12 | Contributor(s): ______________________________________. | |
13 | ||
14 | Alternatively, the contents of this file may be used under the terms of the | |
15 | GNU General Public License (the �GPL�), in which case the provisions of the GPL are | |
16 | applicable instead of those above. If you wish to allow use of your version of this | |
17 | file only under the terms of the GPL and not to allow others to use your version | |
18 | of this file under the MPL, indicate your decision by deleting the provisions above | |
19 | and replace them with the notice and other provisions required by the GPL License. | |
20 | If you do not delete the provisions above, a recipient may use your version of | |
21 | this file under either the MPL or the GPL. | |
22 | ||
23 | */ | |
24 | package ca.uhn.hl7v2.parser; | |
25 | ||
26 | import java.io.BufferedReader; | |
27 | import java.io.IOException; | |
28 | import java.io.InputStream; | |
29 | import java.io.InputStreamReader; | |
30 | import java.text.MessageFormat; | |
31 | import java.util.ArrayList; | |
32 | import java.util.HashMap; | |
33 | import java.util.List; | |
34 | import java.util.Map; | |
35 | ||
36 | import org.slf4j.Logger; | |
37 | import org.slf4j.LoggerFactory; | |
38 | ||
39 | import ca.uhn.hl7v2.ErrorCode; | |
40 | import ca.uhn.hl7v2.HL7Exception; | |
41 | import ca.uhn.hl7v2.Version; | |
42 | import ca.uhn.hl7v2.model.GenericMessage; | |
43 | import ca.uhn.hl7v2.model.Group; | |
44 | import ca.uhn.hl7v2.model.Message; | |
45 | import ca.uhn.hl7v2.model.Segment; | |
46 | import ca.uhn.hl7v2.model.Type; | |
47 | ||
48 | /** | |
49 | * Default implementation of ModelClassFactory. See packageList() for configuration instructions. | |
50 | * | |
51 | * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a> | |
52 | * @version $Revision: 1.9 $ updated on $Date: 2010-08-05 17:51:16 $ by $Author: jamesagnew $ | |
53 | */ | |
54 | 21160 | public class DefaultModelClassFactory extends AbstractModelClassFactory { |
55 | ||
56 | private static final long serialVersionUID = 1; | |
57 | ||
58 | 5 | private static final Logger log = LoggerFactory.getLogger(DefaultModelClassFactory.class); |
59 | ||
60 | static final String CUSTOM_PACKAGES_RESOURCE_NAME_TEMPLATE = "custom_packages/{0}"; | |
61 | 5 | private static final Map<String, String[]> packages = new HashMap<String, String[]>(); |
62 | 5 | private static List<String> ourVersions = null; |
63 | ||
64 | static { | |
65 | 5 | reloadPackages(); |
66 | 5 | } |
67 | ||
68 | ||
69 | /** | |
70 | * <p>Attempts to return the message class corresponding to the given name, by | |
71 | * searching through default and user-defined (as per packageList()) packages. | |
72 | * Returns GenericMessage if the class is not found.</p> | |
73 | * <p>It is important to note that there can only be one implementation of a particular message | |
74 | * structure (i.e. one class with the message structure name, regardless of its package) among | |
75 | * the packages defined as per the <code>packageList()</code> method. If there are duplicates | |
76 | * (e.g. two ADT_A01 classes) the first one in the search order will always be used. However, | |
77 | * this restriction only applies to message classes, not (normally) segment classes, etc. This is because | |
78 | * classes representing parts of a message are referenced explicitly in the code for the message | |
79 | * class, rather than being looked up (using findMessageClass() ) based on the String value of MSH-9. | |
80 | * The exception is that Segments may have to be looked up by name when they appear | |
81 | * in unexpected locations (e.g. by local extension) -- see findSegmentClass().</p> | |
82 | * <p>Note: the current implementation will be slow if there are multiple user- | |
83 | * defined packages, because the JVM will try to load a number of non-existent | |
84 | * classes every parse. This should be changed so that specific classes, rather | |
85 | * than packages, are registered by name.</p> | |
86 | * | |
87 | * @param theName name of the desired structure in the form XXX_YYY | |
88 | * @param theVersion HL7 version (e.g. "2.3") | |
89 | * @param isExplicit true if the structure was specified explicitly in MSH-9-3, false if it | |
90 | * was inferred from MSH-9-1 and MSH-9-2. If false, a lookup may be performed to find | |
91 | * an alternate structure corresponding to that message type and event. | |
92 | * @return corresponding message subclass if found; GenericMessage otherwise | |
93 | */ | |
94 | @SuppressWarnings("unchecked") | |
95 | public Class<? extends Message> getMessageClass(String theName, String theVersion, boolean isExplicit) throws HL7Exception { | |
96 | 3859 | if (!isExplicit) { |
97 | 2716 | theName = getMessageStructureForEvent(theName, Version.versionOf(theVersion)); |
98 | } | |
99 | 3859 | Class<? extends Message> mc = (Class<? extends Message>) findClass(theName, theVersion, "message"); |
100 | 3859 | if (mc == null) |
101 | 90 | mc = GenericMessage.getGenericMessageClass(theVersion); |
102 | 3859 | return mc; |
103 | } | |
104 | ||
105 | /** | |
106 | * @see ca.uhn.hl7v2.parser.ModelClassFactory#getGroupClass(java.lang.String, java.lang.String) | |
107 | */ | |
108 | @SuppressWarnings("unchecked") | |
109 | public Class<? extends Group> getGroupClass(String theName, String theVersion) throws HL7Exception { | |
110 | 0 | return (Class<? extends Group>) findClass(theName, theVersion, "group"); |
111 | } | |
112 | ||
113 | /** | |
114 | * @see ca.uhn.hl7v2.parser.ModelClassFactory#getSegmentClass(java.lang.String, java.lang.String) | |
115 | */ | |
116 | @SuppressWarnings("unchecked") | |
117 | public Class<? extends Segment> getSegmentClass(String theName, String theVersion) throws HL7Exception { | |
118 | 2098 | return (Class<? extends Segment>) findClass(theName, theVersion, "segment"); |
119 | } | |
120 | ||
121 | /** | |
122 | * @see ca.uhn.hl7v2.parser.ModelClassFactory#getTypeClass(java.lang.String, java.lang.String) | |
123 | */ | |
124 | @SuppressWarnings("unchecked") | |
125 | public Class<? extends Type> getTypeClass(String theName, String theVersion) throws HL7Exception { | |
126 | 785 | return (Class<? extends Type>) findClass(theName, theVersion, "datatype"); |
127 | } | |
128 | ||
129 | /** | |
130 | * Retrieves and instantiates a message class by looking in a specific java package for the | |
131 | * message type. | |
132 | * | |
133 | * @param theName The message structure type (e.g. "ADT_A01") | |
134 | * @param theVersion The HL7 version (e.g. "2.3.1") | |
135 | * @param isExplicit If false, the message structure is looked up using {@link Parser#getMessageStructureForEvent(String, String)} and converted to the appropriate structure type. For example, "ADT_A04" would be converted to "ADT_A01" because the A04 trigger uses the A01 message structure according to HL7. | |
136 | * @param packageName The package name to use. Note that if the message type can't be found in this package, HAPI will return the standard type returned by {@link #getMessageClass(String, String, boolean)} | |
137 | * @since 1.3 | |
138 | */ | |
139 | @SuppressWarnings("unchecked") | |
140 | public Class<? extends Message> getMessageClassInASpecificPackage(String theName, String theVersion, boolean isExplicit, String packageName) throws HL7Exception { | |
141 | 0 | if (!isExplicit) { |
142 | 0 | theName = getMessageStructureForEvent(theName, Version.versionOf(theVersion)); |
143 | } | |
144 | ||
145 | 0 | Class<? extends Message> mc = (Class<? extends Message>) findClassInASpecificPackage(theName, theVersion, "message", packageName); |
146 | 0 | if (mc == null) { |
147 | 0 | mc = GenericMessage.getGenericMessageClass(theVersion); |
148 | } | |
149 | ||
150 | 0 | return mc; |
151 | } | |
152 | ||
153 | ||
154 | private static Class<?> findClassInASpecificPackage(String name, String version, String type, String packageName) throws HL7Exception { | |
155 | ||
156 | 0 | if (packageName == null || packageName.length() == 0) { |
157 | 0 | return findClass(name, version, type); |
158 | } | |
159 | ||
160 | 0 | String classNameToTry = packageName + "." + name; |
161 | ||
162 | try { | |
163 | 0 | return Class.forName(classNameToTry); |
164 | 0 | } catch (ClassNotFoundException e) { |
165 | 0 | if (log.isDebugEnabled()) { |
166 | 0 | log.debug("Unable to find class " + classNameToTry + ", using default", e); |
167 | } | |
168 | 0 | return findClass(name, version, type); |
169 | } | |
170 | ||
171 | } | |
172 | ||
173 | ||
174 | /** | |
175 | * Returns the path to the base package for model elements of the given version | |
176 | * - e.g. "ca/uhn/hl7v2/model/v24/". | |
177 | * This package should have the packages datatype, segment, group, and message | |
178 | * under it. The path ends in with a slash. | |
179 | * | |
180 | * @param ver HL7 version | |
181 | * @return package path of the version | |
182 | * @throws HL7Exception if the HL7 version is unknown | |
183 | */ | |
184 | public static String getVersionPackagePath(String ver) throws HL7Exception { | |
185 | 980 | Version v = Version.versionOf(ver); |
186 | 980 | if (v == null) { |
187 | 60 | throw new HL7Exception("The HL7 version " + ver + " is unknown", ErrorCode.UNSUPPORTED_VERSION_ID); |
188 | } | |
189 | 920 | String pkg = v.modelPackageName(); |
190 | 920 | return pkg.replace('.', '/'); |
191 | } | |
192 | ||
193 | /** | |
194 | * Returns the package name for model elements of the given version - e.g. | |
195 | * "ca.uhn.hl7v2.model.v24.". This method | |
196 | * is identical to <code>getVersionPackagePath(...)</code> except that path | |
197 | * separators are replaced with dots. | |
198 | * | |
199 | * @param ver HL7 version | |
200 | * @return package name of the version | |
201 | * @throws HL7Exception if the HL7 version is unknown | |
202 | */ | |
203 | public static String getVersionPackageName(String ver) throws HL7Exception { | |
204 | 910 | String path = DefaultModelClassFactory.getVersionPackagePath(ver); |
205 | 880 | String packg = path.replace('/', '.'); |
206 | 880 | packg = packg.replace('\\', '.'); |
207 | 880 | return packg; |
208 | } | |
209 | ||
210 | /** | |
211 | * <p>Lists all the packages (user-definable) where classes for standard and custom | |
212 | * messages may be found. Each package has subpackages called "message", | |
213 | * "group", "segment", and "datatype" in which classes for these message elements | |
214 | * can be found. </p> | |
215 | * <p>At a minimum, this method returns the standard package for the | |
216 | * given version. For example, for version 2.4, the package list contains <code> | |
217 | * ca.uhn.hl7v2.model.v24</code>. In addition, user-defined packages may be specified | |
218 | * for custom messages.</p> | |
219 | * <p>If you define custom message classes, and want Parsers to be able to | |
220 | * find them, you must register them as follows (otherwise you will get an exception when | |
221 | * the corresponding messages are parsed). For each HL7 version you want to support, you must | |
222 | * put a text file on your classpath, under the folder /custom_packages, named after the version. For example, | |
223 | * for version 2.4, you might put the file "custom_packages/2.4" in your application JAR. Each line in the | |
224 | * file should name a package to search for message classes of that version. For example, if you | |
225 | * work at foo.org, you might create a v2.4 message structure called "ZFO" and define it in the class | |
226 | * <code>org.foo.hl7.custom.message.ZFO<code>. In order for parsers to find this message | |
227 | * class, you would need to enter the following line in custom_packages/2.4:</p> | |
228 | * <p>org.foo.hl7.custom</p> | |
229 | * <p>Packages are searched in the order specified. The standard package for a given version | |
230 | * is searched last, allowing you to override the default implementation. Please note that | |
231 | * if you create custom classes for messages, segments, etc., their names must correspond exactly | |
232 | * to their names in the message text. For example, if you subclass the QBP segment in order to | |
233 | * add your own fields, your subclass must also be called QBP. although it will obviously be in | |
234 | * a different package. To make sure your class is used instead of the default implementation, | |
235 | * put your package in the package list. User-defined packages are searched first, so yours | |
236 | * will be found first and used. </p> | |
237 | * <p>It is important to note that there can only be one implementation of a particular message | |
238 | * structure (i.e. one class with the message structure name, regardless of its package) among | |
239 | * the packages defined as per the <code>packageList()</code> method. If there are duplicates | |
240 | * (e.g. two ADT_A01 classes) the first one in the search order will always be used. However, | |
241 | * this restriction only applies to message classes, not segment classes, etc. This is because | |
242 | * classes representing parts of a message are referenced explicitly in the code for the message | |
243 | * class, rather than being looked up (using findMessageClass() ) based on the String value of MSH-9.<p> | |
244 | * | |
245 | * @param version HL7 version | |
246 | * @return array of package prefix names | |
247 | */ | |
248 | public static String[] packageList(String version) throws HL7Exception { | |
249 | //get package list for this version | |
250 | 6802 | return packages.get(version); |
251 | } | |
252 | ||
253 | /** | |
254 | * Returns a package list for the given version, including the standard package | |
255 | * for that version and also user-defined packages (see packageList()). | |
256 | */ | |
257 | private static String[] loadPackages(String version) throws HL7Exception { | |
258 | 780 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); |
259 | ||
260 | 780 | String customPackagesResourceName = |
261 | 780 | MessageFormat.format( CUSTOM_PACKAGES_RESOURCE_NAME_TEMPLATE, version); |
262 | ||
263 | 780 | InputStream resourceInputStream = classLoader.getResourceAsStream( customPackagesResourceName ); |
264 | ||
265 | 780 | List<String> packageList = new ArrayList<String>(); |
266 | ||
267 | 780 | if ( resourceInputStream != null) { |
268 | 60 | BufferedReader in = new BufferedReader(new InputStreamReader(resourceInputStream)); |
269 | ||
270 | try { | |
271 | 60 | String line = in.readLine(); |
272 | 240 | while (line != null) { |
273 | 180 | log.info( "Adding package to user-defined package list: {}", line ); |
274 | 180 | packageList.add( line ); |
275 | 180 | line = in.readLine(); |
276 | } | |
277 | ||
278 | 0 | } catch (IOException e) { |
279 | 0 | log.error( "Can't load all the custom package list - user-defined classes may not be recognized", e ); |
280 | } finally { | |
281 | 60 | if (in != null) { |
282 | try { | |
283 | 60 | in.close(); |
284 | 0 | } catch (IOException e) { |
285 | 0 | throw new HL7Exception(e); |
286 | 60 | } |
287 | } | |
288 | } | |
289 | ||
290 | 60 | } |
291 | else { | |
292 | 720 | log.debug("No user-defined packages for version {}", version); |
293 | } | |
294 | ||
295 | //add standard package | |
296 | 780 | packageList.add( getVersionPackageName(version) ); |
297 | 780 | return packageList.toArray(new String[packageList.size()]); |
298 | } | |
299 | ||
300 | /** | |
301 | * Finds a message or segment class by name and version. | |
302 | * @param name the segment or message structure name | |
303 | * @param version the HL7 version | |
304 | * @param type 'message', 'group', 'segment', or 'datatype' | |
305 | */ | |
306 | private static Class<?> findClass(String name, String version, String type) throws HL7Exception { | |
307 | 6742 | Parser.assertVersionExists(version); |
308 | ||
309 | //get list of packages to search for the corresponding message class | |
310 | 6742 | String[] packageList = packageList(version); |
311 | ||
312 | 6742 | if (packageList == null) { |
313 | 0 | return null; |
314 | } | |
315 | ||
316 | //get subpackage for component type | |
317 | 6742 | String types = "message|group|segment|datatype"; |
318 | 6742 | if (!types.contains(type)) |
319 | 0 | throw new HL7Exception("Can't find " + name + " for version " + version |
320 | + " -- type must be " + types + " but is " + type); | |
321 | ||
322 | //try to load class from each package | |
323 | 6742 | Class<?> compClass = null; |
324 | 6742 | int c = 0; |
325 | 13499 | while (compClass == null && c < packageList.length) { |
326 | 6757 | String classNameToTry = null; |
327 | try { | |
328 | 6757 | String p = packageList[c]; |
329 | 6757 | if (!p.endsWith(".")) |
330 | 15 | p = p + "."; |
331 | 6757 | classNameToTry = p + type + "." + name; |
332 | ||
333 | 6757 | if (log.isDebugEnabled()) { |
334 | 0 | log.debug("Trying to load: {}", classNameToTry); |
335 | } | |
336 | 6757 | compClass = Class.forName(classNameToTry); |
337 | 6232 | if (log.isDebugEnabled()) { |
338 | 0 | log.debug("Loaded: {} class: {}", classNameToTry, compClass); |
339 | } | |
340 | } | |
341 | 525 | catch (ClassNotFoundException cne) { |
342 | 525 | log.debug("Failed to load: {}", classNameToTry); |
343 | /* just try next one */ | |
344 | 6232 | } |
345 | 6757 | c++; |
346 | 6757 | } |
347 | 6742 | return compClass; |
348 | } | |
349 | ||
350 | ||
351 | /** | |
352 | * Reloads the packages. Note that this should not be performed | |
353 | * after and messages have been parsed or otherwise generated, | |
354 | * as undetermined behaviour may result. | |
355 | */ | |
356 | public static void reloadPackages() { | |
357 | 65 | packages.clear(); |
358 | 65 | ourVersions = new ArrayList<String>(); |
359 | 845 | for (Version v : Version.values()) { |
360 | try { | |
361 | 780 | String[] versionPackages = loadPackages(v.getVersion()); |
362 | 780 | if (versionPackages.length > 0) { |
363 | 780 | ourVersions.add(v.getVersion()); |
364 | } | |
365 | 780 | packages.put(v.getVersion(), versionPackages); |
366 | 0 | } catch (HL7Exception e) { |
367 | 0 | throw new Error("Version \"" + v.getVersion() + "\" is invalid. This is a programming error: ", e); |
368 | 780 | } |
369 | } | |
370 | 65 | } |
371 | ||
372 | ||
373 | /** | |
374 | * Returns a string containing the highest known version of HL7 known to HAPI (i.e. "2.6"). Note that this | |
375 | * is determined by checking which structure JARs are available on the classpath, so if this release of | |
376 | * HAPI supports version 2.6, but only the hapi-structures-v23.jar is available on the classpath, | |
377 | * "2.3" will be returned | |
378 | * | |
379 | * @return the most recent HL7 version known to HAPI | |
380 | */ | |
381 | public static String getHighestKnownVersion() { | |
382 | 5 | if (ourVersions == null || ourVersions.size() == 0) { |
383 | 0 | return null; |
384 | } | |
385 | 5 | return ourVersions.get(ourVersions.size() - 1); |
386 | } | |
387 | ||
388 | /** | |
389 | * Returns the event structure. If nothing could be found, the event name is returned | |
390 | * | |
391 | * @see ca.uhn.hl7v2.parser.AbstractModelClassFactory#getMessageStructureForEvent(java.lang.String, ca.uhn.hl7v2.Version) | |
392 | */ | |
393 | @Override | |
394 | public String getMessageStructureForEvent(String name, Version version) throws HL7Exception { | |
395 | 2721 | String structure = super.getMessageStructureForEvent(name, version); |
396 | 2721 | return structure != null ? structure : name; |
397 | } | |
398 | ||
399 | ||
400 | ||
401 | } |