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;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import ca.uhn.hl7v2.HL7Exception;
034import ca.uhn.hl7v2.Version;
035import ca.uhn.hl7v2.model.Group;
036import ca.uhn.hl7v2.model.Message;
037import ca.uhn.hl7v2.model.Segment;
038import ca.uhn.hl7v2.model.Type;
039import ca.uhn.hl7v2.util.StringUtil;
040
041/**
042 * ModelClassFactory which allows custom packages to search to be specified.
043 * These packages will be searched first, and if nothing is found for a particular
044 * structure, a delegate ModelClassFactory (by default DefaultModelClassFactory) is used. 
045 * <p>
046 * Also, a custom event map location is supported (by setting {@link #setEventMapDirectory(String)}
047 * in a way that only new or modified structures needs to be defined there. If no structure was
048 * found, the event map of the delegate ModelClassFactory serves as fallback.
049 *
050 * @author Christian Ohr
051 * @author James Agnew
052 * @since 1.0
053 */
054public class CustomModelClassFactory extends AbstractModelClassFactory {
055
056    private static final long serialVersionUID = 1;
057    private static Logger LOG = LoggerFactory.getLogger(CustomModelClassFactory.class);
058
059    private final ModelClassFactory delegate;
060    private Map<String, String[]> customModelClasses;
061
062    /**
063     * Constructor which just delegated to {@link DefaultModelClassFactory}
064     */
065    public CustomModelClassFactory() {
066        this((Map<String, String[]>)null);
067    }
068
069
070    /**
071     * Constructor
072     *
073     * @param packageName The base package name to use.
074     * <p>
075     * When searching, package specified here will be appended with .[version].[structure type].
076     * </p>
077     * <p>
078     * 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>"
079     * </p>
080     */
081    public CustomModelClassFactory(String packageName) {
082        this(new HashMap<String, String[]>());
083
084        if (!packageName.endsWith(".")) {
085            packageName += ".";
086        }
087        for (Version v : Version.values()) {
088                addModel(v.getVersion(), new String[] {packageName + v.getPackageVersion()});
089        }
090    }
091
092    
093    /**
094     * Constructor
095     * @param map Map of packages to include.
096     * <p>
097     * Keys are versions of HL7, e.g. "2.5".
098     * </p>
099     * <p>
100     * Values are an array of packages to search in for custom model classes.
101     * When searching, the package name here will be appended with "<b>.[structure type]</b>".
102     * So, for example, to specify a custom message type, you could create the class
103     * <code>foo.example.v23.message.ZRM_Z01</code>, and pass in the string "<code>foo.example.v23</code>".
104     * </p>
105     */
106    public CustomModelClassFactory(Map<String, String[]> map) {
107        this(new DefaultModelClassFactory(), map);
108    }
109    
110    /**
111     * Set an explicit {@link ModelClassFactory} is underlying delegate
112     * @param defaultFactory default factory to be delegated to
113     * @param map custom model map
114     */
115    public CustomModelClassFactory(ModelClassFactory defaultFactory, Map<String, String[]> map) {
116        this.delegate = defaultFactory;
117        customModelClasses = map;
118    }    
119
120    /**
121     * {@inheritDoc }
122     */
123        public Class<? extends Message> getMessageClass(String name, String version, boolean isExplicit) throws HL7Exception {
124        if (!isExplicit) {
125            name = getMessageStructureForEvent(name, Version.versionOf(version));
126        }
127        Class<? extends Message> retVal = findClass("message", name, version);
128        if (retVal == null) {
129            retVal = delegate.getMessageClass(name, version, isExplicit);
130        }
131        return retVal;
132    }
133
134    /**
135     * {@inheritDoc }
136     */
137        public Class<? extends Group> getGroupClass(String name, String version) throws HL7Exception {
138        Class<? extends Group> retVal = findClass("group", name, version);
139        if (retVal == null) {
140            retVal = delegate.getGroupClass(name, version);
141        }
142        return retVal;
143    }
144
145    /**
146     * {@inheritDoc }
147     */
148        public Class<? extends Segment> getSegmentClass(String name, String version) throws HL7Exception {
149        Class<? extends Segment> retVal = findClass("segment", name, version);
150        if (retVal == null) {
151            retVal = delegate.getSegmentClass(name, version);
152        }
153        return retVal;
154    }
155
156    /**
157     * {@inheritDoc }
158     */
159        public Class<? extends Type> getTypeClass(String name, String version) throws HL7Exception {
160        Class<? extends Type> retVal = findClass("datatype", name, version);
161        if (retVal == null) {
162            retVal = delegate.getTypeClass(name, version);
163        }
164        return retVal;
165    }
166
167    /**
168     * Finds appropriate classes to be loaded for the given structure/type
169     */
170    @SuppressWarnings("unchecked")
171        protected <T> Class<T> findClass(String subpackage, String name, String version) throws HL7Exception {
172        Parser.assertVersionExists(version);
173        Class<T> classLoaded = null;
174        if (customModelClasses != null) {
175            if (customModelClasses.containsKey(version)) {
176                for (String next : customModelClasses.get(version)) {
177                    if (!next.endsWith(".")) {
178                        next += ".";
179                    }
180                    String fullyQualifiedName = next + subpackage + '.' + name;
181                    try {
182                        classLoaded = (Class<T>) Class.forName(fullyQualifiedName);
183                        LOG.debug("Found " + fullyQualifiedName + " in custom HL7 model");
184                    } catch (ClassNotFoundException e) {
185                        // ignore
186                    }
187                }
188            }
189        }
190        return classLoaded;
191    }
192
193    /**
194     * Delegates calls to {@link DefaultModelClassFactory#getMessageClassInASpecificPackage(String, String, boolean, String)}
195     */
196        public Class<? extends Message> getMessageClassInASpecificPackage(String theName, String theVersion, boolean theIsExplicit, String thePackageName) throws HL7Exception {
197                return delegate.getMessageClassInASpecificPackage(theName, theVersion, theIsExplicit, thePackageName);
198        }
199
200    /**
201     * Returns the configured custom model classes
202     * @return a map of custom model classes
203     */
204    public Map<String, String[]> getCustomModelClasses() {
205                return customModelClasses;
206        }
207
208        /**
209         * Add model class packages after the object has been instantiated
210         * 
211         * @param addedModelClasses map with version number as key and package names has value
212         */
213        public void addModels(Map<String, String[]> addedModelClasses) {
214        if (customModelClasses == null) {
215                customModelClasses = new HashMap<String, String[]>();
216        }
217        for (Entry<String, String[]> entry : addedModelClasses.entrySet()) {
218                addModel(entry.getKey(), entry.getValue());
219        }
220    }
221        
222        private void addModel(String version, String[] newPackageNames) {
223        if (customModelClasses.containsKey(version)) {
224            // the new packages must be added after the existing ones.
225            String[] existingPackageNames = customModelClasses.get(version);
226            customModelClasses.put(version, StringUtil.concatenate(existingPackageNames, newPackageNames));
227        } else {
228                customModelClasses.put(version, newPackageNames);
229        }               
230        }
231
232
233        /**
234         * Looks up its own event map. If no structure was found, the call is delegated to
235         * the default ModelClassFactory. If nothing can be found, the eventName is returned
236         * as structure.
237         * 
238         * @see ca.uhn.hl7v2.parser.AbstractModelClassFactory#getMessageStructureForEvent(java.lang.String, ca.uhn.hl7v2.Version)
239         */
240        @Override
241        public String getMessageStructureForEvent(String eventName, Version version) throws HL7Exception {
242                String structure = super.getMessageStructureForEvent(eventName, version);
243                if (structure == null) {
244                        structure = delegate.getMessageStructureForEvent(eventName, version);
245                }
246                if (structure != null) {
247                        structure = eventName;
248                }
249                return structure;
250        }
251        
252        
253        
254}