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
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 }