View Javadoc
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.util.HashMap;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.concurrent.ConcurrentHashMap;
30  import java.util.concurrent.ConcurrentMap;
31  
32  import ca.uhn.hl7v2.model.*;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import ca.uhn.hl7v2.HL7Exception;
37  import ca.uhn.hl7v2.Version;
38  import ca.uhn.hl7v2.util.StringUtil;
39  
40  /**
41   * ModelClassFactory which allows custom packages to search to be specified.
42   * These packages will be searched first, and if nothing is found for a particular
43   * structure, a delegate ModelClassFactory (by default DefaultModelClassFactory) is used. 
44   * <p>
45   * Also, a custom event map location is supported (by setting {@link #setEventMapDirectory(String)}
46   * in a way that only new or modified structures needs to be defined there. If no structure was
47   * found, the event map of the delegate ModelClassFactory serves as fallback.
48   *
49   * @author Christian Ohr
50   * @author James Agnew
51   * @since 1.0
52   */
53  public class CustomModelClassFactory extends AbstractModelClassFactory {
54  
55      private static final long serialVersionUID = 1;
56      private static final Logger LOG = LoggerFactory.getLogger(CustomModelClassFactory.class);
57  
58      private final ModelClassFactory delegate;
59      private Map<String, String[]> customModelClasses;
60  
61      // some optimization
62      private final ConcurrentMap<String, Class> cache = new ConcurrentHashMap<>();
63  
64      /**
65       * Constructor which just delegated to {@link DefaultModelClassFactory}
66       */
67      public CustomModelClassFactory() {
68          this((Map<String, String[]>)null);
69      }
70  
71  
72      /**
73       * Constructor
74       *
75       * @param packageName The base package name to use.
76       * <p>
77       * When searching, package specified here will be appended with .[version].[structure type].
78       * </p>
79       * <p>
80       * 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>"
81       * </p>
82       */
83      public CustomModelClassFactory(String packageName) {
84          this(new HashMap<>());
85  
86          if (!packageName.endsWith(".")) {
87              packageName += ".";
88          }
89          for (Version v : Version.values()) {
90          	addModel(v.getVersion(), new String[] {packageName + v.getPackageVersion()});
91          }
92      }
93  
94      
95      /**
96       * Constructor
97       * @param map Map of packages to include.
98       * <p>
99       * 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) {
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<>();
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 }