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 Initial Developer of the Original Code is University Health Network. Copyright (C) 010 2001. All Rights Reserved. 011 012 Contributor(s): ______________________________________. 013 014 Alternatively, the contents of this file may be used under the terms of the 015 GNU General Public License (the ???GPL???), in which case the provisions of the GPL are 016 applicable instead of those above. If you wish to allow use of your version of this 017 file only under the terms of the GPL and not to allow others to use your version 018 of this file under the MPL, indicate your decision by deleting the provisions above 019 and replace them with the notice and other provisions required by the GPL License. 020 If you do not delete the provisions above, a recipient may use your version of 021 this file under either the MPL or the GPL. 022 023 */ 024 package ca.uhn.hl7v2.parser; 025 026 import java.io.BufferedReader; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.io.InputStreamReader; 030 import java.text.MessageFormat; 031 import java.util.ArrayList; 032 import java.util.HashMap; 033 import java.util.List; 034 035 import ca.uhn.hl7v2.HL7Exception; 036 import ca.uhn.hl7v2.model.GenericMessage; 037 import ca.uhn.hl7v2.model.Type; 038 import ca.uhn.hl7v2.model.Segment; 039 import ca.uhn.hl7v2.model.Message; 040 import ca.uhn.hl7v2.model.Group; 041 import ca.uhn.log.HapiLog; 042 import ca.uhn.log.HapiLogFactory; 043 044 /** 045 * Default implementation of ModelClassFactory. See packageList() for configuration instructions. 046 * 047 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a> 048 * @version $Revision: 1.8 $ updated on $Date: 2009/10/03 15:25:46 $ by $Author: jamesagnew $ 049 */ 050 public class DefaultModelClassFactory implements ModelClassFactory { 051 052 private static final long serialVersionUID = 1; 053 054 private static final HapiLog log = HapiLogFactory.getHapiLog(DefaultModelClassFactory.class); 055 056 private static final String CUSTOM_PACKAGES_RESOURCE_NAME_TEMPLATE = "custom_packages/{0}"; 057 private static final HashMap<String, String[]> packages = new HashMap<String, String[]>(); 058 private static List<String> ourVersions = null; 059 060 static { 061 reloadPackages(); 062 } 063 064 065 /** 066 * <p>Attempts to return the message class corresponding to the given name, by 067 * searching through default and user-defined (as per packageList()) packages. 068 * Returns GenericMessage if the class is not found.</p> 069 * <p>It is important to note that there can only be one implementation of a particular message 070 * structure (i.e. one class with the message structure name, regardless of its package) among 071 * the packages defined as per the <code>packageList()</code> method. If there are duplicates 072 * (e.g. two ADT_A01 classes) the first one in the search order will always be used. However, 073 * this restriction only applies to message classes, not (normally) segment classes, etc. This is because 074 * classes representing parts of a message are referenced explicitly in the code for the message 075 * class, rather than being looked up (using findMessageClass() ) based on the String value of MSH-9. 076 * The exception is that Segments may have to be looked up by name when they appear 077 * in unexpected locations (e.g. by local extension) -- see findSegmentClass().</p> 078 * <p>Note: the current implementation will be slow if there are multiple user- 079 * defined packages, because the JVM will try to load a number of non-existent 080 * classes every parse. This should be changed so that specific classes, rather 081 * than packages, are registered by name.</p> 082 * 083 * @param name name of the desired structure in the form XXX_YYY 084 * @param version HL7 version (e.g. "2.3") 085 * @param isExplicit true if the structure was specified explicitly in MSH-9-3, false if it 086 * was inferred from MSH-9-1 and MSH-9-2. If false, a lookup may be performed to find 087 * an alternate structure corresponding to that message type and event. 088 * @return corresponding message subclass if found; GenericMessage otherwise 089 */ 090 public Class<? extends Message> getMessageClass(String theName, String theVersion, boolean isExplicit) throws HL7Exception { 091 Class<? extends Message> mc = null; 092 if (!isExplicit) { 093 theName = Parser.getMessageStructureForEvent(theName, theVersion); 094 } 095 mc = (Class<? extends Message>) findClass(theName, theVersion, "message"); 096 if (mc == null) 097 mc = GenericMessage.getGenericMessageClass(theVersion); 098 return mc; 099 } 100 101 /** 102 * @see ca.uhn.hl7v2.parser.ModelClassFactory#getGroupClass(java.lang.String, java.lang.String) 103 */ 104 public Class<? extends Group> getGroupClass(String theName, String theVersion) throws HL7Exception { 105 return (Class<? extends Group>) findClass(theName, theVersion, "group"); 106 } 107 108 /** 109 * @see ca.uhn.hl7v2.parser.ModelClassFactory#getSegmentClass(java.lang.String, java.lang.String) 110 */ 111 public Class<? extends Segment> getSegmentClass(String theName, String theVersion) throws HL7Exception { 112 return (Class<? extends Segment>) findClass(theName, theVersion, "segment"); 113 } 114 115 /** 116 * @see ca.uhn.hl7v2.parser.ModelClassFactory#getTypeClass(java.lang.String, java.lang.String) 117 */ 118 public Class<? extends Type> getTypeClass(String theName, String theVersion) throws HL7Exception { 119 return (Class<? extends Type>) findClass(theName, theVersion, "datatype"); 120 } 121 122 /** 123 * Returns the path to the base package for model elements of the given version 124 * - e.g. "ca/uhn/hl7v2/model/v24/". 125 * This package should have the packages datatype, segment, group, and message 126 * under it. The path ends in with a slash. 127 */ 128 public static String getVersionPackagePath(String ver) throws HL7Exception { 129 if (Parser.validVersion(ver) == false) { 130 throw new HL7Exception("The HL7 version " + ver + " is not recognized", HL7Exception.UNSUPPORTED_VERSION_ID); 131 } 132 StringBuffer path = new StringBuffer("ca/uhn/hl7v2/model/v"); 133 char[] versionChars = new char[ver.length()]; 134 ver.getChars(0, ver.length(), versionChars, 0); 135 for (int i = 0; i < versionChars.length; i++) { 136 if (versionChars[i] != '.') path.append(versionChars[i]); 137 } 138 path.append('/'); 139 return path.toString(); 140 } 141 142 /** 143 * Returns the package name for model elements of the given version - e.g. 144 * "ca.uhn.hl7v2.model.v24.". This method 145 * is identical to <code>getVersionPackagePath(...)</code> except that path 146 * separators are replaced with dots. 147 */ 148 public static String getVersionPackageName(String ver) throws HL7Exception { 149 String path = DefaultModelClassFactory.getVersionPackagePath(ver); 150 String packg = path.replace('/', '.'); 151 packg = packg.replace('\\', '.'); 152 return packg; 153 } 154 155 /** 156 * <p>Lists all the packages (user-definable) where classes for standard and custom 157 * messages may be found. Each package has subpackages called "message", 158 * "group", "segment", and "datatype" in which classes for these message elements 159 * can be found. </p> 160 * <p>At a minimum, this method returns the standard package for the 161 * given version. For example, for version 2.4, the package list contains <code> 162 * ca.uhn.hl7v2.model.v24</code>. In addition, user-defined packages may be specified 163 * for custom messages.</p> 164 * <p>If you define custom message classes, and want Parsers to be able to 165 * find them, you must register them as follows (otherwise you will get an exception when 166 * the corresponding messages are parsed). For each HL7 version you want to support, you must 167 * put a text file on your classpath, under the folder /custom_packages, named after the version. For example, 168 * for version 2.4, you might put the file "custom_packages/2.4" in your application JAR. Each line in the 169 * file should name a package to search for message classes of that version. For example, if you 170 * work at foo.org, you might create a v2.4 message structure called "ZFO" and define it in the class 171 * <code>org.foo.hl7.custom.message.ZFO<code>. In order for parsers to find this message 172 * class, you would need to enter the following line in custom_packages/2.4:</p> 173 * <p>org.foo.hl7.custom</p> 174 * <p>Packages are searched in the order specified. The standard package for a given version 175 * is searched last, allowing you to override the default implementation. Please note that 176 * if you create custom classes for messages, segments, etc., their names must correspond exactly 177 * to their names in the message text. For example, if you subclass the QBP segment in order to 178 * add your own fields, your subclass must also be called QBP. although it will obviously be in 179 * a different package. To make sure your class is used instead of the default implementation, 180 * put your package in the package list. User-defined packages are searched first, so yours 181 * will be found first and used. </p> 182 * <p>It is important to note that there can only be one implementation of a particular message 183 * structure (i.e. one class with the message structure name, regardless of its package) among 184 * the packages defined as per the <code>packageList()</code> method. If there are duplicates 185 * (e.g. two ADT_A01 classes) the first one in the search order will always be used. However, 186 * this restriction only applies to message classes, not segment classes, etc. This is because 187 * classes representing parts of a message are referenced explicitly in the code for the message 188 * class, rather than being looked up (using findMessageClass() ) based on the String value of MSH-9.<p> 189 */ 190 public static String[] packageList(String version) throws HL7Exception { 191 //get package list for this version 192 return packages.get(version); 193 } 194 195 /** 196 * Returns a package list for the given version, including the standard package 197 * for that version and also user-defined packages (see packageList()). 198 */ 199 private static String[] loadPackages(String version) throws HL7Exception { 200 String[] retVal = null; 201 202 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 203 204 String customPackagesResourceName = 205 MessageFormat.format( CUSTOM_PACKAGES_RESOURCE_NAME_TEMPLATE, new Object[] { version } ); 206 207 InputStream resourceInputStream = classLoader.getResourceAsStream( customPackagesResourceName ); 208 209 List<String> packageList = new ArrayList<String>(); 210 211 if ( resourceInputStream != null) { 212 BufferedReader in = new BufferedReader(new InputStreamReader(resourceInputStream)); 213 214 try { 215 String line = in.readLine(); 216 while (line != null) { 217 log.info( "Adding package to user-defined package list: " + line ); 218 packageList.add( line ); 219 line = in.readLine(); 220 } 221 222 } catch (IOException e) { 223 log.error( "Can't load all the custom package list - user-defined classes may not be recognized", e ); 224 } 225 226 } 227 else { 228 log.debug("No user-defined packages for version " + version); 229 } 230 231 //add standard package 232 packageList.add( getVersionPackageName(version) ); 233 retVal = packageList.toArray(new String[]{}); 234 235 return retVal; 236 } 237 238 /** 239 * Finds a message or segment class by name and version. 240 * @param name the segment or message structure name 241 * @param version the HL7 version 242 * @param type 'message', 'group', 'segment', or 'datatype' 243 */ 244 private static Class<?> findClass(String name, String version, String type) throws HL7Exception { 245 if (Parser.validVersion(version) == false) { 246 throw new HL7Exception( 247 "The HL7 version " + version + " is not recognized", 248 HL7Exception.UNSUPPORTED_VERSION_ID); 249 } 250 251 //get list of packages to search for the corresponding message class 252 String[] packageList = packageList(version); 253 254 if (packageList == null) { 255 return null; 256 } 257 258 //get subpackage for component type 259 String types = "message|group|segment|datatype"; 260 if (types.indexOf(type) < 0) 261 throw new HL7Exception("Can't find " + name + " for version " + version 262 + " -- type must be " + types + " but is " + type); 263 String subpackage = type; 264 265 //try to load class from each package 266 Class<?> compClass = null; 267 int c = 0; 268 while (compClass == null && c < packageList.length) { 269 try { 270 String p = packageList[c]; 271 if (!p.endsWith(".")) 272 p = p + "."; 273 String classNameToTry = p + subpackage + "." + name; 274 275 if (log.isDebugEnabled()) { 276 log.debug("Trying to load: " + classNameToTry); 277 } 278 compClass = Class.forName(classNameToTry); 279 if (log.isDebugEnabled()) { 280 log.debug("Loaded: " + classNameToTry + " class: " + compClass); 281 } 282 } 283 catch (ClassNotFoundException cne) { 284 /* just try next one */ 285 } 286 c++; 287 } 288 return compClass; 289 } 290 291 292 /** 293 * Reloads the packages. Note that this should not be performed 294 * after and messages have been parsed or otherwise generated, 295 * as undetermined behaviour may result. 296 */ 297 public static void reloadPackages() { 298 packages.clear(); 299 ourVersions = new ArrayList<String>(); 300 List<String> versions = Parser.getValidVersions(); 301 for (String version : versions) { 302 try { 303 String[] versionPackages = loadPackages(version); 304 if (versionPackages.length > 0) { 305 ourVersions.add(version); 306 } 307 packages.put(version, versionPackages); 308 } catch (HL7Exception e) { 309 throw new Error("Version \"" + version + "\" is invalid. This is a programming error: ", e); 310 } 311 } 312 } 313 314 315 /** 316 * Returns a string containing the highest known version of HL7 known to HAPI (i.e. "2.6"). Note that this 317 * is determined by checking which structure JARs are available on the classpath, so if this release of 318 * HAPI supports version 2.6, but only the hapi-structures-v23.jar is available on the classpath, 319 * "2.3" will be returned 320 */ 321 public static String getHighestKnownVersion() { 322 if (ourVersions == null || ourVersions.size() == 0) { 323 return null; 324 } 325 return ourVersions.get(ourVersions.size() - 1); 326 } 327 328 329 330 }