GRAILS-1019: Allowing expressions to be used with the 'disabled' attribute for g...
[grails.git] / src / persistence / org / codehaus / groovy / grails / orm / hibernate / metaclass / AbstractClausedStaticPersistentMethod.java
blobbc42bac0ad25e9686b8fae4c9fae36944160f57e
1 /* Copyright 2004-2005 the original author or authors.
2 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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.
14 */
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;
35 /**
36 * @author Graeme Rocher
37 * @since 31-Aug-2005
40 public abstract class AbstractClausedStaticPersistentMethod extends
41 AbstractStaticPersistentMethod {
43 private static final Log LOG = LogFactory.getLog(AbstractClausedStaticPersistentMethod.class);
44 /**
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;
78 this.type = type;
79 this.argumentsRequired = argumentsRequired;
80 this.negation = negation;
83 public String toString() {
84 StringBuffer buf = new StringBuffer("[GrailsMethodExpression] ");
85 buf.append(propertyName)
86 .append(" ")
87 .append(type)
88 .append(" ");
90 for (int i = 0; i < arguments.length; i++) {
91 buf.append(arguments[i]);
92 if(i != arguments.length)
93 buf.append(" and ");
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);
106 if(prop == null)
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()))) {
116 try {
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())) {
122 try {
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());
128 } else {
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");
143 if(negation) {
144 return Expression.not( createCriterion() );
146 else {
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(
154 application,
155 clazz,
156 calcPropertyName(queryParameter, LESS_THAN_OR_EQUAL),
157 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(
167 application,
168 clazz,
169 calcPropertyName(queryParameter, LESS_THAN),
170 LESS_THAN,
171 1, // argument count
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(
181 application,
182 clazz,
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(
195 application,
196 clazz,
197 calcPropertyName(queryParameter, GREATER_THAN),
198 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(
210 application,
211 clazz,
212 calcPropertyName(queryParameter, LIKE),
213 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(
225 application,
226 clazz,
227 calcPropertyName(queryParameter, ILIKE),
228 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(
240 application,
241 clazz,
242 calcPropertyName(queryParameter, IS_NOT_NULL),
243 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(
254 application,
255 clazz,
256 calcPropertyName(queryParameter, IS_NULL),
257 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(
269 application,
270 clazz,
271 calcPropertyName(queryParameter, BETWEEN),
272 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(
283 application,
284 clazz,
285 calcPropertyName(queryParameter, NOT_EQUAL),
286 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]);
296 else {
298 return new GrailsMethodExpression(
299 application,
300 clazz,
301 calcPropertyName(queryParameter, null),
302 EQUAL,
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) {
313 String propName;
314 if(clause != null && !clause.equals( EQUAL )) {
315 int i = queryParameter.indexOf(clause);
316 propName = queryParameter.substring(0,i);
318 else {
319 propName = queryParameter;
321 return propName.endsWith(NOT);
323 private static String calcPropertyName(String queryParameter, String clause) {
324 String propName;
325 if(clause != null && !clause.equals( EQUAL )) {
326 int i = queryParameter.indexOf(clause);
327 propName = queryParameter.substring(0,i);
329 else {
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+)");
355 /* (non-Javadoc)
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 );
363 // find match
364 match.find();
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];
398 try {
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);
407 break;
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);
422 try {
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
433 // throw exception
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);