1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
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
341 Type[] propertyTypes = classMetadata.getPropertyTypes();
342 String[] propertyNames = classMetadata.getPropertyNames();
343
344
345 for (int i = 0; i < propertyNames.length; i++) {
346 String name = propertyNames[i];
347
348
349 if (initializeAllProperties) {
350 criteria.setFetchMode(name, FetchMode.JOIN);
351 }
352
353
354 Object value = classMetadata.getPropertyValue(bean, name, EntityMode.POJO);
355
356 if (value == null) {
357 continue;
358 }
359
360
361 if (value instanceof String) {
362 String string = (String) value;
363
364 if ("".equals(string)) {
365 continue;
366 }
367 }
368
369
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
386 criteria.add(Expression.ilike(name,
387 value.toString(), MatchMode.ANYWHERE));
388 } else {
389
390 criteria.add(Expression.eq(name, value));
391 }
392 }
393
394
395
396
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 }