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

Last change on this file since 1913 was 1913, checked in by robert@…, 9 years ago

Fixed a bug in wildcard searches: now the system also searches through domain fields, instead of only looking in template fields

  • Property svn:keywords set to Rev Author Date
File size: 26.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                                return value?.toString();
197                        case 'File':
198                                return null; // Never search in filenames, since they are not very descriptive
199                        case 'Date':
200                                // The comparison with date values should only be performed iff the value
201                                // contains a parsable date
202                                // and the operator is equals, gte, gt, lt or lte
203                                if( operator == Operator.insearch || operator == Operator.contains )
204                                        return null
205
206                                try {
207                                        Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value );
208                                        return dateCriterion
209                                } catch( Exception e ) {
210                                        return null;
211                                }
212
213                        case 'RelTime':
214                                // The comparison with date values should only be performed iff the value
215                                // contains a long number
216                                // and the operator is equals, gte, gt, lt or lte
217                                if( operator == Operator.insearch || operator == Operator.contains )
218                                        return null
219
220                                try {
221                                        RelTime rt
222
223                                        // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it
224                                        if( value.toString().isLong() ) {
225                                                rt = new RelTime( Long.parseLong( value.toString() ) );
226                                        } else {
227                                                rt = new RelTime( value.toString() );
228                                        }
229
230                                        return rt.getValue()
231                                } catch( Exception e ) {
232                                        return null;
233                                }
234                        case 'Double':
235                                // The comparison with date values should only be performed iff the value
236                                // contains a double number
237                                // and the operator is equals, gte, gt, lt or lte
238                                if( operator == Operator.insearch || operator == Operator.contains )
239                                        return null
240
241                                if( value.isDouble() ) {
242                                        return Double.parseDouble( value )
243                                } else {
244                                        return null;
245                                }
246                        case 'Long':
247                                // The comparison with date values should only be performed iff the value
248                                // contains a long number
249                                // and the operator is equals, gte, gt, lt or lte
250                                if( operator == Operator.insearch || operator == Operator.contains )
251                                        return null
252
253                                if( value.isLong() ) {
254                                        return Long.parseLong( value )
255                                } else {
256                                        return null;
257                                }
258                        case 'Boolean':
259                                // The comparison with boolean values should only be performed iff the value
260                                // contains 'true' or 'false' (case insensitive)
261                                // and the operator is equals
262                                if( operator != Operator.equals )
263                                        return null
264
265                                def lowerCaseValue = value.toString().toLowerCase();
266                                if( lowerCaseValue == 'true' || lowerCaseValue == 'false' ) {
267                                        return Boolean.parseBoolean( this.value )
268                                } else {
269                                        return null
270                                }
271                }
272        }
273
274        /**
275         * Create a HQL where clause from this criterion, in order to be used within a larger HQL statement
276         *
277         * @param       objectToSearchIn        HQL name of the object to search in
278         * @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
279         */
280        public Map toHQL( String prefix, String objectToSearchIn = "object" ) {
281                List whereClause = []
282                String joinClause = "";
283                Map parameters = [:];
284                def emptyCriterion = [ "join": null, "where": null, "parameters": null ];
285
286                // If this criterion is used to search within another search result, we use a special piece of HQL
287                if( this.operator == Operator.insearch ) {
288                        if( this.value?.results ) {
289                                parameters[ prefix + "SearchResults" ] = this.value?.results
290
291                                return [ "join": "", "where": "( " + objectToSearchIn + " in (:" + prefix + "SearchResults) )" , "parameters": parameters ];
292                        } else {
293                                return emptyCriterion;
294                        }
295                }
296               
297                // If no value is given, don't do anything
298                if( !value )
299                        return emptyCriterion;
300               
301                // Check whether the field is a domain field
302                if( isDomainCriterion() ) {
303                        // Determine the types of this criterion, but there will be only 1 for a domain field
304                        def criterionType = criterionType()[0];
305                       
306                        // Some domain fields don't contain a value, but a reference to another table
307                        // These should be handled differently
308                        def fieldName = this.field
309
310                        // Make sure the Template field is referenced as lowercase
311                        if( fieldName == "Template" )
312                                fieldName = "template";
313                                                       
314                        if( 
315                                ( fieldName == "template" ) ||
316                                ( objectToSearchIn.toLowerCase() == "subject" && fieldName.toLowerCase() == "species" ) || 
317                                ( objectToSearchIn.toLowerCase() == "sample" && fieldName.toLowerCase() == "material" ) ||
318                                ( objectToSearchIn.toLowerCase() == "assay" && fieldName.toLowerCase() == "module" ) ||
319                                ( objectToSearchIn.toLowerCase() == "samplingevent" && fieldName.toLowerCase() == "sampletemplate" ) ) {
320                                fieldName += ".name"
321                        }
322                               
323                        def query = extendWhereClause( "( %s )", objectToSearchIn + "." + fieldName, prefix, criterionType, castValue( criterionType ) );
324                        return [ "join": "", "where": query.where, "parameters": query.parameters  ]
325                }
326
327                // Determine the type of this criterion
328                def criterionTypes = criterionType();
329               
330                if( !criterionTypes )
331                        return emptyCriterion;                 
332               
333                // Several types of criteria are handled differently.
334                // The 'wildcard' is handled by searching for all types.
335                // The 'simple' types (string, double) are handled by searching in the associated table
336                // The 'complex' types (stringlist, template etc., referencing another
337                // database table) can't be handled correctly, since the HQL INDEX() function doesn't work on those relations.
338                // We do a search for these types to see whether any field with that type fits this criterion, in order to
339                // filter out false positives later on.
340                criterionTypes.findAll { it }.each { criterionType ->
341                        // Cast criterion value to the right type
342                        def currentValue = castValue( criterionType );
343
344                        // Determine field name
345                        def fieldName = "template" + criterionType + 'Fields'
346                       
347                        switch( criterionType ) {
348                                case "Wildcard":
349                                        // Wildcard search is handled by
350                                        break;
351
352                                case 'String':
353                                case 'Text':
354                                case 'File':
355                                case 'Date':
356                                case 'RelTime':
357                                case 'Double':
358                                case 'Long':
359                                case 'Boolean':
360                                        // 'Simple' field types
361                                        if( currentValue != null ) {
362                                                joinClause += " left join " + objectToSearchIn + "." + fieldName + " as " + prefix + "_" + fieldName + " ";
363       
364                                                def condition = this.oneToManyWhereCondition( prefix + "_" + fieldName, prefix, criterionType, currentValue )
365                                                whereClause += condition[ "where" ];
366       
367                                                condition[ "parameters" ].each {
368                                                        parameters[ it.key ] = it.value;
369                                                }
370                                        }
371                                        break;
372                                       
373                                case 'StringList':
374                                case 'ExtendableStringList':
375                                case 'Term':
376                                case 'Template':
377                                case 'Module':
378                                        // 'Complex' field types
379                                        def condition = this.manyToManyWhereCondition( objectToSearchIn, fieldName, prefix, "name", currentValue )
380                                        whereClause += condition[ "where" ];
381       
382                                        condition[ "parameters" ].each {
383                                                parameters[ it.key ] = it.value;
384                                        }
385                                default:
386                                        break;
387                        }
388                }
389               
390                // Wildcard searches must also search the fixed domain fields of all entities.
391                if( this.field == '*' ) {
392                        def condition = wildcardDomainFields( prefix, objectToSearchIn )
393                       
394                        whereClause += condition[ "where" ];
395                        condition[ "parameters" ].each {
396                                parameters[ it.key ] = it.value;
397                        }
398                }
399
400                def where = whereClause?.findAll { it } ? "( " + whereClause.join( " OR " ) + " )" : ""
401               
402                return [ "join": joinClause, "where": where , "parameters": parameters ];
403        }
404       
405        protected Map wildcardDomainFields( String prefix, String objectToSearchIn ) {
406                def whereClause = [];
407                def parameters = [:];
408               
409                // Determine all domain fields
410                def domainFields
411               
412                switch( objectToSearchIn.toLowerCase() ) {
413                        case "study":   domainFields = Study.giveDomainFields();        break;
414                        case "subject": domainFields = Subject.giveDomainFields();      break;
415                        case "event":   domainFields = Event.giveDomainFields();        break;
416                        case "sample":  domainFields = Sample.giveDomainFields();       break;
417                        case "assay":   domainFields = Assay.giveDomainFields();        break;
418                        case "samplingevent":   domainFields = SamplingEvent.giveDomainFields();        break;
419                }
420               
421                domainFields.each { field ->
422                        def criterionType = field.type?.casedName;
423                       
424                        def fieldName = field.name;
425                       
426                        if(     ( objectToSearchIn.toLowerCase() == "subject" && fieldName.toLowerCase() == "species" ) ||
427                                ( objectToSearchIn.toLowerCase() == "sample" && fieldName.toLowerCase() == "material" ) ||
428                                ( objectToSearchIn.toLowerCase() == "assay" && fieldName.toLowerCase() == "module" ) ||
429                                ( objectToSearchIn.toLowerCase() == "samplingevent" && fieldName.toLowerCase() == "sampletemplate" ) ) {
430                                fieldName += ".name"
431                        }
432                       
433                        // Search in template name
434                        def condition = extendWhereClause( "( %s )", objectToSearchIn + "." + fieldName, prefix, criterionType, castValue( criterionType ) );
435                        whereClause += condition[ "where" ];
436       
437                        condition[ "parameters" ].each {
438                                parameters[ it.key ] = it.value;
439                        }
440                }
441               
442                // Also search in template name
443                def condition = extendWhereClause( "( %s )", objectToSearchIn + ".template.name", prefix, "String", castValue( "String" ) );
444                whereClause += condition[ "where" ];
445
446                condition[ "parameters" ].each {
447                        parameters[ it.key ] = it.value;
448                }
449               
450                return [ "where": whereClause, "parameters": parameters]
451        }
452
453        /**
454         * Extends a given condition with a where clause of this criterion. If you supply "select * from Study where %s", %s will
455         * be replaced by the where clause for the given field. Also, the parameters map will be extended (if needed)
456         *
457         * @param hql                   Initial HQL string where the clause will be put into
458         * @param fieldName             Name of the field that should be referenced
459         * @param uniquePrefix  Unique prefix for this criterion
460         * @param fieldType             Type of field value to search for
461         * @param fieldValue    Field value to search for
462         * @return                              Map with 'where' key referencing the extended where clause and 'parameters' key referencing a map with parameters.
463         */
464        protected Map extendWhereClause( String hql, String fieldName, String uniquePrefix, String fieldType, def fieldValue ) {
465                def parameters = [:]
466                def textFieldTypes = [ 'String', 'Text', 'File', 'StringList', 'ExtendableStringList', 'Term', 'Template', 'Module' ];
467               
468                switch( this.operator ) {
469                        case Operator.contains:
470                                // Text fields should be handled case insensitive
471                                if( textFieldTypes.contains( fieldType ) ) {
472                                        hql = sprintf( hql, "lower( " + fieldName + ") like lower( :" + uniquePrefix + "ValueLike )" );
473                                } else {
474                                        hql = sprintf( hql, fieldName + " like :" + uniquePrefix + "ValueLike" );
475                                }
476                                parameters[ uniquePrefix + "ValueLike" ] = "%" + fieldValue + "%"
477                                break;
478                        case Operator.equals:
479                        case Operator.gte:
480                        case Operator.gt:
481                        case Operator.lte:
482                        case Operator.lt:
483                                if( textFieldTypes.contains( fieldType ) ) {
484                                        hql = sprintf( hql, "lower( " + fieldName + " ) "  + this.operator.name + " lower( :" + uniquePrefix + "Value" + fieldType + ")" );
485                                } else {
486                                        hql = sprintf( hql, fieldName + " "  + this.operator.name + " :" + uniquePrefix + "Value" + fieldType );
487                                }
488                                parameters[ uniquePrefix + "Value" + fieldType ] = fieldValue
489                                break;
490                }
491
492                return [ "where": hql, "parameters": parameters]
493        }
494
495        /**
496         * Creates a condition for this criterion, for a given fieldName and value. The fieldName should reference a collection that has a one-to-many
497         * relation with the object being sought
498         * 
499         * @param fieldName             Name to search in
500         * @param uniquePrefix  Unique prefix for this criterion
501         * @param currentValue  Map with 'value' referencing the value being sought and 'type' referencing
502         *                                              the type of the value as string. The value should be be casted to the right class for this field.
503         * @return                              Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters.
504         */
505        protected Map oneToManyWhereCondition( String fieldName, String uniquePrefix, String fieldType, def fieldValue ) {
506                // Create the where condition for checking the value
507                // First check the name of the field, if needed
508                def condition
509                def parameters = [:]
510
511                if( this.field != '*' ) {
512                        condition = "( %s AND index(" + fieldName + ") = :" + uniquePrefix + "Field )"
513                        parameters[ uniquePrefix + "Field" ] = this.field
514                } else {
515                        condition = "%s";
516                }
517
518                def whereClause = extendWhereClause( condition, fieldName, uniquePrefix, fieldType, fieldValue );
519                parameters.each {
520                        whereClause.parameters[ it.key ] = it.value;
521                }
522
523                return whereClause;
524        }
525
526        /**
527         * Creates a condition for this criterion, for a given fieldName and value. The fieldName should
528         * reference a collection that has a many-to-many relation with the object being sought (e.g. templateTermFields).
529         *
530         * Unfortunately, there is no way to determine the name of the field in HQL for this many-to-many collections, since the
531         * INDEX() function in HQL doesn't work for many-to-many collections.
532         * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4879
533         * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4615
534         * 
535         * @param fieldName             Name to search in
536         * @param uniquePrefix  Unique prefix for this criterion
537         * @param currentValue  Map with 'value' referencing the value being sought and 'type' referencing
538         *                                              the type of the value as string. The value should be be casted to the right class for this field.
539         * @return                              Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters.
540         */
541        protected Map manyToManyWhereCondition( String objectToSearchIn, String collection, String uniquePrefix, String searchField, def value ) {
542                // exists( FROM [objectToSearchIn].[collection] as [uniquePrefix][collection] WHERE [searchField] LIKE [value] )
543                // Create the where condition for checking the value
544                def condition = "exists ( FROM " + objectToSearchIn + "." + collection + " as " + uniquePrefix + "_" + collection + " WHERE %s )";
545
546                return extendWhereClause( condition, uniquePrefix + "_" + collection + "." + searchField, uniquePrefix, "String", value );
547        }
548
549        /**
550         * Retrieves the correct value for this criterion in the given object (with template)
551         *
552         * @param entity                Entity to check for value. Should be a child of template entity
553         * @param criterion             Criterion to match on
554         * @return                              Value of the given field or null if the field doesn't exist
555         */
556        public def getFieldValue( TemplateEntity entity ) {
557                if( entity == null )
558                        return null;
559
560                try {
561                        def fieldValue
562                        if( !field ) {
563                                fieldValue = entity
564                        } else if( field == "Template" ) {
565                                fieldValue = entity.template?.name
566                        } else if( field == "*" ) {
567                                fieldValue = entity.giveFields().collect{
568                                        if( it && it.name ) {
569                                                Search.prepare( entity.getFieldValue( it.name ), entity.giveFieldType( it.name ) )
570                                        }
571                                }
572                        } else {
573                                fieldValue = Search.prepare( entity.getFieldValue( field ), entity.giveFieldType( field ) )
574                        }
575
576                        return fieldValue
577                } catch( Exception e ) {
578                        // An exception occurs if the given field doesn't exist. In that case, this criterion will fail.
579                        // TODO: Maybe give the user a choice whether he want's to include these studies or not
580                        return null;
581                }
582        }
583
584        /**
585         * Tries to match a value against a criterion and returns true if it matches
586         *
587         * @param value         Value of the field to match
588         * @return                      True iff the value matches this criterion, false otherwise
589         */
590        public boolean match( def fieldValue ) {
591                if( fieldValue == null )
592                        return false;
593
594                // in-search criteria have to be handled separately
595                if( this.operator == Operator.insearch ) {
596                        return this.value?.getResults()?.contains( fieldValue );
597                }
598
599                // Other criteria are handled based on the class of the value given.
600                def classname = fieldValue.class.getName();
601                classname = classname[classname.lastIndexOf( '.' ) + 1..-1].toLowerCase();
602
603                def matches = false;
604                try {
605                        switch( classname ) {
606                                case "integer":                                 matches = longCompare( new Long( fieldValue.longValue() ) ); break;
607                                case "long":                                    matches = longCompare( fieldValue ); break;
608                                case "float":                                   matches = doubleCompare( new Long( fieldValue.doubleValue() ) ); break;
609                                case "double":                                  matches = doubleCompare( fieldValue ); break;
610                                case "boolean":                                 matches = booleanCompare( fieldValue ); break;
611                                case "date":                                    matches = dateCompare( fieldValue); break;
612                                case "reltime":                                 matches = relTimeCompare( fieldValue ); break;
613                                case "assaymodule":
614                                case "template":
615                                case "term":
616                                case "templatefieldlistitem":
617                                case "string":
618                                default:                                                matches = compareValues( fieldValue.toString().trim().toLowerCase(), this.operator, value.toString().toLowerCase().trim() ); break;
619                        }
620
621                        return matches;
622                } catch( Exception e ) {
623                        log.error e.class.getName() + ": " + e.getMessage();
624                        return false;
625                }
626        }
627
628        /**
629         * Tries to match a value against a criterion and returns true if it matches
630         *
631         * @param fieldValue            Value of the field to match
632         * @param operator                      Operator to apply
633         * @param criterionValue        Value of the criterion
634         * @return                                      True iff the value matches this criterion value, false otherwise
635         */
636        protected boolean compareValues( def fieldValue, Operator operator, def criterionValue ) {
637                switch( operator ) {
638                        case Operator.gte:
639                                return fieldValue >= criterionValue;
640                        case Operator.gt:
641                                return fieldValue > criterionValue;
642                        case Operator.lt:
643                                return fieldValue < criterionValue;
644                        case Operator.lte:
645                                return fieldValue <= criterionValue;
646                        case Operator.contains:
647                        // Contains operator can only be used on string values
648                                return fieldValue.toString().contains( criterionValue.toString() );
649                        case Operator.equals:
650                        default:
651                                return fieldValue.equals( criterionValue );
652                }
653
654        }
655
656        /**
657         * Tries to match a date value against a criterion and returns true if it matches
658         *
659         * @param value         Date value of the field to match
660         * @return                      True iff the value matches this criterion, false otherwise
661         */
662        protected boolean dateCompare( Date fieldValue ) {
663                try {
664                        Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value );
665                        Date fieldDate = new Date( fieldValue.getTime() );
666
667                        // Clear time in order to just compare dates
668                        dateCriterion.clearTime();
669                        fieldDate.clearTime();
670
671                        return compareValues( fieldDate, this.operator, dateCriterion )
672                } catch( Exception e ) {
673                        log.error e.class.getName() + ": " + e.getMessage();
674                        return false;
675                }
676        }
677
678        /**
679         * Tries to match a long value against a criterion and returns true if it matches
680         *
681         * @param value         Long value of the field to match
682         * @param criterion     Criterion to match on. Should be a map with entries 'operator' and 'value'
683         * @return                      True iff the value matches this criterion, false otherwise
684         */
685        protected boolean longCompare( Long fieldValue ) {
686                Long longCriterion;
687                try {
688                        longCriterion = Long.parseLong( value );
689                } catch( Exception e ) {
690                        try {
691                                // If converting to long doesn't work, try converting to double and rounding it
692                                Double doubleCriterion = Double.parseDouble(value);
693                                longCriterion = new Long( doubleCriterion.longValue() );
694                        } catch( Exception e2 ) {
695                                log.debug "Can't convert value to long for comparison: " + e2.class.getName() + ": " + e2.getMessage();
696                                return false;
697                        }
698                }
699                return compareValues( fieldValue, this.operator, longCriterion );
700        }
701
702        /**
703         * Tries to match a double value against a criterion and returns true if it matches
704         *
705         * @param value         Double value of the field to match
706         * @return                      True iff the value matches this criterion, false otherwise
707         */
708        protected boolean doubleCompare( Double fieldValue ) {
709                try {
710                        Double doubleCriterion = Double.parseDouble( value );
711                        return compareValues( fieldValue, this.operator, doubleCriterion );
712                } catch( Exception e ) {
713                        log.debug "Can't convert value to double for comparison: " + e.class.getName() + ": " + e.getMessage();
714                        return false;
715                }
716        }
717
718
719        /**
720         * Tries to match a boolean value against a criterion and returns true if it matches
721         *
722         * @param value         Boolean value of the field to match
723         * @return                      True iff the value matches this criterion, false otherwise
724         */
725        protected boolean booleanCompare( Boolean fieldValue ) {
726                try {
727                        // The comparison should only be performed iff the value
728                        // contains 'true' or 'false' (case insensitive)
729                        def lowerCaseValue = value.toString().toLowerCase();
730                        if( lowerCaseValue != 'true' && lowerCaseValue != 'false' )
731                                return false;
732
733                        Boolean booleanCriterion = Boolean.parseBoolean( value );
734                        return compareValues( fieldValue, this.operator, booleanCriterion );
735                } catch( Exception e ) {
736                        log.debug "Can't convert value to boolean for comparison: " + e.class.getName() + ": " + e.getMessage();
737                        return false;
738                }
739        }
740
741        /**
742         * Tries to match a relTime value against a criterion and returns true if it matches
743         *
744         * @param value         relTime value of the field to match
745         * @return                      True iff the value matches this criterion, false otherwise
746         */
747        protected boolean relTimeCompare( RelTime fieldValue ) {
748                try {
749                        RelTime rt
750
751                        // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it
752                        if( value.toString().isLong() ) {
753                                rt = new RelTime( Long.parseLong( value.toString() ) );
754                        } else {
755                                rt = new RelTime( value.toString() );
756                        }
757
758                        return compareValues( fieldValue, this.operator, rt );
759                } catch( Exception e ) {
760                        log.debug "Can't convert value to reltime for comparison: " + e.class.getName() + ": " + e.getMessage();
761                        return false;
762                }
763        }
764
765        public static Operator parseOperator( String name ) throws Exception {
766                switch( name.trim() ) {
767                        case "=":
768                                case "equals":          return Operator.equals;
769                        case "contains":        return Operator.contains;
770                        case ">=":
771                                case "gte":                     return Operator.gte;
772                        case ">":
773                                case "gt":                      return Operator.gt;
774                        case "<=":
775                                case "lte":                     return Operator.lte;
776                        case "<":
777                                case "lt":                      return Operator.lt;
778                        case "in":                      return Operator.insearch;
779                        default:
780                                throw new Exception( "Operator not found" );
781                }
782        }
783
784        public String toString() {
785                return "[Criterion " + entityField() + " " + operator + " " + value + "]";
786        }
787
788        public boolean equals( Object o ) {
789                if( o == null )
790                        return false;
791
792                if( !( o instanceof Criterion ) )
793                        return false;
794
795                Criterion otherCriterion = (Criterion) o;
796                return  this.entity == otherCriterion.entity &&
797                this.field == otherCriterion.field &&
798                this.operator == otherCriterion.operator &&
799                this.value == otherCriterion.value;
800        }
801}
Note: See TracBrowser for help on using the repository browser.