View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2006 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  
18  package ch.elca.el4j.services.persistence.generic.dao.impl;
19  
20  import java.lang.reflect.Proxy;
21  import java.util.Map;
22  import java.util.concurrent.ConcurrentHashMap;
23  
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  import org.springframework.aop.framework.AopProxyUtils;
27  import org.springframework.beans.BeansException;
28  import org.springframework.context.ApplicationContext;
29  import org.springframework.context.ApplicationContextAware;
30  import org.springframework.util.PatternMatchUtils;
31  
32  import ch.elca.el4j.core.context.ModuleApplicationListener;
33  import ch.elca.el4j.core.context.RefreshableModuleApplicationContext;
34  import ch.elca.el4j.services.monitoring.notification.CoreNotificationHelper;
35  import ch.elca.el4j.services.persistence.generic.dao.DaoRegistry;
36  import ch.elca.el4j.services.persistence.generic.dao.GenericDao;
37  
38  import net.sf.cglib.proxy.Enhancer;
39  
40  /**
41   * A DaoRegistry where DAOs can be registered either explicitly 
42   * (via its map configuration) or implicitly
43   * (by collecting all beans that have the GenericDao interface). <br>
44   *
45   *  This can be used together with
46   *   <ul>
47   *	  <li> context:component-scan configuration setting and the 
48   *		   @AutocollectedGenericDao annotation to load all DAOs with this
49   *         annotation into the spring application context.
50   *    <li> {@link HibernateSessionFactoryInjectorPostProcessor} or
51   *         {@link IbatisSqlMapClientTemplateInjectorBeanPostProcessor}
52   *         to automatically set the session factory/
53   *        sql map client template.
54   *   </ul>
55   *   
56   * Important: This class has to be created inside a ModuleApplicationContext,
57   * because it waits for this context to be fully initialized. If another
58   * context is taken it will wait forever.
59   *
60   * @svnLink $Revision: 4071 $;$Date: 2010-01-06 10:07:00 +0100 (Mi, 06. Jan 2010) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/core/src/main/java/ch/elca/el4j/services/persistence/generic/dao/impl/DefaultDaoRegistry.java $
61   *
62   * @author Adrian Moos (AMS)
63   * @author Alex Mathey (AMA)
64   * @author Jonas Hauenstein (JHN)
65   */
66  public class DefaultDaoRegistry implements DaoRegistry, ApplicationContextAware, ModuleApplicationListener {
67  
68  	/**
69  	 * Private logger of this class.
70  	 */
71  	private static Logger s_logger
72  		= LoggerFactory.getLogger(DefaultDaoRegistry.class);
73  	
74  	/**
75  	 * Whether to collect DAOs automatically.
76  	 */
77  	protected boolean m_collectDaos = true;
78  	
79  	/**
80  	 * The map containing the registered DAOs with the bean class as key.
81  	 */
82  	protected Map<Class<?>, GenericDao<?>> m_beanClassDaos = new ConcurrentHashMap<Class<?>, GenericDao<?>>();
83  
84  	/**
85  	 * The map containing the registered DAOs with the dao class as key.
86  	 */
87  	protected Map<Class<?>, GenericDao<?>> m_daoClassDaos = new ConcurrentHashMap<Class<?>, GenericDao<?>>();
88  	
89  	/** 
90  	 * The application context. 
91  	 */
92  	protected ApplicationContext m_applicationContext;
93  	
94  	/**
95  	 * The dao matching pattern. All GenericDaos whose names match this pattern
96  	 * are collected. 
97  	 */
98  	protected String m_daoNamePattern = "*";
99  	
100 	/** 
101 	 * Was {@link initDaosFromSpringBeans} already called?
102 	 */
103 	protected boolean m_initialized = false;
104 	
105 	/**
106 	 * The thread ID of the thread that set the application context (in general the thread that created this object).
107 	 */
108 	protected long m_creatorThreadId;
109 	
110 	/**
111 	 * Is Spring context completely initialized?
112 	 */
113 	protected volatile boolean m_applicationContextIsReady = false;
114 	
115 	/** {@inheritDoc} */
116 	public synchronized void onContextRefreshed() {
117 		m_applicationContextIsReady = true;
118 		this.notify();
119 	}
120 	
121 	/**
122 	 * Check if Spring context is completely initialized and wait if we are in multi-threaded environment.
123 	 */
124 	public synchronized void waitUntilApplicationContextIsReady() {
125 		// if we are in the same thread, waiting probably doesn't make sense, so we have to check this.
126 		if (Thread.currentThread().getId() != m_creatorThreadId) {
127 			// access from another thread -> wait
128 			while (!m_applicationContextIsReady) {
129 				try {
130 					s_logger.debug("Waiting for Spring context to be fully initialized.");
131 					this.wait();
132 				} catch (InterruptedException e) {
133 					s_logger.debug("Interrupted: Application context might be ready now.");
134 				}
135 			}
136 		}
137 		
138 		// context might be ready but caller got the contextRefreshed-event earlier than we did.
139 		if (!m_applicationContextIsReady) {
140 			if (m_applicationContext instanceof RefreshableModuleApplicationContext) {
141 				RefreshableModuleApplicationContext context
142 					= (RefreshableModuleApplicationContext) m_applicationContext;
143 				m_applicationContextIsReady = context.isRefreshed();
144 			}
145 		}
146 		
147 		if (!m_applicationContextIsReady) {
148 			CoreNotificationHelper.notifyMisconfiguration("Trying to get DAOs before Spring context is "
149 				+ "fully initialized. Some DAOs might not be found. "
150 				+ "Implement ch.elca.el4j.core.context.ModuleApplicationListener to get notified as soon "
151 				+ "Spring context is fully initialized");
152 		}
153 	}
154 	
155 	/**
156 	 * {@inheritDoc}
157 	 */
158 	@SuppressWarnings("unchecked")
159 	public <T> GenericDao<T> getFor(Class<T> entityType) {
160 		
161 		if ((!m_initialized) && m_collectDaos) {
162 			m_initialized = true;
163 			initDaosFromSpringBeans();
164 		}
165 		
166 		Class<T> actualEntityType = entityType;
167 		// ensure this works when the entityType is proxied by an cglib proxy:
168 		//  Thanks Ky (QKP) for the hint!
169 		if (Enhancer.isEnhanced(entityType)) {
170 			// "undo" cglib proxying:
171 			actualEntityType = (Class<T>) entityType.getSuperclass();
172 		}
173 		
174 		GenericDao<T> candidateReturn = (GenericDao<T>) m_beanClassDaos.get(actualEntityType);
175 		
176 		if (candidateReturn != null) {
177 			return candidateReturn;
178 		} else if (Proxy.isProxyClass(actualEntityType)) {
179 			// if a jdk proxy and candidateReturn is null, try improving
180 			Class[] otherPossibilities
181 				= AopProxyUtils.proxiedUserInterfaces(actualEntityType);
182 			if (otherPossibilities != null) {
183 				s_logger.info("Trying to unwrap JDK proxy to get DAO for type");
184 				for (Class c : otherPossibilities) {
185 					candidateReturn = (GenericDao<T>) m_beanClassDaos.get(c);
186 					if (candidateReturn != null) {
187 						return candidateReturn;
188 					}
189 				}
190 			}
191 		}
192 		// we give up
193 		return null;
194 
195 	}
196 	
197 	/** {@inheritDoc} */
198 	@SuppressWarnings("unchecked")
199 	public <T> T getDao(Class<T> doaType) {
200 		
201 		if ((!m_initialized) && m_collectDaos) {
202 			m_initialized = true;
203 			initDaosFromSpringBeans();
204 		}
205 		
206 		T candidateReturn = (T) m_daoClassDaos.get(doaType);	
207 		return candidateReturn;
208 	}
209 
210 	/**
211 	 * Set a new DAO name pattern. Only DAOs whose bean names match this pattern
212 	 * are collected. Allowed wildcards are '*' which match any characters,
213 	 * the default is {@code "*"} which matches all DAOs.
214 	 * @param namePattern The name pattern to set.
215 	 */
216 	public void setNamePattern(String namePattern) {
217 		m_daoNamePattern = namePattern;
218 		
219 	}
220 	
221 	/**
222 	 * Load all GenericDaos from this spring bean's bean factory.
223 	 */
224 	protected void initDaosFromSpringBeans() {
225 		waitUntilApplicationContextIsReady();
226 		
227 		String[] beanNamesToLoad = m_applicationContext.getBeanNamesForType(GenericDao.class);
228 		for (String name : beanNamesToLoad) {
229 			if (!PatternMatchUtils.simpleMatch(m_daoNamePattern, name)) {
230 				// Doesn't match - so skip it.
231 				continue;
232 			}
233 			
234 			GenericDao<?> dao = (GenericDao<?>) m_applicationContext.getBean(name);
235 			
236 			// avoid adding a DAO again
237 			if (!m_beanClassDaos.values().contains(dao)) {
238 				initDao(dao);
239 				m_beanClassDaos.put(dao.getPersistentClass(), dao);
240 				
241 				//add all implemented interfaces of the dao class since
242 				//the parameter for method getDao could be any implemented interface  
243 				Class<?>[] interfaceList = dao.getClass().getInterfaces();
244 				for (Class<?> c : interfaceList) {
245 					//only add interfaces which are at least derived from GenericDao
246 					if (GenericDao.class.isAssignableFrom(c)) {
247 						m_daoClassDaos.put(c, dao);
248 					}
249 				}
250 			}
251 		}
252 	}
253 	
254 	/**
255 	 * This method can be overridden by child classes to initialize
256 	 *  all DAOs even further.
257 	 *  @param dao The dao to initialize.
258 	 */
259 	protected void initDao(GenericDao<?> dao) {
260 	}
261 	
262 	/** {@inheritDoc} */
263 	public Map<Class<?>, ? extends GenericDao<?>> getDaos() {
264 		if ((!m_initialized) && m_collectDaos) {
265 			m_initialized = true;
266 			initDaosFromSpringBeans();
267 		}
268 		
269 		return m_beanClassDaos;
270 	}
271 
272 	/**
273 	 * @param daos Registers the DAOs.
274 	 */
275 	public void setDaos(Map<Class<?>, GenericDao<?>> daos) {
276 		m_beanClassDaos = daos;
277 		//clear the second map
278 		m_daoClassDaos.clear();
279 		for (GenericDao<?> dao : daos.values()) {
280 			initDao(dao);
281 			//also initialize the second map
282 			m_daoClassDaos.put(dao.getClass(), dao);
283 		}
284 	}
285 
286 	/**
287 	 * {@inheritDoc}
288 	 */
289 	public void setApplicationContext(ApplicationContext applicationContext)
290 		throws BeansException {
291 		m_applicationContext = applicationContext;
292 		m_creatorThreadId = Thread.currentThread().getId();
293 	}
294 
295 	/**
296 	 * See {@link setCollectDaos}.
297 	 * @return Whether to collect DAOs automatically.
298 	 */
299 	public boolean isCollectDaos() {
300 		return m_collectDaos;
301 	}
302 	
303 	/**
304 	 * By default we automatically collect here all generic DAOs from the spring
305 	 *  application context (all DAOs that implement the GenericDao interface).
306 	 *  This setter method allows to change this default.
307 	 * @param collectDaos The new value for collecting daos.
308 	 */
309 	public void setCollectDaos(boolean collectDaos) {
310 		m_collectDaos = collectDaos;
311 	}
312 }