001    /*
002     * Copyright 2003-2004 The Apache Software Foundation.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.apache.commons.mapper;
018    
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.mapper.util.ObjectFactory;
026    
027    /**
028     * <code>MapperFactory</code> is responsible for creating mappers based on 
029     * domain class names or any other String name.  This allows a domain class to 
030     * be a value bean and delegate data storage responsibility to a specific 
031     * <code>Mapper</code> subclass.
032     * <p>
033     * <code>MapperFactory.getMapper()</code> method uses caching to prevent 
034     * creating mappers multiple times.  This means that a Mapper returned from 
035     * <code>getMapper()</code> will not be a new instance which could cause 
036     * problems in a multi-threaded environment (the servlet container environment 
037     * is multi-threaded).  Mappers returned by the <code>MapperFactory</code> 
038     * should not use instance variables to maintain state, rather, they should 
039     * use local method variables so they are thread-safe.
040     * <p>
041     * Note that you do not need to subclass <code>MapperFactory</code> to specify 
042     * the specific <code>Mapper</code> implementation classes to return from 
043     * <code>getMapper()</code>.  Because <code>Mapper</code> implementation 
044     * classes are defined in a <code>Map</code>, you can have any combination of 
045     * <code>Mapper</code> types in use at the same time.  For example, you could 
046     * have EjbPersonMapper using an EJB to persist Person objects and a 
047     * JdbcOrderMapper using JDBC calls to persist Order objects.  You can register 
048     * <code>MapperFactoryListener</code> implementations with the factory to 
049     * initialize the various types of Mappers when they are created.
050     * 
051     * @see Mapper
052     * @see MapperFactoryListener
053     */
054    public class MapperFactory {
055    
056        /** 
057         * This factory actually creates the mappers. 
058         */
059        protected ObjectFactory factory = null;
060    
061        /** 
062         * Holds mapper instances that have already been created.   The key is a 
063         * String name which is often a domain class' name and the value is a 
064         * <code>Mapper</code> instance.
065         */
066        private Map mapperCache = new HashMap();
067    
068        /**
069         * The List of listeners to notify when a Mapper is created.
070         */
071        private List listeners = new ArrayList();
072    
073        /**
074         * Create a new MapperFactory with the mappings contained in the given Map.
075         * @param map Any map containing logical names (often domain class names) 
076         * as the keys and mapper class names as the values.
077         * @throws IllegalArgumentException if there is a problem finding the 
078         * mapped classes.  
079         */
080        public MapperFactory(Map map) {
081            super();
082            this.factory = new ObjectFactory(map);
083        }
084    
085        /**
086         * Factory method for getting the mapper associated with the given class.
087         * @param mappedClass The class whose mapper should be returned
088         * @return Mapper the mapper associated with the given class
089         * @throws IllegalArgumentException if the given Class was not found in 
090         * the map or there was an error creating an instance of the Mapper.  This 
091         * is usually caused by a missing public no-arg constructor.
092         */
093        public Mapper getMapper(Class mappedClass) {
094            return this.getMapper(mappedClass.getName());
095        }
096    
097        /**
098         * Factory method for getting the mapper associated with the given class.
099         * @param name The name of the mapper to be returned.
100         * @return Mapper the mapper associated with the given name.
101         * @throws IllegalArgumentException if the given name was not found in 
102         * the map or there was an error creating an instance of the Mapper.  This 
103         * is usually caused by a missing public no-arg constructor.
104         */
105        public Mapper getMapper(String name) {
106    
107            synchronized (this.mapperCache) {
108                Mapper m = (Mapper) this.mapperCache.get(name);
109                if (m == null) {
110                    m = (Mapper) this.factory.create(name);
111                    this.fireMapperCreated(m);
112                    this.mapperCache.put(name, m);
113                }
114    
115                return m;
116            }
117        }
118    
119        /**
120         * Register a listener with this MapperFactory to receive notifications 
121         * of events.
122         * @param listener
123         */
124        public void addMapperFactoryListener(MapperFactoryListener listener) {
125            synchronized (this.listeners) {
126                this.listeners.add(listener);
127            }
128        }
129    
130        /**
131         * Unregister a listener from this MapperFactory.
132         * @param listener
133         */
134        public void removeMapperFactoryListener(MapperFactoryListener listener) {
135            synchronized (this.listeners) {
136                this.listeners.remove(listener);
137            }
138        }
139    
140        /**
141         * Call the mapperCreated() method of all registered listeners.
142         * @param created The Mapper that was just created and should be passed to
143         * the listeners for initialization.
144         */
145        private void fireMapperCreated(Mapper created) {
146            MapperFactoryEvent event = new MapperFactoryEvent(this, created);
147    
148            synchronized (this.listeners) {
149                Iterator iter = this.listeners.iterator();
150                while (iter.hasNext()) {
151                    MapperFactoryListener listener = (MapperFactoryListener) iter.next();
152                    listener.mapperCreated(event);
153                }
154            }
155    
156        }
157    
158    }