View Javadoc

1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.domain.hibernate;
17  
18  import java.io.Serializable;
19  import java.lang.reflect.Method;
20  import java.util.Collection;
21  import java.util.List;
22  
23  import org.acegisecurity.domain.PersistableEntity;
24  import org.acegisecurity.domain.dao.Dao;
25  import org.acegisecurity.domain.dao.DetachmentContextHolder;
26  import org.acegisecurity.domain.dao.EvictionCapable;
27  import org.acegisecurity.domain.dao.InitializationCapable;
28  import org.acegisecurity.domain.dao.PaginatedList;
29  import org.acegisecurity.domain.util.GenericsUtils;
30  import org.acegisecurity.domain.validation.ValidationManager;
31  
32  import org.hibernate.Criteria;
33  import org.hibernate.EntityMode;
34  import org.hibernate.FetchMode;
35  import org.hibernate.Hibernate;
36  import org.hibernate.HibernateException;
37  import org.hibernate.Session;
38  import org.hibernate.criterion.Expression;
39  import org.hibernate.criterion.MatchMode;
40  import org.hibernate.criterion.Order;
41  import org.hibernate.metadata.ClassMetadata;
42  import org.hibernate.type.Type;
43  import org.springframework.dao.DataIntegrityViolationException;
44  import org.springframework.orm.hibernate3.HibernateCallback;
45  import org.springframework.orm.hibernate3.HibernateTemplate;
46  import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
47  import org.springframework.util.Assert;
48  import org.springframework.validation.BindException;
49  
50  
51  /***
52   * Generics supporting {@link Dao} implementation that uses Hibernate 3 for persistence.
53   *
54   * @author Ben Alex
55   * @author Matthew Porter
56   * @version $Id: DaoHibernate.java,v 1.11 2005/11/17 00:55:49 benalex Exp $
57   */
58  public class DaoHibernate<E extends PersistableEntity> extends HibernateDaoSupport implements Dao<E>,
59      EvictionCapable, InitializationCapable {
60      //~ Instance fields ========================================================
61  	
62      /*** The class that this instance provides services for */
63      private Class supportsClass;
64  	
65  	/*** Enables mutator methods to validate an object prior to persistence */
66  	private ValidationManager validationManager;
67  
68  	public DaoHibernate() {
69  		this.supportsClass = GenericsUtils.getGeneric(getClass());
70  		if (this.supportsClass == null) {
71  			if (logger.isWarnEnabled()) {
72  				logger.warn("Could not determine the generics type - you will need to set manually");
73  			}
74  		}
75  	}
76  	
77      //~ Methods ================================================================
78  
79  	/***
80  	 * Obtains a <code>HibernateTemplate</code> that uses the appropriate <code>Session</code>
81  	 * based on the value of {@link DetachmentContextHolder}.
82  	 * 
83  	 * <p>Specifically, if <code>DetachmentContextHolder</code> requires detached instances,
84  	 * the method will build a new <code>Session</code> (ignore the current thread-bound
85  	 * <code>Session</code>) and use that new <code>Session</code> in the <code>HibernateTemplate</code>.
86  	 * If <code>DetachmentContextHolder</code> is at its fault <code>false</code> value, the
87  	 * returned <code>HibernateTemplate</code> will simply use the <code>Session</code> obtained
88  	 * from the superclass, which is generally the same <code>Session</code> as used for the
89  	 * transaction.
90  	 * 
91  	 * @return the template, containing the correct <code>Session</code> based on the
92  	 * <code>DetachmentContactHolder</code> request
93  	 */
94  	protected HibernateTemplate doGetHibernateTemplate() {
95  		if (DetachmentContextHolder.isForceReturnOfDetachedInstances()) {
96  			HibernateTemplate hibernateTemplate = new HibernateTemplate(getSessionFactory());
97  			hibernateTemplate.setAlwaysUseNewSession(true);
98  			return hibernateTemplate;
99  		} else {
100 			return super.getHibernateTemplate();
101 		}
102 	}
103 	
104     public void setSupportsClass(Class supportClass) {
105         this.supportsClass = supportClass;
106     }
107 
108     public Class getSupportsClass() {
109         return supportsClass;
110     }
111 
112     public ValidationManager getValidationManager() {
113 		return validationManager;
114 	}
115 
116 	public void setValidationManager(ValidationManager validationManager) {
117 		this.validationManager = validationManager;
118 	}
119 
120 	public E create(E value) {
121         Assert.notNull(value);
122 		validate(value);
123         doGetHibernateTemplate().save(value);
124 
125         return readId(value.getInternalId());
126     }
127 	
128 	protected void validate(E value) throws DataIntegrityViolationException {
129 		try {
130 			validationManager.validate(value);
131 		} catch (BindException bindException) {
132 			throw new DataIntegrityViolationException("Entity state is invalid", bindException);
133 		}
134 	}
135 
136     public void delete(E value) {
137         Assert.notNull(value);
138 		validate(value);
139         doGetHibernateTemplate().delete(value);
140     }
141 
142     public void evict(PersistableEntity entity) {
143         Assert.notNull(entity);
144         doGetHibernateTemplate().evict(entity);
145     }
146 
147     public List<E> findAll() {
148         return doGetHibernateTemplate().loadAll(supportsClass);
149     }
150 
151     public List<E> findId(Collection<Serializable> ids) {
152         Assert.notNull(ids, "Collection of IDs cannot be null");
153         Assert.notEmpty(ids, "There must be some values in the Collection list");
154 
155         return (List) doGetHibernateTemplate().execute(getFindByIdCallback(ids));
156     }
157 
158     private E readId(final Serializable id, final boolean populate) {
159         Assert.notNull(id);
160 		return (E) doGetHibernateTemplate().execute(new HibernateCallback() {
161 			public Object doInHibernate(Session session) throws HibernateException {
162 				E obj = (E) session.get(supportsClass, id);
163 				if (populate) {
164 					initializeAllZeroArgumentGetters(obj);
165 				}
166 				return obj;
167 			}
168 		}, true);
169     }
170 
171     public E readId(Serializable id) {
172         Assert.notNull(id);
173 		return readId(id, false);
174     }
175 
176 	public E readPopulatedId(Serializable id) {
177         Assert.notNull(id);
178         return readId(id, true);
179 	}
180 	
181 	/***
182 	 * Locates every <code>get*()</code> method against the passed entity
183 	 * and calls it. This method does not nest its initialization beyond
184 	 * the immediately passed entity.
185 	 * 
186 	 * <p>For example, a Foo object might provide a getBar() method.
187 	 * Passing the Foo instance to this method will guarantee getBar() is
188 	 * available to the services layer. However, if getBar() returned a Bar
189 	 * which in turn provided a getCar() method, there is NO GUARANTEE
190 	 * the getCar() method will be initialized.
191 	 * 
192 	 * @param entity for which its immediate getters should be initialized
193 	 */
194 	protected void initializeAllZeroArgumentGetters(E entity) {
195 		Method[] methods = entity.getClass().getMethods();
196 		for (int i = 0; i < methods.length; i++) {
197 			if (methods[i].getName().startsWith("get") && methods[i].getParameterTypes().length == 0) {
198 				try {
199 					Hibernate.initialize(methods[i].invoke(entity, new Object[] {}));
200 				} catch (Exception ignored) {}
201 			}
202 		}
203 	}
204 	
205     public PaginatedList<E> scroll(E value, int firstElement,
206         int maxElements, String orderByAsc) {
207 		validateScrollMethod(value, firstElement, maxElements, orderByAsc);
208         return (PaginatedList) doGetHibernateTemplate().execute(getFindByValueCallback(
209                 value.getClass(), false, value, firstElement, maxElements, Order.asc(orderByAsc)));
210     }
211 
212     public PaginatedList<E> scrollWithSubclasses(E value, int firstElement,
213 	        int maxElements, String orderByAsc) {
214 			validateScrollMethod(value, firstElement, maxElements, orderByAsc);
215 	        return (PaginatedList) doGetHibernateTemplate().execute(getFindByValueCallback(
216 	                this.supportsClass, false, value, firstElement, maxElements, Order.asc(orderByAsc)));
217 	    }
218 	
219     public PaginatedList<E> scrollPopulated(E value, int firstElement,
220 	        int maxElements, String orderByAsc) {
221 			validateScrollMethod(value, firstElement, maxElements, orderByAsc);
222 	        return (PaginatedList) doGetHibernateTemplate().execute(getFindByValueCallback(
223 	                value.getClass(), true, value, firstElement, maxElements, Order.asc(orderByAsc)));
224 	    }
225 
226 	public PaginatedList<E> scrollPopulatedWithSubclasses(E value, int firstElement,
227 		        int maxElements, String orderByAsc) {
228 				validateScrollMethod(value, firstElement, maxElements, orderByAsc);
229 		        return (PaginatedList) doGetHibernateTemplate().execute(getFindByValueCallback(
230 		                this.supportsClass, true, value, firstElement, maxElements, Order.asc(orderByAsc)));
231 		    }
232 
233 	private void validateScrollMethod(E value, int firstElement, int MaxElements, String orderByAsc) {
234         Assert.notNull(value);
235         Assert.hasText(orderByAsc,
236             "An orderByAsc is required (why not use your identity property?)");
237 		Assert.isInstanceOf(this.supportsClass, value, "Can only scroll with values this DAO supports");
238 	}
239 
240 	public boolean supports(Class clazz) {
241         Assert.notNull(clazz);
242 
243         return this.supportsClass.equals(clazz);
244     }
245 
246     public E update(E value) {
247         Assert.notNull(value);
248 		validate(value);
249         doGetHibernateTemplate().update(value);
250 
251         return readId(value.getInternalId());
252     }
253 
254     /***
255      * Custom initialization behavior. Called by superclass.
256      *
257      * @throws Exception if initialization fails
258      */
259     protected final void initDao() throws Exception {
260         Assert.notNull(supportsClass, "supportClass is required");
261 		Assert.notNull(validationManager, "validationManager is required");
262         Assert.isTrue(PersistableEntity.class.isAssignableFrom(supportsClass), "supportClass is not an implementation of PersistableEntity");
263         initHibernateDao();
264     }
265 
266     /***
267      * Allows subclasses to provide custom initialization behaviour. Called
268      * during {@link #initDao()}.
269      *
270      * @throws Exception
271      */
272     protected void initHibernateDao() throws Exception {}
273 
274     public void initialize(Object entity) {
275 		Hibernate.initialize(entity);
276 	}
277 
278 	public boolean isInitialized(Object entity) {
279 		return Hibernate.isInitialized(entity);
280 	}
281 
282 	/***
283      * Provides a <code>HibernateCallback</code> that will load a list of
284      * objects by a <code>Collection</code> of identities.
285      *
286      * @param ids collection of identities to be loaded
287      *
288      * @return a <code>List</code> containing the matching objects
289      */
290     private HibernateCallback getFindByIdCallback(final Collection<Serializable> ids) {
291         return new HibernateCallback() {
292                 public Object doInHibernate(Session session)
293                     throws HibernateException {
294                     Criteria criteria = session.createCriteria(supportsClass);
295 
296                     ClassMetadata classMetadata = getSessionFactory()
297                                                       .getClassMetadata(supportsClass);
298 
299                     String idPropertyName = classMetadata
300                         .getIdentifierPropertyName();
301                     criteria.add(Expression.in(idPropertyName, ids));
302 
303                     return criteria.list();
304                 }
305             };
306     }
307 
308     /***
309      * Get a new <code>HibernateCallback</code> for finding objects by a bean
310      * property values, paginating the results. Properties with null values
311      * and collections and empty Strings are ignored, as is any property with
312      * the "version" name. If the property is mapped as String find a partial
313      * match, otherwise find by exact match.
314      *
315      * @param whichClass the class (and subclasses) which results will be limited to including
316      * @param initializeAllProperties indicates whether lazy initialized properties
317      *        should be initialized in the returned results
318      * @param bean bean with the values of the parameters
319      * @param firstElement the first result, numbered from 0
320      * @param count the maximum number of results
321      * @param order DOCUMENT ME!
322      *
323      * @return a PaginatedList containing the requested objects
324      */
325     private HibernateCallback getFindByValueCallback(final Class whichClass, final boolean initializeAllProperties, final Object bean,
326         final int firstElement, final int count, final Order order) {
327         return new HibernateCallback() {
328                 public Object doInHibernate(Session session)
329                     throws HibernateException {
330                     Criteria criteria = session.createCriteria(whichClass);
331 
332                     criteria.addOrder(order);
333 
334                     ClassMetadata classMetadata = getSessionFactory()
335                                                       .getClassMetadata(bean
336                             .getClass());
337 
338 					Assert.notNull(classMetadata, "ClassMetadata for " + bean.getClass() + " unavailable from Hibernate - have you mapped this class against the SessionFactory?");
339 					
340                     /* get persistent properties */
341                     Type[] propertyTypes = classMetadata.getPropertyTypes();
342                     String[] propertyNames = classMetadata.getPropertyNames();
343 
344                     /* for each persistent property of the bean */
345                     for (int i = 0; i < propertyNames.length; i++) {
346                         String name = propertyNames[i];
347                         
348 						// Indicate preferred fetching here
349 						if (initializeAllProperties) {
350 							criteria.setFetchMode(name, FetchMode.JOIN);
351 						}
352 						
353                         // TODO: Check if EntityMode.POJO appropriate
354                         Object value = classMetadata.getPropertyValue(bean, name, EntityMode.POJO);
355 
356                         if (value == null) {
357                             continue;
358                         }
359 
360                         // ignore empty Strings
361                         if (value instanceof String) {
362                             String string = (String) value;
363 
364                             if ("".equals(string)) {
365                                 continue;
366                             }
367                         }
368 
369                         // ignore any collections
370                         if (propertyTypes[i].isCollectionType()) {
371                             continue;
372                         }
373 
374                         Type type = classMetadata.getPropertyType(name);
375 
376                         if (name.equals("version")) {
377                             continue;
378                         }
379                         
380                         if (name.equals("lastUpdatedUsername") || name.equals("lastUpdatedDate")) {
381                         	continue;
382                         }
383 
384                         if (type.equals(Hibernate.STRING)) {
385                             // if the property is mapped as String, find partial match
386                             criteria.add(Expression.ilike(name,
387                                     value.toString(), MatchMode.ANYWHERE));
388                         } else {
389                             // find exact match
390                             criteria.add(Expression.eq(name, value));
391                         }
392                     }
393 
394                     /*
395                      * TODO Use Criteria.count() when available in next Hibernate
396                      * versions
397                      */
398                     int size = criteria.list().size();
399 
400                     List<E> list = criteria.setFirstResult(firstElement).setMaxResults(count).list();
401 
402                     return new PaginatedList<E>(list, firstElement, count, size);
403                 }
404             };
405     }
406 }