View Javadoc

1   /*
2    * The SmartWeb Framework
3    * Copyright (C) 2004-2006
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   *
19   * For further informations on the SmartWeb Framework please visit
20   *
21   *                        http://smartweb.sourceforge.net
22   */
23  package net.smartlab.web;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  
36  import net.smartlab.web.DataAccessObject.SearchInfo.Filter;
37  import net.smartlab.web.bean.ConversionException;
38  import net.smartlab.web.bean.ConverterManager;
39  import net.smartlab.web.config.FactoryConfigurationStrategy;
40  import net.smartlab.web.config.JNDIConfigurationStrategy;
41  
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.hibernate.Criteria;
45  import org.hibernate.FetchMode;
46  import org.hibernate.HibernateException;
47  import org.hibernate.Query;
48  import org.hibernate.Session;
49  import org.hibernate.SessionFactory;
50  import org.hibernate.Transaction;
51  import org.hibernate.criterion.Expression;
52  import org.hibernate.criterion.Order;
53  import org.hibernate.criterion.Projections;
54  import org.hibernate.impl.CriteriaImpl;
55  import org.hibernate.impl.SessionImpl;
56  import org.hibernate.metadata.ClassMetadata;
57  import org.hibernate.persister.entity.EntityPersister;
58  import org.hibernate.transform.ResultTransformer;
59  
60  /**
61   * This class provides basic template for an Hibernate based implementation of
62   * the DAO pattern.
63   * 
64   * @author rlogiacco,gperrone
65   * @uml.dependency supplier="net.smartlab.web.DataTransferObject"
66   * @uml.dependency 
67   *                 supplier="net.smartlab.web.config.FactoryConfigurationStrategy"
68   */
69  public abstract class BusinessObjectFactory implements DataAccessObject {
70  
71  	/**
72  	 * Provides logging capabilities to the factory.
73  	 */
74  	protected final Log logger = LogFactory.getLog(this.getClass());
75  
76  	/**
77  	 * Stores the Hibernate Session instance available for this request.
78  	 */
79  	private static final ThreadLocal sessions = new ThreadLocal();
80  
81  	/**
82  	 * Stores the Hibernate Transaction instance available for this request.
83  	 */
84  	private static final ThreadLocal transaction = new ThreadLocal();
85  
86  	/**
87  	 * Caches a reference to the Hibernate Session Factory to avoid further
88  	 * lookups.
89  	 */
90  	protected SessionFactory factory;
91  
92  	/**
93  	 * Strategy to retrieve the configuration file.
94  	 */
95  	private static FactoryConfigurationStrategy strategy = new JNDIConfigurationStrategy();
96  	static {
97  		try {
98  			String strategy = System.getProperty("smartweb.factory.strategy");
99  			if (strategy != null) {
100 				BusinessObjectFactory.strategy = (FactoryConfigurationStrategy)Class.forName(strategy).newInstance();
101 			} else {
102 				LogFactory.getLog(BusinessObjectFactory.class).warn(
103 						"No configuration found: falling back to default configuration");
104 			}
105 		} catch (Exception e) {
106 			LogFactory.getLog(BusinessObjectFactory.class).fatal("Error configuring SmartWeb", e);
107 		}
108 	}
109 
110 
111 	/**
112 	 * Changes the strategy used to configure the framework.
113 	 * 
114 	 * @param strategy an implementation of the
115 	 *        <code>FactoryConfigurationStrategy</code> interface.
116 	 */
117 	public static void setConfigurationStrategy(FactoryConfigurationStrategy strategy) {
118 		BusinessObjectFactory.strategy = strategy;
119 	}
120 
121 	/**
122 	 * Closes the connection to the persistence tier.
123 	 * 
124 	 * @throws DAOException if the persistence tier generates any error while
125 	 *         performing the resource release.
126 	 */
127 	public static void close() throws DAOException {
128 		Map sessions = (Map)BusinessObjectFactory.sessions.get();
129 		if (sessions != null) {
130 			Iterator iterator = sessions.values().iterator();
131 			while (iterator.hasNext()) {
132 				Session session = (Session)iterator.next();
133 				if (session != null && session.isOpen()) {
134 					try {
135 						session.flush();
136 						session.close();
137 					} catch (HibernateException he) {
138 						throw new DAOException("session.error.close", he);
139 					} finally {
140 						BusinessObjectFactory.sessions.set(null);
141 					}
142 				}
143 			}
144 		}
145 	}
146 
147 	/**
148 	 * @TODO documentation
149 	 * @throws DAOException
150 	 */
151 	public static void flush() throws DAOException {
152 		Map sessions = (Map)BusinessObjectFactory.sessions.get();
153 		if (sessions != null) {
154 			Iterator iterator = sessions.keySet().iterator();
155 			while (iterator.hasNext()) {
156 				Session session = (Session)sessions.get(iterator.next());
157 				if (session != null && session.isOpen()) {
158 					try {
159 						session.flush();
160 					} catch (HibernateException he) {
161 						throw new DAOException("session.error.close", he);
162 					} finally {
163 						BusinessObjectFactory.sessions.set(null);
164 					}
165 				}
166 			}
167 		}
168 	}
169 
170 	/**
171 	 * Protected default constructor to allow subclassing. Provides the startup
172 	 * initialization and the Hibernate Session Factory lookup. The Hibernate
173 	 * configuration file used must reside into the <code>META-INF</code>
174 	 * directory of the topmost packaging archive and should be named
175 	 * <code>smartweb.jar.hcf</code> or have the name of the JAR file containing
176 	 * the subclass followed by the <code>.hcf</code> suffix. TODO example
177 	 */
178 	protected BusinessObjectFactory() {
179 		this.factory = strategy.getSessionFactory(this);
180 	}
181 
182 	/**
183 	 * Returns the current Hibernate Session. If a Session was previously
184 	 * established during the same request the already established instance is
185 	 * returned otherwise a new instance is retrieved from the Session Factory.
186 	 * 
187 	 * @return the current connection to the persistence layer or a new one if
188 	 *         not previously established.
189 	 * @throws DAOException if an error occurs trying to establish the
190 	 *         connection.
191 	 */
192 	protected Session current() throws DAOException {
193 		if (logger.isDebugEnabled()) {
194 			logger.debug("current() - start");
195 		}
196 		Map sessions = (Map)BusinessObjectFactory.sessions.get();
197 		if (sessions == null) {
198 			sessions = new HashMap();
199 			BusinessObjectFactory.sessions.set(sessions);
200 		}
201 		try {
202 			Session session = (Session)sessions.get(factory);
203 			if (session == null) {
204 				try {
205 					session = factory.openSession();
206 					sessions.put(factory, session);
207 				} catch (Exception e) {
208 					throw new DAOException("session.error.config", e);
209 				}
210 			}
211 			return session;
212 		} catch (Exception e) {
213 			throw new DAOException("session.error.config.not-found", e);
214 		}
215 	}
216 
217 	/**
218 	 * @see net.smartlab.web.DataAccessObject#findByKey(java.io.Serializable)
219 	 */
220 	public Object findByKey(Serializable key) throws DAOException {
221 		if (logger.isDebugEnabled()) {
222 			logger.debug("findByKey(key = " + key + ") - start");
223 		}
224 		Serializable convertedKey = this.convertKey(key);
225 		if (convertedKey == null) {
226 			try {
227 				logger.debug("findByKey(key = " + key + ") - new instance");
228 				return this.getMappedClass().newInstance();
229 			} catch (Exception e) {
230 				logger.debug("findByKey(key = " + key + ") - instantiation failed", e);
231 				throw new DAOException("session.error.select", e);
232 			}
233 		} else {
234 			Session session = this.current();
235 			try {
236 				return session.get(this.getMappedClass(), convertedKey);
237 			} catch (HibernateException he) {
238 				logger.debug("findByKey(key = " + key + ") - deserialization failed", he);
239 				throw new UndefinedKeyException(key, this.getMappedClass(), he);
240 			}
241 		}
242 	}
243 
244 	/**
245 	 * @TODO documentation
246 	 * @param key
247 	 * @param fetch
248 	 * @return
249 	 * @throws DAOException
250 	 */
251 	public Object findByKey(Serializable key, String[] fetch) throws DAOException {
252 		if (logger.isDebugEnabled()) {
253 			logger.debug("findByKey(key = " + key + ", fetch = " + fetch + ") - start");
254 		}
255 		Serializable convertedKey = this.convertKey(key);
256 		if (convertedKey == null) {
257 			try {
258 				return this.getMappedClass().newInstance();
259 			} catch (Exception e) {
260 				logger.debug("findByKey(key = " + key + ") - instantiation failed", e);
261 				throw new DAOException("session.error.select", e);
262 			}
263 		} else {
264 			Session session = this.current();
265 			try {
266 				EntityPersister persister = ((SessionImpl)session).getFactory().getEntityPersister(
267 						this.getMappedClass().getName());
268 				Criteria criteria = session.createCriteria(this.getMappedClass());
269 				criteria.add(Expression.eq(persister.getIdentifierPropertyName(), convertedKey));
270 				if (fetch != null) {
271 					for (int i = 0; i < fetch.length; i++) {
272 						criteria.setFetchMode(fetch[i], FetchMode.JOIN);
273 					}
274 				}
275 				return criteria.uniqueResult();
276 			} catch (HibernateException he) {
277 				logger.debug("findByKey(key = " + key + ", fetch = " + fetch + ") - deserialization failed", he);
278 				throw new UndefinedKeyException(key, this.getMappedClass(), he);
279 			}
280 		}
281 	}
282 
283 	/**
284 	 * @see net.smartlab.web.DataAccessObject#remove(java.lang.Object)
285 	 */
286 	public void remove(Object object) throws DAOException {
287 		if (logger.isDebugEnabled()) {
288 			logger.debug("remove(object = " + object + ") - start");
289 		}
290 		try {
291 			this.current().delete(object);
292 		} catch (HibernateException he) {
293 			logger.debug("remove(object = " + object + ") - failed", he);
294 			throw new DAOException("session.error.remove", he);
295 		}
296 	}
297 
298 	/**
299 	 * @see net.smartlab.web.DataAccessObject#update(java.lang.Object)
300 	 */
301 	public void update(Object object) throws DAOException {
302 		if (logger.isDebugEnabled()) {
303 			logger.debug("update(object = " + object + ") - start");
304 		}
305 		try {
306 			this.current().saveOrUpdate(object);
307 		} catch (HibernateException he) {
308 			logger.debug("update(object = " + object + ") - failed", he);
309 			this.current().clear();
310 			throw new DAOException("session.error.update", he);
311 		}
312 	}
313 
314 	/**
315 	 * @see net.smartlab.web.DataAccessObject#list(net.smartlab.web.DataAccessObject.SearchInfo)
316 	 */
317 	public Collection list(SearchInfo info) throws DAOException {
318 		if (logger.isDebugEnabled()) {
319 			logger.debug("list(info = " + info + ") - start");
320 		}
321 		Criteria criteria = this.createCriteria(info);
322 		try {
323 			return criteria.list();
324 		} catch (HibernateException he) {
325 			logger.debug("list(info = " + info + ") - failed", he);
326 			throw new DAOException("session.error.search", he);
327 		}
328 	}
329 
330 	/**
331 	 * @TODO documentation
332 	 * @param info
333 	 * @param fetch
334 	 * @return
335 	 * @throws DAOException
336 	 */
337 	public Collection list(SearchInfo info, String[] fetch) throws DAOException {
338 		if (logger.isDebugEnabled()) {
339 			logger.debug("list(info = " + info + ", fetch = " + fetch + ") - start");
340 		}
341 		try {
342 			Criteria criteria = this.createCriteria(info);
343 			for (int i = 0; i < fetch.length; i++) {
344 				criteria.setFetchMode(fetch[i], FetchMode.SELECT);
345 			}
346 			return criteria.list();
347 		} catch (HibernateException he) {
348 			logger.debug("list(info = " + info + ", fetch = " + fetch + ") - deserialization failed", he);
349 			throw new DAOException("session.error.search", he);
350 		}
351 	}
352 
353 	/**
354 	 * Returns a collection of objects representing all the persistence tier
355 	 * informations matching the specified set of unique identification keys and
356 	 * search criterias.
357 	 * 
358 	 * @param keys the set of keys to be matched.
359 	 * @param info the additional criterias to be used to search the persistence
360 	 *        tier.
361 	 * @return a collection of matching entities.
362 	 * @throws DAOException if an error occur while accessing the persistence
363 	 *         tier
364 	 */
365 	public Collection listByKeySet(Set keys, SearchInfo info) throws DAOException {
366 		return this.listByKeySet(keys, info, false);
367 	}
368 	
369 	/**
370 	 * Returns a collection of objects representing all the persistence tier
371 	 * informations matching the specified set of unique identification keys and
372 	 * search criterias.
373 	 * 
374 	 * @param keyFieldName the fieldName to be matched for unique identification
375 	 *        key.
376 	 * @param keys the set of keys to be matched.
377 	 * @param info the additional criterias to be used to search the persistence
378 	 *        tier.
379 	 * @return a collection of matching entities.
380 	 * @throws DAOException if an error occur while accessing the persistence
381 	 *         tier
382 	 * @deprecated the keyFieldName parameter is no more needed
383 	 * @see #listByKeySet(java.util.Set, net.smartlab.web.DataAccessObject.SearchInfo, boolean)
384 	 */
385 	public Collection listByKeySet(String keyFieldName, Set keys, SearchInfo info) throws DAOException {
386 		return this.listByKeySet(keys, info, false);
387 	}
388 
389 	/**
390 	 * Returns a collection of objects representing all the persistence tier
391 	 * informations matching the specified set of unique identification keys and
392 	 * search criterias.
393 	 * 
394 	 * @param keys the set of keys to be matched.
395 	 * @param info the additional criterias to be used to search the persistence
396 	 *        tier.
397 	 * 
398 	 * @param exclude if the keys should be included or excluded from the list.
399 	 * @return a collection of matching entities.
400 	 * @throws DAOException if an error occur while accessing the persistence
401 	 *         tier
402 	 */
403 	public Collection listByKeySet(Set keys, SearchInfo info, boolean exclude) throws DAOException {
404 		if (logger.isDebugEnabled()) {
405 			logger.debug("list(keys = " + keys + ", info = " + info + ", exclude = " + exclude + ") - start");
406 		}
407 		Criteria criteria = this.createCriteria(info);
408 		String identifier = 
409 			factory.getClassMetadata(this.getMappedClass()).getIdentifierPropertyName();
410 		keys = this.convertKeys(keys);
411 		if (identifier != null && !keys.isEmpty()) {
412 			if (exclude) {
413 				criteria.add(Expression.not(Expression.in(identifier, keys)));
414 			} else {
415 				criteria.add(Expression.in(identifier, keys));
416 			}
417 		}
418 		try {
419 			return criteria.list();
420 		} catch (HibernateException he) {
421 			throw new DAOException("session.error.search", he);
422 		}
423 	}
424 
425 	/**
426 	 * Returns a paginable collection of objects representing all the
427 	 * persistence tier informations matching the specified search criterias.
428 	 * 
429 	 * @param info the criterias to be used filter the instances.
430 	 * @return a paginable collection of objects matching the specified search
431 	 *         criterias.
432 	 * @throws DAOException if an error occur while accessing the persistence
433 	 *         tier
434 	 */
435 	public Collection page(SearchInfo info) throws DAOException {
436 		if (logger.isDebugEnabled()) {
437 			logger.debug("page(info = " + info + ") - start");
438 		}
439 		Criteria criteria = this.createCriteria(info);
440 		try {
441 			return new Paginator(criteria);
442 		} catch (HibernateException he) {
443 			logger.debug("page(info = " + info + ") - error", he);
444 			throw new DAOException("session.error.paging", he);
445 		}
446 	}
447 
448 	/**
449 	 * @TODO documentation
450 	 * @param info
451 	 * @param fetch
452 	 * @return
453 	 * @throws DAOException
454 	 */
455 	public Collection page(SearchInfo info, String[] fetch) throws DAOException {
456 		if (logger.isDebugEnabled()) {
457 			logger.debug("page(info = " + info + ", fetch = " + fetch + ") - start");
458 		}
459 		try {
460 			Criteria criteria = this.createCriteria(info);
461 			for (int i = 0; i < fetch.length; i++) {
462 				criteria.setFetchMode(fetch[i], FetchMode.SELECT);
463 			}
464 			return new Paginator(criteria);
465 		} catch (HibernateException he) {
466 			logger.debug("page(info = " + info + ", fetch = " + fetch + ") - deserialization failed", he);
467 			throw new DAOException("session.error.paging", he);
468 		}
469 	}
470 
471 	/**
472 	 * TODO documentation
473 	 * 
474 	 * @return
475 	 */
476 	public abstract Class getMappedClass();
477 
478 	/**
479 	 * Converts a generic serializable object to the type used as unique key
480 	 * identifier for this type of BusinessObject. The default implementation
481 	 * converts String or Number to Long using the <code>o</code> value to
482 	 * identify non-persisted instances. If your business definition requires a
483 	 * different conversion you should override this method to provide your own
484 	 * conversion strategy.
485 	 * 
486 	 * @param key the generic serializable key that needs to be converted.
487 	 * @return the key representation using the correct type or
488 	 *         <code>null</code> if the providen key doesn't represent a valid
489 	 *         identifier for a persisted BusinessObject.
490 	 */
491 	public Serializable convertKey(Serializable key) {
492 		if (key != null) {
493 			if (key instanceof String) {
494 				key = ((String)key).trim();
495 				if (((String)key).length() == 0 || key.equals("0")) {
496 					return null;
497 				} else {
498 					return new Long((String)key);
499 				}
500 			} else if (key instanceof Number) {
501 				if (((Number)key).longValue() == 0) {
502 					return null;
503 				} else {
504 					return new Long(((Number)key).toString());
505 				}
506 			}
507 		}
508 		return key;
509 	}
510 
511 	/**
512 	 * Converts a generic set of serializable objects to a set containing
513 	 * objects of the type used as unique key identifier for this type of
514 	 * BusinessObject using the <code>convertKey()</code> method.
515 	 * 
516 	 * @param keys the <code>java.util.Set</code> of serializable objects to be
517 	 *        converted.
518 	 * @return a <code>java.util.Set</code> containing appropriately converted
519 	 *         keys.
520 	 */
521 	public Set convertKeys(Collection keys) {
522 		if (keys == null) {
523 			return Collections.EMPTY_SET;
524 		}
525 		Set converted = new HashSet(keys.size());
526 		Iterator iterator = keys.iterator();
527 		while (iterator.hasNext()) {
528 			converted.add(this.convertKey((Serializable)iterator.next()));
529 		}
530 		return converted;
531 	}
532 
533 	/**
534 	 * Creates an Hibernate <code>Criteria</code> instance using filtering and
535 	 * ordering rules defined through the <code>SearchInfo</code> structure.
536 	 * 
537 	 * @param info
538 	 * @return
539 	 * @throws DAOException
540 	 */
541 	public Criteria createCriteria(SearchInfo info) throws DAOException {
542 		if (logger.isDebugEnabled()) {
543 			logger.debug("createCriteria(info = " + info + ") - start");
544 		}
545 		Session session = this.current();
546 		Criteria criteria = session.createCriteria(this.getMappedClass());
547 		if (info != null) {
548 			ClassMetadata metadata = session.getSessionFactory().getClassMetadata(this.getMappedClass());
549 			ConverterManager converter = ConverterManager.getDefault();
550 			if (info.isUnion()) {
551 				// TODO the filters are in OR
552 			}
553 			Iterator filters = info.getFilters().iterator();
554 			while (filters.hasNext()) {
555 				Filter filter = (Filter)filters.next();
556 				if (logger.isTraceEnabled()) {
557 					logger.trace("createCriteria(info = " + info + ") - parsing filter `" + filter + "`");
558 				}
559 				Class type = metadata.getPropertyType(filter.getProperty()).getReturnedClass();
560 				try {
561 					switch (filter.getCondition()) {
562 						case SearchInfo.EQUALS:
563 							for (int i = 0; i < filter.getValues().length; i++) {
564 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
565 								criteria.add(Expression.eq(filter.getProperty(), value));
566 							}
567 							break;
568 						case SearchInfo.GREATER:
569 							for (int i = 0; i < filter.getValues().length; i++) {
570 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
571 								criteria.add(Expression.gt(filter.getProperty(), value));
572 							}
573 							break;
574 						case SearchInfo.GREATER_EQUALS:
575 							for (int i = 0; i < filter.getValues().length; i++) {
576 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
577 								criteria.add(Expression.ge(filter.getProperty(), value));
578 							}
579 							break;
580 						case SearchInfo.LESSER:
581 							for (int i = 0; i < filter.getValues().length; i++) {
582 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
583 								criteria.add(Expression.lt(filter.getProperty(), value));
584 							}
585 							break;
586 						case SearchInfo.LESSER_EQUALS:
587 							for (int i = 0; i < filter.getValues().length; i++) {
588 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
589 								criteria.add(Expression.le(filter.getProperty(), value));
590 							}
591 							break;
592 						case SearchInfo.NOT_EQUALS:
593 							for (int i = 0; i < filter.getValues().length; i++) {
594 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
595 								criteria.add(Expression.ne(filter.getProperty(), value));
596 							}
597 							break;
598 						case SearchInfo.BETWEEN:
599 							Object start = converter.convert(type, filter.getValue(0), info.getLocale());
600 							Object stop = converter.convert(type, filter.getValue(1), info.getLocale());
601 							criteria.add(Expression.between(filter.getProperty(), start, stop));
602 							break;
603 						case SearchInfo.IN: {
604 							Collection values = new ArrayList();
605 							for (int i = 0; i < filter.getValues().length; i++) {
606 								Object value = converter.convert(type, filter.getValue(i),
607 										info.getLocale());
608 								if (value != null) {
609 									values.add(value);
610 								}
611 							}
612 							if (!values.isEmpty()) {
613 								criteria.add(Expression.in(filter.getProperty(), values));
614 							}
615 							break;
616 						}
617 						case SearchInfo.NOT_IN: {
618 							Collection values = new ArrayList();
619 							for (int i = 0; i < filter.getValues().length; i++) {
620 								Object value = converter.convert(type, filter.getValue(i),
621 										info.getLocale());
622 								if (value != null) {
623 									values.add(value);
624 								}
625 							}
626 							if (!values.isEmpty()) {
627 								criteria.add(Expression.not(Expression.in(filter.getProperty(), values)));
628 							}
629 							break;
630 						}
631 						case SearchInfo.LIKE:
632 							for (int i = 0; i < filter.getValues().length; i++) {
633 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
634 								criteria.add(Expression.like(filter.getProperty(), value));
635 							}
636 							break;
637 						case SearchInfo.ILIKE:
638 							for (int i = 0; i < filter.getValues().length; i++) {
639 								Object value = converter.convert(type, filter.getValue(i), info.getLocale());
640 								criteria.add(Expression.ilike(filter.getProperty(), value));
641 							}
642 							break;
643 						case SearchInfo.NULL:
644 							criteria.add(Expression.isNull(filter.getProperty()));
645 							break;
646 						case SearchInfo.NOT_NULL:
647 							criteria.add(Expression.isNotNull(filter.getProperty()));
648 							break;
649 					}
650 				} catch (ConversionException ce) {
651 					logger.warn("Unable to convert filter `" + filter + "`", ce);
652 				}
653 			}
654 			if (info.getOrder() != null && info.getOrder().length() > 0) {
655 				if (info.isDescendant()) {
656 					criteria.addOrder(Order.desc(info.getOrder()));
657 				} else {
658 					criteria.addOrder(Order.asc(info.getOrder()));
659 				}
660 			}
661 		}
662 		return criteria;
663 	}
664 
665 	/**
666 	 * TODO documentation
667 	 * 
668 	 * @throws DAOException
669 	 */
670 	public void begin() throws DAOException {
671 		if (logger.isDebugEnabled()) {
672 			logger.debug("begin() - start");
673 		}
674 		if (BusinessObjectFactory.transaction.get() == null) {
675 			try {
676 				BusinessObjectFactory.transaction.set(this.current().beginTransaction());
677 			} catch (HibernateException he) {
678 				logger.debug("begin() - error", he);
679 				throw new DAOException("session.error.begin", he);
680 			}
681 		} else {
682 			logger.warn("begin() - transaction already started");
683 		}
684 	}
685 
686 	/**
687 	 * TODO documentation
688 	 * 
689 	 * @throws DAOException
690 	 */
691 	public void commit() throws DAOException {
692 		if (logger.isDebugEnabled()) {
693 			logger.debug("commit() - start");
694 		}
695 		Transaction transaction = (Transaction)BusinessObjectFactory.transaction.get();
696 		try {
697 			transaction.commit();
698 		} catch (HibernateException he) {
699 			logger.debug("commit() - error", he);
700 			throw new DAOException("session.error.commit", he);
701 		} catch (NullPointerException npe) {
702 			logger.debug("commit() - transaction never started");
703 			throw new DAOException("session.error.commit");
704 		} finally {
705 			BusinessObjectFactory.transaction.set(null);
706 		}
707 	}
708 
709 	/**
710 	 * TODO documentation
711 	 * 
712 	 * @throws DAOException
713 	 */
714 	public void rollback() throws DAOException {
715 		if (logger.isDebugEnabled()) {
716 			logger.debug("rollback() - start");
717 		}
718 		Transaction transaction = (Transaction)BusinessObjectFactory.transaction.get();
719 		try {
720 			transaction.rollback();
721 		} catch (HibernateException he) {
722 			logger.debug("rollback() - error", he);
723 			throw new DAOException("session.error.rollback", he);
724 		} catch (NullPointerException npe) {
725 			logger.warn("rollback() - transaction never started", npe);
726 		} finally {
727 			BusinessObjectFactory.transaction.set(null);
728 		}
729 	}
730 
731 
732 	/**
733 	 * TODO documentation
734 	 */
735 	public class Paginator extends net.smartlab.web.page.Paginator implements Collection {
736 
737 		/**
738 		 * Logger for this class
739 		 */
740 		private final Log logger = LogFactory.getLog(Paginator.class);
741 
742 		/**
743 		 * References the Hibernate query or criteria passed to the constructor.
744 		 * It cannot be null.
745 		 */
746 		private Object statement;
747 
748 
749 		/**
750 		 * Constructs an instance on an Hibernate criteria with an unlimited
751 		 * number of items per page and an unlimited number of pages displayed.
752 		 * 
753 		 * @param criteria the Hibernate criteria whose results must be
754 		 *        paginated.
755 		 * @throws HibernateException if something wrong occurs accessing the
756 		 *         database.
757 		 */
758 		public Paginator(Criteria criteria) throws HibernateException {
759 			this(criteria, Paginator.UNLIMITED_ITEMS, Paginator.UNLIMITED_PAGES);
760 		}
761 
762 		/**
763 		 * Constructs an instance on an Hibernate criteria with the specified
764 		 * number of elements per page and the specified number of pages
765 		 * displayed.
766 		 * 
767 		 * @param criteria the Hibernate criteria whose results must be
768 		 *        paginated.
769 		 * @param size the number of elements per page.
770 		 * @param pages the number of pages to be displayed for quick
771 		 *        navigation.
772 		 * @throws HibernateException if something wrong occurs accessing the
773 		 *         database.
774 		 */
775 		public Paginator(Criteria criteria, int size, int pages) throws HibernateException {
776 			super(size, pages);
777 			this.statement = criteria;
778 			ResultTransformer rt = ((CriteriaImpl)criteria).getResultTransformer();
779 			List temp = new ArrayList();
780 			Iterator orderings = ((CriteriaImpl)criteria).iterateOrderings();
781 			while (orderings.hasNext()) {
782 				Order ordering = ((CriteriaImpl.OrderEntry)orderings.next()).getOrder();
783 				orderings.remove();
784 				temp.add(ordering);
785 			}
786 			criteria.setProjection(Projections.rowCount());
787 			this.setCount(((Integer)criteria.uniqueResult()).intValue());
788 			criteria.setProjection(null);
789 			if (rt == null) {
790 				rt = Criteria.ROOT_ENTITY;
791 			}
792 			criteria.setResultTransformer(rt);
793 			orderings = temp.iterator();
794 			while (orderings.hasNext()) {
795 				criteria.addOrder((Order)orderings.next());
796 			}
797 			this.setSize(size);
798 		}
799 
800 		/**
801 		 * Constructs an instance on an Hibernate query with an unlimited number
802 		 * of items per page and an unlimited number of pages displayed.
803 		 * 
804 		 * @param query the Hibernate query whose results must be paginated.
805 		 * @throws DAOException if something wrong occurs accessing the
806 		 *         underling data layer.
807 		 * @throws HibernateException if something wrong occurs accessing the
808 		 *         database.
809 		 */
810 		public Paginator(Query query) throws DAOException, HibernateException {
811 			this(query, Paginator.UNLIMITED_ITEMS, Paginator.UNLIMITED_PAGES);
812 		}
813 
814 		/**
815 		 * Constructs an instance on an Hibernate query with the specified
816 		 * number of elements per page and the specified number of pages
817 		 * displayed. FIXME the computation of rowCount is still slow on long
818 		 * lists.
819 		 * 
820 		 * @param query the Hibernate query whose results must be paginated.
821 		 * @param size the number of elements per page.
822 		 * @param pages the number of pages to be displayed for quick
823 		 *        navigation.
824 		 * @throws DAOException if something wrong occurs accessing the
825 		 *         underling data layer.
826 		 * @throws HibernateException if something wrong occurs accessing the
827 		 *         database.
828 		 */
829 		public Paginator(Query query, int size, int pages) throws DAOException, HibernateException {
830 			super(size, pages);
831 			this.statement = query;
832 			int queryCount = query.count();
833 			this.setCount(queryCount);
834 			this.setSize(size);
835 		}
836 
837 		/**
838 		 * @see net.smartlab.web.page.Paginator#setPage(int)
839 		 */
840 		public void setPage(int page) {
841 			if (logger.isDebugEnabled()) {
842 				logger.debug("setPage(page = " + page + ") - start");
843 			}
844 			if (statement instanceof Criteria) {
845 				((Criteria)this.statement).setFirstResult((page - 1) * super.getPageSize());
846 			} else {
847 				((Query)this.statement).setFirstResult((page - 1) * super.getPageSize());
848 			}
849 			super.setPage(page);
850 		}
851 
852 		/**
853 		 * TODO documentation
854 		 * 
855 		 * @param size
856 		 */
857 		public void setSize(int size) {
858 			if (logger.isDebugEnabled()) {
859 				logger.debug("setSize(size = " + size + ") - start");
860 			}
861 			if (size != UNLIMITED_ITEMS) {
862 				super.setPageSize(size);
863 				if (statement instanceof Criteria) {
864 					((Criteria)this.statement).setMaxResults(size);
865 				} else {
866 					((Query)this.statement).setMaxResults(size);
867 				}
868 			} else {
869 				super.setPageSize(this.getCount());
870 			}
871 		}
872 
873 		/**
874 		 * @see net.smartlab.web.page.Paginator#setArray()
875 		 */
876 		protected void setArray() {
877 			if (logger.isDebugEnabled()) {
878 				logger.debug("setArray() - start");
879 			}
880 			try {
881 				Iterator elements = null;
882 				if (statement instanceof Criteria) {
883 					elements = ((Criteria)this.statement).list().iterator();
884 				} else {
885 					elements = ((Query)this.statement).list().iterator();
886 				}
887 				for (int i = 0; i < array.length && elements.hasNext(); i++) {
888 					array[i] = elements.next();
889 				}
890 			} catch (HibernateException he) {
891 				logger.error("setArray() - error", he);
892 			}
893 		}
894 
895 		/**
896 		 * @see java.util.Collection#add(java.lang.Object)
897 		 */
898 		public boolean add(Object obj) {
899 			throw new UnsupportedOperationException();
900 		}
901 
902 		/**
903 		 * @see java.util.Collection#addAll(java.util.Collection)
904 		 */
905 		public boolean addAll(Collection collection) {
906 			throw new UnsupportedOperationException();
907 		}
908 
909 		/**
910 		 * @see java.util.Collection#clear()
911 		 */
912 		public void clear() {
913 			throw new UnsupportedOperationException();
914 		}
915 
916 		/**
917 		 * @see java.util.Collection#contains(java.lang.Object)
918 		 */
919 		public boolean contains(Object item) {
920 			if (logger.isDebugEnabled()) {
921 				logger.debug("contains(item = " + item + ") - start");
922 			}
923 			for (int i = 0; i < array.length; i++) {
924 				if (array[i].equals(item)) {
925 					return true;
926 				}
927 			}
928 			return false;
929 		}
930 
931 		/**
932 		 * @see java.util.Collection#containsAll(java.util.Collection)
933 		 */
934 		public boolean containsAll(Collection item) {
935 			throw new UnsupportedOperationException();
936 		}
937 
938 		/**
939 		 * @see java.util.Collection#isEmpty()
940 		 */
941 		public boolean isEmpty() {
942 			return super.getCount() == 0;
943 		}
944 
945 		/**
946 		 * @see java.util.Collection#iterator()
947 		 */
948 		public Iterator iterator() {
949 			return this;
950 		}
951 
952 		/**
953 		 * @see java.util.Collection#remove(java.lang.Object)
954 		 */
955 		public boolean remove(Object obj) {
956 			throw new UnsupportedOperationException();
957 		}
958 
959 		/**
960 		 * @see java.util.Collection#removeAll(java.util.Collection)
961 		 */
962 		public boolean removeAll(Collection collection) {
963 			throw new UnsupportedOperationException();
964 		}
965 
966 		/**
967 		 * @see java.util.Collection#retainAll(java.util.Collection)
968 		 */
969 		public boolean retainAll(Collection collection) {
970 			throw new UnsupportedOperationException();
971 		}
972 
973 		/**
974 		 * @see java.util.Collection#size()
975 		 */
976 		public int size() {
977 			return super.getCount();
978 		}
979 
980 		/**
981 		 * @see java.util.Collection#toArray()
982 		 */
983 		public Object[] toArray() {
984 			return array;
985 		}
986 
987 		/**
988 		 * @see java.util.Collection#toArray(java.lang.Object[])
989 		 */
990 		public Object[] toArray(Object[] array) {
991 			return this.array;
992 		}
993 	}
994 }