View Javadoc

1   /***
2    * Copyright (c) 2003 held jointly by the individual authors.            
3    *                                                                          
4    * This library is free software; you can redistribute it and/or modify it    
5    * under the terms of the GNU Lesser General Public License as published      
6    * by the Free Software Foundation; either version 2.1 of the License, or 
7    * (at your option) any later version.                                            
8    *                                                                            
9    * This library is distributed in the hope that it will be useful, but 
10   * WITHOUT ANY WARRANTY; with out even the implied warranty of 
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
12   * GNU Lesser General Public License for more details.                                                  
13   *                                                                           
14   * You should have received a copy of the GNU Lesser General Public License   
15   * along with this library;  if not, write to the Free Software Foundation,   
16   * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.              
17   *                                                                            
18   * > http://www.gnu.org/copyleft/lesser.html                                  
19   * > http://www.opensource.org/licenses/lgpl-license.php
20   */
21  package net.mlw.vlh.adapter.hibernate;
22  
23  import java.lang.reflect.InvocationTargetException;
24  import java.text.ParseException;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import net.mlw.vlh.DefaultListBackedValueList;
29  import net.mlw.vlh.ValueList;
30  import net.mlw.vlh.ValueListInfo;
31  import net.mlw.vlh.adapter.AbstractValueListAdapter;
32  import net.mlw.vlh.adapter.hibernate.util.ScrollableResultsDecorator;
33  import net.mlw.vlh.adapter.hibernate.util.StatementBuilder;
34  import net.mlw.vlh.adapter.util.ObjectValidator;
35  import net.sf.hibernate.HibernateException;
36  import net.sf.hibernate.Query;
37  import net.sf.hibernate.ScrollableResults;
38  import net.sf.hibernate.Session;
39  import net.sf.hibernate.SessionFactory;
40  
41  import org.apache.commons.beanutils.PropertyUtils;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.springframework.orm.hibernate.SessionFactoryUtils;
45  
46  /*** 
47   *  This adapter wraps the functionality of Hibernate.
48   *  Add extra functionality such as paging, focusing
49   *  and validating of current result set.
50   * <i>
51   *   "Hibernate is a powerful, ultra-high performance 
52   *   object/relational persistence and query service 
53   *   for Java. Hibernate lets you develop persistent 
54   *   classes following common Java idiom - including 
55   *   association, inheritance, polymorphism, composition 
56   *   and the Java collections framework. The Hibernate 
57   *   Query Language, designed as a "minimal" 
58   *   object-oriented extension to SQL, provides an 
59   *   elegant bridge between the object and relational 
60   *   worlds. Hibernate also allows you to express queries 
61   *   using native SQL or Java-based Criteria and Example 
62   *   queries. Hibernate is now the most popular 
63   *   object/relational mapping solution for Java."
64   * </i> -http://www.hibernate.org/
65   * 
66   * @author Matthew L. Wilson, Andrej Zachar
67   * @version $Revision: 1.27 $ $Date: 2006/03/29 19:47:48 $
68   */
69  public class Hibernate20Adapter extends AbstractValueListAdapter
70  {
71     /*** The Hibernate SessionFactory. */
72     private SessionFactory sessionFactory;
73  
74     /***
75      * <p>
76      * If is set, it use special ScrollableResultsDecorator, that enable or
77      * disable to add object in final list.
78      * </p>
79      * <h4>NOTE:</h4>
80      * <p>
81      * Also, it respects the total count of entries that overlap your paged
82      * list.
83      * </p>
84      */
85     private ObjectValidator _validator = null;
86  
87     /*** Commons logger. */
88     private static final Log LOGGER = LogFactory.getLog(Hibernate20Adapter.class);
89  
90     /*** If a new Session should be created if no thread-bound found. */
91     private boolean allowCreate = true;
92  
93     /*** The hibernate query. */
94     private String hql;
95  
96     private String namedQuery;
97  
98     /*** The max rows in ResulSet to doFocus 
99      *  @author Andrej Zachar
100     */
101    private long maxRowsForFocus = Long.MAX_VALUE;
102 
103    /*** The name of object use to get focus property in hibernate sql syntax 
104     * SELECT defaultFocusPropertyObjectAlias.getFocusProperty ...	
105     * 
106     * @author Andrej Zachar
107     */
108    private String defaultFocusPropertyObjectAlias = "";
109 
110    /***
111     * Enable or Disable String length checking of given filters values. If
112     * filter value is null or empty is removed from query.
113     * @author Andrej Zachar
114     */
115    private boolean _isRemoveEmptyStrings = false;
116 
117    private StatementBuilder statementBuilder;
118 
119    /***
120     * Enable or disable optimalization of the query for focus property.
121     */
122    private boolean _focusOptimalization = true;
123 
124    /***
125     * @return Returns the focusOptimalization.
126     */
127    public boolean isFocusOptimalization()
128    {
129       return _focusOptimalization;
130    }
131 
132    /***
133     * Enable or disable optimalization of the query for focus property.
134     * @param focusOptimalization true - enable query with short select, false - query with full select
135     */
136    public void setFocusOptimalization(boolean focusOptimalization)
137    {
138       _focusOptimalization = focusOptimalization;
139    }
140 
141    /***
142     * <p>
143     * If is set, it use special ScrollableResultsDecorator, that enable or
144     * disable to add object in final list.
145     * </p>
146     * <h4>NOTE:</h4>
147     * <p>
148     * Also, it respects the total count of entries that overlap your paged
149     * list.
150     * </p>
151     * @param validator The validator to set.
152     */
153    public void setValidator(ObjectValidator validator)
154    {
155       _validator = validator;
156    }
157 
158    /***
159     * @return Returns the isPrefilterEmpty.
160     */
161    public boolean isRemoveEmptyStrings()
162    {
163       return _isRemoveEmptyStrings;
164    }
165 
166    /***
167     * Enable or Disable String length checking of given filters values. If
168     * filter value is null or empty is removed from query.
169     * 
170     * @param isPrefilterEmpty
171     *            true-remove null and empty, false - remove only null filters.
172     */
173    public void setRemoveEmptyStrings(boolean isPrefilterEmpty)
174    {
175       _isRemoveEmptyStrings = isPrefilterEmpty;
176    }
177 
178    /***
179     * @see net.mlw.vlh.ValueListAdapter#getValueList(java.lang.String,
180     *      net.mlw.vlh.ValueListInfo)
181     */
182    public ValueList getValueList(String name, ValueListInfo info)
183    {
184 
185       LOGGER.debug("getValueList(String, ValueListInfo) - start");
186 
187       if (info.getSortingColumn() == null)
188       {
189          info.setPrimarySortColumn(getDefaultSortColumn());
190          info.setPrimarySortDirection(getDefaultSortDirectionInteger());
191          if (LOGGER.isDebugEnabled())
192          {
193             LOGGER.debug("The default sort column '" + getDefaultSortColumn() + "' with direction '" + getDefaultSortDirectionInteger()
194                   + "' was  set.");
195          }
196       }
197 
198       int numberPerPage = info.getPagingNumberPer();
199 
200       if (numberPerPage == Integer.MAX_VALUE)
201       {
202          numberPerPage = getDefaultNumberPerPage();
203          info.setPagingNumberPer(numberPerPage);
204          if (LOGGER.isDebugEnabled())
205          {
206             LOGGER.debug("The paging number per page '" + numberPerPage + "' was  set.");
207          }
208       }
209 
210       Session session = SessionFactoryUtils.getSession(getSessionFactory(), allowCreate);
211       try
212       {
213          Query query;
214 
215          boolean doFocus = ((getAdapterType() & DO_FOCUS) == 0) && info.isFocusEnabled() && info.isDoFocus() && (namedQuery == null);
216 
217          if (doFocus)
218          {
219             if (LOGGER.isDebugEnabled())
220             {
221                LOGGER.debug("Start to focusing adapterName '" + name + "', ValueListInfo info = " + info + "'");
222             }
223             ScrollableResults results = getScrollableResults(getQueryForFocus(info, session), info);
224             results.beforeFirst();
225             doFocusFor(info, results);
226 
227             if (LOGGER.isDebugEnabled())
228             {
229                LOGGER.debug("Focusing finished for adapterName '" + name + "', ValueListInfo info '" + info + "'");
230             }
231          }
232 
233          query = getQuery(info, session);
234 
235          boolean doPaging = ((getAdapterType() & DO_PAGE) == 0);
236 
237          List list;
238 
239          if (doPaging)
240          {
241             if (LOGGER.isDebugEnabled())
242             {
243                LOGGER.debug("getValueList(String adapterName = " + name + ", ValueListInfo info = " + info
244                      + ") - Start to paging result set");
245             }
246 
247             list = new ArrayList(numberPerPage);
248             ScrollableResults results = getScrollableResults(query, info);
249 
250             results.last();
251             int lastRowNumber = results.getRowNumber();
252             info.setTotalNumberOfEntries(lastRowNumber + 1);
253 
254             if (numberPerPage == 0)
255             {
256                numberPerPage = getDefaultNumberPerPage();
257             }
258 
259             int pageNumber = info.getPagingPage();
260             boolean isResult;
261             if (pageNumber > 1)
262             {
263                if ((pageNumber - 1) * numberPerPage > lastRowNumber)
264                {
265                   pageNumber = (lastRowNumber / numberPerPage) + 1;
266                   info.setPagingPage(pageNumber);
267                }
268             }
269             if (pageNumber > 1)
270             {
271                isResult = results.scroll((pageNumber - 1) * numberPerPage - lastRowNumber);
272             }
273             else
274             {
275                isResult = results.first();
276             }
277 
278             for (int i = 0; i < numberPerPage && isResult; i++)
279             {
280                list.add(results.get(0));
281                isResult = results.next();
282             }
283 
284             LOGGER.debug("Sorting finished.");
285 
286          }
287          else
288          {
289 
290             LOGGER.debug("Retrieving a list directly from the query.");
291 
292             list = query.list();
293             info.setTotalNumberOfEntries(list.size());
294          }
295 
296          ValueList returnValueList = getListBackedValueList(info, list);
297          if (LOGGER.isDebugEnabled())
298          {
299             LOGGER.debug("Retrieved list was wrapped in valuelist, info=" + info);
300          }
301          return returnValueList;
302       }
303       catch (HibernateException e)
304       {
305          LOGGER.error("Error getting data in adapater '" + name + "' with info = '" + info + "'", e);
306          throw SessionFactoryUtils.convertHibernateAccessException(e);
307       }
308       catch (Exception e)
309       {
310          LOGGER.fatal("Fatal error getting data in adapater '" + name + "' with info = '" + info + "'", e);
311          return null;
312       }
313       finally
314       {
315          // deprecated since spring 1.2
316          SessionFactoryUtils.closeSessionIfNecessary(session, getSessionFactory());
317       }
318    }
319 
320    /***     
321     * @param info
322     * @param list
323     * @return DefaultListBackValueList instance
324     */
325    protected ValueList getListBackedValueList(ValueListInfo info, List list)
326    {
327       return new DefaultListBackedValueList(list, info);
328    }
329 
330    /***
331     * @param info
332     * @param results
333     * @throws HibernateException
334     * @throws NoSuchMethodException
335     * @throws InvocationTargetException
336     * @throws IllegalAccessException
337     */
338    private void doFocusFor(ValueListInfo info, ScrollableResults results) throws HibernateException
339    {
340       info.setFocusStatus(ValueListInfo.FOCUS_NOT_FOUND);
341 
342       int currentRow;
343       if (isFocusOptimalization())
344       {
345          if (LOGGER.isDebugEnabled())
346          {
347             LOGGER.debug("Focusing only property '" + info.getFocusProperty() + "' == '" + info.getFocusValue() + "'.");
348          }
349          for (currentRow = 0; ((results.next()) && (currentRow < maxRowsForFocus)); currentRow++)
350          {
351             String value = results.get(0).toString();
352             if (value.equalsIgnoreCase(info.getFocusValue()))
353             {
354                if (LOGGER.isInfoEnabled())
355                {
356                   LOGGER.info("Focus property '" + info.getFocusProperty() + "' in row '" + currentRow + "'.");
357                }
358                info.setPagingPageFromRowNumber(results.getRowNumber());
359                info.setFocusedRowNumberInTable(results.getRowNumber());
360                info.setFocusStatus(ValueListInfo.FOCUS_FOUND);
361                break;
362             }
363          }
364       }
365       else
366       {
367          if (LOGGER.isDebugEnabled())
368          {
369             LOGGER.debug("Focusing object with the property '" + info.getFocusProperty() + "' == '" + info.getFocusValue() + "'.");
370          }
371          for (currentRow = 0; ((results.next()) && (currentRow < maxRowsForFocus)); currentRow++)
372          {
373 
374             Object value;
375             try
376             {
377                value = PropertyUtils.getProperty(results.get(0), info.getFocusProperty());
378             }
379             catch (HibernateException e)
380             {
381                LOGGER.error("Error getting focus property '" + info.getFocusProperty() + "'", e);
382                throw e;
383             }
384             catch (Exception e)
385             {
386                LOGGER.warn("Ingoring error while getting focus property '" + info.getFocusProperty() + "'", e);
387                continue;
388             }
389 
390             if (value.toString().equalsIgnoreCase(info.getFocusValue()))
391             {
392                if (LOGGER.isInfoEnabled())
393                {
394                   LOGGER.info("Focus object's property '" + info.getFocusProperty() + "' was found in the row '" + currentRow + "'.");
395                }
396                info.setPagingPageFromRowNumber(results.getRowNumber());
397                info.setFocusedRowNumberInTable(results.getRowNumber());
398                info.setFocusStatus(ValueListInfo.FOCUS_FOUND);
399                break;
400             }
401          }
402       }
403       if (currentRow == maxRowsForFocus)
404       {
405          if (LOGGER.isInfoEnabled())
406          {
407             LOGGER.info("Focus for property '" + info.getFocusProperty() + "' exceded maximum rows for focus '" + maxRowsForFocus + "'.");
408          }
409          info.setFocusStatus(ValueListInfo.FOCUS_TOO_MANY_ITEMS);
410       }
411    }
412 
413    /***
414     * @param query
415     * @param info ValueListInfo This info will be set to validator. 
416     * @return ScrollableResults, if is set non null _validator, it returns the
417     *         ScrollableResultsDecorator.
418     * @throws HibernateException
419     */
420    private ScrollableResults getScrollableResults(Query query, ValueListInfo info) throws HibernateException
421    {
422       ScrollableResults results;
423 
424       if (_validator == null)
425       {
426          LOGGER.debug("Validator is null, using normal ScrollableResults");
427          results = query.scroll();
428 
429       }
430       else
431       {
432          LOGGER.info("Using decorator of the ScrollableResults with your validator.");
433          _validator.setValueListInfo(info);
434          results = new ScrollableResultsDecorator(query.scroll(), _validator);
435       }
436 
437       return results;
438    }
439 
440    /***
441     * @param info
442     * @param session
443     * @return @throws
444     *         HibernateException
445     */
446    private Query getQuery(ValueListInfo info, Session session) throws HibernateException, ParseException
447    {
448 
449       if (getHql() != null)
450       {
451          return getStatementBuilder().generate(session, new StringBuffer(getHql()), info.getFilters(), _isRemoveEmptyStrings);
452       }
453       else
454       {
455          if (namedQuery != null)
456          {
457             return session.getNamedQuery(getNamedQuery());
458          }
459          else
460          {
461             throw new HibernateException("Please define any QUERY in value list retrieve adpater!");
462          }
463       }
464    }
465 
466    /***
467     * If focus optimalization is true, it select only focus property. For
468     * validator is recommended to set it to false, while you want to validate
469     * properties of retrieved objects.
470     * 
471     * @param info
472     * @param session
473     * @return
474     * @throws HibernateException
475     */
476    private Query getQueryForFocus(ValueListInfo info, Session session) throws HibernateException, ParseException
477    {
478       if (isFocusOptimalization())
479       {
480          LOGGER.info("Focus will use optimalizated query.");
481          return getOptimizedQuery(info, session);
482       }
483       else
484       {
485          LOGGER.info("Focus will use normal (full) query.");
486          return getQuery(info, session);
487       }
488    }
489 
490    /***
491     * 
492     * @param info
493     * @param session
494     * @return query that select only focus property.
495     * @throws HibernateException
496     * @throws ParseException
497     */
498    private Query getOptimizedQuery(ValueListInfo info, Session session) throws HibernateException, ParseException
499    {
500       if (getHql() != null)
501       {
502          return getStatementBuilder().generateForFocus(session, new StringBuffer(getHql()), info.getFilters(), _isRemoveEmptyStrings,
503                defaultFocusPropertyObjectAlias, info.getFocusProperty());
504 
505       }
506       else
507       {
508          throw new HibernateException(
509                "Please define any HQL QUERY in value list retrieve adpater, function is not implemented for NamedQuery!");
510       }
511    }
512 
513    /*** Set the Hibernate SessionFactory to be used by this DAO.
514     * 
515     * @param sessionFactory The Hibernate SessionFactory to be used by this DAO.
516     */
517    public final void setSessionFactory(SessionFactory sessionFactory)
518    {
519       this.sessionFactory = sessionFactory;
520    }
521 
522    /*** Return the Hibernate SessionFactory used by this DAO.
523     * 
524     * @return The Hibernate SessionFactory used by this DAO.
525     */
526    protected final SessionFactory getSessionFactory()
527    {
528       return this.sessionFactory;
529    }
530 
531    /*** Sets the hql used to retrieve the data.
532     * @param query
533     *            The hql to set.
534     * @deprecated use setHql(String)
535     */
536    public void setHsql(String hql)
537    {
538       this.hql = hql;
539    }
540 
541    /*** Sets the hql used to retrieve the data.
542     * @param query
543     *            The hql to set.
544     */
545    public void setHql(String hql)
546    {
547       this.hql = hql;
548    }
549 
550    /*** Returns the namedQuery.
551     * @return Returns the namedQuery.
552     */
553    public String getNamedQuery()
554    {
555       return namedQuery;
556    }
557 
558    /*** Sets the namedQuery.  
559     * <p>
560     *   NOTE: by using this you can not enable sorting, filtering,
561     *   paging of the data, and  focusing of rows.
562     * </p>
563     * @param namedQuery
564     *            The namedQuery to set.
565     */
566    public void setNamedQuery(String namedQuery)
567    {
568       this.namedQuery = namedQuery;
569    }
570 
571    /*** Gets the hql used to retrieve the data.
572     * @return Returns the hql.
573     */
574    public String getHql()
575    {
576       return hql;
577    }
578 
579    /*** Sets: If a new Session should be created if no thread-bound found.
580     * 
581     * @param allowCreate
582     *            The allowCreate to set.
583     */
584    public void setAllowCreate(boolean allowCreate)
585    {
586       this.allowCreate = allowCreate;
587    }
588 
589    /***
590     * Maximum rows to search with Focus
591     * 
592     * @return Returns the maxRowsForFocus.
593     */
594    public long getMaxRowsForFocus()
595    {
596       return maxRowsForFocus;
597    }
598 
599    /***
600     * Maximum rows to search with Focus
601     * 
602     * @param maxRowsForFocus
603     *            The maxRowsForFocus to set.
604     */
605    public void setMaxRowsForFocus(long maxRowsForFocus)
606    {
607       this.maxRowsForFocus = maxRowsForFocus;
608    }
609 
610    /***
611     * @return Returns the defaultFocusPropertyObject.
612     */
613    public String getDefaultFocusPropertyObjectAlias()
614    {
615       return defaultFocusPropertyObjectAlias;
616    }
617 
618    /*** 
619     * The name of object use to get focus property in hibernate hql syntax 
620     * SELECT defaultFocusPropertyObjectAlias.getFocusProperty ...  
621     * 
622     * @param defaultFocusPropertyObjectAlias
623     *            The defaultFocusPropertyObjectAlias to set.
624     */
625    public void setDefaultFocusPropertyObjectAlias(String defaultFocusPropertyObjectAlias)
626    {
627       this.defaultFocusPropertyObjectAlias = defaultFocusPropertyObjectAlias + ".";
628    }
629 
630    /***
631     * @return Returns the statementBuilder.
632     */
633    public StatementBuilder getStatementBuilder()
634    {
635       if (statementBuilder == null)
636       {
637          statementBuilder = new StatementBuilder();
638       }
639       return statementBuilder;
640    }
641 
642    /***
643     * @param statementBuilder The statementBuilder to set.
644     */
645    public void setStatementBuilder(StatementBuilder statementBuilder)
646    {
647       this.statementBuilder = statementBuilder;
648    }
649 }