source: trunk/src/groovy/dbnp/query/Criterion.groovy

Last change on this file was 1917, checked in by robert@…, 10 years ago

Solved bugs in searching:

  • exception when searching for [any field in any object] equals "" (#480)
  • the authentication in searching works properly again
  • Property svn:keywords set to Rev Author Date
File size: 27.5 KB
Line 
1package dbnp.query
2
3import java.text.SimpleDateFormat
4import org.dbnp.gdt.*
5import dbnp.studycapturing.*
6import org.apache.commons.logging.LogFactory;
7
8/**
9 * Available operators for criteria
10 * @author robert
11 *
12 */
13enum Operator {
14        equals( "=" ), contains( "contains" ), gte( ">="), gt( ">" ), lte( "<=" ), lt( "<" ), insearch( "in" )
15        Operator(String name) { this.name = name }
16        private final String name;
17        public String toString() { return name }
18}
19
20/**
21 * Represents a criterion to search on
22 * @author robert
23 *
24 */
25class Criterion {
26        private static final log = LogFactory.getLog(this);
27        public String entity
28        public String field
29        public Operator operator
30        public def value
31
32        /**
33         * Returns the class for the entity of this criterion
34         * @return     
35         */
36        public Class entityClass() {
37                if( this.entity == '*' )
38                        return null;
39               
40                       
41                try {
42                        return TemplateEntity.parseEntity( 'dbnp.studycapturing.' + this.entity)
43                } catch( Exception e ) {
44                        throw new Exception( "Unknown entity for criterion " + this, e );
45                }
46        }
47       
48        /**
49         * Retrieves a combination of the entity and field
50         * @return
51         */
52        public String entityField() {
53                return entity.toString() + ( field ? "." + field.toString() : "" );
54        }
55
56        /**
57         * Retrieves a human readable description of the combination of the entity and field
58         * @return
59         */
60        public String humanReadableEntityField() {
61                if( field == '*' ) {
62                        if( entity == '*' ) {
63                                return "any field in any object"
64                        } else {
65                                return "any field in " + entity.toString();
66                        }
67                } else {
68                        return entityField();
69                }
70        }
71
72        /**
73         * Returns the type of criterion when searching. Multiple types can be returned, since fields
74         * with the same name might have different types.
75         * 
76         * @return      List of strings determining the type of this criterion. Possibilities are:
77         *              [STRING,BOOLEAN,..]:The criterion references a template field that contains a 'simple'
78         *                                                      value (boolean, double, long, string, reltime, date)
79         *              [STRINGLIST,...]:       The criterion references a template field that contains a 'complex'
80         *                                                      value (listitem, ontologyterm, template, module) referencing another
81         *                                                      database table
82         *              Wildcard:                       The criterion references all fields
83         */
84        protected List<String> criterionType() {
85                if( this.entity == '*' || this.field == '*' ) {
86                        return [
87                                 'String',
88                                 'Text',
89                                 'File',
90                                 'Date',
91                                 'RelTime',
92                                 'Double',
93                                 'Long',
94                                 'Boolean',
95                                 'StringList',
96                                 'ExtendableStringList',
97                                 'Term',
98                                 'Template',
99                                 'Module'
100                        ]
101                }
102
103                // Template fields are string fields
104                if( this.field == 'Template' ) 
105                        return [ "String" ]
106               
107                // Determine domain fields of the entity
108                def domainFields = entityClass().giveDomainFields();
109                def domainField = domainFields.find { it.name == this.field };
110                if( domainField )
111                        return [domainField.type?.casedName];
112
113                // If this field is not a domain field, search for the field in the database
114                def entityClass = entityClass()
115
116                if( !entityClass || !this.field )
117                        return null;
118
119                // Find all fields with this name and entity
120                def fields = TemplateField.findAllByName( this.field ).findAll { it.entity == entityClass };
121
122                // If the field is not found, return null
123                if( !fields )
124                        return null
125
126                // Return the (unique) String value of the types
127                return fields*.type.unique()*.casedName;
128        }
129
130        /**
131         * Determines whether the field in this criterion is a domain field
132         *
133         * @return      True iff the field is a domain field, false otherwise
134         */
135        protected boolean isDomainCriterion() {
136                def entityClass = entityClass()
137               
138                if( !entityClass )
139                        return false;
140               
141                // Template fields should be handled as domain criteria
142                if( this.field == "Template" )
143                        return true;
144                       
145                // Determine domain fields of the entity
146                def domainFields = entityClass.giveDomainFields();
147                def domainField = domainFields.find { it.name == this.field };
148
149                return (domainField ? true : false)
150        }
151       
152        /**
153         * Determines whether this criterion references a 'complex' field (i.e. a field that
154         * contains a complex type like Term, ListItem etc.)
155         *
156         * @return
157         */
158        public boolean isComplexCriterion() {
159                if( this.field == '*' )
160                        return false;
161
162                if( isDomainCriterion() )
163                        return false;
164                       
165                def types = criterionType();
166               
167                return types.any { type -> 
168                        switch( type ) {
169                                case 'StringList':
170                                case 'ExtendableStringList':
171                                case 'Term':
172                                case 'Template':
173                                case 'Module':
174                                        return true;
175                        }
176                       
177                        return false;
178                }
179        }
180
181        /**
182         * Case the field value to search on to the given type
183         * @param fieldType     Name of the template field type
184         * @return                      Value casted to the right value
185         */
186        protected def castValue( String fieldType ) {
187                switch( fieldType ) {
188
189                        case 'String':
190                        case 'Text':
191                        case 'StringList':
192                        case 'ExtendableStringList':
193                        case 'Term':
194                        case 'Template':
195                        case 'Module':
196                        case 'File':
197                                return value?.toString();
198                        case 'Date':
199                                // The comparison with date values should only be performed iff the value
200                                // contains a parsable date
201                                // and the operator is equals, gte, gt, lt or lte
202                                if( operator == Operator.insearch || operator == Operator.contains )
203                                        return null
204
205                                try {
206                                        Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value );
207                                        return dateCriterion
208                                } catch( Exception e ) {
209                                        return null;
210                                }
211
212                        case 'RelTime':
213                                // The comparison with date values should only be performed iff the value
214                                // contains a long number
215                                // and the operator is equals, gte, gt, lt or lte
216                                if( operator == Operator.insearch || operator == Operator.contains )
217                                        return null
218
219                                try {
220                                        RelTime rt
221
222                                        // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it
223                                        if( value.toString().isLong() ) {
224                                                rt = new RelTime( Long.parseLong( value.toString() ) );
225                                        } else {
226                                                rt = new RelTime( value.toString() );
227                                        }
228
229                                        return rt.getValue()
230                                } catch( Exception e ) {
231                                        return null;
232                                }
233                        case 'Double':
234                                // The comparison with date values should only be performed iff the value
235                                // contains a double number
236                                // and the operator is equals, gte, gt, lt or lte
237                                if( operator == Operator.insearch || operator == Operator.contains )
238                                        return null
239
240                                if( value.isDouble() ) {
241                                        return Double.parseDouble( value )
242                                } else {
243                                        return null;
244                                }
245                        case 'Long':
246                                // The comparison with date values should only be performed iff the value
247                                // contains a long number
248                                // and the operator is equals, gte, gt, lt or lte
249                                if( operator == Operator.insearch || operator == Operator.contains )
250                                        return null
251
252                                if( value.isLong() ) {
253                                        return Long.parseLong( value )
254                                } else {
255                                        return null;
256                                }
257                        case 'Boolean':
258                                // The comparison with boolean values should only be performed iff the value
259                                // contains 'true' or 'false' (case insensitive)
260                                // and the operator is equals
261                                if( operator != Operator.equals )
262                                        return null
263
264                                def lowerCaseValue = value.toString().toLowerCase();
265                                if( lowerCaseValue == 'true' || lowerCaseValue == 'false' ) {
266                                        return Boolean.parseBoolean( this.value )
267                                } else {
268                                        return null
269                                }
270                }
271        }
272
273        /**
274         * Create a HQL where clause from this criterion, in order to be used within a larger HQL statement
275         *
276         * @param       objectToSearchIn        HQL name of the object to search in
277         * @return      Map with 3 keys:   'join' and'where' with the HQL join and where clause for this criterion and 'parameters' for the query named parameters
278         */
279        public Map toHQL( String prefix, String objectToSearchIn = "object" ) {
280                List whereClause = []
281                String joinClause = "";
282                Map parameters = [:];
283                def emptyCriterion = [ "join": null, "where": null, "parameters": null ];
284
285                // If this criterion is used to search within another search result, we use a special piece of HQL
286                if( this.operator == Operator.insearch ) {
287                        if( this.value?.results ) {
288                                parameters[ prefix + "SearchResults" ] = this.value?.results
289
290                                return [ "join": "", "where": "( " + objectToSearchIn + " in (:" + prefix + "SearchResults) )" , "parameters": parameters ];
291                        } else {
292                                return emptyCriterion;
293                        }
294                }
295               
296                // If no value is given, don't do anything
297                if( value == null )
298                        return emptyCriterion;
299               
300                // Check whether the field is a domain field
301                if( isDomainCriterion() ) {
302                        // Determine the types of this criterion, but there will be only 1 for a domain field
303                        def criterionType = criterionType()[0];
304                       
305                        // Some domain fields don't contain a value, but a reference to another table
306                        // These should be handled differently
307                        def fieldName = this.field
308
309                        // Make sure the Template field is referenced as lowercase
310                        if( fieldName == "Template" )
311                                fieldName = "template";
312                                                       
313                        if( 
314                                ( fieldName == "template" ) ||
315                                ( objectToSearchIn.toLowerCase() == "subject" && fieldName.toLowerCase() == "species" ) || 
316                                ( objectToSearchIn.toLowerCase() == "sample" && fieldName.toLowerCase() == "material" ) ||
317                                ( objectToSearchIn.toLowerCase() == "assay" && fieldName.toLowerCase() == "module" ) ||
318                                ( objectToSearchIn.toLowerCase() == "samplingevent" && fieldName.toLowerCase() == "sampletemplate" ) ) {
319                                fieldName += ".name"
320                        }
321                               
322                        def query = extendWhereClause( "( %s )", objectToSearchIn + "." + fieldName, prefix, criterionType, castValue( criterionType ) );
323                        return [ "join": "", "where": query.where, "parameters": query.parameters  ]
324                }
325
326                // Determine the type of this criterion
327                def criterionTypes = criterionType();
328               
329                if( !criterionTypes )
330                        return emptyCriterion;                 
331               
332                // Several types of criteria are handled differently.
333                // The 'wildcard' is handled by searching for all types.
334                // The 'simple' types (string, double) are handled by searching in the associated table
335                // The 'complex' types (stringlist, template etc., referencing another
336                // database table) can't be handled correctly, since the HQL INDEX() function doesn't work on those relations.
337                // We do a search for these types to see whether any field with that type fits this criterion, in order to
338                // filter out false positives later on.
339                criterionTypes.findAll { it }.each { criterionType ->
340                        // Cast criterion value to the right type
341                        def currentValue = castValue( criterionType );
342
343                        // Determine field name
344                        def fieldName = "template" + criterionType + 'Fields'
345                       
346                        switch( criterionType ) {
347                                case "Wildcard":
348                                        // Wildcard search is handled by
349                                        break;
350
351                                case 'String':
352                                case 'Text':
353                                case 'File':
354                                case 'Date':
355                                case 'RelTime':
356                                case 'Double':
357                                case 'Long':
358                                case 'Boolean':
359                                        // 'Simple' field types
360                                        if( currentValue != null ) {
361                                                joinClause += " left join " + objectToSearchIn + "." + fieldName + " as " + prefix + "_" + fieldName + " ";
362       
363                                                def condition = this.oneToManyWhereCondition( prefix + "_" + fieldName, prefix, criterionType, currentValue )
364                                                whereClause += condition[ "where" ];
365       
366                                                condition[ "parameters" ].each {
367                                                        parameters[ it.key ] = it.value;
368                                                }
369                                        }
370                                        break;
371                                       
372                                case 'StringList':
373                                case 'ExtendableStringList':
374                                case 'Term':
375                                case 'Template':
376                                case 'Module':
377                                        // 'Complex' field types
378                                        def condition = this.manyToManyWhereCondition( objectToSearchIn, fieldName, prefix, "name", currentValue )
379                                        whereClause += condition[ "where" ];
380       
381                                        condition[ "parameters" ].each {
382                                                parameters[ it.key ] = it.value;
383                                        }
384                                default:
385                                        break;
386                        }
387                }
388               
389                // Wildcard searches must also search the fixed domain fields of all entities.
390                if( this.field == '*' ) {
391                        def condition = wildcardDomainFields( prefix, objectToSearchIn )
392                       
393                        whereClause += condition[ "where" ];
394                        condition[ "parameters" ].each {
395                                parameters[ it.key ] = it.value;
396                        }
397                }
398
399                def where = whereClause?.findAll { it } ? "( " + whereClause.findAll { it }.join( " OR " ) + " )" : ""
400               
401                return [ "join": joinClause, "where": where , "parameters": parameters ];
402        }
403       
404        protected Map wildcardDomainFields( String prefix, String objectToSearchIn ) {
405                def whereClause = [];
406                def parameters = [:];
407               
408                // Determine all domain fields
409                def domainFields
410               
411                switch( objectToSearchIn.toLowerCase() ) {
412                        case "study":   domainFields = Study.giveDomainFields();        break;
413                        case "subject": domainFields = Subject.giveDomainFields();      break;
414                        case "event":   domainFields = Event.giveDomainFields();        break;
415                        case "sample":  domainFields = Sample.giveDomainFields();       break;
416                        case "assay":   domainFields = Assay.giveDomainFields();        break;
417                        case "samplingevent":   domainFields = SamplingEvent.giveDomainFields();        break;
418                }
419               
420                domainFields.each { field ->
421                        def criterionType = field.type?.casedName;
422                       
423                        def fieldName = field.name;
424                        def condition
425                       
426                        // Searching in (complex) domain fields with value NULL will result in the whole
427                        // where clause to fail. e.g.:
428                        //
429                        // SELECT * FROM Sample sample WHERE sample.material LIKE '%m%' OR 1 = 1
430                        // should result in all samples. However, it only results in those samples
431                        // with sample.material =! NULL.
432                        //
433                        // For that reason, we use a subquery to select those 
434                        if(     ( objectToSearchIn.toLowerCase() == "subject" && fieldName.toLowerCase() == "species" ) ||
435                                ( objectToSearchIn.toLowerCase() == "sample" && fieldName.toLowerCase() == "material" ) ||
436                                ( objectToSearchIn.toLowerCase() == "assay" && fieldName.toLowerCase() == "module" ) ||
437                                ( objectToSearchIn.toLowerCase() == "samplingevent" && fieldName.toLowerCase() == "sampletemplate" ) ) {
438                               
439                                condition = manyToManyWhereCondition( objectToSearchIn, fieldName, prefix, "name", value );
440                                fieldName += ".name"
441                        } else {
442                                condition = extendWhereClause( "( %s )", objectToSearchIn + "." + fieldName, prefix, criterionType, castValue( criterionType ) );
443                        }
444
445                        // Search in fields
446                        whereClause += condition[ "where" ];
447       
448                        condition[ "parameters" ].each {
449                                parameters[ it.key ] = it.value;
450                        }
451                }
452               
453                // Also search in template name
454                def condition = extendWhereClause( "( %s )", objectToSearchIn + ".template.name", prefix, "String", castValue( "String" ) );
455                whereClause += condition[ "where" ];
456
457                condition[ "parameters" ].each {
458                        parameters[ it.key ] = it.value;
459                }
460               
461                return [ "where": whereClause, "parameters": parameters]
462        }
463
464        /**
465         * Extends a given condition with a where clause of this criterion. If you supply "select * from Study where %s", %s will
466         * be replaced by the where clause for the given field. Also, the parameters map will be extended (if needed)
467         *
468         * @param hql                   Initial HQL string where the clause will be put into
469         * @param fieldName             Name of the field that should be referenced
470         * @param uniquePrefix  Unique prefix for this criterion
471         * @param fieldType             Type of field value to search for
472         * @param fieldValue    Field value to search for
473         * @return                              Map with 'where' key referencing the extended where clause and 'parameters' key referencing a map with parameters.
474         */
475        protected Map extendWhereClause( String hql, String fieldName, String uniquePrefix, String fieldType, def fieldValue ) {
476                def parameters = [:]
477                def textFieldTypes = [ 'String', 'Text', 'File', 'StringList', 'ExtendableStringList', 'Term', 'Template', 'Module' ];
478               
479                switch( this.operator ) {
480                        case Operator.contains:
481                                // Every field contains an empty string, so we don't search on it
482                                if( !fieldValue )
483                                        return [ "where": "", "parameters": [:] ]
484                                       
485                                // Text fields should be handled case insensitive
486                                def clause
487                                if( textFieldTypes.contains( fieldType ) ) {
488                                        clause = "lower( " + fieldName + ") like lower( :" + uniquePrefix + "ValueLike )"
489                                } else {
490                                        clause = fieldName + " like :" + uniquePrefix + "ValueLike";
491                                }
492                               
493                                hql = sprintf( hql, clause );
494                                parameters[ uniquePrefix + "ValueLike" ] = "%" + fieldValue + "%"
495                                break;
496                        case Operator.equals:
497                        case Operator.gte:
498                        case Operator.gt:
499                        case Operator.lte:
500                        case Operator.lt:
501                                // Text fields should be handled case insensitive
502                                def clause;
503                                if( textFieldTypes.contains( fieldType ) ) {
504                                        clause = "lower( " + fieldName + " ) "  + this.operator.name + " lower( :" + uniquePrefix + "Value" + fieldType + ")"
505                                } else {
506                                        clause = fieldName + " "  + this.operator.name + " :" + uniquePrefix + "Value" + fieldType
507                                }
508                               
509                                // If the user searches for an empty string, it should also match the database NULL value
510                                if( operator == Operator.equals && !fieldValue ) {
511                                        clause += " OR " + fieldName + " IS NULL"
512                                }
513                               
514                                hql = sprintf( hql, clause );
515                                parameters[ uniquePrefix + "Value" + fieldType ] = fieldValue
516                                break;
517                }
518
519                return [ "where": hql, "parameters": parameters]
520        }
521
522        /**
523         * Creates a condition for this criterion, for a given fieldName and value. The fieldName should reference a collection that has a one-to-many
524         * relation with the object being sought
525         * 
526         * @param fieldName             Name to search in
527         * @param uniquePrefix  Unique prefix for this criterion
528         * @param currentValue  Map with 'value' referencing the value being sought and 'type' referencing
529         *                                              the type of the value as string. The value should be be casted to the right class for this field.
530         * @return                              Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters.
531         */
532        protected Map oneToManyWhereCondition( String fieldName, String uniquePrefix, String fieldType, def fieldValue ) {
533                // Create the where condition for checking the value
534                // First check the name of the field, if needed
535                def condition
536                def parameters = [:]
537
538                // If looking for a specific field, that field should also be mentioned in the where clause
539                if( this.field != '*' ) {
540                        condition = "( %s AND index(" + fieldName + ") = :" + uniquePrefix + "Field )"
541                        parameters[ uniquePrefix + "Field" ] = this.field
542                } else {
543                        condition = "%s";
544                }
545
546                def whereClause = extendWhereClause( condition, fieldName, uniquePrefix, fieldType, fieldValue );
547                parameters.each {
548                        whereClause.parameters[ it.key ] = it.value;
549                }
550
551                return whereClause;
552        }
553
554        /**
555         * Creates a condition for this criterion, for a given fieldName and value. The fieldName should
556         * reference a collection that has a many-to-many relation with the object being sought (e.g. templateTermFields).
557         *
558         * Unfortunately, there is no way to determine the name of the field in HQL for this many-to-many collections, since the
559         * INDEX() function in HQL doesn't work for many-to-many collections.
560         * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4879
561         * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4615
562         * 
563         * @param fieldName             Name to search in
564         * @param uniquePrefix  Unique prefix for this criterion
565         * @param currentValue  Map with 'value' referencing the value being sought and 'type' referencing
566         *                                              the type of the value as string. The value should be be casted to the right class for this field.
567         * @return                              Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters.
568         */
569        protected Map manyToManyWhereCondition( String objectToSearchIn, String collection, String uniquePrefix, String searchField, def value ) {
570                // exists( FROM [objectToSearchIn].[collection] as [uniquePrefix][collection] WHERE [searchField] LIKE [value] )
571                // Create the where condition for checking the value
572                def condition = "exists ( FROM " + objectToSearchIn + "." + collection + " as " + uniquePrefix + "_" + collection + " WHERE %s )";
573
574                return extendWhereClause( condition, uniquePrefix + "_" + collection + "." + searchField, uniquePrefix, "String", value );
575        }
576
577        /**
578         * Retrieves the correct value for this criterion in the given object (with template)
579         *
580         * @param entity                Entity to check for value. Should be a child of template entity
581         * @param criterion             Criterion to match on
582         * @return                              Value of the given field or null if the field doesn't exist
583         */
584        public def getFieldValue( TemplateEntity entity ) {
585                if( entity == null )
586                        return null;
587
588                try {
589                        def fieldValue
590                        if( !field ) {
591                                fieldValue = entity
592                        } else if( field == "Template" ) {
593                                fieldValue = entity.template?.name
594                        } else if( field == "*" ) {
595                                fieldValue = entity.giveFields().collect{
596                                        if( it && it.name ) {
597                                                Search.prepare( entity.getFieldValue( it.name ), entity.giveFieldType( it.name ) )
598                                        }
599                                }
600                        } else {
601                                fieldValue = Search.prepare( entity.getFieldValue( field ), entity.giveFieldType( field ) )
602                        }
603
604                        return fieldValue
605                } catch( Exception e ) {
606                        // An exception occurs if the given field doesn't exist. In that case, this criterion will fail.
607                        // TODO: Maybe give the user a choice whether he want's to include these studies or not
608                        return null;
609                }
610        }
611
612        /**
613         * Tries to match a value against a criterion and returns true if it matches
614         *
615         * @param value         Value of the field to match
616         * @return                      True iff the value matches this criterion, false otherwise
617         */
618        public boolean match( def fieldValue ) {
619                if( fieldValue == null )
620                        return false;
621
622                // in-search criteria have to be handled separately
623                if( this.operator == Operator.insearch ) {
624                        return this.value?.getResults()?.contains( fieldValue );
625                }
626
627                // Other criteria are handled based on the class of the value given.
628                def classname = fieldValue.class.getName();
629                classname = classname[classname.lastIndexOf( '.' ) + 1..-1].toLowerCase();
630
631                def matches = false;
632                try {
633                        switch( classname ) {
634                                case "integer":                                 matches = longCompare( new Long( fieldValue.longValue() ) ); break;
635                                case "long":                                    matches = longCompare( fieldValue ); break;
636                                case "float":                                   matches = doubleCompare( new Long( fieldValue.doubleValue() ) ); break;
637                                case "double":                                  matches = doubleCompare( fieldValue ); break;
638                                case "boolean":                                 matches = booleanCompare( fieldValue ); break;
639                                case "date":                                    matches = dateCompare( fieldValue); break;
640                                case "reltime":                                 matches = relTimeCompare( fieldValue ); break;
641                                case "assaymodule":
642                                case "template":
643                                case "term":
644                                case "templatefieldlistitem":
645                                case "string":
646                                default:                                                matches = compareValues( fieldValue.toString().trim().toLowerCase(), this.operator, value.toString().toLowerCase().trim() ); break;
647                        }
648
649                        return matches;
650                } catch( Exception e ) {
651                        log.error e.class.getName() + ": " + e.getMessage();
652                        return false;
653                }
654        }
655
656        /**
657         * Tries to match a value against a criterion and returns true if it matches
658         *
659         * @param fieldValue            Value of the field to match
660         * @param operator                      Operator to apply
661         * @param criterionValue        Value of the criterion
662         * @return                                      True iff the value matches this criterion value, false otherwise
663         */
664        protected boolean compareValues( def fieldValue, Operator operator, def criterionValue ) {
665                switch( operator ) {
666                        case Operator.gte:
667                                return fieldValue >= criterionValue;
668                        case Operator.gt:
669                                return fieldValue > criterionValue;
670                        case Operator.lt:
671                                return fieldValue < criterionValue;
672                        case Operator.lte:
673                                return fieldValue <= criterionValue;
674                        case Operator.contains:
675                        // Contains operator can only be used on string values
676                                return fieldValue.toString().contains( criterionValue.toString() );
677                        case Operator.equals:
678                        default:
679                                return fieldValue.equals( criterionValue );
680                }
681
682        }
683
684        /**
685         * Tries to match a date value against a criterion and returns true if it matches
686         *
687         * @param value         Date value of the field to match
688         * @return                      True iff the value matches this criterion, false otherwise
689         */
690        protected boolean dateCompare( Date fieldValue ) {
691                try {
692                        Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value );
693                        Date fieldDate = new Date( fieldValue.getTime() );
694
695                        // Clear time in order to just compare dates
696                        dateCriterion.clearTime();
697                        fieldDate.clearTime();
698
699                        return compareValues( fieldDate, this.operator, dateCriterion )
700                } catch( Exception e ) {
701                        log.error e.class.getName() + ": " + e.getMessage();
702                        return false;
703                }
704        }
705
706        /**
707         * Tries to match a long value against a criterion and returns true if it matches
708         *
709         * @param value         Long value of the field to match
710         * @param criterion     Criterion to match on. Should be a map with entries 'operator' and 'value'
711         * @return                      True iff the value matches this criterion, false otherwise
712         */
713        protected boolean longCompare( Long fieldValue ) {
714                Long longCriterion;
715                try {
716                        longCriterion = Long.parseLong( value );
717                } catch( Exception e ) {
718                        try {
719                                // If converting to long doesn't work, try converting to double and rounding it
720                                Double doubleCriterion = Double.parseDouble(value);
721                                longCriterion = new Long( doubleCriterion.longValue() );
722                        } catch( Exception e2 ) {
723                                log.debug "Can't convert value to long for comparison: " + e2.class.getName() + ": " + e2.getMessage();
724                                return false;
725                        }
726                }
727                return compareValues( fieldValue, this.operator, longCriterion );
728        }
729
730        /**
731         * Tries to match a double value against a criterion and returns true if it matches
732         *
733         * @param value         Double value of the field to match
734         * @return                      True iff the value matches this criterion, false otherwise
735         */
736        protected boolean doubleCompare( Double fieldValue ) {
737                try {
738                        Double doubleCriterion = Double.parseDouble( value );
739                        return compareValues( fieldValue, this.operator, doubleCriterion );
740                } catch( Exception e ) {
741                        log.debug "Can't convert value to double for comparison: " + e.class.getName() + ": " + e.getMessage();
742                        return false;
743                }
744        }
745
746
747        /**
748         * Tries to match a boolean value against a criterion and returns true if it matches
749         *
750         * @param value         Boolean value of the field to match
751         * @return                      True iff the value matches this criterion, false otherwise
752         */
753        protected boolean booleanCompare( Boolean fieldValue ) {
754                try {
755                        // The comparison should only be performed iff the value
756                        // contains 'true' or 'false' (case insensitive)
757                        def lowerCaseValue = value.toString().toLowerCase();
758                        if( lowerCaseValue != 'true' && lowerCaseValue != 'false' )
759                                return false;
760
761                        Boolean booleanCriterion = Boolean.parseBoolean( value );
762                        return compareValues( fieldValue, this.operator, booleanCriterion );
763                } catch( Exception e ) {
764                        log.debug "Can't convert value to boolean for comparison: " + e.class.getName() + ": " + e.getMessage();
765                        return false;
766                }
767        }
768
769        /**
770         * Tries to match a relTime value against a criterion and returns true if it matches
771         *
772         * @param value         relTime value of the field to match
773         * @return                      True iff the value matches this criterion, false otherwise
774         */
775        protected boolean relTimeCompare( RelTime fieldValue ) {
776                try {
777                        RelTime rt
778
779                        // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it
780                        if( value.toString().isLong() ) {
781                                rt = new RelTime( Long.parseLong( value.toString() ) );
782                        } else {
783                                rt = new RelTime( value.toString() );
784                        }
785
786                        return compareValues( fieldValue, this.operator, rt );
787                } catch( Exception e ) {
788                        log.debug "Can't convert value to reltime for comparison: " + e.class.getName() + ": " + e.getMessage();
789                        return false;
790                }
791        }
792
793        public static Operator parseOperator( String name ) throws Exception {
794                switch( name.trim() ) {
795                        case "=":
796                                case "equals":          return Operator.equals;
797                        case "contains":        return Operator.contains;
798                        case ">=":
799                                case "gte":                     return Operator.gte;
800                        case ">":
801                                case "gt":                      return Operator.gt;
802                        case "<=":
803                                case "lte":                     return Operator.lte;
804                        case "<":
805                                case "lt":                      return Operator.lt;
806                        case "in":                      return Operator.insearch;
807                        default:
808                                throw new Exception( "Operator not found" );
809                }
810        }
811
812        public String toString() {
813                return "[Criterion " + entityField() + " " + operator + " " + value + "]";
814        }
815
816        public boolean equals( Object o ) {
817                if( o == null )
818                        return false;
819
820                if( !( o instanceof Criterion ) )
821                        return false;
822
823                Criterion otherCriterion = (Criterion) o;
824                return  this.entity == otherCriterion.entity &&
825                this.field == otherCriterion.field &&
826                this.operator == otherCriterion.operator &&
827                this.value == otherCriterion.value;
828        }
829}
Note: See TracBrowser for help on using the repository browser.