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