View Javadoc

1   /*
2    * Copyright 2004-2005 Emmanouil Batsis
3    * 
4    * Licensed under the GNU General Public 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.gnu.org/licenses/gpl.txt
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   */
17  package com.geekologue.md4j.dao.hibernate;
18  
19  import java.io.Serializable;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  import javax.naming.InitialContext;
27  import org.apache.log4j.Logger;
28  import org.hibernate.Criteria;
29  import org.hibernate.Session;
30  import org.hibernate.SessionFactory;
31  import org.hibernate.criterion.Example;
32  import org.hibernate.criterion.Projections;
33  import org.hibernate.criterion.Property;
34  import org.hibernate.criterion.Restrictions;
35  import com.geekologue.md4j.config.Configuration;
36  import com.geekologue.md4j.dao.AbstractDAO;
37  import com.geekologue.md4j.dao.DataAccessException;
38  import com.geekologue.md4j.dao.Order;
39  import com.geekologue.md4j.dao.Page;
40  import com.geekologue.md4j.util.Md4jException;
41  
42  /***
43   * Hibernate based DAO
44   * 
45   * @author manos
46   * 
47   */
48  public abstract class AbstractHbmDAO extends AbstractDAO {
49      private static Logger    log = Logger.getLogger(AbstractHbmDAO.class);
50  
51      protected SessionFactory sf;
52  
53      /***
54       * @param clazz
55       */
56      public AbstractHbmDAO(Class clazz, String idName) {
57          super(clazz, idName);
58          try {
59              InitialContext inctx = new InitialContext();
60              String sfName = (String) (Configuration.getInstance())
61                      .getProperty("session_factory_name");
62              if (sfName == null) {
63                  sfName = (String) (Configuration.getInstance())
64                          .getProperty("hibernate.session_factory_name");
65              }
66              this.sf = (SessionFactory) inctx.lookup(sfName);
67              log.debug("Obtained Session Factory:" + this.sf);
68          } catch (Exception e) {
69              throw new DataAccessException(
70                      "Failed to obtain a Hibernate Session Factory", e);
71          }
72      }
73  
74      /***
75       * Retreive the object matching the class handled by this DAO and the given
76       * identifier. If the identifier is not match, an exception is thrown
77       * 
78       * @param identifier
79       * @return the maching pojo
80       * @throws DataAccessException
81       * @see com.geekologue.md4j.dao.AbstractDAO#get(java.io.Serializable)
82       */
83      public Object load(Serializable identifier) throws DataAccessException {
84          Object o = null;
85          try {
86              o = this.sf.getCurrentSession().load(this.daoClass, identifier);
87          } catch (Exception e) {
88              throw new DataAccessException("Failed to match identifier: "
89                      + identifier + ", with identifier type: "
90                      + identifier.getClass().getName() + ", for class "
91                      + this.daoClass.getName(), e);
92          }
93          return o;
94      }
95  
96      /***
97       * Retreive the object matching the class handled by this DAO and the given
98       * identifier or null if no match is found.
99       * 
100      * @param identifier
101      * @return the maching pojo if any, <code>null</code> otherwise
102      * @throws DataAccessException
103      * @see com.geekologue.md4j.dao.AbstractDAO#get(java.io.Serializable)
104      */
105     public Object get(Serializable identifier) throws DataAccessException {
106         Object o = null;
107         try {
108             o = this.sf.getCurrentSession().get(this.daoClass, identifier);
109             if (o == null) {
110                 log.warn("Failed to match identifier: " + identifier
111                         + ", with identifier type: "
112                         + identifier.getClass().getName() + ", for class "
113                         + this.daoClass.getName());
114             }
115         } catch (Exception e) {
116             throw new DataAccessException("Failed to match identifier: "
117                     + identifier + ", with identifier type: "
118                     + identifier.getClass().getName() + ", for class "
119                     + this.daoClass.getName(), e);
120         }
121         return o;
122     }
123 
124     /***
125      * Retreive the properties of the object matching the given identifier as a
126      * Map of attribute-value pairs.
127      * 
128      * @param identifier
129      * @param projectionProperties
130      *            the names of properties to retrieve
131      * @return the requested properties for the object matching the identifier,
132      *         if any and if at least one projection property was supplied,
133      *         <code>null</code> otherwise.
134      * @see com.geekologue.md4j.dao.AbstractDAO#get(Serializable, Set)
135      */
136     public Map get(Serializable identifier, Set projectionProperties) {
137         Map result = null;
138         Set relatedNames = null;
139         if (projectionProperties != null && !projectionProperties.isEmpty()) {
140             Session session = this.sf.getCurrentSession();
141             Map params = new HashMap();
142             params.put(this.identifierName, identifier);
143             Criteria query = session.createCriteria(this.daoClass).add(
144                     Restrictions.eq(this.identifierName, identifier));
145             for (Iterator iter = projectionProperties.iterator(); iter
146                     .hasNext();) {
147                 String projectionName = (String) iter.next();
148                 int dotIndex = projectionName.indexOf(".");
149                 if (dotIndex != -1) {
150                     if (relatedNames == null) {
151                         relatedNames = new HashSet();
152                     }
153                     String related = projectionName.substring(0, dotIndex);
154                     // make sure the alias has not been created already
155                     if (!relatedNames.contains(related)) {
156                         relatedNames.add(related);
157                         log.info("Related: " + related);
158                         query.createCriteria(related, related);
159                     }
160                 }
161             }
162             List list = Helper.getQueryResultAsMapList(query,
163                     projectionProperties);
164             if (!list.isEmpty()) {
165                 result = (Map) list.get(0);
166             }
167         }
168         return result;
169     }
170 
171     /***
172      * Update the object matching the given identifier according to the property
173      * value pairs in the given map. If no match for the identifier is found,
174      * return <code>null</code>
175      * 
176      * @param map
177      *            the map with the property value pairs to update
178      * @throws DataAccessException
179      * @see com.geekologue.md4j.dao.AbstractDAO#update(java.util.Map,
180      *      java.io.Serializable)
181      */
182     public void update(Map map) throws DataAccessException {
183         try {
184             this.sf.getCurrentSession().update(updateFromParams(map));
185         } catch (Exception e) {
186             throw new DataAccessException("Date parsing failed: ", e);
187         }
188     }
189 
190     /***
191      * 
192      * @see com.geekologue.md4j.dao.AbstractDAO#update(java.util.Map,
193      *      java.io.Serializable)
194      */
195     public void update(Object pojo) throws DataAccessException {
196         try {
197             this.sf.getCurrentSession().update(pojo);
198         } catch (Exception e) {
199             throw new DataAccessException("Failed updating "
200                     + this.daoClass.getName() + " instance: " + pojo, e);
201         }
202     }
203 
204     /***
205      * 
206      * @see com.geekologue.md4j.dao.AbstractDAO#listAll()
207      */
208     public List listAll() throws DataAccessException {
209         try {
210             Session sess = this.sf.getCurrentSession();
211             Criteria crit = sess.createCriteria(this.daoClass);
212             return crit.list();
213         } catch (Exception e) {
214             e.printStackTrace();
215             throw new DataAccessException(
216                     "Failed to retreive the complete collection of "
217                             + this.daoClass + " objects", e);
218         }
219     }
220 
221     /***
222      * 
223      * @see com.geekologue.md4j.dao.AbstractDAO#save(java.util.Map,
224      *      java.io.Serializable) public Object save(Map map, Serializable
225      *      identifier){ Object pojo = null; Session session =
226      *      this.sf.getCurrentSession(); try { pojo =
227      *      this.daoClass.newInstance(); BeanUtils.populate(pojo, map); if
228      *      (identifier == null) { pojo = session.save(pojo); } else {
229      *      session.save(pojo, identifier); pojo = session.load(this.daoClass,
230      *      identifier); } } catch (Exception e) { throw new
231      *      DataAccessException("Failed to persist pojo from map: " + map, e); }
232      *      return pojo; }
233      * 
234      */
235     /***
236      * 
237      * @see com.geekologue.md4j.dao.AbstractDAO#save(java.lang.Object)
238      */
239     public Serializable save(Object pojo) throws DataAccessException {
240         try {
241             return this.sf.getCurrentSession().save(pojo);
242         } catch (Exception e) {
243             throw new DataAccessException("Failed to persist pojo: " + pojo, e);
244         }
245     }
246 
247     /***
248      * 
249      * @see com.geekologue.md4j.dao.AbstractDAO#save(java.lang.Object,
250      *      java.io.Serializable) public void save(Object pojo, Serializable
251      *      identifier) throws DataAccessException { try { Session session =
252      *      this.sf.getCurrentSession(); session.save(pojo, identifier); pojo =
253      *      session.load(this.daoClass, identifier); } catch (Exception e) {
254      *      throw new DataAccessException("Failed to persist pojo: " + pojo, e); } }
255      * 
256      */
257     /***
258      * 
259      * @see com.geekologue.md4j.dao.AbstractDAO#saveOrUpdate(java.lang.Object)
260      */
261     public Serializable saveOrUpdate(Object pojo) throws DataAccessException {
262         Serializable identifier;
263         try {
264             Session session = this.sf.getCurrentSession();
265             session.saveOrUpdate(pojo);
266             identifier = session.getIdentifier(pojo);
267         } catch (Exception e) {
268             throw new DataAccessException("Failed to persist pojo: " + pojo, e);
269         }
270         return identifier;
271     }
272 
273     /***
274      * 
275      * @see com.geekologue.md4j.dao.AbstractDAO#findByExample(java.lang.Object)
276      */
277     protected List findByExample(Object exampleObject)
278             throws DataAccessException {
279         try {
280             Criteria crit = this.sf.getCurrentSession().createCriteria(
281                     this.daoClass);
282             return crit.add(Example.create(exampleObject)).list();
283         } catch (Exception e) {
284             e.printStackTrace();
285             throw new DataAccessException("Failed to find objects by example",
286                     e);
287         }
288     }
289 
290     /***
291      * 
292      * @see com.geekologue.md4j.dao.AbstractDAO#getPage(Map, Order, int, int)
293      */
294     public Page getPage(Map params, Order order, int pageNumber, int pageSize) {
295         HbmQueryResultPage page = (HbmQueryResultPage) this.getPage(null,
296                 params, order, pageNumber, pageSize);
297         return page;
298     }
299 
300     /***
301      * 
302      * @see com.geekologue.md4j.dao.AbstractDAO#getPage(Set, Map, Order, int,
303      *      int)
304      */
305     public Page getPage(Set projectionProps, Map params, Order order,
306             int pageNumber, int pageSize) {
307         HbmQueryResultPage page = null;
308         try {
309             Session sess = this.sf.getCurrentSession();
310             Criteria criteria = sess.createCriteria(this.daoClass);
311             this.populateCriteria(projectionProps, params, criteria);
312             page = (HbmQueryResultPage) this.getPage(projectionProps,
313                     order, criteria, pageNumber, pageSize);
314         } catch (Md4jException e) {
315             throw e;
316         } catch (Exception e) {
317             throw new DataAccessException(e);
318         }
319         return page;
320     }
321 
322     protected abstract void populateCriteria(Set projectionProps, Map params,
323             Criteria criteria);
324 
325     /***
326      * Create and return a Page of results.
327      * 
328      * @param sess
329      *            The Hibernate Session object to use for the search
330      * @param projectionProps
331      *            The set of properties to return for each result
332      * @param params
333      *            The set of search criteria to use
334      * @param criteria
335      *            The criteria to use for the search, produced by each DAO in
336      *            the object hierarchy using the <code>params</code> map
337      * @param pageNumber
338      *            The page number to return
339      * @param pageSize
340      *            The page size
341      * @return The resulting Page of results
342      */
343     protected Page getPage(Set projectionProps,
344             Order order, Criteria criteria, int pageNumber, int pageSize) {
345         for (Iterator iter = order.getOrderProperties().listIterator(); iter
346                 .hasNext();) {
347             String propertyName = (String) iter.next();
348             boolean ascending = order.isAscending(propertyName);
349             log.info("Adding " + (ascending ? "ascending" : "descending")
350                     + " order for: " + propertyName);
351             if (ascending) {
352                 criteria.addOrder(Property.forName(propertyName).asc());
353             } else {
354                 criteria.addOrder(Property.forName(propertyName).desc());
355             }
356         }
357         HbmQueryResultPage page = new HbmQueryResultPage(criteria,
358                 projectionProps, pageNumber, pageSize);
359         page.setParentOptions(this.getParentOptions());
360         return page;
361     }
362 
363     public void delete(Serializable pojo) throws DataAccessException {
364         try {
365             this.sf.getCurrentSession().delete(pojo);
366         } catch (Exception e) {
367             throw new DataAccessException("Failed to delete object: " + pojo, e);
368         }
369     }
370 
371     protected Object createFromParams(Map map) {
372         Object pojo = getDaoClassInstance();
373         this.copyProperties(map, pojo);
374         return pojo;
375     }
376 
377     /***
378      * Subclasses must override this method to obtain the persisted instance
379      * from here and update properties in their implementation of the method
380      * 
381      * @param map
382      * @return
383      * @throws DataAccessException
384      */
385     protected Object updateFromParams(Map map) {
386         Object pojo = this.get((Serializable) map.get(this.identifierName));
387         if (pojo == null) {
388             throw new DataAccessException("Could not perform update as no "
389                     + this.daoClass.getName()
390                     + " instance was found with identifier: "
391                     + this.identifierName);
392         }
393         this.copyProperties(map, pojo);
394         return pojo;
395     }
396 
397     /***
398      * Checks whether a persisted instance exists with the given property/value
399      * pair. Used to check unique violation constraints before persisting new
400      * records. If an id is provided, the record matching it is excluded from
401      * the search, making the check valid for an update action.
402      * 
403      * @param name
404      *            the name of the property
405      * @param value
406      *            the value to look for
407      * @param id
408      *            the record to exclude from the search, usefull for checking
409      *            constraints in case of an update (<code>null</code> may be
410      *            provided)
411      * @return true if a match is found, false otherwise
412      */
413     public boolean exists(String name, Serializable value, Serializable id) {
414         boolean exists = false;
415         if (id == null || !name.equals(this.identifierName)) {
416             Criteria criteria = this.sf.getCurrentSession().createCriteria(
417                     this.daoClass).add(Restrictions.eq(name, value));
418             // check if we need to exclude an ID from the search (update)
419             // without ending up with "where this_.id=? and this_.id<>?". Yeah
420             // it was stup-ed.
421             if (id != null) {
422                 criteria.add(Restrictions.ne(this.identifierName, id));
423             }
424             exists = ((Integer) criteria.setProjection(Projections.rowCount())
425                     .uniqueResult()).intValue() > 0;
426         }
427         return exists;
428     }
429 
430     public Set getBrokenUConstraints(Map params, Serializable id) {
431         Set brokens = null;
432         Set namesToCheck = this.getUniquePropertyNames();
433         // exclude id if this is an update action (i.e. if id is not null)
434         if (namesToCheck != null && namesToCheck.size() > 0) {
435             brokens = new HashSet();
436             for (Iterator iter = namesToCheck.iterator(); iter.hasNext();) {
437                 String key = (String) iter.next();
438                 Serializable value = (Serializable) params.get(key);
439                 if (value != null) {
440                     if (this.exists(key, value, id)) {
441                         brokens.add(key);
442                     }
443                 }
444             }
445         }
446         return brokens;
447     }
448 
449     /***
450      * Copy the properties from the given Map to the target POJO object,
451      * converting the value type where appropriate.
452      * 
453      * @param from
454      * @param to
455      */
456     public abstract void copyProperties(Map from, Object to);
457 
458     /***
459      * Get the property names for which unique constraints exis
460      * 
461      * @return the array of property names
462      */
463     public abstract Set getUniquePropertyNames();
464 
465     /***
466      * Get data to populate drop downs refering to parent objects by many-to-one
467      * relationships.
468      * 
469      * @return a Map where each key is the string name of the property for that
470      *         parent object. The value for that key is yet another Map where
471      *         the key value pair is used to the value and text (respectively)
472      *         of HTML option elements
473      * @see com.geekologue.md4j.dao.AbstractDAO#getParentOptions()
474      */
475     public Map getParentOptions() {
476         Map map = new HashMap();
477         this.addParentOptions(map);
478         return map;
479     }
480 
481     public abstract void addParentOptions(Map map);
482 }