001/** 002The 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. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Initial Developer of the Original Code is University Health Network. Copyright (C) 0102001. All Rights Reserved. 011 012Contributor(s): ______________________________________. 013 014Alternatively, the contents of this file may be used under the terms of the 015GNU General Public License (the �GPL�), in which case the provisions of the GPL are 016applicable instead of those above. If you wish to allow use of your version of this 017file only under the terms of the GPL and not to allow others to use your version 018of this file under the MPL, indicate your decision by deleting the provisions above 019and replace them with the notice and other provisions required by the GPL License. 020If you do not delete the provisions above, a recipient may use your version of 021this file under either the MPL or the GPL. 022 023*/ 024package ca.uhn.hl7v2.parser; 025 026import java.util.HashMap; 027import java.util.Map; 028import java.util.Map.Entry; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.ConcurrentMap; 031 032import ca.uhn.hl7v2.model.*; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import ca.uhn.hl7v2.HL7Exception; 037import ca.uhn.hl7v2.Version; 038import ca.uhn.hl7v2.util.StringUtil; 039 040/** 041 * ModelClassFactory which allows custom packages to search to be specified. 042 * These packages will be searched first, and if nothing is found for a particular 043 * structure, a delegate ModelClassFactory (by default DefaultModelClassFactory) is used. 044 * <p> 045 * Also, a custom event map location is supported (by setting {@link #setEventMapDirectory(String)} 046 * in a way that only new or modified structures needs to be defined there. If no structure was 047 * found, the event map of the delegate ModelClassFactory serves as fallback. 048 * 049 * @author Christian Ohr 050 * @author James Agnew 051 * @since 1.0 052 */ 053public class CustomModelClassFactory extends AbstractModelClassFactory { 054 055 private static final long serialVersionUID = 1; 056 private static Logger LOG = LoggerFactory.getLogger(CustomModelClassFactory.class); 057 058 private final ModelClassFactory delegate; 059 private Map<String, String[]> customModelClasses; 060 061 // some optimization 062 private ConcurrentMap<String, Class> cache = new ConcurrentHashMap<String, Class>(); 063 064 /** 065 * Constructor which just delegated to {@link DefaultModelClassFactory} 066 */ 067 public CustomModelClassFactory() { 068 this((Map<String, String[]>)null); 069 } 070 071 072 /** 073 * Constructor 074 * 075 * @param packageName The base package name to use. 076 * <p> 077 * When searching, package specified here will be appended with .[version].[structure type]. 078 * </p> 079 * <p> 080 * So, for instance, when looking for a v2.5 segment object, if "<code>com.foo</code>" is passed in, HAPI will look in "<code>com.foo.v25.segment.*</code>" 081 * </p> 082 */ 083 public CustomModelClassFactory(String packageName) { 084 this(new HashMap<String, String[]>()); 085 086 if (!packageName.endsWith(".")) { 087 packageName += "."; 088 } 089 for (Version v : Version.values()) { 090 addModel(v.getVersion(), new String[] {packageName + v.getPackageVersion()}); 091 } 092 } 093 094 095 /** 096 * Constructor 097 * @param map Map of packages to include. 098 * <p> 099 * Keys are versions of HL7, e.g. "2.5". 100 * </p> 101 * <p> 102 * Values are an array of packages to search in for custom model classes. 103 * When searching, the package name here will be appended with "<b>.[structure type]</b>". 104 * So, for example, to specify a custom message type, you could create the class 105 * <code>foo.example.v23.message.ZRM_Z01</code>, and pass in the string "<code>foo.example.v23</code>". 106 * </p> 107 */ 108 public CustomModelClassFactory(Map<String, String[]> map) { 109 this(new DefaultModelClassFactory(), map); 110 } 111 112 /** 113 * Set an explicit {@link ModelClassFactory} is underlying delegate 114 * @param defaultFactory default factory to be delegated to 115 * @param map custom model map 116 */ 117 public CustomModelClassFactory(ModelClassFactory defaultFactory, Map<String, String[]> map) { 118 this.delegate = defaultFactory; 119 customModelClasses = map; 120 } 121 122 /** 123 * {@inheritDoc } 124 */ 125 public Class<? extends Message> getMessageClass(String name, String version, boolean isExplicit) throws HL7Exception { 126 if (!isExplicit) { 127 name = getMessageStructureForEvent(name, Version.versionOf(version)); 128 } 129 String key = "message" + name + version; 130 Class<? extends Message> retVal = cache.get(key); 131 if (retVal != null) return retVal; 132 133 retVal = findClass("message", name, version); 134 if (retVal == null) { 135 retVal = delegate.getMessageClass(name, version, isExplicit); 136 } 137 if (retVal != null) cache.putIfAbsent(key, retVal); 138 return retVal; 139 } 140 141 /** 142 * {@inheritDoc } 143 */ 144 public Class<? extends Group> getGroupClass(String name, String version) throws HL7Exception { 145 String key = "group" + name + version; 146 Class<? extends Group> retVal = cache.get(key); 147 if (retVal != null) return retVal; 148 retVal = findClass("group", name, version); 149 if (retVal == null) { 150 retVal = delegate.getGroupClass(name, version); 151 } 152 if (retVal != null) cache.putIfAbsent(key, retVal); 153 return retVal; 154 } 155 156 /** 157 * {@inheritDoc } 158 */ 159 public Class<? extends Segment> getSegmentClass(String name, String version) throws HL7Exception { 160 String key = "segment" + name + version; 161 Class<? extends Segment> retVal = cache.get(key); 162 if (retVal != null) return retVal; 163 retVal = findClass("segment", name, version); 164 if (retVal == null) { 165 retVal = delegate.getSegmentClass(name, version); 166 } 167 if (retVal != null) cache.putIfAbsent(key, retVal); 168 return retVal; 169 } 170 171 /** 172 * {@inheritDoc } 173 */ 174 public Class<? extends Type> getTypeClass(String name, String version) throws HL7Exception { 175 String key = "datatype" + name + version; 176 Class<? extends Type> retVal = cache.get(key); 177 if (retVal != null) return retVal; 178 retVal = findClass("datatype", name, version); 179 if (retVal == null) { 180 retVal = delegate.getTypeClass(name, version); 181 } 182 if (retVal != null) cache.putIfAbsent(key, retVal); 183 return retVal; 184 } 185 186 /** 187 * Finds appropriate classes to be loaded for the given structure/type 188 */ 189 @SuppressWarnings("unchecked") 190 protected <T> Class<T> findClass(String subpackage, String name, String version) throws HL7Exception { 191 Class<T> classLoaded = null; 192 if (customModelClasses != null) { 193 if (customModelClasses.containsKey(version)) { 194 for (String next : customModelClasses.get(version)) { 195 if (!next.endsWith(".")) { 196 next += "."; 197 } 198 String fullyQualifiedName = next + subpackage + '.' + name; 199 try { 200 classLoaded = (Class<T>) Class.forName(fullyQualifiedName); 201 LOG.debug("Found " + fullyQualifiedName + " in custom HL7 model"); 202 } catch (ClassNotFoundException e) { 203 // ignore 204 } 205 } 206 } 207 } 208 return classLoaded; 209 } 210 211 /** 212 * Delegates calls to {@link DefaultModelClassFactory#getMessageClassInASpecificPackage(String, String, boolean, String)} 213 */ 214 public Class<? extends Message> getMessageClassInASpecificPackage(String theName, String theVersion, boolean theIsExplicit, String thePackageName) throws HL7Exception { 215 return delegate.getMessageClassInASpecificPackage(theName, theVersion, theIsExplicit, thePackageName); 216 } 217 218 /** 219 * Returns the configured custom model classes 220 * @return a map of custom model classes 221 */ 222 public Map<String, String[]> getCustomModelClasses() { 223 return customModelClasses; 224 } 225 226 /** 227 * Add model class packages after the object has been instantiated 228 * 229 * @param addedModelClasses map with version number as key and package names has value 230 */ 231 public void addModels(Map<String, String[]> addedModelClasses) { 232 if (customModelClasses == null) { 233 customModelClasses = new HashMap<String, String[]>(); 234 } 235 for (Entry<String, String[]> entry : addedModelClasses.entrySet()) { 236 addModel(entry.getKey(), entry.getValue()); 237 } 238 } 239 240 private void addModel(String version, String[] newPackageNames) { 241 if (customModelClasses.containsKey(version)) { 242 // the new packages must be added after the existing ones. 243 String[] existingPackageNames = customModelClasses.get(version); 244 customModelClasses.put(version, StringUtil.concatenate(existingPackageNames, newPackageNames)); 245 } else { 246 customModelClasses.put(version, newPackageNames); 247 } 248 } 249 250 251 /** 252 * Looks up its own event map. If no structure was found, the call is delegated to 253 * the default ModelClassFactory. If nothing can be found, the eventName is returned 254 * as structure. 255 * 256 * @see ca.uhn.hl7v2.parser.AbstractModelClassFactory#getMessageStructureForEvent(java.lang.String, ca.uhn.hl7v2.Version) 257 */ 258 @Override 259 public String getMessageStructureForEvent(String eventName, Version version) throws HL7Exception { 260 String structure = super.getMessageStructureForEvent(eventName, version); 261 if (structure != null) return structure; 262 structure = delegate.getMessageStructureForEvent(eventName, version); 263 return structure != null ? structure : eventName; 264 } 265 266}