1 /* Copyright 2004-2005 the original author or authors.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
15 package org
.codehaus
.groovy
.grails
.orm
.hibernate
.metaclass
;
17 import groovy
.lang
.GString
;
18 import groovy
.lang
.MissingMethodException
;
19 import org
.apache
.commons
.logging
.Log
;
20 import org
.apache
.commons
.logging
.LogFactory
;
21 import org
.codehaus
.groovy
.grails
.commons
.*;
22 import org
.hibernate
.SessionFactory
;
23 import org
.hibernate
.criterion
.Criterion
;
24 import org
.hibernate
.criterion
.Expression
;
25 import org
.springframework
.beans
.SimpleTypeConverter
;
26 import org
.springframework
.beans
.TypeConverter
;
27 import org
.springframework
.beans
.TypeMismatchException
;
29 import java
.util
.ArrayList
;
30 import java
.util
.List
;
31 import java
.util
.Locale
;
32 import java
.util
.regex
.Matcher
;
33 import java
.util
.regex
.Pattern
;
36 * @author Graeme Rocher
40 public abstract class AbstractClausedStaticPersistentMethod
extends
41 AbstractStaticPersistentMethod
{
43 private static final Log LOG
= LogFactory
.getLog(AbstractClausedStaticPersistentMethod
.class);
46 * @author Graeme Rocher
49 protected abstract static class GrailsMethodExpression
{
50 private static final String LESS_THAN
= "LessThan";
51 private static final String LESS_THAN_OR_EQUAL
= "LessThanEquals";
52 private static final String GREATER_THAN
= "GreaterThan";
53 private static final String GREATER_THAN_OR_EQUAL
= "GreaterThanEquals";
54 private static final String LIKE
= "Like";
55 private static final String ILIKE
= "Ilike";
56 private static final String BETWEEN
= "Between";
57 private static final String IS_NOT_NULL
= "IsNotNull";
58 private static final String IS_NULL
= "IsNull";
59 private static final String NOT
= "Not";
60 private static final String EQUAL
= "Equal";
61 private static final String NOT_EQUAL
= "NotEqual";
64 protected String propertyName
;
65 protected Object
[] arguments
;
66 protected int argumentsRequired
;
67 protected boolean negation
;
68 protected String type
;
69 protected Class targetClass
;
70 private GrailsApplication application
;
71 private TypeConverter converter
= new SimpleTypeConverter();
74 GrailsMethodExpression(GrailsApplication application
,Class targetClass
,String propertyName
, String type
,int argumentsRequired
,boolean negation
) {
75 this.application
= application
;
76 this.targetClass
= targetClass
;
77 this.propertyName
= propertyName
;
79 this.argumentsRequired
= argumentsRequired
;
80 this.negation
= negation
;
83 public String
toString() {
84 StringBuffer buf
= new StringBuffer("[GrailsMethodExpression] ");
85 buf
.append(propertyName
)
90 for (int i
= 0; i
< arguments
.length
; i
++) {
91 buf
.append(arguments
[i
]);
92 if(i
!= arguments
.length
)
95 return buf
.toString();
98 void setArguments(Object
[] args
)
99 throws IllegalArgumentException
{
100 if(args
.length
!= argumentsRequired
)
101 throw new IllegalArgumentException("Method expression '"+this.type
+"' requires " + argumentsRequired
+ " arguments");
103 GrailsDomainClass dc
= (GrailsDomainClass
) application
.getArtefact(DomainClassArtefactHandler
.TYPE
, targetClass
.getName());
104 GrailsDomainClassProperty prop
= dc
.getPropertyByName(propertyName
);
107 throw new IllegalArgumentException("Property "+propertyName
+" doesn't exist for method expression '"+this.type
+"'");
109 for (int i
= 0; i
< args
.length
; i
++) {
110 if(args
[i
] == null) continue;
111 // convert GStrings to strings
112 if(prop
.getType() == String
.class && (args
[i
] instanceof GString
)) {
113 args
[i
] = args
[i
].toString();
115 else if(!prop
.getType().isAssignableFrom( args
[i
].getClass() ) && !(GrailsClassUtils
.isMatchBetweenPrimativeAndWrapperTypes(prop
.getType(), args
[i
].getClass()))) {
117 args
[i
] = converter
.convertIfNecessary( args
[i
], prop
.getType());
118 } catch ( TypeMismatchException tme
) {
119 // if we cannot perform direct conversion and argument is subclass of Number
120 // we can try to convert it through it's String representation
121 if(Number
.class.isAssignableFrom(args
[i
].getClass())) {
123 args
[i
] = converter
.convertIfNecessary( args
[i
].toString(), prop
.getType());
124 } catch( TypeMismatchException tme1
) {
126 throw new IllegalArgumentException("Cannot convert value " + args
[i
] + " of property '"+propertyName
+"' to required type " + prop
.getType() + ": " + tme1
.getMessage());
129 throw new IllegalArgumentException("Cannot convert value " + args
[i
] + " of property '"+propertyName
+"' to required type " + prop
.getType());
135 this.arguments
= args
;
138 abstract Criterion
createCriterion();
139 protected Criterion
getCriterion() {
140 if(arguments
== null)
141 throw new IllegalStateException("Parameters array must be set before retrieving Criterion");
144 return Expression
.not( createCriterion() );
147 return createCriterion();
151 protected static GrailsMethodExpression
create(final GrailsApplication application
,Class clazz
, String queryParameter
) {
152 if(queryParameter
.endsWith( LESS_THAN_OR_EQUAL
)) {
153 return new GrailsMethodExpression(
156 calcPropertyName(queryParameter
, LESS_THAN_OR_EQUAL
),
159 isNegation(queryParameter
, LESS_THAN_OR_EQUAL
) ) {
160 Criterion
createCriterion() {
161 return Expression
.le( this.propertyName
, arguments
[0] );
165 else if(queryParameter
.endsWith( LESS_THAN
)) {
166 return new GrailsMethodExpression(
169 calcPropertyName(queryParameter
, LESS_THAN
),
172 isNegation(queryParameter
, LESS_THAN
) ) {
173 Criterion
createCriterion() {
174 if(arguments
[0] == null) return Expression
.isNull(this.propertyName
);
175 return Expression
.lt( this.propertyName
, arguments
[0] );
179 else if(queryParameter
.endsWith( GREATER_THAN_OR_EQUAL
)) {
180 return new GrailsMethodExpression(
183 calcPropertyName(queryParameter
, GREATER_THAN_OR_EQUAL
),
184 GREATER_THAN_OR_EQUAL
,
186 isNegation(queryParameter
, GREATER_THAN_OR_EQUAL
) ) {
187 Criterion
createCriterion() {
188 if(arguments
[0] == null) return Expression
.isNull(this.propertyName
);
189 return Expression
.ge( this.propertyName
, arguments
[0] );
193 else if(queryParameter
.endsWith( GREATER_THAN
)) {
194 return new GrailsMethodExpression(
197 calcPropertyName(queryParameter
, GREATER_THAN
),
200 isNegation(queryParameter
, GREATER_THAN
) ) {
201 Criterion
createCriterion() {
202 if(arguments
[0] == null) return Expression
.isNull(this.propertyName
);
203 return Expression
.gt( this.propertyName
, arguments
[0] );
208 else if(queryParameter
.endsWith( LIKE
)) {
209 return new GrailsMethodExpression(
212 calcPropertyName(queryParameter
, LIKE
),
215 isNegation(queryParameter
, LIKE
) ) {
216 Criterion
createCriterion() {
217 if(arguments
[0] == null) return Expression
.isNull(this.propertyName
);
218 return Expression
.like( this.propertyName
, arguments
[0] );
223 else if(queryParameter
.endsWith( ILIKE
)) {
224 return new GrailsMethodExpression(
227 calcPropertyName(queryParameter
, ILIKE
),
230 isNegation(queryParameter
, ILIKE
) ) {
231 Criterion
createCriterion() {
232 if(arguments
[0] == null) return Expression
.isNull(this.propertyName
);
233 return Expression
.ilike( this.propertyName
, arguments
[0] );
238 else if(queryParameter
.endsWith( IS_NOT_NULL
)) {
239 return new GrailsMethodExpression(
242 calcPropertyName(queryParameter
, IS_NOT_NULL
),
245 isNegation(queryParameter
, IS_NOT_NULL
) ) {
246 Criterion
createCriterion() {
247 return Expression
.isNotNull( this.propertyName
);
252 else if(queryParameter
.endsWith( IS_NULL
)) {
253 return new GrailsMethodExpression(
256 calcPropertyName(queryParameter
, IS_NULL
),
259 isNegation(queryParameter
, IS_NULL
) ) {
260 Criterion
createCriterion() {
261 return Expression
.isNull( this.propertyName
);
266 else if(queryParameter
.endsWith( BETWEEN
)) {
268 return new GrailsMethodExpression(
271 calcPropertyName(queryParameter
, BETWEEN
),
274 isNegation(queryParameter
, BETWEEN
) ) {
275 Criterion
createCriterion() {
276 return Expression
.between( this.propertyName
,this.arguments
[0], this.arguments
[1] );
281 else if(queryParameter
.endsWith( NOT_EQUAL
)) {
282 return new GrailsMethodExpression(
285 calcPropertyName(queryParameter
, NOT_EQUAL
),
288 isNegation(queryParameter
, NOT_EQUAL
) ) {
289 Criterion
createCriterion() {
290 if(arguments
[0] == null) return Expression
.isNotNull(this.propertyName
);
291 return Expression
.ne( this.propertyName
,this.arguments
[0]);
298 return new GrailsMethodExpression(
301 calcPropertyName(queryParameter
, null),
304 isNegation(queryParameter
, EQUAL
) ) {
305 Criterion
createCriterion() {
306 if(arguments
[0] == null) return Expression
.isNull(this.propertyName
);
307 return Expression
.eq( this.propertyName
,this.arguments
[0]);
312 private static boolean isNegation(String queryParameter
, String clause
) {
314 if(clause
!= null && !clause
.equals( EQUAL
)) {
315 int i
= queryParameter
.indexOf(clause
);
316 propName
= queryParameter
.substring(0,i
);
319 propName
= queryParameter
;
321 return propName
.endsWith(NOT
);
323 private static String
calcPropertyName(String queryParameter
, String clause
) {
325 if(clause
!= null && !clause
.equals( EQUAL
)) {
326 int i
= queryParameter
.indexOf(clause
);
327 propName
= queryParameter
.substring(0,i
);
330 propName
= queryParameter
;
332 if(propName
.endsWith(NOT
)) {
333 int i
= propName
.lastIndexOf(NOT
);
334 propName
= propName
.substring(0, i
);
336 return propName
.substring(0,1).toLowerCase(Locale
.ENGLISH
)
337 + propName
.substring(1);
341 private final String
[] operators
;
342 private final Pattern
[] operatorPatterns
;
343 protected final GrailsApplication application
;
345 public AbstractClausedStaticPersistentMethod(GrailsApplication application
, SessionFactory sessionFactory
, ClassLoader classLoader
, Pattern pattern
, String
[] operators
) {
346 super(sessionFactory
, classLoader
, pattern
);
347 this.application
= application
;
348 this.operators
= operators
;
349 this.operatorPatterns
= new Pattern
[this.operators
.length
];
350 for (int i
= 0; i
< operators
.length
; i
++) {
351 this.operatorPatterns
[i
] = Pattern
.compile("(\\w+)("+this.operators
[i
]+")(\\p{Upper})(\\w+)");
356 * @see org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractStaticPersistentMethod#doInvokeInternal(java.lang.Class, java.lang.String, java.lang.Object[])
358 protected Object
doInvokeInternal(final Class clazz
, String methodName
,
359 Object
[] arguments
) {
360 List expressions
= new ArrayList();
361 if(arguments
== null) arguments
= new Object
[0];
362 Matcher match
= super.getPattern().matcher( methodName
);
366 String
[] queryParameters
;
367 int totalRequiredArguments
= 0;
368 // get the sequence clauses
369 String querySequence
= match
.group(2);
370 // if it contains operator and split
371 boolean containsOperator
= false;
372 String operatorInUse
= null;
374 for (int i
= 0; i
< operators
.length
; i
++) {
375 Matcher currentMatcher
= operatorPatterns
[i
].matcher( querySequence
);
376 if(currentMatcher
.find()) {
377 containsOperator
= true;
378 operatorInUse
= this.operators
[i
];
380 queryParameters
= new String
[2];
381 queryParameters
[0] = currentMatcher
.group(1);
382 queryParameters
[1] = currentMatcher
.group(3) + currentMatcher
.group(4);
384 // loop through query parameters and create expressions
385 // calculating the numBer of arguments required for the expression
386 int argumentCursor
= 0;
387 for (int j
= 0; j
< queryParameters
.length
; j
++) {
388 GrailsMethodExpression currentExpression
= GrailsMethodExpression
.create(this.application
,clazz
,queryParameters
[j
]);
389 totalRequiredArguments
+= currentExpression
.argumentsRequired
;
390 // populate the arguments into the GrailsExpression from the argument list
391 Object
[] currentArguments
= new Object
[currentExpression
.argumentsRequired
];
392 if((argumentCursor
+ currentExpression
.argumentsRequired
) > arguments
.length
)
393 throw new MissingMethodException(methodName
,clazz
,arguments
);
395 for (int k
= 0; k
< currentExpression
.argumentsRequired
; k
++,argumentCursor
++) {
396 currentArguments
[k
] = arguments
[argumentCursor
];
399 currentExpression
.setArguments(currentArguments
);
400 }catch(IllegalArgumentException iae
) {
401 LOG
.debug(iae
.getMessage(),iae
);
402 throw new MissingMethodException(methodName
,clazz
,arguments
);
404 // add to list of expressions
405 expressions
.add(currentExpression
);
411 // otherwise there is only one expression
412 if(!containsOperator
) {
413 GrailsMethodExpression solo
= GrailsMethodExpression
.create(this.application
, clazz
,querySequence
);
415 if(solo
.argumentsRequired
> arguments
.length
)
416 throw new MissingMethodException(methodName
,clazz
,arguments
);
418 totalRequiredArguments
+= solo
.argumentsRequired
;
419 Object
[] soloArgs
= new Object
[solo
.argumentsRequired
];
421 System
.arraycopy(arguments
, 0, soloArgs
, 0, solo
.argumentsRequired
);
423 solo
.setArguments(soloArgs
);
425 catch(IllegalArgumentException iae
) {
426 LOG
.debug(iae
.getMessage(),iae
);
427 throw new MissingMethodException(methodName
,clazz
,arguments
);
429 expressions
.add(solo
);
432 // if the total of all the arguments necessary does not equal the number of arguments
434 if(totalRequiredArguments
> arguments
.length
)
435 throw new MissingMethodException(methodName
,clazz
,arguments
);
437 // calculate the remaining arguments
438 Object
[] remainingArguments
= new Object
[arguments
.length
- totalRequiredArguments
];
439 if(remainingArguments
.length
> 0) {
440 for (int i
= 0, j
= totalRequiredArguments
; i
< remainingArguments
.length
; i
++,j
++) {
441 remainingArguments
[i
] = arguments
[j
];
445 if(LOG
.isTraceEnabled())
446 LOG
.trace("Calculated expressions: " + expressions
);
448 return doInvokeInternalWithExpressions(clazz
, methodName
, remainingArguments
, expressions
, operatorInUse
);
451 protected abstract Object
doInvokeInternalWithExpressions(Class clazz
, String methodName
, Object
[] arguments
, List expressions
, String operatorInUse
);