View Javadoc

1   package ch.elca.el4j.services.persistence.hibernate.entityfinder;
2   
3   import java.beans.BeanInfo;
4   import java.beans.Introspector;
5   import java.beans.PropertyDescriptor;
6   import java.lang.reflect.Array;
7   import java.lang.reflect.Field;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.Collection;
11  import java.util.HashSet;
12  import java.util.Iterator;
13  import java.util.Map;
14  
15  import javax.persistence.Entity;
16  
17  import org.apache.commons.lang.ArrayUtils;
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  import org.hibernate.HibernateException;
21  import org.hibernate.SessionFactory;
22  import org.hibernate.cfg.Configuration;
23  import org.hibernate.ejb.Ejb3Configuration;
24  import org.hibernate.ejb.HibernateEntityManagerFactory;
25  import org.hibernate.ejb.event.CallbackHandlerConsumer;
26  import org.hibernate.event.EventListeners;
27  import org.hibernate.secure.JACCSecurityListener;
28  import org.springframework.beans.factory.InitializingBean;
29  import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
30  import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
31  import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
32  import org.springframework.util.Assert;
33  import org.springframework.util.StringUtils;
34  
35  
36  /**
37   * Extends <a
38   * href="http://www.springframework.org/docs/api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html"
39   * target="_new">AnnotationSessionFactoryBean</a>. <br>
40   *
41   * Detects all classes that have a \@Entity annotation under a given parent package
42   * (indicated with the property <code>autoDetectEntityPackage</code>). <a>
43   *
44   *
45   * Source of the idea: secutix project
46   *
47   * @svnLink $Revision: 3874 $;$Date: 2009-08-04 14:25:40 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/hibernate/src/main/java/ch/elca/el4j/services/persistence/hibernate/entityfinder/EntityDetectorAnnotationSessionFactoryBean.java $
48   */
49  public class EntityDetectorAnnotationSessionFactoryBean extends AnnotationSessionFactoryBean
50  	implements InitializingBean {
51  
52  	private static final Logger s_logger =
53  		LoggerFactory.getLogger(EntityDetectorAnnotationSessionFactoryBean.class);
54  
55  	/**
56  	 * Package name for given set of entities.
57  	 */
58  	private String autoDetectEntityPackage[];
59  
60  	/**
61  	 * To simplify testing (there is not access to parent
62  	 *  field of same name)
63  	 */
64  	private Class<?>[] localAnnotatedClasses;
65  	
66  	/**
67  	 * Is full support for the JPA enabled.
68  	 */
69  	private boolean jpaFullSupportEnabled;
70  
71  	/**
72  	 * Sets the 1 or n packages in which the entities are defined
73  	 *  Sample package: "org.hibernate.tests.entities"
74  	 *
75  	 * @param pack
76  	 *            The parent package name that contains the target entities
77  	 *            	(as strings)
78  	 */
79  	public void setAutoDetectEntityPackage(String... pack) {
80  		autoDetectEntityPackage = pack;
81  	}
82  
83  	/**
84  	 * @return Returns the package prefixes in which we do the auto detection of entities.
85  	 */
86  	protected String[] getAutoDetectEntityPackage() {
87  		return autoDetectEntityPackage;
88  	}
89  	
90  	/**
91  	 * Specify the names of annotated packages, for which (including all
92  	 * sub packages) package-level JDK 1.5+ annotation metadata will be read.
93  	 * 
94  	 * @param annotatedPackages    a list of annotated packages
95  	 */
96  	public void setAutoDetectAnnotatedPackages(String[] annotatedPackages) {
97  		final Package[] packages = Package.getPackages();
98  		
99  		HashSet<String> detectedAnnotatedPackages = new HashSet<String>();
100 		for (String prefix : annotatedPackages) {
101 			for (Package p : packages) {
102 				if (p.getName().startsWith(prefix)) {
103 					detectedAnnotatedPackages.add(p.getName());
104 				}
105 			}
106 		}
107 		setAnnotatedPackages(detectedAnnotatedPackages.toArray(new String[0]));
108 	}
109 
110 	/**
111 	 * Explicitly specify annotated entity classes.
112 	 */
113 	@Override
114 	@SuppressWarnings("unchecked")
115 	public void setAnnotatedClasses(Class[] annotatedClasses) {
116 		ArrayList<Class> classes;
117 		if (!ArrayUtils.isEmpty(annotatedClasses)) {
118 			classes = new ArrayList<Class>(Arrays.asList(annotatedClasses));
119 		} else {
120 			classes = new ArrayList<Class>();
121 		}
122 		localAnnotatedClasses = (Class[]) classes.toArray(new Class[classes.size()]);
123 		super.setAnnotatedClasses(localAnnotatedClasses);
124 	}
125 
126 	/**
127 	 * Just to have them available for tests
128 	 * @return
129 	 */
130 	public Class<?>[] getAnnotatedClasses() {
131 		return localAnnotatedClasses;
132 	}
133 	
134 	/**
135 	 * Set the enabled flag for the full JPA support.
136 	 * @param enabled
137 	 */
138 	public void setJpaFullSupportEnabled(boolean enabled) {
139 		jpaFullSupportEnabled = enabled;
140 	}
141 	
142 	/**
143 	 * @return if the full Java Persistence API support is enabled.
144 	 */
145 	public boolean isJpaFullSupportEnabled() {
146 		return jpaFullSupportEnabled;
147 	}
148 
149 	/**
150 	 * really do the searching.
151 	 */
152 	public void afterPropertiesSet() throws Exception {
153 		if (getAutoDetectEntityPackage() != null) {
154 			ArrayList<Class<?>> classes = new ArrayList<Class<?>>();;
155 			try {
156 				ClassLocator cl = new ClassLocator(getAutoDetectEntityPackage());
157 				for (ClassLocation loc : cl.getAllClassLocations()) {
158 					Class<?> clazz = Class.forName(loc.getClassName());
159 					Entity isEntity = (Entity) clazz.getAnnotation(Entity.class);
160 					if (isEntity != null) {
161 						classes.add(clazz);
162 						if (s_logger.isDebugEnabled()) {
163 							s_logger.debug("Adding entity " + clazz);
164 						}
165 					}
166 				}
167 
168 				s_logger.debug("all detected hibernate entities"+
169 						StringUtils.arrayToCommaDelimitedString(classes.toArray()));
170 
171 				// merge existing classes and new classes
172 				if (localAnnotatedClasses != null) {
173 					classes.addAll(Arrays.asList(localAnnotatedClasses));
174 				}
175 
176 				localAnnotatedClasses = (Class[]) classes.toArray(new Class[classes.size()]);
177 				s_logger.debug("number of classes detected:"+localAnnotatedClasses.length);
178 				super.setAnnotatedClasses(localAnnotatedClasses);
179 
180 			} catch (Exception e) {
181 				s_logger.error(e.toString());
182 				throw new RuntimeException(e);
183 			}
184 
185 
186 		}
187 		super.afterPropertiesSet();
188 	}
189 	
190 	/** {@inheritDoc} */
191 	protected SessionFactory newSessionFactory(Configuration config) throws HibernateException {
192 		if (jpaFullSupportEnabled) {
193 			Ejb3Configuration cfg = new Ejb3Configuration();
194 			// merge the ejb3 event listeners and the configured listeners we get
195 			
196 			try {
197 				Field listeners = LocalSessionFactoryBean.class.getDeclaredField("eventListeners");
198 				listeners.setAccessible(true);
199 				Map<?, ?> listenerMap = (Map<?, ?>) listeners.get(this);
200 				Configuration newConfig = mergeEventListeners(config, cfg.getHibernateConfiguration(), listenerMap);
201 				
202 				Field f = cfg.getClass().getDeclaredField("cfg");
203 				f.setAccessible(true);
204 				f.set(cfg, newConfig);
205 				DefaultPersistenceUnitManager pum = new DefaultPersistenceUnitManager();
206 				pum.setPersistenceXmlLocation("classpath*:META-INF/defaultPersistence.xml");
207 				pum.preparePersistenceUnitInfos();
208 				Ejb3Configuration configured = cfg.configure(
209 					pum.obtainPersistenceUnitInfo("DefaultPersistenceUnit"),
210 					null
211 				);
212 				return configured.getHibernateConfiguration().buildSessionFactory();
213 			} catch (Throwable e) {
214 				s_logger.debug("Could not load SessionFactory with Ejb3Configuration. "
215 						+ "Not all JPA Annotations are available!");
216 				return super.newSessionFactory(config);
217 			}
218 		} else {
219 			return super.newSessionFactory(config);
220 		}
221 	}
222 	
223 	/**
224 	 * Replace the event listeners in the targetConfig with the ones in sourceConfig 
225 	 * and overwrite with the listeners from the listenerMap.
226 	 * @param targetConfig    the configuration to take as base.
227 	 * @param sourceConfig  the configuration to take the event listeners from.
228 	 * @param listenerMap	the Map containing specified event listeners (overwrite other ones).
229 	 * @return  the new configuration with the merged listeners.
230 	 */
231 	private Configuration mergeEventListeners(Configuration targetConfig, Configuration sourceConfig, Map listenerMap) {
232 		
233 		final Object[] readerMethodArgs = new Object[0];
234 		// get the event listeners
235 		EventListeners listenerConfig = targetConfig.getEventListeners();
236 		EventListeners secondListenerConfig = sourceConfig.getEventListeners();
237 
238 		BeanInfo beanInfo = null;
239 		try {
240 			if (!listenerConfig.getClass().equals(secondListenerConfig.getClass())) {
241 				throw new HibernateException("Listeners are not of the same type!");
242 			}
243 			// get all the read methods from the introspector
244 			beanInfo = Introspector.getBeanInfo(listenerConfig.getClass(), Object.class);
245 			PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
246 			try {
247 				int i = 0;
248 				for (int max = pds.length; i < max; i++) {
249 					// read the specific listener arrays
250 					final Object listeners = pds[i].getReadMethod().invoke(secondListenerConfig, readerMethodArgs);
251 					if (listeners == null) {
252 						throw new HibernateException("Listener [" + pds[i].getName() + "] was null");
253 					}
254 					if (listeners instanceof Object[]) {
255 						
256 						// write the new array
257 						pds[i].getWriteMethod().invoke(listenerConfig, listeners);
258 					}
259 				}
260 			} catch (HibernateException e) {
261 				throw e;
262 			} catch (Throwable t) {
263 				throw new HibernateException("Unable to validate listener config", t);
264 			}
265 		} catch (Exception t) {
266 			throw new HibernateException("Unable to copy listeners", t);
267 		} finally {
268 			if (beanInfo != null) {
269 				// release the jdk internal caches everytime to ensure this
270 				// plays nicely with destroyable class-loaders
271 				Introspector.flushFromCaches(getClass());
272 			}
273 		}
274 		if (listenerMap != null) {
275 			// Register specified Hibernate event listeners.
276 			for (Iterator<?> it = listenerMap.entrySet().iterator(); it.hasNext();) {
277 				Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
278 				Assert.isTrue(entry.getKey() instanceof String, "Event listener key needs to be of type String");
279 				String listenerType = (String) entry.getKey();
280 				Object listenerObject = entry.getValue();
281 				if (listenerObject instanceof Collection) {
282 					Collection<?> listeners = (Collection<?>) listenerObject;
283 					EventListeners listenerRegistry = targetConfig.getEventListeners();
284 					Object[] listenerArray = (Object[]) Array.newInstance(
285 						listenerRegistry.getListenerClassFor(listenerType), 
286 						listeners.size());
287 					listenerArray = listeners.toArray(listenerArray);
288 					targetConfig.setListeners(listenerType, listenerArray);
289 				} else {
290 					targetConfig.setListener(listenerType, listenerObject);
291 				}
292 			}
293 		}
294 		return targetConfig;
295 	}
296 	
297 }