1 | package dbnp.query |
---|
2 | |
---|
3 | import java.text.SimpleDateFormat |
---|
4 | import org.dbnp.gdt.* |
---|
5 | import org.apache.commons.logging.LogFactory; |
---|
6 | |
---|
7 | /** |
---|
8 | * Available operators for criteria |
---|
9 | * @author robert |
---|
10 | * |
---|
11 | */ |
---|
12 | enum 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 | */ |
---|
24 | class 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 | // Determine domain fields of the entity |
---|
103 | def domainFields = entityClass().giveDomainFields(); |
---|
104 | def domainField = domainFields.find { it.name == this.field }; |
---|
105 | if( domainField ) |
---|
106 | return [domainField.type?.casedName]; |
---|
107 | |
---|
108 | // If this field is not a domain field, search for the field in the database |
---|
109 | def entityClass = entityClass() |
---|
110 | |
---|
111 | if( !entityClass || !this.field ) |
---|
112 | return null; |
---|
113 | |
---|
114 | // Find all fields with this name and entity |
---|
115 | def fields = TemplateField.findAllByName( this.field ).findAll { it.entity == entityClass }; |
---|
116 | |
---|
117 | // If the field is not found, return null |
---|
118 | if( !fields ) |
---|
119 | return null |
---|
120 | |
---|
121 | // Return the (unique) String value of the types |
---|
122 | return fields*.type.unique()*.casedName; |
---|
123 | } |
---|
124 | |
---|
125 | /** |
---|
126 | * Determines whether the field in this criterion is a domain field |
---|
127 | * |
---|
128 | * @return True iff the field is a domain field, false otherwise |
---|
129 | */ |
---|
130 | protected boolean isDomainCriterion() { |
---|
131 | def entityClass = entityClass() |
---|
132 | |
---|
133 | if( !entityClass ) |
---|
134 | return false; |
---|
135 | |
---|
136 | // Determine domain fields of the entity |
---|
137 | def domainFields = entityClass.giveDomainFields(); |
---|
138 | def domainField = domainFields.find { it.name == this.field }; |
---|
139 | |
---|
140 | return (domainField ? true : false) |
---|
141 | } |
---|
142 | |
---|
143 | /** |
---|
144 | * Determines whether this criterion references a 'complex' field (i.e. a field that |
---|
145 | * contains a complex type like Term, ListItem etc.) |
---|
146 | * |
---|
147 | * @return |
---|
148 | */ |
---|
149 | public boolean isComplexCriterion() { |
---|
150 | if( this.field == '*' ) |
---|
151 | return false; |
---|
152 | |
---|
153 | if( isDomainCriterion() ) |
---|
154 | return false; |
---|
155 | |
---|
156 | def types = criterionType(); |
---|
157 | |
---|
158 | return types.any { type -> |
---|
159 | switch( type ) { |
---|
160 | case 'StringList': |
---|
161 | case 'ExtendableStringList': |
---|
162 | case 'Term': |
---|
163 | case 'Template': |
---|
164 | case 'Module': |
---|
165 | return true; |
---|
166 | } |
---|
167 | |
---|
168 | return false; |
---|
169 | } |
---|
170 | } |
---|
171 | |
---|
172 | /** |
---|
173 | * Case the field value to search on to the given type |
---|
174 | * @param fieldType Name of the template field type |
---|
175 | * @return Value casted to the right value |
---|
176 | */ |
---|
177 | protected def castValue( String fieldType ) { |
---|
178 | switch( fieldType ) { |
---|
179 | |
---|
180 | case 'String': |
---|
181 | case 'Text': |
---|
182 | case 'StringList': |
---|
183 | case 'ExtendableStringList': |
---|
184 | case 'Term': |
---|
185 | case 'Template': |
---|
186 | case 'Module': |
---|
187 | return value?.toString(); |
---|
188 | case 'File': |
---|
189 | return null; // Never search in filenames, since they are not very descriptive |
---|
190 | case 'Date': |
---|
191 | // The comparison with date values should only be performed iff the value |
---|
192 | // contains a parsable date |
---|
193 | // and the operator is equals, gte, gt, lt or lte |
---|
194 | if( operator == Operator.insearch || operator == Operator.contains ) |
---|
195 | return null |
---|
196 | |
---|
197 | try { |
---|
198 | Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value ); |
---|
199 | return dateCriterion |
---|
200 | } catch( Exception e ) { |
---|
201 | return null; |
---|
202 | } |
---|
203 | |
---|
204 | case 'RelTime': |
---|
205 | // The comparison with date values should only be performed iff the value |
---|
206 | // contains a long number |
---|
207 | // and the operator is equals, gte, gt, lt or lte |
---|
208 | if( operator == Operator.insearch || operator == Operator.contains ) |
---|
209 | return null |
---|
210 | |
---|
211 | try { |
---|
212 | RelTime rt |
---|
213 | |
---|
214 | // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it |
---|
215 | if( value.toString().isLong() ) { |
---|
216 | rt = new RelTime( Long.parseLong( value.toString() ) ); |
---|
217 | } else { |
---|
218 | rt = new RelTime( value.toString() ); |
---|
219 | } |
---|
220 | |
---|
221 | return rt.getValue() |
---|
222 | } catch( Exception e ) { |
---|
223 | return null; |
---|
224 | } |
---|
225 | case 'Double': |
---|
226 | // The comparison with date values should only be performed iff the value |
---|
227 | // contains a double number |
---|
228 | // and the operator is equals, gte, gt, lt or lte |
---|
229 | if( operator == Operator.insearch || operator == Operator.contains ) |
---|
230 | return null |
---|
231 | |
---|
232 | if( value.isDouble() ) { |
---|
233 | return Double.parseDouble( value ) |
---|
234 | } else { |
---|
235 | return null; |
---|
236 | } |
---|
237 | case 'Long': |
---|
238 | // The comparison with date values should only be performed iff the value |
---|
239 | // contains a long number |
---|
240 | // and the operator is equals, gte, gt, lt or lte |
---|
241 | if( operator == Operator.insearch || operator == Operator.contains ) |
---|
242 | return null |
---|
243 | |
---|
244 | if( value.isLong() ) { |
---|
245 | return Long.parseLong( value ) |
---|
246 | } else { |
---|
247 | return null; |
---|
248 | } |
---|
249 | case 'Boolean': |
---|
250 | // The comparison with boolean values should only be performed iff the value |
---|
251 | // contains 'true' or 'false' (case insensitive) |
---|
252 | // and the operator is equals |
---|
253 | if( operator != Operator.equals ) |
---|
254 | return null |
---|
255 | |
---|
256 | def lowerCaseValue = value.toString().toLowerCase(); |
---|
257 | if( lowerCaseValue == 'true' || lowerCaseValue == 'false' ) { |
---|
258 | return Boolean.parseBoolean( this.value ) |
---|
259 | } else { |
---|
260 | return null |
---|
261 | } |
---|
262 | } |
---|
263 | } |
---|
264 | |
---|
265 | /** |
---|
266 | * Create a HQL where clause from this criterion, in order to be used within a larger HQL statement |
---|
267 | * |
---|
268 | * @param objectToSearchIn HQL name of the object to search in |
---|
269 | * @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 |
---|
270 | */ |
---|
271 | public Map toHQL( String prefix, String objectToSearchIn = "object" ) { |
---|
272 | List whereClause = [] |
---|
273 | String joinClause = ""; |
---|
274 | Map parameters = [:]; |
---|
275 | def emptyCriterion = [ "join": null, "where": null, "parameters": null ]; |
---|
276 | |
---|
277 | // If this criterion is used to search within another search result, we use a special piece of HQL |
---|
278 | if( this.operator == Operator.insearch ) { |
---|
279 | if( this.value?.results ) { |
---|
280 | parameters[ prefix + "SearchResults" ] = this.value?.results |
---|
281 | |
---|
282 | return [ "join": "", "where": "( " + objectToSearchIn + " in (:" + prefix + "SearchResults) )" , "parameters": parameters ]; |
---|
283 | } else { |
---|
284 | return emptyCriterion; |
---|
285 | } |
---|
286 | } |
---|
287 | |
---|
288 | // If no value is given, don't do anything |
---|
289 | if( !value ) |
---|
290 | return emptyCriterion; |
---|
291 | |
---|
292 | // Check whether the field is a domain field |
---|
293 | if( isDomainCriterion() ) { |
---|
294 | // Determine the types of this criterion, but there will be only 1 for a domain field |
---|
295 | def criterionType = criterionType()[0]; |
---|
296 | |
---|
297 | // Some domain fields don't contain a value, but a reference to another table |
---|
298 | // These should be handled differently |
---|
299 | def fieldName = this.field |
---|
300 | |
---|
301 | if( |
---|
302 | ( objectToSearchIn == "subject" && fieldName == "species" ) || |
---|
303 | ( objectToSearchIn == "sample" && fieldName == "material" ) || |
---|
304 | ( objectToSearchIn == "assay" && fieldName == "module" ) || |
---|
305 | ( objectToSearchIn == "samplingEvent" && fieldName == "sampleTemplate" ) ) { |
---|
306 | fieldName += ".name" |
---|
307 | } |
---|
308 | |
---|
309 | def query = extendWhereClause( "( %s )", objectToSearchIn + "." + fieldName, prefix, criterionType, castValue( criterionType ) ); |
---|
310 | return [ "join": "", "where": query.where, "parameters": query.parameters ] |
---|
311 | } |
---|
312 | |
---|
313 | // Determine the type of this criterion |
---|
314 | def criterionTypes = criterionType(); |
---|
315 | |
---|
316 | if( !criterionTypes ) |
---|
317 | return emptyCriterion; |
---|
318 | |
---|
319 | |
---|
320 | // Several types of criteria are handled differently. |
---|
321 | // The 'wildcard' is handled by searching for all types. |
---|
322 | // The 'simple' types (string, double) are handled by searching in the associated table |
---|
323 | // The 'complex' types (stringlist, template etc., referencing another |
---|
324 | // database table) can't be handled correctly, since the HQL INDEX() function doesn't work on those relations. |
---|
325 | // We do a search for these types to see whether any field with that type fits this criterion, in order to |
---|
326 | // filter out false positives later on. |
---|
327 | criterionTypes.findAll { it }.each { criterionType -> |
---|
328 | // Cast criterion value to the right type |
---|
329 | def currentValue = castValue( criterionType ); |
---|
330 | |
---|
331 | // Determine field name |
---|
332 | def fieldName = "template" + criterionType + 'Fields' |
---|
333 | |
---|
334 | switch( criterionType ) { |
---|
335 | case "Wildcard": |
---|
336 | // Wildcard search is handled by |
---|
337 | break; |
---|
338 | |
---|
339 | case 'String': |
---|
340 | case 'Text': |
---|
341 | case 'File': |
---|
342 | case 'Date': |
---|
343 | case 'RelTime': |
---|
344 | case 'Double': |
---|
345 | case 'Long': |
---|
346 | case 'Boolean': |
---|
347 | // 'Simple' field types |
---|
348 | if( currentValue != null ) { |
---|
349 | joinClause += " left join " + objectToSearchIn + "." + fieldName + " as " + prefix + "_" + fieldName + " "; |
---|
350 | |
---|
351 | def condition = this.oneToManyWhereCondition( prefix + "_" + fieldName, prefix, criterionType, currentValue ) |
---|
352 | whereClause += condition[ "where" ]; |
---|
353 | |
---|
354 | condition[ "parameters" ].each { |
---|
355 | parameters[ it.key ] = it.value; |
---|
356 | } |
---|
357 | } |
---|
358 | break; |
---|
359 | |
---|
360 | case 'StringList': |
---|
361 | case 'ExtendableStringList': |
---|
362 | case 'Term': |
---|
363 | case 'Template': |
---|
364 | case 'Module': |
---|
365 | // 'Complex' field types |
---|
366 | def condition = this.manyToManyWhereCondition( objectToSearchIn, fieldName, prefix, "name", currentValue ) |
---|
367 | whereClause += condition[ "where" ]; |
---|
368 | |
---|
369 | condition[ "parameters" ].each { |
---|
370 | parameters[ it.key ] = it.value; |
---|
371 | } |
---|
372 | default: |
---|
373 | break; |
---|
374 | } |
---|
375 | } |
---|
376 | |
---|
377 | def where = whereClause?.findAll { it } ? "( " + whereClause.join( " OR " ) + " )" : "" |
---|
378 | |
---|
379 | return [ "join": joinClause, "where": where , "parameters": parameters ]; |
---|
380 | } |
---|
381 | |
---|
382 | /** |
---|
383 | * Extends a given condition with a where clause of this criterion. If you supply "select * from Study where %s", %s will |
---|
384 | * be replaced by the where clause for the given field. Also, the parameters map will be extended (if needed) |
---|
385 | * |
---|
386 | * @param hql Initial HQL string where the clause will be put into |
---|
387 | * @param fieldName Name of the field that should be referenced |
---|
388 | * @param uniquePrefix Unique prefix for this criterion |
---|
389 | * @param fieldType Type of field value to search for |
---|
390 | * @param fieldValue Field value to search for |
---|
391 | * @return Map with 'where' key referencing the extended where clause and 'parameters' key referencing a map with parameters. |
---|
392 | */ |
---|
393 | protected Map extendWhereClause( String hql, String fieldName, String uniquePrefix, String fieldType, def fieldValue ) { |
---|
394 | def parameters = [:] |
---|
395 | def textFieldTypes = [ 'String', 'Text', 'File', 'StringList', 'ExtendableStringList', 'Term', 'Template', 'Module' ]; |
---|
396 | |
---|
397 | switch( this.operator ) { |
---|
398 | case Operator.contains: |
---|
399 | // Text fields should be handled case insensitive |
---|
400 | if( textFieldTypes.contains( fieldType ) ) { |
---|
401 | hql = sprintf( hql, "lower( " + fieldName + ") like lower( :" + uniquePrefix + "ValueLike )" ); |
---|
402 | } else { |
---|
403 | hql = sprintf( hql, fieldName + " like :" + uniquePrefix + "ValueLike" ); |
---|
404 | } |
---|
405 | parameters[ uniquePrefix + "ValueLike" ] = "%" + fieldValue + "%" |
---|
406 | break; |
---|
407 | case Operator.equals: |
---|
408 | case Operator.gte: |
---|
409 | case Operator.gt: |
---|
410 | case Operator.lte: |
---|
411 | case Operator.lt: |
---|
412 | if( textFieldTypes.contains( fieldType ) ) { |
---|
413 | hql = sprintf( hql, "lower( " + fieldName + " ) " + this.operator.name + " lower( :" + uniquePrefix + "Value" + fieldType + ")" ); |
---|
414 | } else { |
---|
415 | hql = sprintf( hql, fieldName + " " + this.operator.name + " :" + uniquePrefix + "Value" + fieldType ); |
---|
416 | } |
---|
417 | parameters[ uniquePrefix + "Value" + fieldType ] = fieldValue |
---|
418 | break; |
---|
419 | } |
---|
420 | |
---|
421 | return [ "where": hql, "parameters": parameters] |
---|
422 | } |
---|
423 | |
---|
424 | /** |
---|
425 | * Creates a condition for this criterion, for a given fieldName and value. The fieldName should reference a collection that has a one-to-many |
---|
426 | * relation with the object being sought |
---|
427 | * |
---|
428 | * @param fieldName Name to search in |
---|
429 | * @param uniquePrefix Unique prefix for this criterion |
---|
430 | * @param currentValue Map with 'value' referencing the value being sought and 'type' referencing |
---|
431 | * the type of the value as string. The value should be be casted to the right class for this field. |
---|
432 | * @return Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters. |
---|
433 | */ |
---|
434 | protected Map oneToManyWhereCondition( String fieldName, String uniquePrefix, String fieldType, def fieldValue ) { |
---|
435 | // Create the where condition for checking the value |
---|
436 | // First check the name of the field, if needed |
---|
437 | def condition |
---|
438 | def parameters = [:] |
---|
439 | |
---|
440 | if( this.field != '*' ) { |
---|
441 | condition = "( %s AND index(" + fieldName + ") = :" + uniquePrefix + "Field )" |
---|
442 | parameters[ uniquePrefix + "Field" ] = this.field |
---|
443 | } else { |
---|
444 | condition = "%s"; |
---|
445 | } |
---|
446 | |
---|
447 | def whereClause = extendWhereClause( condition, fieldName, uniquePrefix, fieldType, fieldValue ); |
---|
448 | parameters.each { |
---|
449 | whereClause.parameters[ it.key ] = it.value; |
---|
450 | } |
---|
451 | |
---|
452 | return whereClause; |
---|
453 | } |
---|
454 | |
---|
455 | /** |
---|
456 | * Creates a condition for this criterion, for a given fieldName and value. The fieldName should |
---|
457 | * reference a collection that has a many-to-many relation with the object being sought (e.g. templateTermFields). |
---|
458 | * |
---|
459 | * Unfortunately, there is no way to determine the name of the field in HQL for this many-to-many collections, since the |
---|
460 | * INDEX() function in HQL doesn't work for many-to-many collections. |
---|
461 | * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4879 |
---|
462 | * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-4615 |
---|
463 | * |
---|
464 | * @param fieldName Name to search in |
---|
465 | * @param uniquePrefix Unique prefix for this criterion |
---|
466 | * @param currentValue Map with 'value' referencing the value being sought and 'type' referencing |
---|
467 | * the type of the value as string. The value should be be casted to the right class for this field. |
---|
468 | * @return Map with 'where' key referencing the where clause and 'parameters' key referencing a map with parameters. |
---|
469 | */ |
---|
470 | protected Map manyToManyWhereCondition( String objectToSearchIn, String collection, String uniquePrefix, String searchField, def value ) { |
---|
471 | // exists( FROM [objectToSearchIn].[collection] as [uniquePrefix][collection] WHERE [searchField] LIKE [value] ) |
---|
472 | // Create the where condition for checking the value |
---|
473 | def condition = "exists ( FROM " + objectToSearchIn + "." + collection + " as " + uniquePrefix + "_" + collection + " WHERE %s )"; |
---|
474 | |
---|
475 | return extendWhereClause( condition, uniquePrefix + "_" + collection + "." + searchField, uniquePrefix, "STRING", value ); |
---|
476 | } |
---|
477 | |
---|
478 | /** |
---|
479 | * Retrieves the correct value for this criterion in the given object (with template) |
---|
480 | * |
---|
481 | * @param entity Entity to check for value. Should be a child of template entity |
---|
482 | * @param criterion Criterion to match on |
---|
483 | * @return Value of the given field or null if the field doesn't exist |
---|
484 | */ |
---|
485 | public def getFieldValue( TemplateEntity entity ) { |
---|
486 | if( entity == null ) |
---|
487 | return null; |
---|
488 | |
---|
489 | try { |
---|
490 | def fieldValue |
---|
491 | if( !field ) { |
---|
492 | fieldValue = entity |
---|
493 | } else if( field == "Template" ) { |
---|
494 | fieldValue = entity.template?.name |
---|
495 | } else if( field == "*" ) { |
---|
496 | fieldValue = entity.giveFields().collect{ |
---|
497 | if( it && it.name ) { |
---|
498 | Search.prepare( entity.getFieldValue( it.name ), entity.giveFieldType( it.name ) ) |
---|
499 | } |
---|
500 | } |
---|
501 | } else { |
---|
502 | fieldValue = Search.prepare( entity.getFieldValue( field ), entity.giveFieldType( field ) ) |
---|
503 | } |
---|
504 | |
---|
505 | return fieldValue |
---|
506 | } catch( Exception e ) { |
---|
507 | // An exception occurs if the given field doesn't exist. In that case, this criterion will fail. |
---|
508 | // TODO: Maybe give the user a choice whether he want's to include these studies or not |
---|
509 | return null; |
---|
510 | } |
---|
511 | } |
---|
512 | |
---|
513 | /** |
---|
514 | * Tries to match a value against a criterion and returns true if it matches |
---|
515 | * |
---|
516 | * @param value Value of the field to match |
---|
517 | * @return True iff the value matches this criterion, false otherwise |
---|
518 | */ |
---|
519 | public boolean match( def fieldValue ) { |
---|
520 | if( fieldValue == null ) |
---|
521 | return false; |
---|
522 | |
---|
523 | // in-search criteria have to be handled separately |
---|
524 | if( this.operator == Operator.insearch ) { |
---|
525 | return this.value?.getResults()?.contains( fieldValue ); |
---|
526 | } |
---|
527 | |
---|
528 | // Other criteria are handled based on the class of the value given. |
---|
529 | def classname = fieldValue.class.getName(); |
---|
530 | classname = classname[classname.lastIndexOf( '.' ) + 1..-1].toLowerCase(); |
---|
531 | |
---|
532 | def matches = false; |
---|
533 | try { |
---|
534 | switch( classname ) { |
---|
535 | case "integer": matches = longCompare( new Long( fieldValue.longValue() ) ); break; |
---|
536 | case "long": matches = longCompare( fieldValue ); break; |
---|
537 | case "float": matches = doubleCompare( new Long( fieldValue.doubleValue() ) ); break; |
---|
538 | case "double": matches = doubleCompare( fieldValue ); break; |
---|
539 | case "boolean": matches = booleanCompare( fieldValue ); break; |
---|
540 | case "date": matches = dateCompare( fieldValue); break; |
---|
541 | case "reltime": matches = relTimeCompare( fieldValue ); break; |
---|
542 | case "assaymodule": |
---|
543 | case "template": |
---|
544 | case "term": |
---|
545 | case "templatefieldlistitem": |
---|
546 | case "string": |
---|
547 | default: matches = compareValues( fieldValue.toString().trim().toLowerCase(), this.operator, value.toString().toLowerCase().trim() ); break; |
---|
548 | } |
---|
549 | |
---|
550 | return matches; |
---|
551 | } catch( Exception e ) { |
---|
552 | log.error e.class.getName() + ": " + e.getMessage(); |
---|
553 | return false; |
---|
554 | } |
---|
555 | } |
---|
556 | |
---|
557 | /** |
---|
558 | * Tries to match a value against a criterion and returns true if it matches |
---|
559 | * |
---|
560 | * @param fieldValue Value of the field to match |
---|
561 | * @param operator Operator to apply |
---|
562 | * @param criterionValue Value of the criterion |
---|
563 | * @return True iff the value matches this criterion value, false otherwise |
---|
564 | */ |
---|
565 | protected boolean compareValues( def fieldValue, Operator operator, def criterionValue ) { |
---|
566 | switch( operator ) { |
---|
567 | case Operator.gte: |
---|
568 | return fieldValue >= criterionValue; |
---|
569 | case Operator.gt: |
---|
570 | return fieldValue > criterionValue; |
---|
571 | case Operator.lt: |
---|
572 | return fieldValue < criterionValue; |
---|
573 | case Operator.lte: |
---|
574 | return fieldValue <= criterionValue; |
---|
575 | case Operator.contains: |
---|
576 | // Contains operator can only be used on string values |
---|
577 | return fieldValue.toString().contains( criterionValue.toString() ); |
---|
578 | case Operator.equals: |
---|
579 | default: |
---|
580 | return fieldValue.equals( criterionValue ); |
---|
581 | } |
---|
582 | |
---|
583 | } |
---|
584 | |
---|
585 | /** |
---|
586 | * Tries to match a date value against a criterion and returns true if it matches |
---|
587 | * |
---|
588 | * @param value Date value of the field to match |
---|
589 | * @return True iff the value matches this criterion, false otherwise |
---|
590 | */ |
---|
591 | protected boolean dateCompare( Date fieldValue ) { |
---|
592 | try { |
---|
593 | Date dateCriterion = new SimpleDateFormat( "yyyy-MM-dd" ).parse( value ); |
---|
594 | Date fieldDate = new Date( fieldValue.getTime() ); |
---|
595 | |
---|
596 | // Clear time in order to just compare dates |
---|
597 | dateCriterion.clearTime(); |
---|
598 | fieldDate.clearTime(); |
---|
599 | |
---|
600 | return compareValues( fieldDate, this.operator, dateCriterion ) |
---|
601 | } catch( Exception e ) { |
---|
602 | log.error e.class.getName() + ": " + e.getMessage(); |
---|
603 | return false; |
---|
604 | } |
---|
605 | } |
---|
606 | |
---|
607 | /** |
---|
608 | * Tries to match a long value against a criterion and returns true if it matches |
---|
609 | * |
---|
610 | * @param value Long value of the field to match |
---|
611 | * @param criterion Criterion to match on. Should be a map with entries 'operator' and 'value' |
---|
612 | * @return True iff the value matches this criterion, false otherwise |
---|
613 | */ |
---|
614 | protected boolean longCompare( Long fieldValue ) { |
---|
615 | Long longCriterion; |
---|
616 | try { |
---|
617 | longCriterion = Long.parseLong( value ); |
---|
618 | } catch( Exception e ) { |
---|
619 | try { |
---|
620 | // If converting to long doesn't work, try converting to double and rounding it |
---|
621 | Double doubleCriterion = Double.parseDouble(value); |
---|
622 | longCriterion = new Long( doubleCriterion.longValue() ); |
---|
623 | } catch( Exception e2 ) { |
---|
624 | log.debug "Can't convert value to long for comparison: " + e2.class.getName() + ": " + e2.getMessage(); |
---|
625 | return false; |
---|
626 | } |
---|
627 | } |
---|
628 | return compareValues( fieldValue, this.operator, longCriterion ); |
---|
629 | } |
---|
630 | |
---|
631 | /** |
---|
632 | * Tries to match a double value against a criterion and returns true if it matches |
---|
633 | * |
---|
634 | * @param value Double value of the field to match |
---|
635 | * @return True iff the value matches this criterion, false otherwise |
---|
636 | */ |
---|
637 | protected boolean doubleCompare( Double fieldValue ) { |
---|
638 | try { |
---|
639 | Double doubleCriterion = Double.parseDouble( value ); |
---|
640 | return compareValues( fieldValue, this.operator, doubleCriterion ); |
---|
641 | } catch( Exception e ) { |
---|
642 | log.debug "Can't convert value to double for comparison: " + e.class.getName() + ": " + e.getMessage(); |
---|
643 | return false; |
---|
644 | } |
---|
645 | } |
---|
646 | |
---|
647 | |
---|
648 | /** |
---|
649 | * Tries to match a boolean value against a criterion and returns true if it matches |
---|
650 | * |
---|
651 | * @param value Boolean value of the field to match |
---|
652 | * @return True iff the value matches this criterion, false otherwise |
---|
653 | */ |
---|
654 | protected boolean booleanCompare( Boolean fieldValue ) { |
---|
655 | try { |
---|
656 | // The comparison should only be performed iff the value |
---|
657 | // contains 'true' or 'false' (case insensitive) |
---|
658 | def lowerCaseValue = value.toString().toLowerCase(); |
---|
659 | if( lowerCaseValue != 'true' && lowerCaseValue != 'false' ) |
---|
660 | return false; |
---|
661 | |
---|
662 | Boolean booleanCriterion = Boolean.parseBoolean( value ); |
---|
663 | return compareValues( fieldValue, this.operator, booleanCriterion ); |
---|
664 | } catch( Exception e ) { |
---|
665 | log.debug "Can't convert value to boolean for comparison: " + e.class.getName() + ": " + e.getMessage(); |
---|
666 | return false; |
---|
667 | } |
---|
668 | } |
---|
669 | |
---|
670 | /** |
---|
671 | * Tries to match a relTime value against a criterion and returns true if it matches |
---|
672 | * |
---|
673 | * @param value relTime value of the field to match |
---|
674 | * @return True iff the value matches this criterion, false otherwise |
---|
675 | */ |
---|
676 | protected boolean relTimeCompare( RelTime fieldValue ) { |
---|
677 | try { |
---|
678 | RelTime rt |
---|
679 | |
---|
680 | // Numbers are taken to be seconds, if a non-numeric value is given, try to parse it |
---|
681 | if( value.toString().isLong() ) { |
---|
682 | rt = new RelTime( Long.parseLong( value.toString() ) ); |
---|
683 | } else { |
---|
684 | rt = new RelTime( value.toString() ); |
---|
685 | } |
---|
686 | |
---|
687 | return compareValues( fieldValue, this.operator, rt ); |
---|
688 | } catch( Exception e ) { |
---|
689 | log.debug "Can't convert value to reltime for comparison: " + e.class.getName() + ": " + e.getMessage(); |
---|
690 | return false; |
---|
691 | } |
---|
692 | } |
---|
693 | |
---|
694 | public static Operator parseOperator( String name ) throws Exception { |
---|
695 | switch( name.trim() ) { |
---|
696 | case "=": |
---|
697 | case "equals": return Operator.equals; |
---|
698 | case "contains": return Operator.contains; |
---|
699 | case ">=": |
---|
700 | case "gte": return Operator.gte; |
---|
701 | case ">": |
---|
702 | case "gt": return Operator.gt; |
---|
703 | case "<=": |
---|
704 | case "lte": return Operator.lte; |
---|
705 | case "<": |
---|
706 | case "lt": return Operator.lt; |
---|
707 | case "in": return Operator.insearch; |
---|
708 | default: |
---|
709 | throw new Exception( "Operator not found" ); |
---|
710 | } |
---|
711 | } |
---|
712 | |
---|
713 | public String toString() { |
---|
714 | return "[Criterion " + entityField() + " " + operator + " " + value + "]"; |
---|
715 | } |
---|
716 | |
---|
717 | public boolean equals( Object o ) { |
---|
718 | if( o == null ) |
---|
719 | return false; |
---|
720 | |
---|
721 | if( !( o instanceof Criterion ) ) |
---|
722 | return false; |
---|
723 | |
---|
724 | Criterion otherCriterion = (Criterion) o; |
---|
725 | return this.entity == otherCriterion.entity && |
---|
726 | this.field == otherCriterion.field && |
---|
727 | this.operator == otherCriterion.operator && |
---|
728 | this.value == otherCriterion.value; |
---|
729 | } |
---|
730 | } |
---|