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.hibernate.dao;
19  
20  import java.io.Serializable;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.ParameterizedType;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  import org.apache.commons.collections.map.ReferenceMap;
30  import org.hibernate.Criteria;
31  import org.hibernate.EntityMode;
32  import org.hibernate.FetchMode;
33  import org.hibernate.Hibernate;
34  import org.hibernate.HibernateException;
35  import org.hibernate.LockMode;
36  import org.hibernate.SessionFactory;
37  import org.hibernate.TransientObjectException;
38  import org.hibernate.criterion.CriteriaSpecification;
39  import org.hibernate.criterion.DetachedCriteria;
40  import org.hibernate.criterion.Order;
41  import org.hibernate.criterion.ProjectionList;
42  import org.hibernate.criterion.Projections;
43  import org.hibernate.criterion.Restrictions;
44  import org.hibernate.metadata.ClassMetadata;
45  import org.hibernate.metadata.CollectionMetadata;
46  import org.hibernate.proxy.HibernateProxy;
47  import org.hibernate.type.CollectionType;
48  import org.hibernate.type.EntityType;
49  import org.hibernate.type.Type;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  import org.springframework.beans.factory.InitializingBean;
53  import org.springframework.dao.DataAccessException;
54  import org.springframework.dao.DataIntegrityViolationException;
55  import org.springframework.dao.DataRetrievalFailureException;
56  import org.springframework.dao.OptimisticLockingFailureException;
57  import org.springframework.transaction.annotation.Propagation;
58  import org.springframework.transaction.annotation.Transactional;
59  
60  import ch.elca.el4j.services.persistence.generic.dao.annotations.ReturnsUnchangedParameter;
61  import ch.elca.el4j.services.persistence.hibernate.criteria.CriteriaTransformer;
62  import ch.elca.el4j.services.persistence.hibernate.dao.extent.DataExtent;
63  import ch.elca.el4j.services.persistence.hibernate.dao.extent.ExtentCollection;
64  import ch.elca.el4j.services.persistence.hibernate.dao.extent.ExtentEntity;
65  import ch.elca.el4j.services.search.QueryObject;
66  import ch.elca.el4j.util.codingsupport.Reject;
67  
68  /**
69   * This class is a Hibernate-specific implementation of the
70   * ConvenienceGenericDao interface.
71   *
72   * @svnLink $Revision: 4173 $;$Date: 2010-09-20 15:55:50 +0200 (Mo, 20. Sep 2010) $;$Author: sstelca $;$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/dao/GenericHibernateDao.java $
73   *
74   * @param <T>
75   *            The domain class the DAO is responsible for
76   * @param <ID>
77   *            The type of the domain class' identifier
78   *
79   * @author Philipp Oser (POS)
80   * @author Alex Mathey (AMA)
81   * @author Jonas Hauenstein (JHN)
82   */
83  public class GenericHibernateDao<T, ID extends Serializable>
84  	extends ConvenienceHibernateDaoSupport
85  	implements ConvenienceGenericHibernateDao<T, ID>, InitializingBean {
86  		
87  	/**
88  	 * Maximal number of entities which are deleted
89  	 * with a single HQL statement.
90  	 */
91  	private static final int MAX_BULK_DELETE = 100;
92  
93  	/**
94  	 * The logger.
95  	 */
96  	private static Logger s_logger = LoggerFactory.getLogger(GenericHibernateDao.class);
97  	
98  	/**
99  	 * The domain class this DAO is responsible for.
100 	 */
101 	private Class<T> m_persistentClass;
102 	
103 	/**
104 	 * The default hibernate {@link Order} to order results.
105 	 */
106 	private Order[] m_defaultOrder = null;
107 	
108 	/**
109 	 * Set up the Generic Dao. Auto-derive the parametrized type.
110 	 */
111 	@SuppressWarnings("unchecked")
112 	public GenericHibernateDao() {
113 		try {
114 			this.m_persistentClass = (Class<T>) ((ParameterizedType) getClass()
115 					.getGenericSuperclass()).getActualTypeArguments()[0];
116 		} catch (Exception e) {
117 			// ignore issues (e.g. when the subclass is not a parametrized type)
118 			// in that case, one needs to set the persistencClass otherwise.
119 		}
120 	}
121 	
122 	/**
123 	 * New: this callback is in general no longer required (the constructor
124 	 *  figures the type out itself).
125 	 *
126 	 * @param c
127 	 *           Mandatory. The domain class this DAO is responsible for.
128 	 */
129 	public void setPersistentClass(Class<T> c) {
130 		Reject.ifNull(c);
131 		m_persistentClass = c;
132 	}
133 
134 	/**
135 	 * @return Returns the domain class this DAO is responsible for.
136 	 */
137 	public Class<T> getPersistentClass() {
138 		assert m_persistentClass != null;
139 		return m_persistentClass;
140 	}
141 	
142 	/** {@inheritDoc} */
143 	public Order[] getDefaultOrder() {
144 		return m_defaultOrder;
145 	}
146 	
147 	/** {@inheritDoc} */
148 	public void setDefaultOrder(Order... defaultOrder) {
149 		m_defaultOrder = defaultOrder;
150 	}
151 
152 	/**
153 	 * Retrieves a domain object by identifier, optionally obtaining a database
154 	 * lock for this operation.  <br>
155 	 *
156 	 * (For hibernate specialists: we do a "get()"
157 	 * in this method. In case you require only a "load()" (e.g. for lazy
158 	 * loading to work) we recommend that you write your own find method in the
159 	 * interface's subclass.)
160 	 *
161 	 * @param id
162 	 *            The id of a domain object
163 	 * @param lock
164 	 *            Indicates whether a database lock should be obtained for this
165 	 *            operation
166 	 * @throws DataAccessException
167 	 *             If general data access problem occurred
168 	 * @throws DataRetrievalFailureException
169 	 *             If domain object could not be retrieved
170 	 * @return The desired domain object
171 	 */
172 	@SuppressWarnings("unchecked")
173 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
174 	public T findById(ID id, boolean lock)
175 		throws DataAccessException, DataRetrievalFailureException {
176 		
177 		T entity;
178 		if (lock) {
179 			entity = (T) getConvenienceHibernateTemplate().get(getPersistentClass(), id, LockMode.UPGRADE);
180 		} else {
181 			entity = (T) getConvenienceHibernateTemplate().get(getPersistentClass(), id);
182 		}
183 		if (entity == null) {
184 			throw new DataRetrievalFailureException("The desired domain object could not be retrieved.");
185 		}
186 		return entity;
187 	}
188 	
189 	/**
190 	 * {@inheritDoc}
191 	 */
192 	@SuppressWarnings("unchecked")
193 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
194 	public T findById(ID id, boolean lock, DataExtent extent)
195 		throws DataAccessException, DataRetrievalFailureException {
196 		
197 		T entity;
198 		if (lock) {
199 			entity = (T) getConvenienceHibernateTemplate().get(getPersistentClass(), id, LockMode.UPGRADE);
200 		} else {
201 			entity = (T) getConvenienceHibernateTemplate().get(getPersistentClass(), id);
202 		}
203 		if (entity == null) {
204 			throw new DataRetrievalFailureException("The desired domain object could not be retrieved.");
205 		}
206 		return fetchExtent(entity, extent);
207 	}
208 	
209 	/**
210 	 * {@inheritDoc}
211 	 */
212 	@SuppressWarnings("unchecked")
213 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
214 	public T findById(ID id)
215 		throws DataAccessException, DataRetrievalFailureException {
216 		return (T) getConvenienceHibernateTemplate().getByIdStrong(
217 			getPersistentClass(), id, getPersistentClassName());
218 	}
219 
220 	/**
221 	 * {@inheritDoc}
222 	 */
223 	@SuppressWarnings("unchecked")
224 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
225 	public T findById(ID id, DataExtent extent)
226 		throws DataAccessException, DataRetrievalFailureException {
227 		return fetchExtent((T) getConvenienceHibernateTemplate().getByIdStrong(
228 			getPersistentClass(), id, getPersistentClassName()), extent);
229 	}
230 	
231 	/**
232 	 * {@inheritDoc}
233 	 */
234 	@SuppressWarnings("unchecked")
235 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
236 	public T findByIdLazy(ID id)
237 		throws DataAccessException, DataRetrievalFailureException {
238 		return (T) getConvenienceHibernateTemplate().getByIdStrongLazy(
239 			getPersistentClass(), id, getPersistentClassName());
240 	}
241 
242 	/**
243 	 * {@inheritDoc}
244 	 */
245 	@SuppressWarnings("unchecked")
246 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
247 	public List<T> getAll() throws DataAccessException {
248 		return getConvenienceHibernateTemplate().findByCriteria(getOrderedCriteria());
249 	}
250 	
251 	/**
252 	 * {@inheritDoc}
253 	 */
254 	@SuppressWarnings("unchecked")
255 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
256 	public List<T> getAll(DataExtent extent) throws DataAccessException {
257 		return fetchExtent(getConvenienceHibernateTemplate().findByCriteria(getOrderedCriteria()), extent);
258 	}
259 
260 	
261 	/**
262 	 * {@inheritDoc}
263 	 *
264 	 * This method supports paging (see QueryObject for info on
265 	 *  how to use this).
266 	 *
267 	 */
268 	@SuppressWarnings("unchecked")
269 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
270 	public List<T> findByQuery(QueryObject q) throws DataAccessException {
271 		DetachedCriteria hibernateCriteria = getCriteria(q);
272 		
273 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
274 		
275 		return template.findByCriteria(hibernateCriteria, q.getFirstResult(), q.getMaxResults());
276 	}
277 	
278 	/**
279 	 * {@inheritDoc}
280 	 *
281 	 * This method supports paging (see QueryObject for info on
282 	 *  how to use this).
283 	 *
284 	 */
285 	@SuppressWarnings("unchecked")
286 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
287 	public List<T> findByQuery(QueryObject q, DataExtent extent) throws DataAccessException {
288 		DetachedCriteria hibernateCriteria = getCriteria(q);
289 		
290 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
291 		
292 		return fetchExtent(template.findByCriteria(hibernateCriteria, q.getFirstResult(), 
293 			q.getMaxResults()), extent);
294 	}
295 
296 	/**
297 	 * {@inheritDoc}
298 	 *
299 	 * This method supports paging (see QueryObject for info on
300 	 *  how to use this).
301 	 *
302 	 * @return how many elements do we find with the given query
303 	 */
304 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
305 	public int findCountByQuery(QueryObject q) throws DataAccessException {
306 		DetachedCriteria hibernateCriteria = getCriteria(q);
307 		
308 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
309 
310 		return template.findCountByCriteria(hibernateCriteria);
311 	}
312 	
313 	/** {@inheritDoc} */
314 	@SuppressWarnings("unchecked")
315 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
316 	public List<T> findByCriteria(DetachedCriteria hibernateCriteria)
317 		throws DataAccessException {
318 		
319 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
320 		
321 		return template.findByCriteria(hibernateCriteria);
322 	}
323 	
324 	/** {@inheritDoc} */
325 	@SuppressWarnings("unchecked")
326 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
327 	public List<T> findByCriteria(DetachedCriteria hibernateCriteria, DataExtent extent)
328 		throws DataAccessException {
329 		
330 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
331 		
332 		return fetchExtent(template.findByCriteria(hibernateCriteria), extent);
333 	}
334 	
335 	/** {@inheritDoc} */
336 	@SuppressWarnings("unchecked")
337 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
338 	public List<T> findByCriteria(DetachedCriteria hibernateCriteria, int firstResult, int maxResults)
339 		throws DataAccessException {
340 		
341 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
342 		
343 		return template.findByCriteria(hibernateCriteria, firstResult, maxResults);
344 	}
345 
346 	/** {@inheritDoc} */
347 	@SuppressWarnings("unchecked")
348 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
349 	public List<T> findByCriteria(DetachedCriteria hibernateCriteria, int firstResult, int maxResults,
350 			DataExtent extent) throws DataAccessException {
351 		
352 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
353 		
354 		return fetchExtent(template.findByCriteria(hibernateCriteria, firstResult, maxResults), extent);
355 	}
356 	
357 	/** {@inheritDoc} */
358 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
359 	public int findCountByCriteria(DetachedCriteria hibernateCriteria)
360 		throws DataAccessException {
361 		
362 		ConvenienceHibernateTemplate template = getConvenienceHibernateTemplate();
363 
364 		return template.findCountByCriteria(hibernateCriteria);
365 	}
366 	
367 	/** {@inheritDoc} */
368 	@ReturnsUnchangedParameter
369 	@Transactional(propagation = Propagation.REQUIRED)
370 	public T saveOrUpdate(T entity) throws DataAccessException,
371 		DataIntegrityViolationException, OptimisticLockingFailureException {
372 		
373 		getConvenienceHibernateTemplate().saveOrUpdateStrong(entity, getPersistentClassName());
374 		return entity;
375 	}
376 	
377 	
378 	/** {@inheritDoc} */
379 	@ReturnsUnchangedParameter
380 	@Transactional(propagation = Propagation.REQUIRED)
381 	public T saveOrUpdateAndFlush(T entity) throws DataAccessException,
382 		DataIntegrityViolationException, OptimisticLockingFailureException {
383 		
384 		T tmp = saveOrUpdate(entity);
385 		flush();
386 		return tmp;
387 	}
388 
389 	/** {@inheritDoc} */
390 	@Transactional(propagation = Propagation.REQUIRED)
391 	public void delete(T entity) throws DataAccessException {
392 		getConvenienceHibernateTemplate().delete(entity);
393 	}
394 	
395 	/** {@inheritDoc} */
396 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
397 	public T refresh(T entity) throws DataAccessException,
398 	DataRetrievalFailureException {
399 		getConvenienceHibernateTemplate().refresh(entity);
400 		return entity;
401 	}
402 	
403 	/** {@inheritDoc} */
404 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
405 	public T refresh(T entity, DataExtent extent) throws DataAccessException,
406 	DataRetrievalFailureException {
407 		getConvenienceHibernateTemplate().refresh(entity);
408 		return fetchExtent(entity, extent);
409 	}
410 	
411 	/** {@inheritDoc} */
412 	@SuppressWarnings("unchecked")
413 	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
414 	public T reload(T entity) throws DataAccessException,
415 		DataRetrievalFailureException {
416 		ID id = (ID) getSessionFactory().getClassMetadata(entity.getClass())
417 			.getIdentifier(entity, EntityMode.POJO);
418 		return findById(id);
419 	}
420 	
421 	/** {@inheritDoc} */
422 	@SuppressWarnings("unchecked")
423 	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
424 	public T reload(T entity, DataExtent extent) throws DataAccessException,
425 		DataRetrievalFailureException {
426 		ID id = (ID) getSessionFactory().getClassMetadata(entity.getClass())
427 			.getIdentifier(entity, EntityMode.POJO);
428 		T refresh = (T) findById(id, extent);
429 		return refresh;
430 
431 	}
432 	
433 	/** {@inheritDoc} */
434 	@Deprecated
435 	@Transactional(propagation = Propagation.REQUIRED)
436 	public void delete(ID id) throws DataAccessException {
437 		getConvenienceHibernateTemplate().deleteStrong(getPersistentClass(),
438 			id, getPersistentClassName());
439 	}
440 	
441 	/** {@inheritDoc} */
442 	@Transactional(propagation = Propagation.REQUIRED)
443 	public void deleteById(ID id) throws DataAccessException {
444 		getConvenienceHibernateTemplate().deleteStrong(getPersistentClass(),
445 			id, getPersistentClassName());
446 	}
447 	
448 	/** {@inheritDoc} */
449 	public void delete(Collection<T> entities) throws DataAccessException,
450 			DataIntegrityViolationException, OptimisticLockingFailureException {
451 		getConvenienceHibernateTemplate().deleteAll(entities);
452 	}
453 
454 	/** {@inheritDoc} */
455 	public void deleteNoCascade(Collection<T> entities) throws DataAccessException,
456 			DataIntegrityViolationException, OptimisticLockingFailureException {	
457 
458 		//search for method with @Id annotation
459 		Method[] methods = getPersistentClass().getMethods();
460 		Method idMethod = null;
461 		for (Method m : methods) {
462 			if (m.isAnnotationPresent(javax.persistence.Id.class)) {
463 				idMethod = m;
464 			}
465 		}
466 		
467 		if (idMethod != null) {
468 			
469 			ArrayList<Object> hqlParameter = new ArrayList<Object>();
470 			StringBuilder hqlQuery = new StringBuilder("delete ");
471 			hqlQuery.append(getPersistentClassName());
472 			hqlQuery.append(" where ");
473 			
474 			Iterator<T> it = entities.iterator();
475 			T entity;
476 			boolean fallback;
477 			int querycount = 0;
478 			//creating hql bulk delete statement for all entities
479 			//by calling the annotated method to get @Id value
480 			while (it.hasNext()) {
481 				fallback = false;
482 				entity = it.next();
483 				Object o = null;
484 				try {
485 					o = idMethod.invoke(entity);
486 				} catch (IllegalArgumentException e) {
487 					fallback = true;
488 				} catch (IllegalAccessException e) {
489 					fallback = true;
490 				} catch (InvocationTargetException e) {
491 					fallback = true;
492 				}
493 				if (o == null) {
494 					fallback = true;
495 				}
496 				//if we encountered an error, use the given delete method for this entity object
497 				if (!fallback) {
498 					
499 					//check if maximal query length is reached
500 					if (querycount >= MAX_BULK_DELETE) {
501 						//execute present query
502 						getHibernateTemplate().bulkUpdate(hqlQuery.toString(), hqlParameter.toArray());
503 						//reinitialize new vars
504 						hqlParameter.clear();
505 						hqlQuery = new StringBuilder("delete ");
506 						hqlQuery.append(getPersistentClassName());
507 						hqlQuery.append(" where ");
508 						querycount = 0;
509 					}
510 					if (querycount > 0) {
511 						hqlQuery.append("or ");
512 					}
513 					hqlQuery.append("id = ? ");
514 					hqlParameter.add(o);
515 					querycount++;
516 				} else {
517 					//something went wrong (in the reflective method call)
518 					//use the given delete method to delete this entity
519 					s_logger.warn(idMethod.getName() + "could not be called in " + getPersistentClassName()
520 						+ ". Not using HQL bulk delete for this entity.");
521 					getConvenienceHibernateTemplate().delete(entity);
522 				}
523 			}
524 			
525 			//check if there is (still) something to do
526 			if (querycount > 0) {
527 				getHibernateTemplate().bulkUpdate(hqlQuery.toString(), hqlParameter.toArray());
528 			}
529 			
530 		} else {
531 			//if no method with @Id annotation found, delete
532 			//all entities with the given delete method
533 			s_logger.warn("No @Id annotation was found in " + getPersistentClassName()
534 				+ ". Not using HQL bulk delete for all entities.");
535 			getConvenienceHibernateTemplate().deleteAll(entities);
536 		}
537 	}
538 	
539 
540 	/** {@inheritDoc} */
541 	public void deleteAll()
542 		throws OptimisticLockingFailureException, DataAccessException {
543 		List<T> list = getAll();
544 		if (list.size() > 0) {
545 			delete(list);
546 		}
547 	}
548 	
549 
550 	/** {@inheritDoc} */
551 	public void deleteAllNoCascade()
552 		throws OptimisticLockingFailureException, DataAccessException {
553 		
554 		String hqlQuery = "delete " + getPersistentClassName();
555 		getHibernateTemplate().bulkUpdate(hqlQuery);
556 	}
557 	
558 	/** {@inheritDoc} */
559 	@Transactional(propagation = Propagation.REQUIRED)
560 	public void flush() {
561 		getConvenienceHibernateTemplate().flush();
562 	}
563 	
564 	/** {@inheritDoc} */
565 	public DetachedCriteria getOrderedCriteria() {
566 		DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentClass());
567 		
568 		return addOrder(makeDistinct(criteria));
569 	}
570 
571 	/** 
572 	 * Prototype of Extent-based fetching,
573 	 * steps through all the retrieved objects and calls
574 	 * the methods of the extent to ensure loading from db.
575 	 * 
576 	 * @param objects	list of objects to load in given extent
577 	 * @param extent	the fetch-extent
578 	 * @return returns the new list of objects.
579 	 * 
580 	 * @throws DataAccessException 
581 	 */
582 	protected List<T> fetchExtent(List<T> objects, DataExtent extent)
583 		throws DataAccessException {
584 		
585 		if (extent != null) {
586 			ReferenceMap fetchedObjects = new ReferenceMap();
587 			for (Object obj : objects) {
588 				fetchExtentObject(obj, extent.getRootEntity(), fetchedObjects);
589 			}
590 		}
591 		return objects;
592 	}
593 	
594 	/** 
595 	 * Prototype of Extent-based fetching,
596 	 * steps through all the retrieved objects and calls
597 	 * the methods of the extent to ensure loading from db.
598 	 * 
599 	 * @param object	object to load in given extent
600 	 * @param extent	the fetch-extent
601 	 * @return returns the new object.
602 	 * 
603 	 * @throws DataAccessException 
604 	 */
605 	protected T fetchExtent(T object, DataExtent extent)
606 		throws DataAccessException {
607 		
608 		if (extent != null) {
609 			ReferenceMap fetchedObjects = new ReferenceMap();
610 			fetchExtentObject(object, extent.getRootEntity(), fetchedObjects);
611 		}
612 		return object;
613 	}
614 	
615 	/**
616 	 * Sub-method of the extent-based fetching, steps
617 	 * through the entities and calls the required methods.
618 	 * @param object			the object to load in given extent
619 	 * @param entity			the extent entity
620 	 * @param fetchedObjects	the HashMap with all the already fetched objects
621 	 * 
622 	 * @throws DataAccessException
623 	 */
624 	private void fetchExtentObject(Object object, ExtentEntity entity, ReferenceMap fetchedObjects)
625 		throws DataAccessException {
626 		
627 		Object[] nullArg = null;
628 		if (object == null || entity == null || fetchedObjects == null) {
629 			return;
630 		}
631 		fetchedObjects.put(object, entity);
632 		try {
633 			// fetch the majority of all data using join queries
634 			if (entity.isRoot()) {
635 				fetchExtentUsingJoinQuery(object, entity);
636 			}
637 			
638 			// we still need to fetch individual fields, e.g. if transient getters (included in the DataExtent)
639 			// access some fields (that were not included in the DataExtent)
640 			for (ExtentEntity ent : entity.getChildEntities()) {
641 				Object obj = ent.getMethod().invoke(object, nullArg);
642 				// Initialize the object if it is a proxy
643 				if (obj instanceof HibernateProxy && !Hibernate.isInitialized(obj)) {
644 					Hibernate.initialize(obj);
645 				}
646 				if (!fetchedObjects.containsKey(obj) || !fetchedObjects.get(obj).equals(ent)) {
647 					fetchExtentObject(obj, ent, fetchedObjects);
648 				}
649 			}
650 			
651 			// Fetch the collections. Since we assume batch fetching for collections
652 			for (ExtentCollection c : entity.getCollections()) {
653 				Collection<?> coll = (Collection<?>) c.getMethod().invoke(object, nullArg);
654 				if (coll != null) {
655 					for (Object o : coll) {
656 						// Initialize the object if it is a proxy
657 						if (o instanceof HibernateProxy && !Hibernate.isInitialized(o)) {
658 							Hibernate.initialize(o);
659 						}
660 						if (!fetchedObjects.containsKey(o) || !fetchedObjects.get(o).equals(c.getContainedEntity())) {
661 							fetchExtentObject(o, c.getContainedEntity(), fetchedObjects);
662 						}
663 					}
664 				}
665 			}
666 		} catch (Exception e) {
667 			throw new RuntimeException(e);
668 		}
669 		
670 	}
671 	
672 	/**
673 	 * builds and executes an efficient CriteriaQuery to fetch almost all data in the given ExtentEntity
674 	 * and its sub-entities.
675 	 * 
676 	 * This method cannot fetch ALL data since transient getters are unknown to Hibernate, therefore we
677 	 * cannot use a JOIN to get data that would otherwise be retrieved when a transient getter is called.
678 	 * 
679 	 * @param object the object to load in the given extent
680 	 * @param entity the entity entity
681 	 */
682 	private void fetchExtentUsingJoinQuery(Object object, ExtentEntity entity) {
683 		ID id;
684 		ClassMetadata metadata = getSessionFactory().getClassMetadata(object.getClass());
685 		if (metadata == null) {
686 			// object is not a hibernate-entity, so skip it.
687 			return;
688 		}
689 		id = (ID) metadata.getIdentifier(object, EntityMode.POJO);
690 		// Fetch the child entities via JOINs wherever possible
691 		Criteria criteria = getSession().createCriteria(entity.getEntityClass());
692 		criteria.add(Restrictions.idEq(id));
693 		if (buildJoinCriteria(criteria, entity)) {
694 			// Execute the query to fetch all listed data
695 			criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
696 			criteria.list();
697 		}
698 	}
699 	
700 	/**
701 	 * Starts the recursive buildJoinCriteria with the initial value for the alias argument.
702 	 * 
703 	 * @see GenericHibernateDao#buildJoinCriteria(Criteria, ExtentEntity, String)
704 	 * 
705 	 * @param criteria the criteria object to modify
706 	 * @param rootEntity the ExtentEntity
707 	 * @return true if at least one JOIN could be added to the Criteria
708 	 */
709 	private boolean buildJoinCriteria(Criteria criteria, ExtentEntity rootEntity) {
710 		// call the recursive builder with initial alias = null
711 		return buildJoinCriteria(criteria, rootEntity, null);
712 	}
713 
714 	/**
715 	 * Builds the Criteria Query. 
716 	 * Recursively traverses the ExtentEntity structure and creates aliases for JOINed tables . 
717 	 * 
718 	 * @param criteria the criteria object to modify
719 	 * @param entity the ExtentEntity
720 	 * @param alias the prefix to use when creating a new alias for an indirectly accessible property 
721 	 * @return true if at least one JOIN could be added to the Criteria
722 	 */
723 	private boolean buildJoinCriteria(Criteria criteria, ExtentEntity entity, String alias) {
724 		ClassMetadata metadata = getSessionFactory().getClassMetadata(entity.getEntityClass());
725 		
726 		boolean couldJoin = false;
727 		
728 		if (metadata == null) {
729 			// entity not mapped, nothing to do here
730 			return false;
731 		}
732 		
733 		String prefix;
734 		String aliasPrefix;
735 		if (alias == null) {
736 			prefix = "";
737 			aliasPrefix = "ALIAS_";
738 		} else {
739 			aliasPrefix = alias + "_";
740 			prefix = alias + ".";
741 		}
742 		
743 		// fetch the primitive fields of the object
744 		for (String field : entity.getFields()) {
745 			try {
746 				metadata.getPropertyType(field);
747 			} catch (HibernateException e) {
748 				// skip this field
749 				continue;
750 			}
751 			criteria.setFetchMode(prefix + field, FetchMode.JOIN);
752 		}
753 		
754 		// fetch the other associations of the object 
755 		for (ExtentEntity e : entity.getChildEntities()) {
756 			Type type;
757 			try {
758 				type = metadata.getPropertyType(e.getName());
759 			} catch (HibernateException he) {
760 				// not a property that is managed by Hibernate
761 				// it could be a transient field though which fetches data, 
762 				// so just skip it and let fetchExtentObject() call the getter
763 				continue;
764 			}
765 			if (type instanceof EntityType) {
766 				String fieldAlias = aliasPrefix + e.getName();
767 				criteria.createAlias(prefix + e.getName(), fieldAlias, Criteria.LEFT_JOIN);
768 				couldJoin = true;
769 				criteria.setFetchMode(prefix + e.getName(), FetchMode.JOIN);
770 				buildJoinCriteria(criteria, e, fieldAlias);
771 			} else {
772 				criteria.setFetchMode(prefix + e.getName(), FetchMode.JOIN);
773 			}
774 		}
775 		
776 		// don't optimize queries on collections, since we assume that 
777 		// batch fetching is enabled there
778 		
779 		return couldJoin;
780 	}
781 	
782 	/**
783 	 * Returns the simple name of the persistent class this DAO is responsible
784 	 * for.
785 	 *
786 	 * @return The simple name of the persistent class this DAO is responsible
787 	 *         for.
788 	 */
789 	protected String getPersistentClassName() {
790 		return getPersistentClass().getSimpleName();
791 	}
792 	
793 	/**
794 	 * @param queryObject    an EL4J {@link QueryObject} that should be converted to a {@link DetachedCriteria}
795 	 * @return               a suitable {@link DetachedCriteria}
796 	 */
797 	protected DetachedCriteria getCriteria(QueryObject queryObject) {
798 		DetachedCriteria criteria = CriteriaTransformer.transform(queryObject, getPersistentClass());
799 		
800 		if (queryObject.getOrderConstraints().size() == 0) {
801 			criteria = addOrder(criteria);
802 		}
803 		
804 		return makeDistinct(criteria);
805 	}
806 	
807 	
808 	/**
809 	 * @param criteria    the criteria to modify
810 	 * @return            the criteria enhanced with order constraints (if set using setDefaultOrder)
811 	 */
812 	protected DetachedCriteria addOrder(DetachedCriteria criteria) {
813 		if (m_defaultOrder != null) {
814 			for (Order order : m_defaultOrder) {
815 				criteria.addOrder(order);
816 			}
817 		}
818 		return criteria;
819 	}
820 	
821 	/**
822 	 * @param criteria    the criteria to modify
823 	 * @return            the criteria enhanced with distinct restrictions
824 	 */
825 	protected DetachedCriteria makeDistinct(DetachedCriteria criteria) {
826 		criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
827 		
828 		return criteria;
829 	}
830 
831 }