View Javadoc

1   /*
2    * Copyright 2004 Carlos Sanchez.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.oness.common.model.dao.hibernate;
17  
18  import java.io.Serializable;
19  import java.lang.reflect.InvocationTargetException;
20  import java.util.Collection;
21  import java.util.List;
22  
23  import net.sf.hibernate.Criteria;
24  import net.sf.hibernate.Hibernate;
25  import net.sf.hibernate.HibernateException;
26  import net.sf.hibernate.MappingException;
27  import net.sf.hibernate.Session;
28  import net.sf.hibernate.expression.Expression;
29  import net.sf.hibernate.expression.MatchMode;
30  import net.sf.hibernate.metadata.ClassMetadata;
31  import net.sf.hibernate.metadata.CollectionMetadata;
32  import net.sf.hibernate.type.CompositeCustomType;
33  import net.sf.hibernate.type.PersistentCollectionType;
34  import net.sf.hibernate.type.Type;
35  import net.sf.oness.common.model.bo.AuditableBusinessObject;
36  import net.sf.oness.common.model.dao.Dao;
37  import net.sf.oness.common.model.temporal.DateRange;
38  import net.sf.oness.common.model.temporal.hibernate.DateRangeType;
39  import net.sf.oness.common.model.util.PaginatedList;
40  
41  import org.apache.commons.beanutils.BeanUtils;
42  import org.springframework.orm.ObjectRetrievalFailureException;
43  import org.springframework.orm.hibernate.HibernateCallback;
44  
45  /***
46   * Dao that uses Hibernate for persistence
47   * 
48   * @author Carlos Sanchez
49   * @version $Revision: 1.18 $
50   */
51  public class HibernateDao extends HibernateDaoSupport implements Dao {
52  
53      private static final int INITIALIZE_LAZY = 1, NULLIFY_LAZY = 2;
54  
55      public static final String WHERE_NOT_DELETED = "where this.transactionTime.end is null";
56  
57      private Class theClass;
58  
59      /***
60       * Create a HibernateDao for the class specified
61       * 
62       * @param className
63       * @throws HibernateException
64       */
65      public HibernateDao(Class theClass) {
66          this.theClass = theClass;
67      }
68  
69      /***
70       * Create a HibernateDao for the class with name specified by className
71       * 
72       * @param className
73       * @throws ClassNotFoundException
74       * @throws HibernateException
75       */
76      public HibernateDao(String className) throws ClassNotFoundException {
77          this.theClass = Class.forName(className);
78      }
79  
80      /***
81       * Return the persistent instance with the given identifier, assuming that
82       * the instance exists.
83       * 
84       * @param id
85       *            a valid identifier of an existing persistent instance of the
86       *            class
87       * @return
88       */
89      protected AuditableBusinessObject load(Serializable id) {
90          return (AuditableBusinessObject) getHibernateTemplate().load(theClass,
91                  id);
92      }
93  
94      /***
95       * @see net.sf.oness.common.model.dao.AuditableDao#findById(java.io.Serializable)
96       */
97      public AuditableBusinessObject findById(Serializable id) {
98          return (AuditableBusinessObject) super.loadAndClone(theClass, id);
99      }
100 
101     /***
102      * @see net.sf.oness.common.model.dao.AuditableDao#findById(java.util.Collection)
103      */
104     public List findById(Collection ids) {
105         return (List) getHibernateTemplate().execute(getFindByIdCallback(ids));
106     }
107 
108     /***
109      * @see net.sf.oness.common.model.dao.Dao#findWithDetails(java.io.Serializable)
110      */
111     public AuditableBusinessObject findWithDetails(Serializable id) {
112         AuditableBusinessObject o = load(id);
113         return (AuditableBusinessObject) getHibernateTemplate().execute(
114                 getProcessCollectionsCallback(o, INITIALIZE_LAZY));
115     }
116 
117     /***
118      * Get a new HibernateCallback to do some operation on lazy initialized
119      * collections (initialize or set to null).
120      * 
121      * @param object
122      *            the persistent object
123      * @param operation
124      *            the operation to do with collection properties
125      *            (INITIALIZE_LAZY or NULLIFY_LAZY)
126      * @return a new non persistent object with collections processed
127      */
128     private HibernateCallback getProcessCollectionsCallback(
129             final AuditableBusinessObject object, final int operation) {
130 
131         return new HibernateCallback() {
132 
133             public Object doInHibernate(Session session)
134                     throws HibernateException {
135 
136                 ClassMetadata classMetadata = getSessionFactory()
137                         .getClassMetadata(object.getClass());
138 
139                 /* get persistent properties */
140                 Type[] propertyTypes = classMetadata.getPropertyTypes();
141                 String[] propertyNames = classMetadata.getPropertyNames();
142 
143                 /* destination bean */
144                 AuditableBusinessObject dest = findById(object.getId());
145 
146                 /* for each persistent property of the bean */
147                 for (int i = 0; i < propertyTypes.length; i++) {
148 
149                     if (!propertyTypes[i].isPersistentCollectionType())
150                         continue;
151 
152                     CollectionMetadata collectionMetadata = getSessionFactory()
153                             .getCollectionMetadata(
154                                     ((PersistentCollectionType) propertyTypes[i])
155                                             .getRole());
156 
157                     /* if it's a lazy collection operate on it */
158                     if (collectionMetadata.isLazy()) {
159                         switch (operation) {
160                         case (INITIALIZE_LAZY):
161                             Collection c = filterNotDeleted((Collection) classMetadata
162                                     .getPropertyValue(object, propertyNames[i]));
163                             classMetadata.setPropertyValue(dest,
164                                     propertyNames[i], c);
165                             break;
166                         case (NULLIFY_LAZY):
167                             classMetadata.setPropertyValue(dest,
168                                     propertyNames[i], null);
169                             break;
170                         default:
171                             throw new IllegalArgumentException(
172                                     "Unknown operation");
173                         }
174                     }
175                 }
176                 return dest;
177             }
178         };
179     }
180 
181     /***
182      * Get a new HibernateCallback for loading objects by a Collection of ids
183      * 
184      * @param ids
185      *            collection of ids
186      * @return
187      */
188     private HibernateCallback getFindByIdCallback(final Collection ids) {
189 
190         return new HibernateCallback() {
191 
192             public Object doInHibernate(Session session)
193                     throws HibernateException {
194 
195                 Criteria criteria = session.createCriteria(theClass);
196 
197                 ClassMetadata classMetadata = getSessionFactory()
198                         .getClassMetadata(theClass);
199 
200                 String idPropertyName = classMetadata
201                         .getIdentifierPropertyName();
202                 criteria.add(Expression.in(idPropertyName, ids));
203 
204                 return criteria.list();
205             }
206         };
207     }
208 
209     /***
210      * @see net.sf.oness.common.model.dao.AuditableDao#create(net.sf.oness.common.model.bo.Auditable)
211      */
212     public AuditableBusinessObject create(final AuditableBusinessObject bo) {
213         bo.setId(null);
214         getHibernateTemplate().save(bo);
215         return findById(bo.getId());
216     }
217 
218     /***
219      * @see net.sf.oness.common.model.dao.AuditableDao#update(net.sf.oness.common.model.bo.Auditable)
220      */
221     public AuditableBusinessObject update(AuditableBusinessObject bo) {
222         Long id = bo.getId();
223         Object o = getHibernateTemplate().load(theClass, id);
224         try {
225             BeanUtils.copyProperties(o, bo);
226         } catch (IllegalAccessException e) {
227             throw new ObjectRetrievalFailureException(theClass, id, e
228                     .getMessage(), e);
229         } catch (InvocationTargetException e) {
230             throw new ObjectRetrievalFailureException(theClass, id, e
231                     .getMessage(), e);
232         }
233         getHibernateTemplate().update(o);
234         return findById(bo.getId());
235     }
236 
237     /***
238      * @see net.sf.oness.common.model.dao.FinderDao#find(net.sf.oness.common.model.bo.Auditable,
239      *      int, int)
240      */
241     public PaginatedList find(AuditableBusinessObject bo, int firstElement,
242             int maxElements) {
243         return (PaginatedList) getHibernateTemplate().execute(
244                 getFindByValueCallback(bo, firstElement, maxElements));
245     }
246 
247     /***
248      * Get a new HibernateCallback for finding objects by a bean property
249      * values, paginating the results. Properties with null values and
250      * collections are ignored. If the property is mapped as String find a
251      * partial match, otherwise find by exact match.
252      * 
253      * @todo Use Criteria.count() when available in next Hibernate versions
254      * 
255      * @param bean
256      *            bean with the values of the parameters
257      * @param firstElement
258      *            the first result, numbered from 0
259      * @param count
260      *            the maximum number of results
261      * @return
262      */
263     private HibernateCallback getFindByValueCallback(final Object bean,
264             final int firstElement, final int count) {
265 
266         return new HibernateCallback() {
267 
268             public Object doInHibernate(Session session)
269                     throws HibernateException {
270                 Criteria criteria = session.createCriteria(bean.getClass());
271 
272                 ClassMetadata classMetadata = getSessionFactory()
273                         .getClassMetadata(bean.getClass());
274 
275                 if (classMetadata == null)
276                     throw new MappingException("No persister for: "
277                             + bean.getClass().getName());
278 
279                 /* get persistent properties */
280                 Type[] propertyTypes = classMetadata.getPropertyTypes();
281                 String[] propertyNames = classMetadata.getPropertyNames();
282 
283                 /* for each persistent property of the bean */
284                 for (int i = 0; i < propertyNames.length; i++) {
285                     String name = propertyNames[i];
286                     Object value = classMetadata.getPropertyValue(bean, name);
287 
288                     if (value == null)
289                         continue;
290 
291                     /* ignore collections */
292                     if (propertyTypes[i].isPersistentCollectionType())
293                         continue;
294 
295                     Type type = classMetadata.getPropertyType(name);
296                     /* if the property is mapped as String find partial match */
297                     if (type.equals(Hibernate.STRING))
298                         criteria.add(Expression.ilike(name, value.toString(),
299                                 MatchMode.ANYWHERE));
300                     /*
301                      * if the property is mapped as DateRange split it in start
302                      * and end
303                      */
304                     else if (type.equals(Hibernate.custom(DateRangeType.class))) {
305                         CompositeCustomType customType = (CompositeCustomType) type;
306                         String[] names = customType.getPropertyNames();
307                         DateRange dr = (DateRange) value;
308                         Object values[] = customType.getPropertyValues(value);
309                         if (dr.getStart() != null)
310                             if (values[0] != null)
311                                 criteria.add(Expression.eq(name + "."
312                                         + names[0], values[0]));
313                             else
314                                 criteria.add(Expression.isNull(name + "."
315                                         + names[0]));
316                         if (dr.getEnd() != null)
317                             if (values[1] != null)
318                                 criteria.add(Expression.eq(name + "."
319                                         + names[1], values[1]));
320                             else
321                                 criteria.add(Expression.isNull(name + "."
322                                         + names[1]));
323                     }
324                     /* else find exact match */
325                     else
326                         criteria.add(Expression.eq(name, value));
327                 }
328 
329                 /*
330                  * TODO Use Criteria.count() when available in next Hibernate
331                  * versions
332                  */
333                 int size = criteria.list().size();
334 
335                 List list = criteria.setFirstResult(firstElement)
336                         .setMaxResults(count).list();
337                 return new PaginatedList(list, firstElement, count, size);
338             }
339         };
340     }
341 
342     /***
343      * Not implemented
344      * 
345      * @throws UnsupportedOperationException
346      * @see net.sf.oness.common.model.dao.AuditableDao#delete(java.io.Serializable)
347      */
348     public void delete(Serializable id) {
349         throw new UnsupportedOperationException();
350     }
351 
352     /***
353      * Return all persistent instances of the given class
354      * 
355      * @return List of objects
356      */
357     public List findAll() {
358         return getHibernateTemplate().loadAll(theClass);
359     }
360 
361     /***
362      * Filter a persistent collection, getting not deleted values. Allow
363      * efficient access to very large lazy collections. (Executing the filter
364      * does not initialize the collection.)
365      * 
366      * @param collection
367      *            a persistent collection to filter
368      * @return Collection the resulting collection
369      * 
370      * @see Session.filter()
371      */
372     public Collection filterNotDeleted(Collection collection) {
373         return filter(collection, WHERE_NOT_DELETED);
374     }
375 
376 }