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

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

Resolving issues with searching (#446)

  • Property svn:keywords set to Rev Author Date
File size: 24.4 KB
Line 
1package dbnp.query
2
3import java.text.SimpleDateFormat
4import org.dbnp.gdt.*
5import org.apache.commons.logging.LogFactory;
6
7/**
8 * Available operators for criteria
9 * @author robert
10 *
11 */
12enum Operator {
13        equals( "=" ), contains( "contains" ), gte( ">="), gt( ">" ), lte( "<=" ), lt( "<" ), insearch( "in" )
14        Operator(String name) { this.name = name }
15        private final String name;
16        public String toString() { return name }
17}
18
19/**
20 * Represents a criterion to search on
21 * @author robert
22 *
23 */
24class Criterion {
25        private static final log = LogFactory.getLog(this);
26        public String entity
27        public String field
28        public Operator operator
29        public def value
30
31        /**
32         * Returns the class for the entity of this criterion
33         * @return     
34         */
35        public Class entityClass() {
36                if( this.entity == '*' )
37                        return null;
38               
39                       
40                try {
41                        return TemplateEntity.parseEntity( 'dbnp.studycapturing.' + this.entity)
42                } catch( Exception e ) {
43                        throw new Exception( "Unknown entity for criterion " + this, e );
44                }
45        }
46       
47        /**
48         * Retrieves a combination of the entity and field
49         * @return
50         */
51        public String entityField() {
52                return entity.toString() + ( field ? "." + field.toString() : "" );
53        }
54
55        /**
56         * Retrieves a human readable description of the combination of the entity and field
57         * @return
58         */
59        public String humanReadableEntityField() {
60                if( field == '*' ) {
61                        if( entity == '*' ) {
62                                return "any field in any object"
63                        } else {
64                                return "any field in " + entity.toString();
65                        }
66                } else {
67                        return entityField();
68                }
69        }
70
71        /**
72         * Returns the type of criterion when searching. Multiple types can be returned, since fields
73         * with the same name might have different types.
74         * 
75         * @return      List of strings determining the type of this criterion. Possibilities are:
76         *              [STRING,BOOLEAN,..]:The criterion references a template field that contains a 'simple'
77         *                                                      value (boolean, double, long, string, reltime, date)
78         *              [STRINGLIST,...]:       The criterion references a template field that contains a 'complex'
79         *                                                      value (listitem, ontologyterm, template, module) referencing another
80         *                                                      database table
81         *              Wildcard:                       The criterion references all fields
82         */
83        protected List<String> criterionType() {
84                if( this.entity == '*' || this.field == '*' ) {
85                        return [
86                                 'String',
87                                 'Text',
88                                 'File',
89                                 'Date',
90                                 'RelTime',
91                                 'Double',
92                                 'Long',
93                                 'Boolean',
94                                 'StringList',
95                                 'ExtendableStringList',
96                                 'Term',
97                                 'Template',
98                                 'Module'
99                        ]
100                }
101
102                // Template fields are string fields
103                if( this.field == 'Template' ) 
104                        return [ "String" ]
105               
106                // Determine domain fields of the entity
107                def domainFields = entityClass().giveDomainFields();
108                def domainField = domainFields.find { it.name == this.field };
109                if( domainField )
110                        return [domainField.type?.casedName];
111
112                // If this field is not a domain field, search for the field in the database
113                def entityClass = entityClass()
114
115                if( !entityClass || !this.field )
116                        return null;
117
118                // Find all fields with this name and entity
119                def fields = TemplateField.findAllByName( this.field ).findAll { it.entity == entityClass };
120
121                // If the field is not found, return null
122                if( !fields )
123                        return null
124
125                // Return the (unique) String value of the types
126                return fields*.type.unique()*.casedName;
127        }
128
129        /**
130         * Determines whether the field in this criterion is a domain field
131         *
132         * @return      True iff the field is a domain field, false otherwise
133         */
134        protected boolean isDomainCriterion() {
135                def entityClass = entityClass()
136               
137                if( !entityClass )
138                        return false;
139               
140                // Template fields should be handled as domain criteria
141                if( this.field == "Template" )
142                        return true;
143                       
144                // Determine domain fields of the entity
145                def domainFields = entityClass.giveDomainFields();
146                def domainField = domainFields.find { it.name == this.field };
147
148                return (domainField ? true : false)
149        }
150       
151        /**
152         * Determines whether this criterion references a 'complex' field (i.e. a field that
153         * contains a complex type like Term, ListItem etc.)
154         *
155         * @return
156         */
157        public boolean isComplexCriterion() {
158                if( this.field == '*' )
159                        return false;
160
161                if( isDomainCriterion() )
162                        return false;
163                       
164                def types = criterionType();
165               
166                return types.any { type -> 
167                        switch( type ) {
168                                case 'StringList':
169                                case 'ExtendableStringList':
170                                case 'Term':
171                                case 'Template':
172                                case 'Module':
173                                        return true;
174                        }
175                       
176                        return false;
177                }
178        }
179
180        /**
181         * Case the field value to search on to the given type
182         * @param fieldType     Name of the template field type
183         * @return                      Value casted to the right value
184         */
185        protected def castValue( String fieldType ) {
186                switch( fieldType ) {
187
188                        case 'String':
189                        case 'Text':
190                        case 'StringList':
191                        case 'ExtendableStringList':
192                        case 'Term':
193                        case 'Template':
194                        case 'Module':
195                                return value?.toString();
196                        case 'File':
197                                return null; // Never search in filenames, since they are not very descriptive
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 )
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                def where = whereClause?.findAll { it } ? "( " + whereClause.join( " OR " ) + " )" : ""
390               
391                return [ "join": joinClause, "where": where , "parameters": parameters ];
392        }
393
394        /**
395         * Extends a given condition with a where clause of this criterion. If you supply "select * from Study where %s", %s will
396         * be replaced by the where clause for the given field. Also, the parameters map will be extended (if needed)
397         *
398         * @param hql                   Initial HQL string where the clause will be put into
399         * @param fieldName             Name of the field that should be referenced
400         * @param uniquePrefix  Unique prefix for this criterion
401         * @param fieldType             Type of field value to search for
402         * @param fieldValue    Field value to search for
403         * @return                              Map with 'where' key referencing the extended where clause and 'parameters' key referencing a map with parameters.
404         */
405        protected Map extendWhereClause( String hql, String fieldName, String uniquePrefix, String fieldType, def fieldValue ) {
406                def parameters = [:]
407                def textFieldTypes = [ 'String', 'Text', 'File', 'StringList', 'ExtendableStringList', 'Term', 'Template', 'Module' ];
408               
409                switch( this.operator ) {
410                        case Operator.contains:
411                                // Text fields should be handled case insensitive
412                                if( textFieldTypes.contains( fieldType ) ) {
413                                        hql = sprintf( hql, "lower( " + fieldName + ") like lower( :" + uniquePrefix + "ValueLike )" );
414                                } else {
415                                        hql = sprintf( hql, fieldName + " like :" + uniquePrefix + "ValueLike" );
416                                }
417                                parameters[ uniquePrefix + "ValueLike" ] = "%" + fieldValue + "%"
418                                break;
419                        case Operator.equals:
420                        case Operator.gte:
421                        case Operator.gt:
422                        case Operator.lte:
423                        case Operator.lt:
424                                if( textFieldTypes.contains( fieldType ) ) {
425                                        hql = sprintf( hql, "lower( " + fieldName + " ) "  + this.operator.name + " lower( :" + uniquePrefix + "Value" + fieldType + ")" );
426                                } else {
427                                        hql = sprintf( hql, fieldName + " "  + this.operator.name + " :" + uniquePrefix + "Value" + fieldType );
428                                }
429                                parameters[ uniquePrefix + "Value" + fieldType ] = fieldValue
430                                break;
431                }
432
433                return [ "where": hql, "parameters": parameters]
434        }
435
436        /**
437         * Creates a condition for this criterion, for a given fieldName and value. The fieldName should reference a collection that has a one-to-many
438         * relation with the object being sought
439         * 
440         * @param fieldName             Name to search in
441         * @param uniquePrefix  Unique prefix for this criterion
442         * @param currentValue  Map with 'value' referencing the value being sought and 'type' referencing
443         *                                              the type of the value as string. The value should be be casted to the right class for this field.
444         * @return                              Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters.
445         */
446        protected Map oneToManyWhereCondition( String fieldName, String uniquePrefix, String fieldType, def fieldValue ) {
447                // Create the where condition for checking the value
448                // First check the name of the field, if needed
449                def condition
450                def parameters = [:]
451
452                if( this.field != '*' ) {
453                        condition = "( %s AND index(" + fieldName + ") = :" + uniquePrefix + "Field )"
454                        parameters[ uniquePrefix + "Field" ] = this.field
455                } else {
456                        condition = "%s";
457                }
458
459                def whereClause = extendWhereClause( condition, fieldName, uniquePrefix, fieldType, fieldValue );
460                parameters.each {
461                        whereClause.parameters[ it.key ] = it.value;
462                }
463
464                return whereClause;
465        }
466
467        /**
468         * Creates a condition for this criterion, for a given fieldName and value. The fieldName should
469         * reference a collection that has a many-to-many relation with the object being sought (e.g. templateTermFields).
470         *
471         * Unfortunately, there is no way to determine the name of the field in HQL for this many-to-many collections, since the
472         * INDEX() function in HQL doesn't work for many-to-many collections.
473         * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4879
474         * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4615
475         * 
476         * @param fieldName             Name to search in
477         * @param uniquePrefix  Unique prefix for this criterion
478         * @param currentValue  Map with 'value' referencing the value being sought and 'type' referencing
479         *                                              the type of the value as string. The value should be be casted to the right class for this field.
480         * @return                              Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters.
481         */
482        protected Map manyToManyWhereCondition( String objectToSearchIn, String collection, String uniquePrefix, String searchField, def value ) {
483                // exists( FROM [objectToSearchIn].[collection] as [uniquePrefix][collection] WHERE [searchField] LIKE [value] )
484                // Create the where condition for checking the value
485                def condition = "exists ( FROM " + objectToSearchIn + "." + collection + " as " + uniquePrefix + "_" + collection + " WHERE %s )";
486
487                return extendWhereClause( condition, uniquePrefix + "_" + collection + "." + searchField, uniquePrefix, "STRING", value );
488        }
489
490        /**
491         * Retrieves the correct value for this criterion in the given object (with template)
492         *
493         * @param entity                Entity to check for value. Should be a child of template entity
494         * @param criterion             Criterion to match on
495         * @return                              Value of the given field or null if the field doesn't exist
496         */
497        public def getFieldValue( TemplateEntity entity ) {
498                if( entity == null )
499                        return null;
500
501                try {
502                        def fieldValue
503                        if( !field ) {
504                                fieldValue = entity
505                        } else if( field == "Template" ) {
506                                fieldValue = entity.template?.name
507                        } else if( field == "*" ) {
508                                fieldValue = entity.giveFields().collect{
509                                        if( it && it.name ) {
510                                                Search.prepare( entity.getFieldValue( it.name ), entity.giveFieldType( it.name ) )
511                                        }
512                                }
513                        } else {
514                                fieldValue = Search.prepare( entity.getFieldValue( field ), entity.giveFieldType( field ) )
515                        }
516
517                        return fieldValue
518                } catch( Exception e ) {
519                        // An exception occurs if the given field doesn't exist. In that case, this criterion will fail.
520                        // TODO: Maybe give the user a choice whether he want's to include these studies or not
521                        return null;
522                }
523        }
524
525        /**
526         * Tries to match a value against a criterion and returns true if it matches
527         *
528         * @param value         Value of the field to match
529         * @return                      True iff the value matches this criterion, false otherwise
530         */
531        public boolean match( def fieldValue ) {
532                if( fieldValue == null )
533                        return false;
534
535                // in-search criteria have to be handled separately
536                if( this.operator == Operator.insearch ) {
537                        return this.value?.getResults()?.contains( fieldValue );
538                }
539
540                // Other criteria are handled based on the class of the value given.
541                def classname = fieldValue.class.getName();
542                classname = classname[classname.lastIndexOf( '.' ) + 1..-1].toLowerCase();
543
544                def matches = false;
545                try {
546                        switch( classname ) {
547                                case "integer":                                 matches = longCompare( new Long( fieldValue.longValue() ) ); break;
548                                case "long":                                    matches = longCompare( fieldValue ); break;
549                                case "float":                                   matches = doubleCompare( new Long( fieldValue.doubleValue() ) ); break;
550                                case "double":                                  matches = doubleCompare( fieldValue ); break;
551                                case "boolean":                                 matches = booleanCompare( fieldValue ); break;
552                                case "date":                                    matches = dateCompare( fieldValue); break;
553                                case "reltime":                                 matches = relTimeCompare( fieldValue ); break;
554                                case "assaymodule":
555                                case "template":
556                                case "term":
557                                case "templatefieldlistitem":
558                                case "string":
559                                default:                                                matches = compareValues( fieldValue.toString().trim().toLowerCase(), this.operator, value.toString().toLowerCase().trim() ); break;
560                        }
561
562                        return matches;
563                } catch( Exception e ) {
564                        log.error e.class.getName() + ": " + e.getMessage();
565                        return false;
566                }
567        }
568
569        /**
570         * Tries to match a value against a criterion and returns true if it matches
571         *
572         * @param fieldValue            Value of the field to match
573         * @param operator                      Operator to apply
574         * @param criterionValue        Value of the criterion
575         * @return                                      True iff the value matches this criterion value, false otherwise
576         */
577        protected boolean compareValues( def fieldValue, Operator operator, def criterionValue ) {
578                switch( operator ) {
579                        case Operator.gte:
580                                return fieldValue >= criterionValue;
581                        case Operator.gt:
582                                return fieldValue > criterionValue;
583                        case Operator.lt:
584                                return fieldValue < criterionValue;
585                        case Operator.lte:
586                                return fieldValue <= criterionValue;
587                        case Operator.contains:
588                        // Contains operator can only be used on string values
589                                return fieldValue.toString().contains( criterionValue.toString() );
590                        case Operator.equals:
591                        default:
592                                return fieldValue.equals( criterionValue );
593                }
594
595        }
596
597        /**
598         * Tries to match a date value against a criterion and returns true if it matches
599         *
600         * @param value         Date value of the field to match
601         * @return                      True iff the value matches this criterion, false otherwise
602         */
603        protected boolean dateCompare( Date fieldValue ) {
604                try {
605                        Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value );
606                        Date fieldDate = new Date( fieldValue.getTime() );
607
608                        // Clear time in order to just compare dates
609                        dateCriterion.clearTime();
610                        fieldDate.clearTime();
611
612                        return compareValues( fieldDate, this.operator, dateCriterion )
613                } catch( Exception e ) {
614                        log.error e.class.getName() + ": " + e.getMessage();
615                        return false;
616                }
617        }
618
619        /**
620         * Tries to match a long value against a criterion and returns true if it matches
621         *
622         * @param value         Long value of the field to match
623         * @param criterion     Criterion to match on. Should be a map with entries 'operator' and 'value'
624         * @return                      True iff the value matches this criterion, false otherwise
625         */
626        protected boolean longCompare( Long fieldValue ) {
627                Long longCriterion;
628                try {
629                        longCriterion = Long.parseLong( value );
630                } catch( Exception e ) {
631                        try {
632                                // If converting to long doesn't work, try converting to double and rounding it
633                                Double doubleCriterion = Double.parseDouble(value);
634                                longCriterion = new Long( doubleCriterion.longValue() );
635                        } catch( Exception e2 ) {
636                                log.debug "Can't convert value to long for comparison: " + e2.class.getName() + ": " + e2.getMessage();
637                                return false;
638                        }
639                }
640                return compareValues( fieldValue, this.operator, longCriterion );
641        }
642
643        /**
644         * Tries to match a double value against a criterion and returns true if it matches
645         *
646         * @param value         Double value of the field to match
647         * @return                      True iff the value matches this criterion, false otherwise
648         */
649        protected boolean doubleCompare( Double fieldValue ) {
650                try {
651                        Double doubleCriterion = Double.parseDouble( value );
652                        return compareValues( fieldValue, this.operator, doubleCriterion );
653                } catch( Exception e ) {
654                        log.debug "Can't convert value to double for comparison: " + e.class.getName() + ": " + e.getMessage();
655                        return false;
656                }
657        }
658
659
660        /**
661         * Tries to match a boolean value against a criterion and returns true if it matches
662         *
663         * @param value         Boolean value of the field to match
664         * @return                      True iff the value matches this criterion, false otherwise
665         */
666        protected boolean booleanCompare( Boolean fieldValue ) {
667                try {
668                        // The comparison should only be performed iff the value
669                        // contains 'true' or 'false' (case insensitive)
670                        def lowerCaseValue = value.toString().toLowerCase();
671                        if( lowerCaseValue != 'true' && lowerCaseValue != 'false' )
672                                return false;
673
674                        Boolean booleanCriterion = Boolean.parseBoolean( value );
675                        return compareValues( fieldValue, this.operator, booleanCriterion );
676                } catch( Exception e ) {
677                        log.debug "Can't convert value to boolean for comparison: " + e.class.getName() + ": " + e.getMessage();
678                        return false;
679                }
680        }
681
682        /**
683         * Tries to match a relTime value against a criterion and returns true if it matches
684         *
685         * @param value         relTime value of the field to match
686         * @return                      True iff the value matches this criterion, false otherwise
687         */
688        protected boolean relTimeCompare( RelTime fieldValue ) {
689                try {
690                        RelTime rt
691
692                        // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it
693                        if( value.toString().isLong() ) {
694                                rt = new RelTime( Long.parseLong( value.toString() ) );
695                        } else {
696                                rt = new RelTime( value.toString() );
697                        }
698
699                        return compareValues( fieldValue, this.operator, rt );
700                } catch( Exception e ) {
701                        log.debug "Can't convert value to reltime for comparison: " + e.class.getName() + ": " + e.getMessage();
702                        return false;
703                }
704        }
705
706        public static Operator parseOperator( String name ) throws Exception {
707                switch( name.trim() ) {
708                        case "=":
709                                case "equals":          return Operator.equals;
710                        case "contains":        return Operator.contains;
711                        case ">=":
712                                case "gte":                     return Operator.gte;
713                        case ">":
714                                case "gt":                      return Operator.gt;
715                        case "<=":
716                                case "lte":                     return Operator.lte;
717                        case "<":
718                                case "lt":                      return Operator.lt;
719                        case "in":                      return Operator.insearch;
720                        default:
721                                throw new Exception( "Operator not found" );
722                }
723        }
724
725        public String toString() {
726                return "[Criterion " + entityField() + " " + operator + " " + value + "]";
727        }
728
729        public boolean equals( Object o ) {
730                if( o == null )
731                        return false;
732
733                if( !( o instanceof Criterion ) )
734                        return false;
735
736                Criterion otherCriterion = (Criterion) o;
737                return  this.entity == otherCriterion.entity &&
738                this.field == otherCriterion.field &&
739                this.operator == otherCriterion.operator &&
740                this.value == otherCriterion.value;
741        }
742}
Note: See TracBrowser for help on using the repository browser.