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.jpa.dao;
19  
20  import java.io.Serializable;
21  import java.lang.reflect.ParameterizedType;
22  import java.util.Collection;
23  import java.util.List;
24  
25  import javax.persistence.EntityManager;
26  import javax.persistence.PersistenceException;
27  import javax.persistence.PersistenceUnitUtil;
28  import javax.persistence.criteria.CriteriaQuery;
29  
30  import org.apache.commons.collections.map.ReferenceMap;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.springframework.beans.factory.InitializingBean;
34  import org.springframework.dao.DataAccessException;
35  import org.springframework.dao.DataIntegrityViolationException;
36  import org.springframework.dao.DataRetrievalFailureException;
37  import org.springframework.dao.OptimisticLockingFailureException;
38  import org.springframework.orm.jpa.JpaCallback;
39  import org.springframework.transaction.annotation.Propagation;
40  import org.springframework.transaction.annotation.Transactional;
41  
42  import ch.elca.el4j.services.persistence.generic.dao.annotations.ReturnsUnchangedParameter;
43  import ch.elca.el4j.services.persistence.hibernate.dao.extent.DataExtent;
44  import ch.elca.el4j.services.persistence.hibernate.dao.extent.ExtentEntity;
45  import ch.elca.el4j.services.persistence.jpa.criteria.QueryBuilder;
46  import ch.elca.el4j.services.persistence.jpa.dao.extentstrategies.ExtentFetcher;
47  import ch.elca.el4j.util.codingsupport.Reject;
48  
49  /**
50   * This class is a JPA-specific implementation of the
51   * ConvenienceGenericDao interface.
52   *
53   * @svnLink $Revision: 4253 $;$Date: 2010-12-21 11:08:04 +0100 (Di, 21. Dez 2010) $;$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/jpa/dao/GenericJpaDao.java $
54   *
55   * @param <T>
56   *            The domain class the DAO is responsible for
57   * @param <ID>
58   *            The type of the domain class' identifier
59   *
60   * @author Simon Stelling (SST)
61   */
62  public class GenericJpaDao<T, ID extends Serializable>
63  	extends ConvenienceJpaDaoSupport
64  	implements ConvenienceGenericJpaDao<T, ID>, InitializingBean {
65  	
66  	/**
67  	 * The logger.
68  	 */
69  	private static Logger s_logger = LoggerFactory.getLogger(GenericJpaDao.class);
70  	
71  	/**
72  	 * The domain class this DAO is responsible for.
73  	 */
74  	private Class<T> persistentClass;
75  	
76  	/**
77  	 * The ExtentFetcher used to fetch extents. 
78  	 * Injected by JpaExtentFetcherInjectorBeanPostprocessor.
79  	 */
80  	private ExtentFetcher extentFetcher;
81  
82  	/**
83  	 * Set up the Generic Dao. Auto-derive the parametrized type.
84  	 */
85  	@SuppressWarnings("unchecked")
86  	public GenericJpaDao() {
87  		try {
88  			this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
89  					.getGenericSuperclass()).getActualTypeArguments()[0];
90  			// Checkstyle: EmptyBlock off
91  		} catch (Exception e) {
92  			// ignore issues (e.g. when the subclass is not a parametrized type)
93  			// in that case, one needs to set the persistencClass otherwise.
94  			// Checkstyle: EmptyBlock on
95  		}
96  	}
97  	
98  	/**
99  	 * New: this callback is in general no longer required (the constructor
100 	 *  figures the type out itself).
101 	 *
102 	 * @param c
103 	 *           Mandatory. The domain class this DAO is responsible for.
104 	 */
105 	public void setPersistentClass(Class<T> c) {
106 		Reject.ifNull(c);
107 		persistentClass = c;
108 	}
109 
110 	/**
111 	 * @return Returns the domain class this DAO is responsible for.
112 	 */
113 	public Class<T> getPersistentClass() {
114 		assert persistentClass != null;
115 		return persistentClass;
116 	}
117 	
118 	/**
119 	 * {@inheritDoc}
120 	 */
121 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
122 	public T findById(ID id)
123 		throws DataAccessException, DataRetrievalFailureException {
124 		return (T) getConvenienceJpaTemplate().findByIdStrong(
125 			getPersistentClass(), id, getPersistentClassName());
126 	}
127 	
128 	/**
129 	 * {@inheritDoc}
130 	 */
131 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
132 	public T findByIdLazy(ID id)
133 		throws DataAccessException, DataRetrievalFailureException {
134 		return (T) getConvenienceJpaTemplate().findByIdStrongLazy(
135 			getPersistentClass(), id, getPersistentClassName());
136 	}
137 
138 	/**
139 	 * {@inheritDoc}
140 	 */
141 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
142 	public List<T> getAll() throws DataAccessException {
143 		return getConvenienceJpaTemplate().findByCriteria(getOrderedCriteria());
144 	}
145 	
146 	/** {@inheritDoc} */
147 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
148 	public List<T> findByQuery(final QueryBuilder criteria)
149 		throws DataAccessException {
150 		
151 		ConvenienceJpaTemplate template = getConvenienceJpaTemplate();
152 		
153 		return template.execute(new JpaCallback<List<T>>() {
154 
155 			@Override
156 			public List<T> doInJpa(EntityManager em) throws PersistenceException {
157 				return criteria.applySelect(em).getResultList(persistentClass);
158 			}
159 			
160 		});
161 	}
162 	
163 	/** {@inheritDoc} */
164 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
165 	public List<T> findByQuery(final QueryBuilder criteria, final int firstResult, final int maxResults)
166 		throws DataAccessException {
167 		
168 		ConvenienceJpaTemplate template = getConvenienceJpaTemplate();
169 		
170 		return template.execute(new JpaCallback<List<T>>() {
171 
172 			@Override
173 			public List<T> doInJpa(EntityManager em) throws PersistenceException {
174 				return criteria.applySelect(em).getResultList(persistentClass, firstResult, maxResults);
175 			}
176 			
177 		});
178 	}
179 
180 	/** {@inheritDoc} */
181 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
182 	public int findCountByQuery(final QueryBuilder criteria)
183 		throws DataAccessException {
184 		
185 		ConvenienceJpaTemplate template = getConvenienceJpaTemplate();
186 
187 		return template.execute(new JpaCallback<Integer>() {
188 
189 			@Override
190 			public Integer doInJpa(EntityManager em) throws PersistenceException {
191 				return criteria.applyCount(em).getCount();
192 			}
193 			
194 		});
195 	}
196 	
197 	/** {@inheritDoc} */
198 	@SuppressWarnings("unchecked")
199 	@ReturnsUnchangedParameter
200 	@Transactional(propagation = Propagation.REQUIRED)
201 	public T merge(T entity) throws DataAccessException,
202 		DataIntegrityViolationException, OptimisticLockingFailureException {
203 		
204 		return (T) getConvenienceJpaTemplate().mergeStrong(entity, getPersistentClassName());
205 	}
206 	
207 	/**
208 	 * {@inheritDoc}
209 	 */
210 	@ReturnsUnchangedParameter
211 	@Transactional(propagation = Propagation.REQUIRED)
212 	public T persist(T entity) throws DataAccessException,
213 		DataIntegrityViolationException, OptimisticLockingFailureException {
214 		
215 		getConvenienceJpaTemplate().persist(entity);
216 		return entity;
217 	}
218 	
219 	/**
220 	 * Deprecated: Use merge instead.
221 	 * 
222 	 * Note: this method is NOT equivalent to Hibernate's <code>saveOrUpdate</code> but to
223 	 * <code>saveOrUpdateCopy</code>, i.e. you need to use the return value:
224 	 * 
225 	 * <pre>
226 	 * dom = dao.saveOrUpdate(dom);
227 	 * </pre>
228 	 */
229 	@Deprecated
230 	public T saveOrUpdate(T entity) {
231 		return merge(entity);
232 	}
233 	
234 	/** {@inheritDoc} */
235 	@Deprecated
236 	@ReturnsUnchangedParameter
237 	@Transactional(propagation = Propagation.REQUIRED)
238 	public T saveOrUpdateAndFlush(T entity) throws DataAccessException,
239 		DataIntegrityViolationException, OptimisticLockingFailureException {
240 		
241 		T tmp = saveOrUpdate(entity);
242 		flush();
243 		return tmp;
244 	}
245 
246 	/** {@inheritDoc} */
247 	@Transactional(propagation = Propagation.REQUIRED)
248 	public void delete(T entity) throws DataAccessException {
249 		T e = entity;
250 		getConvenienceJpaTemplate().remove(e);
251 	}
252 	
253 	/** {@inheritDoc} */
254 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
255 	public T refresh(T entity) throws DataAccessException,
256 	DataRetrievalFailureException {
257 		T e = entity;
258 		getConvenienceJpaTemplate().refresh(e);
259 		return e;
260 	}
261 		
262 	/** {@inheritDoc} */
263 	@Deprecated
264 	@Transactional(propagation = Propagation.REQUIRED)
265 	public void delete(ID id) throws DataAccessException {
266 		getConvenienceJpaTemplate().removeStrong(getPersistentClass(),
267 			id, getPersistentClassName());
268 	}
269 	
270 	/** {@inheritDoc} */
271 	@Transactional(propagation = Propagation.REQUIRED)
272 	public void deleteById(ID id) throws DataAccessException {
273 		getConvenienceJpaTemplate().removeStrong(getPersistentClass(),
274 			id, getPersistentClassName());
275 	}
276 	
277 	/** {@inheritDoc} */
278 	@Transactional(propagation = Propagation.REQUIRED)
279 	public void delete(Collection<T> entities) throws DataAccessException,
280 			DataIntegrityViolationException, OptimisticLockingFailureException {
281 		getConvenienceJpaTemplate().removeAll(entities);
282 	}
283 
284 	/** {@inheritDoc} */
285 	@Transactional(propagation = Propagation.REQUIRED)
286 	public void deleteAll()
287 		throws OptimisticLockingFailureException, DataAccessException {
288 		List<T> list = getAll();
289 		if (list.size() > 0) {
290 			delete(list);
291 		}
292 	}
293 	
294 	/** {@inheritDoc} */
295 	@Transactional(propagation = Propagation.REQUIRED)
296 	public void flush() {
297 		getConvenienceJpaTemplate().flush();
298 	}
299 	
300 	/** {@inheritDoc} */
301 	public CriteriaQuery<T> getOrderedCriteria() {
302 		CriteriaQuery<T> criteria 
303 			= getJpaTemplate().execute(new JpaCallback<CriteriaQuery<T>>() {
304 
305 				@Override
306 				public CriteriaQuery<T> doInJpa(EntityManager em) throws PersistenceException {
307 					CriteriaQuery<T> criteria = em.getCriteriaBuilder().createQuery(persistentClass);
308 					criteria.from(persistentClass);
309 					return criteria;
310 				}
311 			});
312 		
313 		return makeDistinct(criteria);
314 	}
315 
316 	/**
317 	 * Returns the simple name of the persistent class this DAO is responsible
318 	 * for.
319 	 *
320 	 * @return The simple name of the persistent class this DAO is responsible
321 	 *         for.
322 	 */
323 	protected String getPersistentClassName() {
324 		return getPersistentClass().getSimpleName();
325 	}
326 	
327 	/**
328 	 * @param criteria    the criteria to modify
329 	 * @return            the criteria enhanced with distinct restrictions
330 	 */
331 	protected CriteriaQuery<T> makeDistinct(CriteriaQuery<T> criteria) {
332 		return criteria.distinct(true);
333 	}
334 	
335 	/**
336 	 * {@inheritDoc}
337 	 */
338 	@SuppressWarnings("unchecked")
339 	@Override
340 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
341 	public T reload(T entity) throws DataAccessException, DataRetrievalFailureException {
342 		PersistenceUnitUtil util = getConvenienceJpaTemplate().getEntityManagerFactory().getPersistenceUnitUtil();
343 		return findById((ID) util.getIdentifier(entity));
344 	}
345 
346 	// extent-related methods:
347 	
348 	/**
349 	 * @return Returns the extentFetcher.
350 	 */
351 	public ExtentFetcher getExtentFetcher() {
352 		return extentFetcher;
353 	}
354 
355 	/**
356 	 * @param extentFetcher Is the extentFetcher to set.
357 	 */
358 	public void setExtentFetcher(ExtentFetcher extentFetcher) {
359 		this.extentFetcher = extentFetcher;
360 	}
361 	
362 	/** 
363 	 * Prototype of Extent-based fetching,
364 	 * steps through all the retrieved objects and calls
365 	 * the methods of the extent to ensure loading from db.
366 	 * 
367 	 * @param objects	list of objects to load in given extent
368 	 * @param extent	the fetch-extent
369 	 * @return returns the new list of objects.
370 	 * 
371 	 * @throws DataAccessException 
372 	 */
373 	protected List<T> fetchExtent(List<T> objects, DataExtent extent)
374 		throws DataAccessException {
375 		
376 		if (extent != null) {
377 			ReferenceMap fetchedObjects = new ReferenceMap();
378 			for (Object obj : objects) {
379 				fetchExtentObject(obj, extent.getRootEntity(), fetchedObjects);
380 			}
381 		}
382 		return objects;
383 	}
384 	
385 	/** 
386 	 * Prototype of Extent-based fetching,
387 	 * steps through all the retrieved objects and calls
388 	 * the methods of the extent to ensure loading from db.
389 	 * 
390 	 * @param object	object to load in given extent
391 	 * @param extent	the fetch-extent
392 	 * @return returns the new object.
393 	 * 
394 	 * @throws DataAccessException 
395 	 */
396 	protected T fetchExtent(T object, DataExtent extent)
397 		throws DataAccessException {
398 		
399 		if (extent != null) {
400 			ReferenceMap fetchedObjects = new ReferenceMap();
401 			fetchExtentObject(object, extent.getRootEntity(), fetchedObjects);
402 		}
403 		return object;
404 	}
405 	
406 	/**
407 	 * Sub-method of the extent-based fetching, steps
408 	 * through the entities and calls the required methods.
409 	 * <p>
410 	 * @param object			the object to load in given extent
411 	 * @param entity			the extent entity
412 	 * @param fetchedObjects	the HashMap with all the already fetched objects
413 	 * 
414 	 * @throws DataAccessException
415 	 */
416 	private void fetchExtentObject(Object object, ExtentEntity entity, ReferenceMap fetchedObjects)
417 		throws DataAccessException {
418 		s_logger.debug("using extent-fetcher " + extentFetcher.getClass());
419 		extentFetcher.fetchExtentObject(object, entity, fetchedObjects);
420 	}
421 
422 	/** {@inheritDoc} */
423 	@Override
424 	public List<T> findByQuery(QueryBuilder criteria, DataExtent extent) throws DataAccessException {
425 		return fetchExtent(findByQuery(criteria), extent);
426 	}
427 
428 	/** {@inheritDoc} */
429 	@Override
430 	public List<T> findByQuery(QueryBuilder criteria, int firstResult, int maxResults, DataExtent extent)
431 		throws DataAccessException {
432 		return fetchExtent(findByQuery(criteria, firstResult, maxResults), extent);
433 	}
434 
435 	/** {@inheritDoc} */
436 	@Override
437 	public T findById(ID id, DataExtent extent) throws DataRetrievalFailureException, DataAccessException {
438 		return fetchExtent((T) getConvenienceJpaTemplate().findByIdStrong(
439 			getPersistentClass(), id, getPersistentClassName()), extent);
440 	}
441 
442 	/** {@inheritDoc} */
443 	@Override
444 	public List<T> getAll(DataExtent extent) throws DataAccessException {
445 		return fetchExtent(getConvenienceJpaTemplate().findByCriteria(getOrderedCriteria()), extent);
446 	}
447 
448 	/** {@inheritDoc} */
449 	@Override
450 	public T refresh(T entity, DataExtent extent) throws DataAccessException, DataRetrievalFailureException {
451 		getConvenienceJpaTemplate().refresh(entity);
452 		return fetchExtent(entity, extent);
453 	}
454 
455 	/** {@inheritDoc} */
456 	@Override
457 	public T reload(T entity, DataExtent extent) throws DataAccessException, DataRetrievalFailureException {
458 		return fetchExtent(reload(entity), extent);
459 	}
460 
461 }