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