Coverage Report - ca.uhn.hl7v2.parser.DefaultModelClassFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultModelClassFactory
75%
78/104
60%
28/46
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  
 }