View Javadoc

1   package net.sf.r4h;
2   
3   import static net.sf.r4h.Restrictions.equal;
4   
5   import java.beans.Introspector;
6   import java.beans.PropertyDescriptor;
7   import java.io.Serializable;
8   import java.sql.Types;
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  import java.util.Collection;
12  import java.util.Collections;
13  import java.util.HashMap;
14  import java.util.HashSet;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  
19  import net.sf.r4h.Restrictions.CaseAwareRestriction;
20  import net.sf.r4h.Restrictions.EmptinessAwareRestriction;
21  import net.sf.r4h.Restrictions.ExampleRestriction;
22  
23  import org.hibernate.Criteria;
24  import org.hibernate.HibernateException;
25  import org.hibernate.PropertyAccessException;
26  import org.hibernate.Query;
27  import org.hibernate.Session;
28  import org.hibernate.SessionFactory;
29  import org.hibernate.criterion.Conjunction;
30  import org.hibernate.criterion.CriteriaQuery;
31  import org.hibernate.criterion.Criterion;
32  import org.hibernate.criterion.Example;
33  import org.hibernate.criterion.LikeExpression;
34  import org.hibernate.criterion.Projections;
35  import org.hibernate.criterion.SimpleExpression;
36  import org.hibernate.engine.spi.SessionFactoryImplementor;
37  import org.hibernate.internal.SessionFactoryImpl;
38  import org.hibernate.persister.entity.EntityPersister;
39  import org.hibernate.sql.JoinType;
40  import org.hibernate.tuple.NonIdentifierAttribute;
41  import org.hibernate.tuple.StandardProperty;
42  import org.hibernate.type.CollectionType;
43  import org.hibernate.type.ManyToOneType;
44  import org.hibernate.type.Type;
45  
46  /**
47   * /**
48   * 
49   * The goal of this class is to provide API that allows to write common CRUD operations in ONE line of code.
50   * 
51   * In addition to it's methods it provides paging, improved ordering (see {@link Order}) and restriction (see {@link Restrictions})
52   * 
53   * In methods with paging pages numbers start from 1. By default Restriction is {@link Restrictions#EQUAL}
54   * 
55   * @author vitaliy.se@gmail.com
56   * @since 1.0.0
57   * @version 4.0.0
58   * 
59   * 
60   * @param <E>
61   *            a type of entity
62   * @param <I>
63   *            a type of identity key
64   */
65  public class HibernateRepository<E, I extends Serializable> {
66  
67  	private static final Partition ALL_IN_ONE_PARTITION = new Partition(0, Integer.MAX_VALUE);
68  	private final SessionFactory sessionFactory;
69  	private final Class<E> entityClass;
70  
71  	/**
72  	 * 
73  	 * @param sessionFactory
74  	 *            - a Hibernate's SessionFactory which implements org.hibernate.engine.spi.SessionFactoryImplementor
75  	 * @param entityClass
76  	 *            - an entity class for repository
77  	 */
78  	public HibernateRepository(SessionFactory sessionFactory, Class<E> entityClass) {
79  		if (sessionFactory != null && !(sessionFactory instanceof SessionFactoryImpl)) {
80  			throw new IllegalArgumentException("SessionFactory is not instance of " + SessionFactoryImpl.class.getCanonicalName());
81  		}
82  		this.sessionFactory = sessionFactory;
83  		this.entityClass = entityClass;
84  	}
85  
86  	public List<E> list(ExampleRestriction restriction, E exampleObject, Page page, Order... orderStrategies) {
87  		return list("", restriction, exampleObject, pageToPartition(page), orderStrategies);
88  	}
89  
90  	public List<E> list(ExampleRestriction restriction, E exampleObject, Partition partition, Order... orderStrategies) {
91  		return list("", restriction, exampleObject, partition, orderStrategies);
92  	}
93  
94  	public List<E> list(E exampleObject, Page page, Order... orderStrategies) {
95  		return list(Restrictions.example(), exampleObject, pageToPartition(page), orderStrategies);
96  	}
97  
98  	public List<E> list(E exampleObject, Partition partition, Order... orderStrategies) {
99  		return list(Restrictions.example(), exampleObject, partition, orderStrategies);
100 	}
101 
102 	@SuppressWarnings("unchecked")
103 	public List<E> list(ExampleRestriction restriction, Object exampleObject, Order... orderStrategies) {
104 		return list(restriction, (E) exampleObject, ALL_IN_ONE_PARTITION, orderStrategies);
105 	}
106 
107 	public List<E> list(E exampleObject, Order... orderStrategies) {
108 		return list(Restrictions.example(), exampleObject, orderStrategies);
109 	}
110 
111 	public Long count(ExampleRestriction restriction, E exampleObject) {
112 		return count("", restriction, exampleObject);
113 	}
114 
115 	public Long count(E exampleObject) {
116 		return count(Restrictions.example(), exampleObject);
117 	}
118 
119 	public List<E> list(String[] properties, Restrictions.Restriction[] restrictions, Object[] values, Page page, Order... orderStrategies) {
120 		return execute(new CriteriaHibernateCallback<E>(entityClass, properties, restrictions, values, pageToPartition(page), getSessionFactoryImplementor()).addOrder(orderStrategies));
121 	}
122 
123 	public List<E> list(String[] properties, Restrictions.Restriction[] restrictions, Object[] values, Partition partition, Order... orderStrategies) {
124 		return execute(new CriteriaHibernateCallback<E>(entityClass, properties, restrictions, values, partition, getSessionFactoryImplementor()).addOrder(orderStrategies));
125 	}
126 
127 	public List<E> list(String[] properties, Restrictions.Restriction[] restrictions, Object[] values, Order... orderStrategies) {
128 		return list(properties, restrictions, values, ALL_IN_ONE_PARTITION, orderStrategies);
129 	}
130 
131 	public Long count(String[] properties, Restrictions.Restriction[] restrictions, Object[] values) {
132 		return execute(new CriteriaHibernateCallback<E>(entityClass, properties, restrictions, values, getSessionFactoryImplementor()).setReturnCount());
133 	}
134 
135 	public List<E> list(String property, Restrictions.Restriction restriction, Object value, Page page, Order... orderStrategies) {
136 		return list(new String[] { property }, new Restrictions.Restriction[] { restriction }, new Object[] { value }, pageToPartition(page), orderStrategies);
137 	}
138 
139 	public List<E> list(String property, Restrictions.Restriction restriction, Object value, Partition partition, Order... orderStrategies) {
140 		return list(new String[] { property }, new Restrictions.Restriction[] { restriction }, new Object[] { value }, partition, orderStrategies);
141 	}
142 
143 	public List<E> list(String property, Restrictions.Restriction restriction, Object value, Order... orderStrategies) {
144 		return list(new String[] { property }, new Restrictions.Restriction[] { restriction }, new Object[] { value }, orderStrategies);
145 	}
146 
147 	public Long count(String property, Restrictions.Restriction restriction, Object value) {
148 		return count(new String[] { property }, new Restrictions.Restriction[] { restriction }, new Object[] { value });
149 	}
150 
151 	public List<E> list(String property, Object value, Page page, Order... orderStrategies) {
152 		return list(property, equal(), value, pageToPartition(page), orderStrategies);
153 	}
154 
155 	public List<E> list(String property, Object value, Partition partition, Order... orderStrategies) {
156 		return list(property, equal(), value, partition, orderStrategies);
157 	}
158 
159 	public List<E> list(String property, Object value, Order... orderStrategies) {
160 		return list(property, value, ALL_IN_ONE_PARTITION, orderStrategies);
161 	}
162 
163 	public Long count(String property, Object value) {
164 		return count(property, equal(), value);
165 	}
166 
167 	public List<E> list(String[] properties, Object[] values, Page page, Order... orderStrategies) {
168 		return list(properties, HibernateRepository.createArrayOfEqualRestrictions(properties.length), values, pageToPartition(page), orderStrategies);
169 	}
170 
171 	public List<E> list(String[] properties, Object[] values, Partition partition, Order... orderStrategies) {
172 		return list(properties, HibernateRepository.createArrayOfEqualRestrictions(properties.length), values, partition, orderStrategies);
173 	}
174 
175 	public List<E> list(String[] properties, Object[] values, Order... orderStrategies) {
176 		return list(properties, HibernateRepository.createArrayOfEqualRestrictions(properties.length), values, orderStrategies);
177 	}
178 
179 	public Long count(String[] properties, Object[] values) {
180 		return count(properties, HibernateRepository.createArrayOfEqualRestrictions(properties.length), values);
181 	}
182 
183 	public List<E> list(Page page, Order... orderStrategies) {
184 		return list(HibernateRepositoryUtils.properties(), HibernateRepositoryUtils.values(), pageToPartition(page), orderStrategies);
185 	}
186 
187 	public List<E> list(Partition partition, Order... orderStrategies) {
188 		return list(HibernateRepositoryUtils.properties(), HibernateRepositoryUtils.values(), partition, orderStrategies);
189 	}
190 
191 	public List<E> list(Order... orderStrategies) {
192 		return list(ALL_IN_ONE_PARTITION, orderStrategies);
193 	}
194 
195 	/**
196 	 * @return count of all entities of the entityType specified in constructor
197 	 */
198 	public Long count() {
199 		return execute(new CriteriaHibernateCallback<E>(entityClass).setReturnCount());
200 	}
201 
202 	@SuppressWarnings("unchecked")
203 	public E get(I id) {
204 		return (E) sessionFactory.getCurrentSession().get(entityClass, id);
205 	}
206 
207 	public boolean contains(E entity) {
208 		return sessionFactory.getCurrentSession().contains(entity);
209 	}
210 
211 	@SuppressWarnings("unchecked")
212 	public <T> List<T> listByHql(String hql, String[] namedParams, Object[] values) {
213 		Query createQuery = sessionFactory.getCurrentSession().createQuery(hql);
214 		for (int i = 0; i < values.length; i++) {
215 			createQuery.setParameter(namedParams[i], values[i]);
216 		}
217 		return createQuery.list();
218 	}
219 
220 	@SuppressWarnings("unchecked")
221 	public <T> List<T> listByHql(String hql) {
222 		return sessionFactory.getCurrentSession().createQuery(hql).list();
223 	}
224 
225 	@SuppressWarnings("unchecked")
226 	public <T> List<T> listByHql(String hql, Object... values) {
227 		Query createQuery = sessionFactory.getCurrentSession().createQuery(hql);
228 		for (int i = 0; i < values.length; i++) {
229 			createQuery.setParameter(i, values[i]);
230 		}
231 		return createQuery.list();
232 	}
233 
234 	@SuppressWarnings("unchecked")
235 	public E unique(String[] properties, Restrictions.Restriction[] restrictions, Object[] values) {
236 		// this cast fixes the compiler error: Restriction parameters of <T>T cannot be determined; no unique maximal instance exists for Restriction variable T with upper bounds T,java.lang.Object
237 		return (E) execute(new CriteriaHibernateCallback<E>(entityClass, properties, restrictions, values, getSessionFactoryImplementor()).setUnique());
238 	}
239 
240 	public E unique(String[] properties, Object[] values) {
241 		return unique(properties, HibernateRepository.createArrayOfEqualRestrictions(properties.length), values);
242 	}
243 
244 	public E unique(String propertyName, Restrictions.Restriction restrictions, Object propertyValue) {
245 		return unique(new String[] { propertyName }, HibernateRepositoryUtils.restrictions(restrictions), new Object[] { propertyValue });
246 	}
247 
248 	public E unique(String propertyName, Object propertyValue) {
249 		return unique(propertyName, equal(), propertyValue);
250 	}
251 
252 	public Session getCurrentSession() {
253 		return sessionFactory.getCurrentSession();
254 	}
255 
256 	@SuppressWarnings("unchecked")
257 	public E load(I id) {
258 		return (E) getCurrentSession().load(entityClass, id);
259 	}
260 
261 	@SuppressWarnings("unchecked")
262 	public List<E> loadAll() {
263 		return getCurrentSession().createCriteria(entityClass).list();
264 	}
265 
266 	public E saveOrUpdate(E entity) {
267 		getCurrentSession().saveOrUpdate(entity);
268 		return entity;
269 	}
270 
271 	/**
272 	 * Saves or updates entity and flushes session, this allows to use saved entity id
273 	 * 
274 	 * @param entity
275 	 * @return saves entity
276 	 */
277 	public E saveOrUpdateAndFlush(E entity) {
278 		entity = saveOrUpdate(entity);
279 		getCurrentSession().flush();
280 		return entity;
281 	}
282 
283 	public E save(E entity) {
284 		getCurrentSession().save(entity);
285 		return entity;
286 	}
287 
288 	/**
289 	 * Saves entity and flushes session, this allows to use saved entity id
290 	 * 
291 	 * @param entity
292 	 * @return saves entity
293 	 */
294 	public E saveAndFlush(E entity) {
295 		entity = save(entity);
296 		getCurrentSession().flush();
297 		return entity;
298 	}
299 
300 	public E merge(E entity) {
301 		getCurrentSession().merge(entity);
302 		return entity;
303 	}
304 
305 	public E delete(E entity) {
306 		getCurrentSession().delete(entity);
307 		return entity;
308 	}
309 
310 	public E delete(Serializable id) {
311 		@SuppressWarnings("unchecked")
312 		E entity = (E) getCurrentSession().load(entityClass, id);
313 		getCurrentSession().delete(entity);
314 		return entity;
315 	}
316 
317 	/**
318 	 * Deletes all entities of given entityName
319 	 * 
320 	 * @param entityName
321 	 * @return number of deleted entities
322 	 */
323 	public int deleteAll(String entityName) {
324 		return getCurrentSession().createQuery("delete from " + entityName).executeUpdate();
325 	}
326 
327 	/**
328 	 * Deletes all entities of given entityClass
329 	 * 
330 	 * @return number of deleted entities
331 	 */
332 	public int deleteAll() {
333 		return deleteAll(entityClass.getName());
334 	}
335 
336 	/**
337 	 * First fetches all entities and than deletes it one by one. Is needed to correctly delete graphs of objects since it deletes references and unlike {@link HibernateRepository#deleteAll()} avoids constraint violation
338 	 * WARNING: This method is useful in tests, but seldom needed on production since it is very inefficient.
339 	 * 
340 	 * @return
341 	 */
342 	public List<E> deleteAllPreFetched() {
343 		List<E> entities = list();
344 		for (E e : entities) {
345 			delete(e);
346 		}
347 		return entities;
348 	}
349 
350 	@SuppressWarnings("unchecked")
351 	protected <T> T execute(CriteriaHibernateCallback<?> criteriaHibernateCallback) {
352 		return (T) criteriaHibernateCallback.doInHibernate(getCurrentSession());
353 	}
354 
355 	private SessionFactoryImpl getSessionFactoryImplementor() {
356 		return (SessionFactoryImpl) sessionFactory;
357 	}
358 
359 	private static Restrictions.Restriction[] createArrayOfEqualRestrictions(int size) {
360 		Restrictions.Restriction[] restrictions = new Restrictions.Restriction[size];
361 		Arrays.fill(restrictions, equal());
362 		return restrictions;
363 	}
364 
365 	/**
366 	 * HibernateCallback using Hibernate criteria API for setting restriction and ordering.
367 	 * 
368 	 * Used in {@link HibernateRepository} and by brave souls who is not afraid to dig into Hibernate's source code.
369 	 * 
370 	 * @param <T>
371 	 *            entity for query
372 	 * 
373 	 * @author vitaliy.se@gmail.com
374 	 * @since 1.0.0
375 	 */
376 	private static class CriteriaHibernateCallback<T> {
377 
378 		private final String[] properties;
379 		private final Object[] values;
380 		private final Restrictions.Restriction[] restrictions;
381 		private final Partition partition;
382 		private final SessionFactoryImpl implementor;
383 		private final List<Order> orders = new ArrayList<Order>();
384 		private final Class<?> entityClass;
385 		private boolean isUnique;
386 		private boolean returnCount;
387 		private boolean returnNothing;
388 
389 		/**
390 		 * 
391 		 * @param entityClass
392 		 *            the type to return
393 		 * @param properties
394 		 *            properties for restrictions to be applied
395 		 * @param restrictions
396 		 * @param values
397 		 * @param partition
398 		 * @param pageSize
399 		 * @param implementor
400 		 */
401 		public CriteriaHibernateCallback(Class<?> entityClass, String properties[], Restrictions.Restriction[] restrictions, Object values[], Partition partition, SessionFactoryImpl implementor) {
402 			this.entityClass = entityClass;
403 			this.partition = partition;
404 			this.implementor = implementor;
405 			this.properties = properties.clone();
406 			this.values = values.clone();
407 			this.restrictions = restrictions.clone();
408 		}
409 
410 		public CriteriaHibernateCallback(Class<?> entityClass, String properties[], Restrictions.Restriction[] restrictions, Object values[], SessionFactoryImpl implementor) {
411 			this(entityClass, properties, restrictions, values, ALL_IN_ONE_PARTITION, implementor);
412 		}
413 
414 		public CriteriaHibernateCallback(Class<?> entityClass) {
415 			this(entityClass, new String[] {}, new Restrictions.Restriction[] {}, new Object[] {}, ALL_IN_ONE_PARTITION, null);
416 		}
417 
418 		public boolean isUnique() {
419 			return isUnique;
420 		}
421 
422 		/**
423 		 * @return true if it is a count query
424 		 */
425 		public boolean isReturnCount() {
426 			return returnCount;
427 		}
428 
429 		public CriteriaHibernateCallback<T> setReturnCount() {
430 			this.returnCount = true;
431 			return this;
432 		}
433 
434 		public CriteriaHibernateCallback<T> setUnique() {
435 			this.isUnique = true;
436 			return this;
437 		}
438 
439 		/**
440 		 * 
441 		 * @param orders
442 		 *            for query
443 		 * @return {@link CriteriaHibernateCallback}
444 		 */
445 		public CriteriaHibernateCallback<T> addOrder(Order... orders) {
446 			this.orders.addAll(Arrays.asList(orders));
447 			return this;
448 		}
449 
450 		public final T doInHibernate(Session session) {
451 			session.flush();// FIXME fix for http://opensource.atlassian.com/projects/hibernate/browse/HHH-3813
452 			Criteria criteria = session.createCriteria(entityClass);
453 			Map<String, Criteria> assosiationPathCriteriaMap = new HashMap<String, Criteria>();
454 			applyRestriction(criteria, assosiationPathCriteriaMap);
455 			if (!returnNothing) {
456 				if (isReturnCount()) {
457 					criteria.setProjection(Projections.rowCount());
458 				} else {
459 					for (Order order : orders) {
460 						// TODO find out why it was commented
461 						// creationAssociation(criteria, assosiationPathCriteriaMap, order.getSortedFieldPath()).addOrder(createHibernateOrder(order, getLastPropertyName(order.getSortedFieldPath())));
462 						creationAssociation(criteria, assosiationPathCriteriaMap, order.getSortedFieldPath()).addOrder(new NullsAwareHibernateOrder(order));
463 					}
464 				}
465 			}
466 			return extractResults(criteria);
467 		}
468 
469 		@SuppressWarnings("unchecked")
470 		private T extractResults(Criteria criteria) {
471 			if (isReturnCount()) {
472 				return (T) criteria.uniqueResult();
473 			} else if (isUnique()) {
474 				return returnNothing ? null : (T) criteria.uniqueResult();
475 			}
476 			return returnNothing ? (T) Collections.EMPTY_LIST : (T) criteria.list();
477 		}
478 
479 		public void applyRestriction(Criteria criteria, Map<String, Criteria> assosiationPathCriteriaMap) {
480 			if (partition.getMax() == 0) {
481 				returnNothing = true;// there is no need to proceed;
482 				return;// TODO Consider throwing IllegalArugmentException in case the pageSise is Negative
483 			}
484 			criteria.setFirstResult(partition.getFirst()).setMaxResults(partition.getMax());
485 
486 			for (int i = 0; i < properties.length; i++) {
487 				Object value = i < values.length ? values[i] : null; // allows not to specify values in the end if they are null
488 				boolean isEmptyCollectionOrArray = ((value instanceof Collection<?>) && ((Collection<?>) value).isEmpty()) || ((value instanceof Object[]) && ((Object[]) value).length == 0);
489 				if (restrictions[i] instanceof Restrictions.ExampleRestriction) {
490 					criteria.add(createExample(value, (ExampleRestriction) restrictions[i]));
491 					if (properties[i].length() == 0) {
492 						addAssosiationFilteringByExample(value, criteria, assosiationPathCriteriaMap, "", new HashMap<Class<?>, Set<String>>(), (ExampleRestriction) restrictions[i]);
493 					} else {
494 						// TODO test this approach
495 						Criteria subCriteria = creationAssociation(criteria, assosiationPathCriteriaMap, properties[i]);
496 						addAssosiationFilteringByExample(value, subCriteria, assosiationPathCriteriaMap, properties[i], new HashMap<Class<?>, Set<String>>(), (ExampleRestriction) restrictions[i]);
497 					}
498 				} else if (value != null && !(restrictions[i] instanceof EmptinessAwareRestriction && ((EmptinessAwareRestriction) restrictions[i]).isDisabledIfValueIsEmpty()) && isEmptyCollectionOrArray) {
499 					returnNothing = true;// IN Statment with empty list, there is no need to proceed cause we do not have OR conditions
500 					criteria.add(org.hibernate.criterion.Restrictions.sqlRestriction("1=0"));// needed for count to return 0
501 					return;
502 				} else if (!((restrictions[i].isDisabledIfValueIsNull() && value == null) || (restrictions[i] instanceof EmptinessAwareRestriction)
503 						&& ((EmptinessAwareRestriction) restrictions[i]).isDisabledIfValueIsEmpty() && isEmptyCollectionOrArray)) {
504 					Criteria subCriteria = creationAssociation(criteria, assosiationPathCriteriaMap, properties[i]);
505 					Criterion criterion = createCriterion(getLastPropertyName(properties[i]), restrictions[i], value);
506 					if (restrictions[i] instanceof CaseAwareRestriction && !((CaseAwareRestriction) restrictions[i]).isCaseSensitive()) {
507 						((SimpleExpression) criterion).ignoreCase();
508 					}
509 					subCriteria.add(criterion);
510 				} else { // i.e. this is either isDisabledIfEmpty or isDisabledIfValueIsNull
511 					// do nothing
512 				}
513 			}
514 		}
515 
516 		private Criterion createCriterion(String property, Restrictions.Restriction restriction, Object value) {
517 			switch (restriction.getType()) {
518 			case LESS:
519 				return org.hibernate.criterion.Restrictions.lt(property, value);
520 			case LESS_OR_EQUAL:
521 				return org.hibernate.criterion.Restrictions.le(property, value);
522 			case EQUAL:
523 				return org.hibernate.criterion.Restrictions.eq(property, value);
524 			case NOT_EQUAL:
525 				return org.hibernate.criterion.Restrictions.ne(property, value);
526 			case GREATER_OR_EQUAL:
527 				return org.hibernate.criterion.Restrictions.ge(property, value);
528 			case GREATER:
529 				return org.hibernate.criterion.Restrictions.gt(property, value);
530 			case LIKE:
531 				return org.hibernate.criterion.Restrictions.like(property, value);
532 			case ILIKE:
533 				// TODO revert once this bug is closed https://hibernate.onjira.com/browse/HHH-6915
534 				return new ILikeExpression(property, value.toString());
535 				// return org.hibernate.criterion.Restrictions.ilike(property, value);
536 			case IN:
537 				if (value instanceof Collection<?>) {
538 					return org.hibernate.criterion.Restrictions.in(property, (Collection<?>) value);
539 				} else {
540 					return org.hibernate.criterion.Restrictions.in(property, (Object[]) value);
541 				}
542 			case NOT_IN:
543 				return org.hibernate.criterion.Restrictions.not(createCriterion(property, Restrictions.in(), value));
544 			case IS_EMPTY:
545 				return org.hibernate.criterion.Restrictions.isEmpty(property);
546 			case NOT_EMPTY:
547 				return org.hibernate.criterion.Restrictions.isNotEmpty(property);
548 			case IS_NULL:
549 				return org.hibernate.criterion.Restrictions.isNull(property);
550 			case NOT_NULL:
551 				return org.hibernate.criterion.Restrictions.isNotNull(property);
552 			case SIZE_LESS:
553 				return org.hibernate.criterion.Restrictions.sizeLt(property, (Integer) value);
554 			case SIZE_LESS_OR_EQUAL:
555 				return org.hibernate.criterion.Restrictions.sizeLe(property, (Integer) value);
556 			case SIZE_EQUAL:
557 				return org.hibernate.criterion.Restrictions.sizeEq(property, (Integer) value);
558 			case SIZE_GREATER_OR_EQUAL:
559 				return org.hibernate.criterion.Restrictions.sizeGe(property, (Integer) value);
560 			case SIZE_GREATER:
561 				return org.hibernate.criterion.Restrictions.sizeGt(property, (Integer) value);
562 			case PROPERTY_LESS:
563 				return org.hibernate.criterion.Restrictions.ltProperty(property, (String) value);
564 			case PROPERTY_LESS_OR_EQUAL:
565 				return org.hibernate.criterion.Restrictions.leProperty(property, (String) value);
566 			case PROPERTY_EQUAL:
567 				return org.hibernate.criterion.Restrictions.eqProperty(property, (String) value);
568 			case PROPERTY_GREATER_OR_EQUAL:
569 				return org.hibernate.criterion.Restrictions.geProperty(property, (String) value);
570 			case PROPERTY_GREATER:
571 				return org.hibernate.criterion.Restrictions.gtProperty(property, (String) value);
572 			default:
573 				throw new IllegalArgumentException("Unknown restriction=" + restriction + " was passed to method");
574 			}
575 		}
576 
577 		private void addAssosiationFilteringByExample(Object example, Criteria criteria, Map<String, Criteria> assosiationPathCriteriaMap, String previousAssosiationPath,
578 				Map<Class<?>, Set<String>> entityWalkedProperties, ExampleRestriction exampleRestriction) {
579 			Set<String> visitedProperties = entityWalkedProperties.get(example.getClass());
580 			if (visitedProperties == null) {
581 				visitedProperties = new HashSet<String>();
582 				entityWalkedProperties.put(example.getClass(), visitedProperties);
583 			}
584 			for (String entityName : implementor.getImplementors(example.getClass().getName())) {
585 				EntityPersister persister = implementor.getEntityPersister(entityName);
586 				for (NonIdentifierAttribute property : persister.getEntityMetamodel().getProperties()) {
587 					if (visitedProperties.contains(property.getName())) {
588 						continue;
589 					}
590 					if (property.getType() instanceof CollectionType || property.getType() instanceof ManyToOneType) {
591 						visitedProperties.add(property.getName());
592 					}
593 					Object propertyValue = null;
594 					try {
595 						// we have to get rid of apache beanuits because it depends on common-logging
596 						// propertyValue = PropertyUtils.getProperty(example, property.getName());
597 						for (PropertyDescriptor propertyDescriptor : Introspector.getBeanInfo(example.getClass()).getPropertyDescriptors()) {
598 							if (propertyDescriptor.getName().equals(property.getName())) {
599 								propertyValue = propertyDescriptor.getReadMethod().invoke(example);
600 							}
601 						}
602 
603 					} catch (Exception e) {
604 						// It is safe to catch Exception here because example and property are not null
605 						// This situation is unlikely to happen, because Hibernate disallows usage of such objects
606 						String errorMessage = e.getMessage() + " probably entity " + entityName + " ManyToOne.class annotates a property that has not coresponding public get method";
607 						throw new PropertyAccessException(e, errorMessage, false, example.getClass(), property.getName());
608 					}
609 					if (propertyValue != null) {
610 						String newAssosiationPath = previousAssosiationPath + property.getName();
611 						Conjunction conjunction = org.hibernate.criterion.Restrictions.conjunction();
612 						if (property.getType() instanceof CollectionType) {
613 							@SuppressWarnings("unchecked")
614 							Collection<Object> collection = (Collection<Object>) propertyValue;
615 							if (!collection.isEmpty()) {
616 								Criteria subCriteria = criteria.createCriteria(property.getName(), property.getName(), JoinType.LEFT_OUTER_JOIN).add(conjunction);
617 								assosiationPathCriteriaMap.put(newAssosiationPath, subCriteria);
618 								addAssosiationFilteringByExample(collection.iterator().next(), subCriteria, assosiationPathCriteriaMap, newAssosiationPath + ".", entityWalkedProperties, exampleRestriction);
619 								for (Object exampleObject : collection) {
620 									conjunction.add(createExample(exampleObject, exampleRestriction));
621 								}
622 							}
623 						} else if (property.getType() instanceof ManyToOneType) {
624 							Criteria subCriteria = criteria.createCriteria(property.getName(), property.getName(), JoinType.LEFT_OUTER_JOIN).add(createExample(propertyValue, exampleRestriction));
625 							assosiationPathCriteriaMap.put(newAssosiationPath, subCriteria);
626 							addAssosiationFilteringByExample(propertyValue, subCriteria, assosiationPathCriteriaMap, newAssosiationPath + ".", entityWalkedProperties, exampleRestriction);
627 
628 						}
629 					}
630 
631 				}
632 			}
633 		}
634 
635 		private static Example createExample(Object object, ExampleRestriction exampleRestriction) {
636 			Example example = Example.create(object);
637 			if (exampleRestriction.areZeroesExcluded()) {
638 				example.excludeZeroes();
639 			}
640 			if (exampleRestriction.isCaseIgnored()) {
641 				example.ignoreCase();
642 			}
643 			if (exampleRestriction.isLikeEnabled()) {
644 				example.enableLike();
645 			}
646 			if (exampleRestriction.isExcludeNone()) {
647 				example.excludeNone();
648 			}
649 			return example;
650 		}
651 
652 		private static Criteria creationAssosiation(Criteria criteria, Map<String, Criteria> assosiationPathCriteriaMap, String fullAssosiationPath, String previosAssosiationPath) {
653 			Criteria subCriteria;
654 			int dotIndex = fullAssosiationPath.indexOf('.');
655 			if (dotIndex == -1) {
656 				subCriteria = criteria;
657 			} else {
658 				String propertyName = fullAssosiationPath.substring(0, dotIndex);
659 				String newAssosiationPath = previosAssosiationPath + propertyName;
660 				subCriteria = assosiationPathCriteriaMap.get(newAssosiationPath);
661 				if (subCriteria == null) {
662 					subCriteria = criteria.createCriteria(propertyName, propertyName, JoinType.LEFT_OUTER_JOIN);
663 					assosiationPathCriteriaMap.put(newAssosiationPath, subCriteria);
664 				}
665 				subCriteria = creationAssosiation(subCriteria, assosiationPathCriteriaMap, fullAssosiationPath.substring(dotIndex + 1), newAssosiationPath + ".");
666 			}
667 			return subCriteria;
668 		}
669 
670 		/**
671 		 * 
672 		 * @param criteria
673 		 * @param associationPathCriteriaMap
674 		 *            vman with key as association and criteria as value
675 		 * @param fullAssociationPath
676 		 * @return criteria with full association path for given fullAssociationPath
677 		 */
678 		private static Criteria creationAssociation(Criteria criteria, Map<String, Criteria> associationPathCriteriaMap, String fullAssociationPath) {
679 			return creationAssosiation(criteria, associationPathCriteriaMap, fullAssociationPath, "");
680 		}
681 
682 		private static String getLastPropertyName(String fullAssosiationPath) {
683 			int dotIndex = fullAssosiationPath.lastIndexOf('.');
684 			if (dotIndex > -1) {
685 				return fullAssosiationPath.substring(dotIndex + 1);
686 			}
687 			return fullAssosiationPath;
688 		}
689 
690 		private static class NullsAwareHibernateOrder extends org.hibernate.criterion.Order {
691 			private static final long serialVersionUID = 3353395213896284011L;
692 			private final Order order;
693 			private final String propertyName;
694 
695 			NullsAwareHibernateOrder(final Order order) {
696 				// here we are passing useless parameters to super class
697 				super(order.getSortedFieldPath(), order.getSortingDirection() == Order.SortingDirection.ASCENDING ? true : false);
698 				this.order = order;
699 				propertyName = getLastPropertyName(order.getSortedFieldPath());
700 			}
701 
702 			@Override
703 			public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
704 				// TODO consider if we should use here sortedFieldPath instead of propertyName
705 				String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, propertyName);
706 				Type type = criteriaQuery.getTypeUsingProjection(criteria, propertyName);
707 				StringBuilder sql = new StringBuilder();
708 				for (int i = 0; i < columns.length; i++) {
709 					SessionFactoryImplementor factory = criteriaQuery.getFactory();
710 					boolean lower = order.getGetCaseSensitivity() == Order.CaseSensitivity.INSENSITIVE && type.sqlTypes(factory)[i] == Types.VARCHAR;
711 					if (lower) {
712 						sql.append(factory.getDialect().getLowercaseFunction()).append('(');
713 					}
714 					sql.append(columns[i]);
715 					if (lower) {
716 						sql.append(')');
717 					}
718 					sql.append(order.getSortingDirection() == Order.SortingDirection.ASCENDING ? " asc" : " desc");
719 					if (order.getNullsPosition() != Order.NullsPosition.NOT_SPECIFIED) {
720 						sql.append(order.getNullsPosition() == Order.NullsPosition.FIRST ? " nulls first" : " nulls last");
721 					}
722 					if (i < columns.length - 1) {
723 						sql.append(", ");
724 					}
725 				}
726 				return sql.toString();
727 			}
728 		}
729 
730 	}
731 
732 	/**
733 	 * Workaround for Hibernate has a bug https://hibernate.onjira.com/browse/HHH-6915
734 	 * 
735 	 */
736 	private static class ILikeExpression extends LikeExpression {
737 
738 		private static final long serialVersionUID = 200053570285372152L;
739 
740 		public ILikeExpression(String propertyName, String value) {
741 			super(propertyName, value, null, true);
742 		}
743 
744 	}
745 
746 	/**
747 	 * Needed for cglib usage ONLY
748 	 */
749 	HibernateRepository() {
750 		this(null, null);// allows us to use final with fields despite CGLib usage
751 	}
752 
753 	static Partition pageToPartition(Page page) {
754 		return new Partition((page.getNumber() - 1) * page.getSize(), page.getSize());
755 	}
756 
757 }