1 /*
2  * Copyright 2004-2005 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package grails.orm;
17 
18 import groovy.lang.Closure;
19 import groovy.lang.GroovyObjectSupport;
20 import groovy.lang.GroovySystem;
21 import groovy.lang.MetaClass;
22 import groovy.lang.MetaMethod;
23 import groovy.lang.MissingMethodException;
24 
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 
33 import org.codehaus.groovy.grails.commons.GrailsClassUtils;
34 import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil;
35 import org.hibernate.Criteria;
36 import org.hibernate.EntityMode;
37 import org.hibernate.FetchMode;
38 import org.hibernate.LockMode;
39 import org.hibernate.Session;
40 import org.hibernate.SessionFactory;
41 import org.hibernate.criterion.AggregateProjection;
42 import org.hibernate.criterion.CountProjection;
43 import org.hibernate.criterion.CriteriaSpecification;
44 import org.hibernate.criterion.Criterion;
45 import org.hibernate.criterion.Junction;
46 import org.hibernate.criterion.Order;
47 import org.hibernate.criterion.Projection;
48 import org.hibernate.criterion.ProjectionList;
49 import org.hibernate.criterion.Projections;
50 import org.hibernate.criterion.PropertyProjection;
51 import org.hibernate.criterion.Restrictions;
52 import org.hibernate.criterion.SimpleExpression;
53 import org.hibernate.engine.SessionFactoryImplementor;
54 import org.hibernate.metadata.ClassMetadata;
55 import org.hibernate.transform.ResultTransformer;
56 import org.hibernate.type.AssociationType;
57 import org.hibernate.type.Type;
58 import org.springframework.beans.BeanUtils;
59 import org.springframework.beans.BeanWrapper;
60 import org.springframework.beans.BeanWrapperImpl;
61 import org.springframework.orm.hibernate3.SessionHolder;
62 import org.springframework.transaction.support.TransactionSynchronizationManager;
63 
64 /**
65  * <p>Wraps the Hibernate Criteria API in a builder. The builder can be retrieved through the "createCriteria()" dynamic static
66  * method of Grails domain classes (Example in Groovy):
67  *
68  * <pre>
69  *         def c = Account.createCriteria()
70  *         def results = c {
71  *             projections {
72  *                 groupProperty("branch")
73  *             }
74  *             like("holderFirstName", "Fred%")
75  *             and {
76  *                 between("balance", 500, 1000)
77  *                 eq("branch", "London")
78  *             }
79  *             maxResults(10)
80  *             order("holderLastName", "desc")
81  *         }
82  * </pre>
83  *
84  * <p>The builder can also be instantiated standalone with a SessionFactory and persistent Class instance:
85  *
86  * <pre>
87  *      new HibernateCriteriaBuilder(clazz, sessionFactory).list {
88  *         eq("firstName", "Fred")
89  *      }
90  * </pre>
91  *
92  * @author Graeme Rocher
93  */
94 public class HibernateCriteriaBuilder extends GroovyObjectSupport {
95 
96     public static final String AND = "and"; // builder
97     public static final String IS_NULL = "isNull"; // builder
98     public static final String IS_NOT_NULL = "isNotNull"; // builder
99     public static final String NOT = "not";// builder
100     public static final String OR = "or"; // builder
101     public static final String ID_EQUALS = "idEq"; // builder
102     public static final String IS_EMPTY = "isEmpty"; //builder
103     public static final String IS_NOT_EMPTY = "isNotEmpty"; //builder
104     public static final String RLIKE = "rlike";//method
105     public static final String BETWEEN = "between";//method
106     public static final String EQUALS = "eq";//method
107     public static final String EQUALS_PROPERTY = "eqProperty";//method
108     public static final String GREATER_THAN = "gt";//method
109     public static final String GREATER_THAN_PROPERTY = "gtProperty";//method
110     public static final String GREATER_THAN_OR_EQUAL = "ge";//method
111     public static final String GREATER_THAN_OR_EQUAL_PROPERTY = "geProperty";//method
112     public static final String ILIKE = "ilike";//method
113     public static final String IN = "in";//method
114     public static final String LESS_THAN = "lt"; //method
115     public static final String LESS_THAN_PROPERTY = "ltProperty";//method
116     public static final String LESS_THAN_OR_EQUAL = "le";//method
117     public static final String LESS_THAN_OR_EQUAL_PROPERTY = "leProperty";//method
118     public static final String LIKE = "like";//method
119     public static final String NOT_EQUAL = "ne";//method
120     public static final String NOT_EQUAL_PROPERTY = "neProperty";//method
121     public static final String SIZE_EQUALS = "sizeEq"; //method
122     public static final String ORDER_DESCENDING = "desc";
123     public static final String ORDER_ASCENDING = "asc";
124     private static final String ROOT_DO_CALL = "doCall";
125     private static final String ROOT_CALL = "call";
126     private static final String LIST_CALL = "list";
127     private static final String LIST_DISTINCT_CALL = "listDistinct";
128     private static final String COUNT_CALL = "count";
129     private static final String GET_CALL = "get";
130     private static final String SCROLL_CALL = "scroll";
131     private static final String SET_RESULT_TRANSFORMER_CALL = "setResultTransformer";
132     private static final String PROJECTIONS = "projections";
133 
134     private SessionFactory sessionFactory;
135     private Session hibernateSession;
136     private Class<?> targetClass;
137     private Criteria criteria;
138     private MetaClass criteriaMetaClass;
139 
140     private boolean uniqueResult = false;
141     private List<LogicalExpression> logicalExpressionStack = new ArrayList<LogicalExpression>();
142     private List<String> associationStack = new ArrayList<String>();
143     private boolean participate;
144     private boolean scroll;
145     private boolean count;
146     private ProjectionList projectionList = Projections.projectionList();
147     private BeanWrapper targetBean;
148     private List<String> aliasStack = new ArrayList<String>();
149     private List<Criteria> aliasInstanceStack = new ArrayList<Criteria>();
150     private Map<String, String> aliasMap = new HashMap<String, String>();
151     private static final String ALIAS = "_alias";
152     private ResultTransformer resultTransformer;
153     private int aliasCount;
154 
155     private boolean paginationEnabledList = false;
156     private List<Order> orderEntries;
157 
158     @SuppressWarnings({"unchecked","rawtypes"})
HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory)159     public HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory) {
160         this.targetClass = targetClass;
161         targetBean = new BeanWrapperImpl(BeanUtils.instantiateClass(targetClass));
162         this.sessionFactory = sessionFactory;
163     }
164 
165     @SuppressWarnings("rawtypes")
HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory, boolean uniqueResult)166     public HibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory, boolean uniqueResult) {
167         this.targetClass = targetClass;
168         this.sessionFactory = sessionFactory;
169         this.uniqueResult = uniqueResult;
170     }
171 
172     /**
173      * Returns the criteria instance
174      * @return The criteria instance
175      */
getInstance()176     public Criteria getInstance() {
177         return criteria;
178     }
179 
180     /**
181      * Set whether a unique result should be returned
182      * @param uniqueResult True if a unique result should be returned
183      */
setUniqueResult(boolean uniqueResult)184     public void setUniqueResult(boolean uniqueResult) {
185         this.uniqueResult = uniqueResult;
186     }
187 
188     /**
189      * A projection that selects a property name
190      * @param propertyName The name of the property
191      */
property(String propertyName)192     public void property(String propertyName) {
193         property(propertyName, null);
194     }
195 
196     /**
197      * A projection that selects a property name
198      * @param propertyName The name of the property
199      * @param alias The alias to use
200      */
property(String propertyName, String alias)201     public void property(String propertyName, String alias) {
202         final PropertyProjection propertyProjection = Projections.property(calculatePropertyName(propertyName));
203         addProjectionToList(propertyProjection, alias);
204     }
205 
206     /**
207      * Adds a projection to the projectList for the given alias
208      *
209      * @param propertyProjection The projection
210      * @param alias The alias
211      */
addProjectionToList(Projection propertyProjection, String alias)212     protected void addProjectionToList(Projection propertyProjection, String alias) {
213         if (alias != null) {
214             projectionList.add(propertyProjection,alias);
215         }
216         else {
217             projectionList.add(propertyProjection);
218         }
219     }
220 
221     /**
222      * A projection that selects a distince property name
223      * @param propertyName The property name
224      */
distinct(String propertyName)225     public void distinct(String propertyName) {
226         distinct(propertyName, null);
227     }
228 
229     /**
230      * A projection that selects a distince property name
231      * @param propertyName The property name
232      * @param alias The alias to use
233      */
distinct(String propertyName, String alias)234     public void distinct(String propertyName, String alias) {
235         final Projection proj = Projections.distinct(Projections.property(calculatePropertyName(propertyName)));
236         addProjectionToList(proj,alias);
237     }
238 
239     /**
240      * A distinct projection that takes a list
241      *
242      * @param propertyNames The list of distince property names
243      */
244     @SuppressWarnings("rawtypes")
distinct(Collection propertyNames)245     public void distinct(Collection propertyNames) {
246         distinct(propertyNames, null);
247     }
248 
249     /**
250      * A distinct projection that takes a list
251      *
252      * @param propertyNames The list of distince property names
253      * @param alias The alias to use
254      */
255     @SuppressWarnings("rawtypes")
distinct(Collection propertyNames, String alias)256     public void distinct(Collection propertyNames, String alias) {
257         ProjectionList list = Projections.projectionList();
258         for (Object o : propertyNames) {
259             list.add(Projections.property(calculatePropertyName(o.toString())));
260         }
261         final Projection proj = Projections.distinct(list);
262         addProjectionToList(proj, alias);
263     }
264 
265     /**
266      * Adds a projection that allows the criteria to return the property average value
267      *
268      * @param propertyName The name of the property
269      */
avg(String propertyName)270     public void avg(String propertyName) {
271         avg(propertyName, null);
272     }
273 
274     /**
275      * Adds a projection that allows the criteria to return the property average value
276      *
277      * @param propertyName The name of the property
278      * @param alias The alias to use
279      */
avg(String propertyName, String alias)280     public void avg(String propertyName, String alias) {
281         final AggregateProjection aggregateProjection = Projections.avg(calculatePropertyName(propertyName));
282         addProjectionToList(aggregateProjection, alias);
283     }
284 
285     /**
286      * Use a join query
287      *
288      * @param associationPath The path of the association
289      */
join(String associationPath)290     public void join(String associationPath) {
291         criteria.setFetchMode(calculatePropertyName(associationPath), FetchMode.JOIN);
292     }
293 
294     /**
295      * Whether a pessimistic lock should be obtained.
296      *
297      * @param shouldLock True if it should
298      */
lock(boolean shouldLock)299     public void lock(boolean shouldLock) {
300         String lastAlias = getLastAlias();
301 
302         if (shouldLock) {
303             if (lastAlias != null) {
304                 criteria.setLockMode(lastAlias, LockMode.UPGRADE);
305             }
306             else {
307                 criteria.setLockMode(LockMode.UPGRADE);
308             }
309         }
310         else {
311             if (lastAlias != null) {
312                 criteria.setLockMode(lastAlias, LockMode.NONE);
313             }
314             else {
315                 criteria.setLockMode(LockMode.NONE);
316             }
317         }
318     }
319 
320     /**
321      * Use a select query
322      *
323      * @param associationPath The path of the association
324      */
select(String associationPath)325     public void select(String associationPath) {
326         criteria.setFetchMode(calculatePropertyName(associationPath), FetchMode.SELECT);
327     }
328 
329     /**
330      * Whether to use the query cache
331      * @param shouldCache True if the query should be cached
332      */
cache(boolean shouldCache)333     public void cache(boolean shouldCache) {
334         criteria.setCacheable(shouldCache);
335     }
336 
337     /**
338      * Calculates the property name including any alias paths
339      *
340      * @param propertyName The property name
341      * @return The calculated property name
342      */
calculatePropertyName(String propertyName)343     private String calculatePropertyName(String propertyName) {
344         String lastAlias = getLastAlias();
345         if (lastAlias != null) {
346             return lastAlias +'.'+propertyName;
347         }
348 
349         return propertyName;
350     }
351 
getLastAlias()352     private String getLastAlias() {
353         if (aliasStack.size() > 0) {
354             return aliasStack.get(aliasStack.size() - 1).toString();
355         }
356         return null;
357     }
358 
359     /**
360      * Calculates the property value, converting GStrings if necessary
361      *
362      * @param propertyValue The property value
363      * @return The calculated property value
364      */
calculatePropertyValue(Object propertyValue)365     private Object calculatePropertyValue(Object propertyValue) {
366         if (propertyValue instanceof CharSequence) {
367             return propertyValue.toString();
368         }
369         return propertyValue;
370     }
371 
372     /**
373      * Adds a projection that allows the criteria to return the property count
374      *
375      * @param propertyName The name of the property
376      */
count(String propertyName)377     public void count(String propertyName) {
378         count(propertyName, null);
379     }
380 
381     /**
382      * Adds a projection that allows the criteria to return the property count
383      *
384      * @param propertyName The name of the property
385      * @param alias The alias to use
386      */
count(String propertyName, String alias)387     public void count(String propertyName, String alias) {
388         final CountProjection proj = Projections.count(calculatePropertyName(propertyName));
389         addProjectionToList(proj, alias);
390     }
391 
392     /**
393      * Adds a projection that allows the criteria to return the distinct property count
394      *
395      * @param propertyName The name of the property
396      */
countDistinct(String propertyName)397     public void countDistinct(String propertyName) {
398         countDistinct(propertyName, null);
399     }
400 
401     /**
402      * Adds a projection that allows the criteria to return the distinct property count
403      *
404      * @param propertyName The name of the property
405      * @param alias The alias to use
406      */
countDistinct(String propertyName, String alias)407     public void countDistinct(String propertyName, String alias) {
408         final CountProjection proj = Projections.countDistinct(calculatePropertyName(propertyName));
409         addProjectionToList(proj, alias);
410     }
411 
412     /**
413      * Adds a projection that allows the criteria's result to be grouped by a property
414      *
415      * @param propertyName The name of the property
416      */
groupProperty(String propertyName)417     public void groupProperty(String propertyName) {
418         groupProperty(propertyName, null);
419     }
420 
421     /**
422      * Adds a projection that allows the criteria's result to be grouped by a property
423      *
424      * @param propertyName The name of the property
425      * @param alias The alias to use
426      */
groupProperty(String propertyName, String alias)427     public void groupProperty(String propertyName, String alias) {
428         final PropertyProjection proj = Projections.groupProperty(calculatePropertyName(propertyName));
429         addProjectionToList(proj, alias);
430     }
431 
432     /**
433      * Adds a projection that allows the criteria to retrieve a  maximum property value
434      *
435      * @param propertyName The name of the property
436      */
max(String propertyName)437     public void max(String propertyName) {
438         max(propertyName, null);
439     }
440 
441     /**
442      * Adds a projection that allows the criteria to retrieve a  maximum property value
443      *
444      * @param propertyName The name of the property
445      * @param alias The alias to use
446      */
max(String propertyName, String alias)447     public void max(String propertyName, String alias) {
448         final AggregateProjection proj = Projections.max(calculatePropertyName(propertyName));
449         addProjectionToList(proj, alias);
450     }
451 
452     /**
453      * Adds a projection that allows the criteria to retrieve a  minimum property value
454      *
455      * @param propertyName The name of the property
456      */
min(String propertyName)457     public void min(String propertyName) {
458         min(propertyName, null);
459     }
460 
461     /**
462      * Adds a projection that allows the criteria to retrieve a  minimum property value
463      *
464      * @param alias The alias to use
465      */
min(String propertyName, String alias)466     public void min(String propertyName, String alias) {
467         final AggregateProjection aggregateProjection = Projections.min(calculatePropertyName(propertyName));
468         addProjectionToList(aggregateProjection, alias);
469     }
470 
471     /**
472      * Adds a projection that allows the criteria to return the row count
473      *
474      */
rowCount()475     public void rowCount() {
476         rowCount(null);
477     }
478 
479     /**
480      * Adds a projection that allows the criteria to return the row count
481      *
482      * @param alias The alias to use
483      */
rowCount(String alias)484     public void rowCount(String alias) {
485         final Projection proj = Projections.rowCount();
486         addProjectionToList(proj, alias);
487     }
488 
489     /**
490      * Adds a projection that allows the criteria to retrieve the sum of the results of a property
491      *
492      * @param propertyName The name of the property
493      */
sum(String propertyName)494     public void sum(String propertyName) {
495         sum(propertyName, null);
496     }
497 
498     /**
499      * Adds a projection that allows the criteria to retrieve the sum of the results of a property
500      *
501      * @param propertyName The name of the property
502      * @param alias The alias to use
503      */
sum(String propertyName, String alias)504     public void sum(String propertyName, String alias) {
505         final AggregateProjection proj = Projections.sum(calculatePropertyName(propertyName));
506         addProjectionToList(proj, alias);
507     }
508 
509     /**
510      * Sets the fetch mode of an associated path
511      *
512      * @param associationPath The name of the associated path
513      * @param fetchMode The fetch mode to set
514      */
fetchMode(String associationPath, FetchMode fetchMode)515     public void fetchMode(String associationPath, FetchMode fetchMode) {
516         if (criteria != null) {
517             criteria.setFetchMode(associationPath, fetchMode);
518         }
519     }
520 
521     /**
522      * Sets the resultTransformer.
523      * @param resultTransformer The result transformer to use.
524      */
resultTransformer(ResultTransformer transformer)525     public void resultTransformer(ResultTransformer transformer) {
526         if (criteria == null) {
527             throwRuntimeException(new IllegalArgumentException("Call to [resultTransformer] not supported here"));
528         }
529         resultTransformer = transformer;
530     }
531 
532     /**
533      * Creates a Criterion that compares to class properties for equality
534      * @param propertyName The first property name
535      * @param otherPropertyName The second property name
536      * @return A Criterion instance
537      */
eqProperty(String propertyName, String otherPropertyName)538     public Object eqProperty(String propertyName, String otherPropertyName) {
539         if (!validateSimpleExpression()) {
540             throwRuntimeException(new IllegalArgumentException("Call to [eqProperty] with propertyName [" +
541                     propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
542         }
543 
544         propertyName = calculatePropertyName(propertyName);
545         otherPropertyName = calculatePropertyName(otherPropertyName);
546         return addToCriteria(Restrictions.eqProperty(propertyName, otherPropertyName));
547     }
548 
549     /**
550      * Creates a Criterion that compares to class properties for !equality
551      * @param propertyName The first property name
552      * @param otherPropertyName The second property name
553      * @return A Criterion instance
554      */
neProperty(String propertyName, String otherPropertyName)555     public Object neProperty(String propertyName, String otherPropertyName) {
556         if (!validateSimpleExpression()) {
557             throwRuntimeException(new IllegalArgumentException("Call to [neProperty] with propertyName [" +
558                     propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
559         }
560 
561         propertyName = calculatePropertyName(propertyName);
562         otherPropertyName = calculatePropertyName(otherPropertyName);
563         return addToCriteria(Restrictions.neProperty(propertyName, otherPropertyName));
564     }
565 
566     /**
567      * Creates a Criterion that tests if the first property is greater than the second property
568      * @param propertyName The first property name
569      * @param otherPropertyName The second property name
570      * @return A Criterion instance
571      */
gtProperty(String propertyName, String otherPropertyName)572     public Object gtProperty(String propertyName, String otherPropertyName) {
573         if (!validateSimpleExpression()) {
574             throwRuntimeException(new IllegalArgumentException("Call to [gtProperty] with propertyName [" +
575                     propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
576         }
577 
578         propertyName = calculatePropertyName(propertyName);
579         otherPropertyName = calculatePropertyName(otherPropertyName);
580         return addToCriteria(Restrictions.gtProperty(propertyName, otherPropertyName));
581     }
582 
583     /**
584      * Creates a Criterion that tests if the first property is greater than or equal to the second property
585      * @param propertyName The first property name
586      * @param otherPropertyName The second property name
587      * @return A Criterion instance
588      */
geProperty(String propertyName, String otherPropertyName)589     public Object geProperty(String propertyName, String otherPropertyName) {
590         if (!validateSimpleExpression()) {
591             throwRuntimeException(new IllegalArgumentException("Call to [geProperty] with propertyName [" +
592                     propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
593         }
594 
595         propertyName = calculatePropertyName(propertyName);
596         otherPropertyName = calculatePropertyName(otherPropertyName);
597         return addToCriteria(Restrictions.geProperty(propertyName, otherPropertyName));
598     }
599 
600     /**
601      * Creates a Criterion that tests if the first property is less than the second property
602      * @param propertyName The first property name
603      * @param otherPropertyName The second property name
604      * @return A Criterion instance
605      */
ltProperty(String propertyName, String otherPropertyName)606     public Object ltProperty(String propertyName, String otherPropertyName) {
607         if (!validateSimpleExpression()) {
608             throwRuntimeException(new IllegalArgumentException("Call to [ltProperty] with propertyName [" +
609                     propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
610         }
611 
612         propertyName = calculatePropertyName(propertyName);
613         otherPropertyName = calculatePropertyName(otherPropertyName);
614         return addToCriteria(Restrictions.ltProperty(propertyName, otherPropertyName));
615     }
616 
617     /**
618      * Creates a Criterion that tests if the first property is less than or equal to the second property
619      * @param propertyName The first property name
620      * @param otherPropertyName The second property name
621      * @return A Criterion instance
622      */
leProperty(String propertyName, String otherPropertyName)623     public Object leProperty(String propertyName, String otherPropertyName) {
624         if (!validateSimpleExpression()) {
625             throwRuntimeException(new IllegalArgumentException("Call to [leProperty] with propertyName [" +
626                     propertyName + "] and other property name [" + otherPropertyName + "] not allowed here."));
627         }
628 
629         propertyName = calculatePropertyName(propertyName);
630         otherPropertyName = calculatePropertyName(otherPropertyName);
631         return addToCriteria(Restrictions.leProperty(propertyName, otherPropertyName));
632     }
633 
634     /**
635      * Creates a "greater than" Criterion based on the specified property name and value
636      * @param propertyName The property name
637      * @param propertyValue The property value
638      * @return A Criterion instance
639      */
gt(String propertyName, Object propertyValue)640     public Object gt(String propertyName, Object propertyValue) {
641         if (!validateSimpleExpression()) {
642             throwRuntimeException(new IllegalArgumentException("Call to [gt] with propertyName [" +
643                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
644         }
645 
646         propertyName = calculatePropertyName(propertyName);
647         propertyValue = calculatePropertyValue(propertyValue);
648         return addToCriteria(Restrictions.gt(propertyName, propertyValue));
649     }
650 
651     /**
652      * Creates a "greater than or equal to" Criterion based on the specified property name and value
653      * @param propertyName The property name
654      * @param propertyValue The property value
655      * @return A Criterion instance
656      */
ge(String propertyName, Object propertyValue)657     public Object ge(String propertyName, Object propertyValue) {
658         if (!validateSimpleExpression()) {
659             throwRuntimeException(new IllegalArgumentException("Call to [ge] with propertyName [" +
660                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
661         }
662         propertyName = calculatePropertyName(propertyName);
663         propertyValue = calculatePropertyValue(propertyValue);
664         return addToCriteria(Restrictions.ge(propertyName, propertyValue));
665     }
666 
667     /**
668      * Creates a "less than" Criterion based on the specified property name and value
669      * @param propertyName The property name
670      * @param propertyValue The property value
671      * @return A Criterion instance
672      */
lt(String propertyName, Object propertyValue)673     public Object lt(String propertyName, Object propertyValue) {
674         if (!validateSimpleExpression()) {
675             throwRuntimeException(new IllegalArgumentException("Call to [lt] with propertyName [" +
676                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
677         }
678 
679         propertyName = calculatePropertyName(propertyName);
680         propertyValue = calculatePropertyValue(propertyValue);
681         return addToCriteria(Restrictions.lt(propertyName, propertyValue));
682     }
683 
684     /**
685      * Creates a "less than or equal to" Criterion based on the specified property name and value
686      * @param propertyName The property name
687      * @param propertyValue The property value
688      * @return A Criterion instance
689      */
le(String propertyName, Object propertyValue)690     public Object le(String propertyName, Object propertyValue) {
691         if (!validateSimpleExpression()) {
692             throwRuntimeException(new IllegalArgumentException("Call to [le] with propertyName [" +
693                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
694         }
695 
696         propertyName = calculatePropertyName(propertyName);
697         propertyValue = calculatePropertyValue(propertyValue);
698         return addToCriteria(Restrictions.le(propertyName, propertyValue));
699     }
700 
701     /**
702      * Creates an "equals" Criterion based on the specified property name and value. Case-sensitive.
703      * @param propertyName The property name
704      * @param propertyValue The property value
705      *
706      * @return A Criterion instance
707      */
eq(String propertyName, Object propertyValue)708     public Object eq(String propertyName, Object propertyValue) {
709         return eq(propertyName, propertyValue, Collections.emptyMap());
710     }
711 
712     /**
713      * Groovy moves the map to the first parameter if using the idiomatic form, e.g.
714      * <code>eq 'firstName', 'Fred', ignoreCase: true</code>.
715      * @param params optional map with customization parameters; currently only 'ignoreCase' is supported.
716      * @param propertyName
717      * @param propertyValue
718      * @return A Criterion instance
719      */
720     @SuppressWarnings("rawtypes")
eq(Map params, String propertyName, Object propertyValue)721     public Object eq(Map params, String propertyName, Object propertyValue) {
722         return eq(propertyName, propertyValue, params);
723     }
724 
725     /**
726      * Creates an "equals" Criterion based on the specified property name and value.
727      * Supports case-insensitive search if the <code>params</code> map contains <code>true</code>
728      * under the 'ignoreCase' key.
729      * @param propertyName The property name
730      * @param propertyValue The property value
731      * @param params optional map with customization parameters; currently only 'ignoreCase' is supported.
732      *
733      * @return A Criterion instance
734      */
735     @SuppressWarnings("rawtypes")
eq(String propertyName, Object propertyValue, Map params)736     public Object eq(String propertyName, Object propertyValue, Map params) {
737         if (!validateSimpleExpression()) {
738             throwRuntimeException(new IllegalArgumentException("Call to [eq] with propertyName [" +
739                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
740         }
741 
742         propertyName = calculatePropertyName(propertyName);
743         propertyValue = calculatePropertyValue(propertyValue);
744         SimpleExpression eq =  Restrictions.eq(propertyName, propertyValue);
745         if (params != null) {
746             Object ignoreCase = params.get("ignoreCase");
747             if (ignoreCase instanceof Boolean && (Boolean)ignoreCase) {
748                 eq = eq.ignoreCase();
749             }
750         }
751         return addToCriteria(eq);
752     }
753 
754     /**
755      * Applies a sql restriction to the results to allow something like:
756       <pre>
757        def results = Person.withCriteria {
758            sqlRestriction "char_length(first_name) <= 4"
759        }
760       </pre>
761      *
762      * @param sqlRestriction the sql restriction
763      * @return a Criterion instance
764      */
sqlRestriction(String sqlRestriction)765     public Object sqlRestriction(String sqlRestriction) {
766         if (!validateSimpleExpression()) {
767             throwRuntimeException(new IllegalArgumentException("Call to [sqlRestriction] with value [" +
768                     sqlRestriction + "] not allowed here."));
769         }
770         return addToCriteria(Restrictions.sqlRestriction(sqlRestriction));
771     }
772 
773     /**
774      * Creates a Criterion with from the specified property name and "like" expression
775      * @param propertyName The property name
776      * @param propertyValue The like value
777      *
778      * @return A Criterion instance
779      */
like(String propertyName, Object propertyValue)780     public Object like(String propertyName, Object propertyValue) {
781         if (!validateSimpleExpression()) {
782             throwRuntimeException(new IllegalArgumentException("Call to [like] with propertyName [" +
783                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
784         }
785 
786         propertyName = calculatePropertyName(propertyName);
787         propertyValue = calculatePropertyValue(propertyValue);
788         return addToCriteria(Restrictions.like(propertyName, propertyValue));
789     }
790 
791     /**
792      * Creates a Criterion with from the specified property name and "rlike" (a regular expression version of "like") expression
793      * @param propertyName The property name
794      * @param propertyValue The ilike value
795      *
796      * @return A Criterion instance
797      */
rlike(String propertyName, Object propertyValue)798     public Object rlike(String propertyName, Object propertyValue) {
799         if (!validateSimpleExpression()) {
800             throwRuntimeException(new IllegalArgumentException("Call to [rlike] with propertyName [" +
801                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
802         }
803 
804         propertyName = calculatePropertyName(propertyName);
805         propertyValue = calculatePropertyValue(propertyValue);
806         return addToCriteria(new RlikeExpression(propertyName, propertyValue));
807     }
808 
809     /**
810      * Creates a Criterion with from the specified property name and "ilike" (a case sensitive version of "like") expression
811      * @param propertyName The property name
812      * @param propertyValue The ilike value
813      *
814      * @return A Criterion instance
815      */
ilike(String propertyName, Object propertyValue)816     public Object ilike(String propertyName, Object propertyValue) {
817         if (!validateSimpleExpression()) {
818             throwRuntimeException(new IllegalArgumentException("Call to [ilike] with propertyName [" +
819                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
820         }
821 
822         propertyName = calculatePropertyName(propertyName);
823         propertyValue = calculatePropertyValue(propertyValue);
824         return addToCriteria(Restrictions.ilike(propertyName, propertyValue));
825     }
826 
827     /**
828      * Applys a "in" contrain on the specified property
829      * @param propertyName The property name
830      * @param values A collection of values
831      *
832      * @return A Criterion instance
833      */
834     @SuppressWarnings("rawtypes")
in(String propertyName, Collection values)835     public Object in(String propertyName, Collection values) {
836         if (!validateSimpleExpression()) {
837             throwRuntimeException(new IllegalArgumentException("Call to [in] with propertyName [" +
838                     propertyName + "] and values [" + values + "] not allowed here."));
839         }
840 
841         propertyName = calculatePropertyName(propertyName);
842         return addToCriteria(Restrictions.in(propertyName, values));
843     }
844 
845     /**
846      * Delegates to in as in is a Groovy keyword
847      */
848     @SuppressWarnings("rawtypes")
inList(String propertyName, Collection values)849     public Object inList(String propertyName, Collection values) {
850         return in(propertyName, values);
851     }
852 
853     /**
854      * Delegates to in as in is a Groovy keyword
855      */
inList(String propertyName, Object[] values)856     public Object inList(String propertyName, Object[] values) {
857         return in(propertyName, values);
858     }
859 
860     /**
861      * Applys a "in" contrain on the specified property
862      * @param propertyName The property name
863      * @param values A collection of values
864      *
865      * @return A Criterion instance
866      */
in(String propertyName, Object[] values)867     public Object in(String propertyName, Object[] values) {
868         if (!validateSimpleExpression()) {
869             throwRuntimeException(new IllegalArgumentException("Call to [in] with propertyName [" +
870                     propertyName + "] and values [" + values + "] not allowed here."));
871         }
872 
873         propertyName = calculatePropertyName(propertyName);
874         return addToCriteria(Restrictions.in(propertyName, values));
875     }
876 
877     /**
878      * Orders by the specified property name (defaults to ascending)
879      *
880      * @param propertyName The property name to order by
881      * @return A Order instance
882      */
order(String propertyName)883     public Object order(String propertyName) {
884         if (criteria == null) {
885             throwRuntimeException(new IllegalArgumentException("Call to [order] with propertyName [" +
886                     propertyName + "]not allowed here."));
887         }
888         propertyName = calculatePropertyName(propertyName);
889         Order o = Order.asc(propertyName);
890         if (paginationEnabledList) {
891             orderEntries.add(o);
892         }
893         else {
894             criteria.addOrder(o);
895         }
896         return o;
897     }
898 
899     /**
900      * Orders by the specified property name and direction
901      *
902      * @param propertyName The property name to order by
903      * @param direction Either "asc" for ascending or "desc" for descending
904      *
905      * @return A Order instance
906      */
order(String propertyName, String direction)907     public Object order(String propertyName, String direction) {
908         if (criteria == null) {
909             throwRuntimeException(new IllegalArgumentException("Call to [order] with propertyName [" +
910                     propertyName + "]not allowed here."));
911         }
912         propertyName = calculatePropertyName(propertyName);
913         Order o;
914         if (direction.equals(ORDER_DESCENDING)) {
915             o = Order.desc(propertyName);
916         }
917         else {
918             o = Order.asc(propertyName);
919         }
920         if (paginationEnabledList) {
921             orderEntries.add(o);
922         }
923         else {
924             criteria.addOrder(o);
925         }
926         return o;
927     }
928 
929     /**
930      * Creates a Criterion that contrains a collection property by size
931      *
932      * @param propertyName The property name
933      * @param size The size to constrain by
934      *
935      * @return A Criterion instance
936      */
sizeEq(String propertyName, int size)937     public Object sizeEq(String propertyName, int size) {
938         if (!validateSimpleExpression()) {
939             throwRuntimeException(new IllegalArgumentException("Call to [sizeEq] with propertyName [" +
940                     propertyName + "] and size [" + size + "] not allowed here."));
941         }
942 
943         propertyName = calculatePropertyName(propertyName);
944         return addToCriteria(Restrictions.sizeEq(propertyName, size));
945     }
946 
947     /**
948      * Creates a Criterion that contrains a collection property to be greater than the given size
949      *
950      * @param propertyName The property name
951      * @param size The size to constrain by
952      *
953      * @return A Criterion instance
954      */
sizeGt(String propertyName, int size)955     public Object sizeGt(String propertyName, int size) {
956         if (!validateSimpleExpression()) {
957             throwRuntimeException(new IllegalArgumentException("Call to [sizeGt] with propertyName [" +
958                     propertyName + "] and size [" + size + "] not allowed here."));
959         }
960 
961         propertyName = calculatePropertyName(propertyName);
962         return addToCriteria(Restrictions.sizeGt(propertyName, size));
963     }
964 
965     /**
966      * Creates a Criterion that contrains a collection property to be greater than or equal to the given size
967      *
968      * @param propertyName The property name
969      * @param size The size to constrain by
970      *
971      * @return A Criterion instance
972      */
sizeGe(String propertyName, int size)973     public Object sizeGe(String propertyName, int size) {
974         if (!validateSimpleExpression()) {
975             throwRuntimeException(new IllegalArgumentException("Call to [sizeGe] with propertyName [" +
976                     propertyName + "] and size [" + size + "] not allowed here."));
977         }
978 
979         propertyName = calculatePropertyName(propertyName);
980         return addToCriteria(Restrictions.sizeGe(propertyName, size));
981     }
982 
983     /**
984      * Creates a Criterion that contrains a collection property to be less than or equal to the given size
985      *
986      * @param propertyName The property name
987      * @param size The size to constrain by
988      *
989      * @return A Criterion instance
990      */
sizeLe(String propertyName, int size)991     public Object sizeLe(String propertyName, int size) {
992         if (!validateSimpleExpression()) {
993             throwRuntimeException(new IllegalArgumentException("Call to [sizeLe] with propertyName [" +
994                     propertyName + "] and size [" + size + "] not allowed here."));
995         }
996 
997         propertyName = calculatePropertyName(propertyName);
998         return addToCriteria(Restrictions.sizeLe(propertyName, size));
999     }
1000 
1001     /**
1002      * Creates a Criterion that contrains a collection property to be less than to the given size
1003      *
1004      * @param propertyName The property name
1005      * @param size The size to constrain by
1006      *
1007      * @return A Criterion instance
1008      */
sizeLt(String propertyName, int size)1009     public Object sizeLt(String propertyName, int size) {
1010         if (!validateSimpleExpression()) {
1011             throwRuntimeException(new IllegalArgumentException("Call to [sizeLt] with propertyName [" +
1012                     propertyName + "] and size [" + size + "] not allowed here."));
1013         }
1014 
1015         propertyName = calculatePropertyName(propertyName);
1016         return addToCriteria(Restrictions.sizeLt(propertyName, size));
1017     }
1018 
1019     /**
1020      * Creates a Criterion that contrains a collection property to be not equal to the given size
1021      *
1022      * @param propertyName The property name
1023      * @param size The size to constrain by
1024      *
1025      * @return A Criterion instance
1026      */
sizeNe(String propertyName, int size)1027     public Object sizeNe(String propertyName, int size) {
1028         if (!validateSimpleExpression()) {
1029             throwRuntimeException(new IllegalArgumentException("Call to [sizeNe] with propertyName [" +
1030                     propertyName + "] and size [" + size + "] not allowed here."));
1031         }
1032 
1033         propertyName = calculatePropertyName(propertyName);
1034         return addToCriteria(Restrictions.sizeNe(propertyName, size));
1035     }
1036 
1037     /**
1038      * Creates a "not equal" Criterion based on the specified property name and value
1039      * @param propertyName The property name
1040      * @param propertyValue The property value
1041      * @return The criterion object
1042      */
ne(String propertyName, Object propertyValue)1043     public Object ne(String propertyName, Object propertyValue) {
1044         if (!validateSimpleExpression()) {
1045             throwRuntimeException(new IllegalArgumentException("Call to [ne] with propertyName [" +
1046                     propertyName + "] and value [" + propertyValue + "] not allowed here."));
1047         }
1048 
1049         propertyName = calculatePropertyName(propertyName);
1050         propertyValue = calculatePropertyValue(propertyValue);
1051         return addToCriteria(Restrictions.ne(propertyName, propertyValue));
1052     }
1053 
notEqual(String propertyName, Object propertyValue)1054     public Object notEqual(String propertyName, Object propertyValue) {
1055         return ne(propertyName, propertyValue);
1056     }
1057 
1058     /**
1059      * Creates a "between" Criterion based on the property name and specified lo and hi values
1060      * @param propertyName The property name
1061      * @param lo The low value
1062      * @param hi The high value
1063      * @return A Criterion instance
1064      */
between(String propertyName, Object lo, Object hi)1065     public Object between(String propertyName, Object lo, Object hi) {
1066         if (!validateSimpleExpression()) {
1067             throwRuntimeException(new IllegalArgumentException("Call to [between] with propertyName [" +
1068                     propertyName + "]  not allowed here."));
1069         }
1070 
1071         propertyName = calculatePropertyName(propertyName);
1072         return addToCriteria(Restrictions.between(propertyName, lo, hi));
1073     }
1074 
validateSimpleExpression()1075     private boolean validateSimpleExpression() {
1076         return criteria != null;
1077     }
1078 
1079     @SuppressWarnings("rawtypes")
1080     @Override
invokeMethod(String name, Object obj)1081     public Object invokeMethod(String name, Object obj) {
1082         Object[] args = obj.getClass().isArray() ? (Object[])obj : new Object[]{obj};
1083 
1084         if (paginationEnabledList && SET_RESULT_TRANSFORMER_CALL.equals(name) && args.length == 1 &&
1085                 args[0] instanceof ResultTransformer) {
1086             resultTransformer = (ResultTransformer) args[0];
1087             return null;
1088         }
1089 
1090         if (isCriteriaConstructionMethod(name, args)) {
1091             if (criteria != null) {
1092                 throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
1093             }
1094 
1095             if (name.equals(GET_CALL)) {
1096                 uniqueResult = true;
1097             }
1098             else if (name.equals(SCROLL_CALL)) {
1099                 scroll = true;
1100             }
1101             else if (name.equals(COUNT_CALL)) {
1102                 count = true;
1103             }
1104             else if (name.equals(LIST_DISTINCT_CALL)) {
1105                 resultTransformer = CriteriaSpecification.DISTINCT_ROOT_ENTITY;
1106             }
1107 
1108             createCriteriaInstance();
1109 
1110             // Check for pagination params
1111             if (name.equals(LIST_CALL) && args.length == 2) {
1112                 paginationEnabledList = true;
1113                 orderEntries = new ArrayList<Order>();
1114                 invokeClosureNode(args[1]);
1115             }
1116             else {
1117                 invokeClosureNode(args[0]);
1118             }
1119 
1120             if (resultTransformer != null) {
1121                 criteria.setResultTransformer(resultTransformer);
1122             }
1123             Object result;
1124             if (!uniqueResult) {
1125                 if (scroll) {
1126                     result = criteria.scroll();
1127                 }
1128                 else if (count) {
1129                     criteria.setProjection(Projections.rowCount());
1130                     result = criteria.uniqueResult();
1131                 }
1132                 else if (paginationEnabledList) {
1133                     // Calculate how many results there are in total. This has been
1134                     // moved to before the 'list()' invocation to avoid any "ORDER
1135                     // BY" clause added by 'populateArgumentsForCriteria()', otherwise
1136                     // an exception is thrown for non-string sort fields (GRAILS-2690).
1137                     criteria.setFirstResult(0);
1138                     criteria.setMaxResults(Integer.MAX_VALUE);
1139                     criteria.setProjection(Projections.rowCount());
1140                     int totalCount = ((Integer)criteria.uniqueResult()).intValue();
1141 
1142                     // Drop the projection, add settings for the pagination parameters,
1143                     // and then execute the query.
1144                     criteria.setProjection(null);
1145                     for (Iterator<Order> it = orderEntries.iterator(); it.hasNext();) {
1146                         criteria.addOrder(it.next());
1147                     }
1148                     if (resultTransformer == null) {
1149                         criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
1150                     }
1151                     else if (paginationEnabledList) {
1152                         // relevant to GRAILS-5692
1153                         criteria.setResultTransformer(resultTransformer);
1154                     }
1155                     GrailsHibernateUtil.populateArgumentsForCriteria(targetClass, criteria, (Map)args[0]);
1156                     PagedResultList pagedRes = new PagedResultList(criteria.list());
1157 
1158                     // Updated the paged results with the total number of records calculated previously.
1159                     pagedRes.setTotalCount(totalCount);
1160                     result = pagedRes;
1161                 }
1162                 else {
1163                     result = criteria.list();
1164                 }
1165             }
1166             else {
1167                 result = GrailsHibernateUtil.unwrapIfProxy(criteria.uniqueResult());
1168             }
1169             if (!participate) {
1170                 hibernateSession.close();
1171             }
1172             return result;
1173         }
1174 
1175         if (criteria == null) createCriteriaInstance();
1176 
1177         MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args);
1178         if (metaMethod != null) {
1179             return metaMethod.invoke(this, args);
1180         }
1181 
1182         metaMethod = criteriaMetaClass.getMetaMethod(name, args);
1183         if (metaMethod != null) {
1184             return metaMethod.invoke(criteria, args);
1185         }
1186         metaMethod = criteriaMetaClass.getMetaMethod(GrailsClassUtils.getSetterName(name), args);
1187         if (metaMethod != null) {
1188             return metaMethod.invoke(criteria, args);
1189         }
1190 
1191         if (args.length == 1 && args[0] instanceof Closure) {
1192             if (name.equals(AND) || name.equals(OR) || name.equals(NOT)) {
1193                 if (criteria == null) {
1194                     throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
1195                 }
1196 
1197                 logicalExpressionStack.add(new LogicalExpression(name));
1198                 invokeClosureNode(args[0]);
1199 
1200                 LogicalExpression logicalExpression = logicalExpressionStack.remove(logicalExpressionStack.size()-1);
1201                 addToCriteria(logicalExpression.toCriterion());
1202 
1203                 return name;
1204             }
1205 
1206             if (name.equals(PROJECTIONS) && args.length == 1 && (args[0] instanceof Closure)) {
1207                 if (criteria == null) {
1208                     throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
1209                 }
1210 
1211                 projectionList = Projections.projectionList();
1212                 invokeClosureNode(args[0]);
1213 
1214                 if (projectionList != null && projectionList.getLength() > 0) {
1215                     criteria.setProjection(projectionList);
1216                 }
1217 
1218                 return name;
1219             }
1220 
1221             if (targetBean.isReadableProperty(name.toString())) {
1222                 ClassMetadata meta = sessionFactory.getClassMetadata(targetBean.getWrappedClass());
1223                 Type type = meta.getPropertyType(name.toString());
1224                 if (type.isAssociationType()) {
1225                     String otherSideEntityName =
1226                         ((AssociationType) type).getAssociatedEntityName((SessionFactoryImplementor) sessionFactory);
1227                     Class oldTargetClass = targetClass;
1228                     targetClass = sessionFactory.getClassMetadata(otherSideEntityName).getMappedClass(EntityMode.POJO);
1229                     BeanWrapper oldTargetBean = targetBean;
1230                     targetBean = new BeanWrapperImpl(BeanUtils.instantiateClass(targetClass));
1231                     associationStack.add(name.toString());
1232                     final String associationPath = getAssociationPath();
1233                     createAliasIfNeccessary(name, associationPath);
1234                     // the criteria within an association node are grouped with an implicit AND
1235                     logicalExpressionStack.add(new LogicalExpression(AND));
1236                     invokeClosureNode(args[0]);
1237                     aliasStack.remove(aliasStack.size() - 1);
1238                     if (!aliasInstanceStack.isEmpty()) {
1239                         aliasInstanceStack.remove(aliasInstanceStack.size() - 1);
1240                     }
1241                     LogicalExpression logicalExpression = logicalExpressionStack.remove(logicalExpressionStack.size()-1);
1242                     if (!logicalExpression.args.isEmpty()) {
1243                         addToCriteria(logicalExpression.toCriterion());
1244                     }
1245                     associationStack.remove(associationStack.size()-1);
1246                     targetClass = oldTargetClass;
1247                     targetBean = oldTargetBean;
1248                     return name;
1249                 }
1250             }
1251         }
1252         else if (args.length == 1 && args[0] != null) {
1253             if (criteria == null) {
1254                 throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here"));
1255             }
1256 
1257             Object value = args[0];
1258             Criterion c = null;
1259             if (name.equals(ID_EQUALS)) {
1260                 return eq("id", value);
1261             }
1262 
1263             if (name.equals(IS_NULL) ||
1264                     name.equals(IS_NOT_NULL) ||
1265                     name.equals(IS_EMPTY) ||
1266                     name.equals(IS_NOT_EMPTY)) {
1267                 if (!(value instanceof String)) {
1268                     throwRuntimeException(new IllegalArgumentException("call to [" + name + "] with value [" +
1269                             value + "] requires a String value."));
1270                 }
1271                 String propertyName = calculatePropertyName((String)value);
1272                 if (name.equals(IS_NULL)) {
1273                     c = Restrictions.isNull(propertyName) ;
1274                 }
1275                 else if (name.equals(IS_NOT_NULL)) {
1276                     c = Restrictions.isNotNull(propertyName);
1277                 }
1278                 else if (name.equals(IS_EMPTY)) {
1279                     c = Restrictions.isEmpty(propertyName);
1280                 }
1281                 else if (name.equals(IS_NOT_EMPTY)) {
1282                     c = Restrictions.isNotEmpty(propertyName);
1283                 }
1284             }
1285 
1286             if (c != null) {
1287                 return addToCriteria(c);
1288             }
1289         }
1290 
1291         throw new MissingMethodException(name, getClass(), args) ;
1292     }
1293 
1294     @SuppressWarnings("unused")
addToCurrentOrAliasedCriteria(Criterion criterion)1295     private void addToCurrentOrAliasedCriteria(Criterion criterion) {
1296         if (!aliasInstanceStack.isEmpty()) {
1297             Criteria c = aliasInstanceStack.get(aliasInstanceStack.size()-1);
1298             c.add(criterion);
1299         }
1300         else {
1301             criteria.add(criterion);
1302         }
1303     }
1304 
createAliasIfNeccessary(String associationName, String associationPath)1305     private void createAliasIfNeccessary(String associationName, String associationPath) {
1306         String newAlias;
1307         if (aliasMap.containsKey(associationPath)) {
1308             newAlias = aliasMap.get(associationPath);
1309         }
1310         else {
1311             aliasCount++;
1312             newAlias = associationName + ALIAS + aliasCount;
1313             aliasMap.put(associationPath, newAlias);
1314             aliasInstanceStack.add(criteria.createAlias(associationPath, newAlias,
1315                     CriteriaSpecification.LEFT_JOIN));
1316         }
1317         aliasStack.add(newAlias);
1318     }
1319 
getAssociationPath()1320     private String getAssociationPath() {
1321         StringBuilder fullPath = new StringBuilder();
1322         for (Object anAssociationStack : associationStack) {
1323             String propertyName = (String) anAssociationStack;
1324             if (fullPath.length() > 0) fullPath.append(".");
1325             fullPath.append(propertyName);
1326         }
1327         final String associationPath = fullPath.toString();
1328         return associationPath;
1329     }
1330 
isCriteriaConstructionMethod(String name, Object[] args)1331     private boolean isCriteriaConstructionMethod(String name, Object[] args) {
1332         return (name.equals(LIST_CALL) && args.length == 2 && args[0] instanceof Map && args[1] instanceof Closure) ||
1333                   (name.equals(ROOT_CALL) ||
1334                     name.equals(ROOT_DO_CALL) ||
1335                     name.equals(LIST_CALL) ||
1336                     name.equals(LIST_DISTINCT_CALL) ||
1337                     name.equals(GET_CALL) ||
1338                     name.equals(COUNT_CALL) ||
1339                     name.equals(SCROLL_CALL) && args.length == 1 && args[0] instanceof Closure);
1340     }
1341 
buildCriteria(Closure criteriaClosure)1342     public Criteria buildCriteria(Closure criteriaClosure) {
1343         createCriteriaInstance();
1344         criteriaClosure.setDelegate(this);
1345         criteriaClosure.call();
1346         return criteria;
1347     }
1348 
createCriteriaInstance()1349     private void createCriteriaInstance() {
1350         if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
1351             participate = true;
1352             hibernateSession = ((SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory)).getSession();
1353         }
1354         else {
1355             hibernateSession = sessionFactory.openSession();
1356         }
1357 
1358         criteria = hibernateSession.createCriteria(targetClass);
1359         GrailsHibernateUtil.cacheCriteriaByMapping(targetClass, criteria);
1360         criteriaMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(criteria.getClass());
1361     }
1362 
invokeClosureNode(Object args)1363     private void invokeClosureNode(Object args) {
1364         Closure callable = (Closure)args;
1365         callable.setDelegate(this);
1366         callable.setResolveStrategy(Closure.DELEGATE_FIRST);
1367         callable.call();
1368     }
1369 
1370     /**
1371      * Throws a runtime exception where necessary to ensure the session gets closed
1372      */
throwRuntimeException(RuntimeException t)1373     private void throwRuntimeException(RuntimeException t) {
1374         closeSessionFollowingException();
1375         throw t;
1376     }
1377 
closeSessionFollowingException()1378     private void closeSessionFollowingException() {
1379         if (hibernateSession != null && hibernateSession.isOpen() && !participate) {
1380             hibernateSession.close();
1381         }
1382         criteria = null;
1383     }
1384 
1385     /**
1386      * adds and returns the given criterion to the currently active criteria set.
1387      * this might be either the root criteria or a currently open
1388      * LogicalExpression.
1389      */
addToCriteria(Criterion c)1390     private Criterion addToCriteria(Criterion c) {
1391         if (!logicalExpressionStack.isEmpty()) {
1392             logicalExpressionStack.get(logicalExpressionStack.size() - 1).args.add(c);
1393         }
1394         else {
1395             criteria.add(c);
1396         }
1397         return c;
1398     }
1399 
1400     /**
1401      * instances of this class are pushed onto the logicalExpressionStack
1402      * to represent all the unfinished "and", "or", and "not" expressions.
1403      */
1404     private class LogicalExpression {
1405         final Object name;
1406         final List<Criterion> args = new ArrayList<Criterion>();
1407 
LogicalExpression(Object name)1408         LogicalExpression(Object name) {
1409             this.name = name;
1410         }
1411 
toCriterion()1412         Criterion toCriterion() {
1413             if (name.equals(NOT)) {
1414                 switch (args.size()) {
1415                     case 0:
1416                         throwRuntimeException(new IllegalArgumentException("Logical expression [not] must contain at least 1 expression"));
1417                         return null;
1418 
1419                     case 1:
1420                         return Restrictions.not(args.get(0));
1421 
1422                     default:
1423                         // treat multiple sub-criteria as an implicit "OR"
1424                         return Restrictions.not(buildJunction(Restrictions.disjunction(), args));
1425                 }
1426             }
1427 
1428             if (name.equals(AND)) {
1429                 return buildJunction(Restrictions.conjunction(), args);
1430             }
1431 
1432             if (name.equals(OR)) {
1433                 return buildJunction(Restrictions.disjunction(), args);
1434             }
1435 
1436             throwRuntimeException(new IllegalStateException("Logical expression [" + name + "] not handled!"));
1437             return null;
1438         }
1439 
1440         // add the Criterion objects in the given list to the given junction.
buildJunction(Junction junction, List<Criterion> criterions)1441         Junction buildJunction(Junction junction, List<Criterion> criterions) {
1442             for (Criterion c : criterions) {
1443                 junction.add(c);
1444             }
1445 
1446             return junction;
1447         }
1448     }
1449 }
1450