1 | /** |
---|
2 | * Search Domain Class |
---|
3 | * |
---|
4 | * Abstract class containing search criteria and search results when querying. |
---|
5 | * Should be subclassed in order to enable searching for different entities. |
---|
6 | * |
---|
7 | * @author Robert Horlings (robert@isdat.nl) |
---|
8 | * @since 20110118 |
---|
9 | * @package dbnp.query |
---|
10 | * |
---|
11 | * Revision information: |
---|
12 | * $Rev: 1717 $ |
---|
13 | * $Author: robert@isdat.nl $ |
---|
14 | * $Date: 2011-04-06 15:24:14 +0000 (wo, 06 apr 2011) $ |
---|
15 | */ |
---|
16 | package dbnp.query |
---|
17 | |
---|
18 | import org.dbnp.gdt.* |
---|
19 | import java.util.List; |
---|
20 | import java.text.DateFormat; |
---|
21 | import java.text.SimpleDateFormat |
---|
22 | import java.util.List; |
---|
23 | |
---|
24 | import org.springframework.context.ApplicationContext |
---|
25 | import org.springframework.web.context.request.RequestContextHolder; |
---|
26 | import org.codehaus.groovy.grails.commons.ApplicationHolder; |
---|
27 | |
---|
28 | import dbnp.authentication.* |
---|
29 | |
---|
30 | /** |
---|
31 | * Available boolean operators for searches |
---|
32 | * @author robert |
---|
33 | * |
---|
34 | */ |
---|
35 | enum SearchMode { |
---|
36 | and, or |
---|
37 | } |
---|
38 | |
---|
39 | class Search { |
---|
40 | /** |
---|
41 | * User that is performing this search. This has impact on the search results returned. |
---|
42 | */ |
---|
43 | public SecUser user; |
---|
44 | |
---|
45 | /** |
---|
46 | * Date of execution of this search |
---|
47 | */ |
---|
48 | public Date executionDate; |
---|
49 | |
---|
50 | /** |
---|
51 | * Public identifier of this search. Is only used when this query is saved in session |
---|
52 | */ |
---|
53 | public int id; |
---|
54 | |
---|
55 | /** |
---|
56 | * Human readable entity name of the entities that can be found using this search |
---|
57 | */ |
---|
58 | public String entity; |
---|
59 | |
---|
60 | /** |
---|
61 | * Mode to search: OR or AND. |
---|
62 | * @see SearchMode |
---|
63 | */ |
---|
64 | public SearchMode searchMode = SearchMode.and |
---|
65 | |
---|
66 | protected List criteria; |
---|
67 | protected List results; |
---|
68 | protected Map resultFields = [:]; |
---|
69 | |
---|
70 | /** |
---|
71 | * Constructor of this search object. Sets the user field to the |
---|
72 | * currently logged in user |
---|
73 | * @see #user |
---|
74 | */ |
---|
75 | public Search() { |
---|
76 | def ctx = ApplicationHolder.getApplication().getMainContext(); |
---|
77 | def authenticationService = ctx.getBean("authenticationService"); |
---|
78 | def sessionUser = authenticationService?.getLoggedInUser(); |
---|
79 | |
---|
80 | if( sessionUser ) |
---|
81 | this.user = sessionUser; |
---|
82 | else |
---|
83 | this.user = null |
---|
84 | } |
---|
85 | |
---|
86 | /** |
---|
87 | * Returns the number of results found by this search |
---|
88 | * @return |
---|
89 | */ |
---|
90 | public int getNumResults() { |
---|
91 | if( results ) |
---|
92 | return results.size(); |
---|
93 | |
---|
94 | return 0; |
---|
95 | } |
---|
96 | |
---|
97 | /** |
---|
98 | * Executes a search based on the given criteria. Should be filled in by |
---|
99 | * subclasses searching for a specific entity |
---|
100 | * |
---|
101 | * @param c List with criteria to search on |
---|
102 | */ |
---|
103 | public void execute( List c ) { |
---|
104 | setCriteria( c ); |
---|
105 | execute(); |
---|
106 | } |
---|
107 | |
---|
108 | /** |
---|
109 | * Executes a search based on the given criteria. |
---|
110 | */ |
---|
111 | public void execute() { |
---|
112 | this.executionDate = new Date(); |
---|
113 | |
---|
114 | switch( searchMode ) { |
---|
115 | case SearchMode.and: |
---|
116 | executeAnd(); |
---|
117 | break; |
---|
118 | case SearchMode.or: |
---|
119 | executeOr(); |
---|
120 | break; |
---|
121 | } |
---|
122 | |
---|
123 | // Save the value of this results for later use |
---|
124 | saveResultFields(); |
---|
125 | } |
---|
126 | |
---|
127 | /** |
---|
128 | * Executes an inclusive (AND) search based on the given criteria. Should be filled in by |
---|
129 | * subclasses searching for a specific entity |
---|
130 | */ |
---|
131 | protected void executeAnd() { |
---|
132 | |
---|
133 | } |
---|
134 | |
---|
135 | /** |
---|
136 | * Executes an exclusive (OR) search based on the given criteria. Should be filled in by |
---|
137 | * subclasses searching for a specific entity |
---|
138 | */ |
---|
139 | protected void executeOr() { |
---|
140 | |
---|
141 | } |
---|
142 | |
---|
143 | /** |
---|
144 | * Default implementation of an inclusive (AND) search. Can be called by subclasses in order |
---|
145 | * to simplify searches. |
---|
146 | * |
---|
147 | * Filters the list of objects on study, subject, sample, event, samplingevent and assaycriteria, |
---|
148 | * based on the closures defined in valueCallback. Afterwards, the objects are filtered on module |
---|
149 | * criteria |
---|
150 | * |
---|
151 | * @param objects List of objects to search in |
---|
152 | */ |
---|
153 | protected void executeAnd( List objects ) { |
---|
154 | // If no criteria are found, return all studies |
---|
155 | if( !criteria || criteria.size() == 0 ) { |
---|
156 | results = objects; |
---|
157 | return; |
---|
158 | } |
---|
159 | |
---|
160 | // Perform filters |
---|
161 | objects = filterOnStudyCriteria( objects ); |
---|
162 | objects = filterOnSubjectCriteria( objects ); |
---|
163 | objects = filterOnSampleCriteria( objects ); |
---|
164 | objects = filterOnEventCriteria( objects ); |
---|
165 | objects = filterOnSamplingEventCriteria( objects ); |
---|
166 | objects = filterOnAssayCriteria( objects ); |
---|
167 | |
---|
168 | objects = filterOnModuleCriteria( objects ); |
---|
169 | |
---|
170 | // Save matches |
---|
171 | results = objects; |
---|
172 | } |
---|
173 | |
---|
174 | /** |
---|
175 | * Default implementation of an exclusive (OR) search. Can be called by subclasses in order |
---|
176 | * to simplify searches. |
---|
177 | * |
---|
178 | * Filters the list of objects on study, subject, sample, event, samplingevent and assaycriteria, |
---|
179 | * based on the closures defined in valueCallback. Afterwards, the objects are filtered on module |
---|
180 | * criteria |
---|
181 | * |
---|
182 | * @param allObjects List of objects to search in |
---|
183 | */ |
---|
184 | protected void executeOr( List allObjects ) { |
---|
185 | // If no criteria are found, return all studies |
---|
186 | if( !criteria || criteria.size() == 0 ) { |
---|
187 | results = allObjects; |
---|
188 | return; |
---|
189 | } |
---|
190 | |
---|
191 | // Perform filters on those objects not yet found by other criteria |
---|
192 | def objects = [] |
---|
193 | objects = ( objects + filterOnStudyCriteria( allObjects - objects ) ).unique(); |
---|
194 | objects = ( objects + filterOnSubjectCriteria( allObjects - objects ) ).unique(); |
---|
195 | objects = ( objects + filterOnSampleCriteria( allObjects - objects ) ).unique(); |
---|
196 | objects = ( objects + filterOnEventCriteria( allObjects - objects ) ).unique(); |
---|
197 | objects = ( objects + filterOnSamplingEventCriteria( allObjects - objects ) ).unique(); |
---|
198 | objects = ( objects + filterOnAssayCriteria( allObjects - objects ) ).unique(); |
---|
199 | |
---|
200 | // All objects (including the ones already found by another criterion) are sent to |
---|
201 | // be filtered on module criteria, in order to have the module give data about all |
---|
202 | // objects (for showing purposes later on) |
---|
203 | objects = ( objects + filterOnModuleCriteria( allObjects ) ).unique(); |
---|
204 | |
---|
205 | // Save matches |
---|
206 | results = objects; |
---|
207 | } |
---|
208 | |
---|
209 | |
---|
210 | /************************************************************************ |
---|
211 | * |
---|
212 | * These methods are used in querying and can be overridden by subclasses |
---|
213 | * in order to provide custom searching |
---|
214 | * |
---|
215 | ************************************************************************/ |
---|
216 | |
---|
217 | /** |
---|
218 | * Returns a closure for the given entitytype that determines the value for a criterion |
---|
219 | * on the given object. The closure receives two parameters: the object and a criterion. |
---|
220 | * |
---|
221 | * For example: when searching for studies, the object given to the closure is a Study. |
---|
222 | * Also, when searching for samples, the object given is a Sample. When you have the criterion |
---|
223 | * |
---|
224 | * sample.name equals 'sample 1' |
---|
225 | * |
---|
226 | * and searching for samples, it is easy to determine the value of the object for this criterion: |
---|
227 | * |
---|
228 | * object.getFieldValue( "name" ) |
---|
229 | * |
---|
230 | * |
---|
231 | * However, when searching for samples with the criterion |
---|
232 | * |
---|
233 | * study.title contains 'nbic' |
---|
234 | * |
---|
235 | * this determination is more complex: |
---|
236 | * |
---|
237 | * object.parent.getFieldValue( "title" ) |
---|
238 | * |
---|
239 | * |
---|
240 | * The other way around, when searching for studies with |
---|
241 | * |
---|
242 | * sample.name equals 'sample 1' |
---|
243 | * |
---|
244 | * the value of the 'sample.name' property is a list: |
---|
245 | * |
---|
246 | * object.samples*.getFieldValue( "name" ) |
---|
247 | * |
---|
248 | * The other search methods will handle the list and see whether any of the values |
---|
249 | * matches the criterion. |
---|
250 | * |
---|
251 | * NB. The Criterion object has a convenience method to retrieve the field value on a |
---|
252 | * specific (TemplateEntity) object: getFieldValue. This method also handles |
---|
253 | * non-existing fields and casts the value to the correct type. |
---|
254 | * |
---|
255 | * This method should be overridden by all searches |
---|
256 | * |
---|
257 | * @see Criterion.getFieldValue() |
---|
258 | * |
---|
259 | * @return Closure having 2 parameters: object and criterion |
---|
260 | */ |
---|
261 | protected Closure valueCallback( String entity ) { |
---|
262 | switch( entity ) { |
---|
263 | case "Study": |
---|
264 | case "Subject": |
---|
265 | case "Sample": |
---|
266 | case "Event": |
---|
267 | case "SamplingEvent": |
---|
268 | case "Assay": |
---|
269 | return { object, criterion -> return criterion.getFieldValue( object ); } |
---|
270 | default: |
---|
271 | return null; |
---|
272 | } |
---|
273 | } |
---|
274 | |
---|
275 | /***************************************************** |
---|
276 | * |
---|
277 | * The other methods are helper functions for the execution of queries in subclasses |
---|
278 | * |
---|
279 | *****************************************************/ |
---|
280 | |
---|
281 | /** |
---|
282 | * Returns a list of criteria targeted on the given entity |
---|
283 | * @param entity Entity to search criteria for |
---|
284 | * @return List of criteria |
---|
285 | */ |
---|
286 | protected List getEntityCriteria( String entity ) { |
---|
287 | return criteria?.findAll { it.entity == entity } |
---|
288 | } |
---|
289 | |
---|
290 | |
---|
291 | /** |
---|
292 | * Prepares a value from a template entity for comparison, by giving it a correct type |
---|
293 | * |
---|
294 | * @param value Value of the field |
---|
295 | * @param type TemplateFieldType Type of the specific field |
---|
296 | * @return The value of the field in the correct entity |
---|
297 | */ |
---|
298 | public static def prepare( def value, TemplateFieldType type ) { |
---|
299 | if( value == null ) |
---|
300 | return value |
---|
301 | |
---|
302 | switch (type) { |
---|
303 | case TemplateFieldType.DATE: |
---|
304 | try { |
---|
305 | return new SimpleDateFormat( "yyyy-MM-dd" ).parse( value.toString() ) |
---|
306 | } catch( Exception e ) { |
---|
307 | return value.toString(); |
---|
308 | } |
---|
309 | case TemplateFieldType.RELTIME: |
---|
310 | try { |
---|
311 | if( value instanceof Number ) { |
---|
312 | return new RelTime( value ); |
---|
313 | } else if( value.toString().isNumber() ) { |
---|
314 | return new RelTime( Long.parseLong( value.toString() ) ) |
---|
315 | } else { |
---|
316 | return new RelTime( value ); |
---|
317 | } |
---|
318 | } catch( Exception e ) { |
---|
319 | try { |
---|
320 | return Long.parseLong( value ) |
---|
321 | } catch( Exception e2 ) { |
---|
322 | return value.toString(); |
---|
323 | } |
---|
324 | } |
---|
325 | case TemplateFieldType.DOUBLE: |
---|
326 | try { |
---|
327 | return Double.valueOf( value ) |
---|
328 | } catch( Exception e ) { |
---|
329 | return value.toString(); |
---|
330 | } |
---|
331 | case TemplateFieldType.BOOLEAN: |
---|
332 | try { |
---|
333 | return Boolean.valueOf( value ) |
---|
334 | } catch( Exception e ) { |
---|
335 | return value.toString(); |
---|
336 | } |
---|
337 | case TemplateFieldType.LONG: |
---|
338 | try { |
---|
339 | return Long.valueOf( value ) |
---|
340 | } catch( Exception e ) { |
---|
341 | return value.toString(); |
---|
342 | } |
---|
343 | case TemplateFieldType.STRING: |
---|
344 | case TemplateFieldType.TEXT: |
---|
345 | case TemplateFieldType.STRINGLIST: |
---|
346 | case TemplateFieldType.TEMPLATE: |
---|
347 | case TemplateFieldType.MODULE: |
---|
348 | case TemplateFieldType.FILE: |
---|
349 | case TemplateFieldType.ONTOLOGYTERM: |
---|
350 | default: |
---|
351 | return value.toString(); |
---|
352 | } |
---|
353 | |
---|
354 | } |
---|
355 | |
---|
356 | /***************************************************** |
---|
357 | * |
---|
358 | * Methods for filtering lists based on specific (GSCF) criteria |
---|
359 | * |
---|
360 | *****************************************************/ |
---|
361 | |
---|
362 | |
---|
363 | /** |
---|
364 | * Filters a list with entities, based on the given criteria and a closure to check whether a criterion is matched |
---|
365 | * |
---|
366 | * @param entities Original list with entities to check for these criteria |
---|
367 | * @param criteria List with criteria to match on |
---|
368 | * @param check Closure to see whether a specific entity matches a criterion. Gets two arguments: |
---|
369 | * element The element to check |
---|
370 | * criterion The criterion to check on. |
---|
371 | * Returns true if the criterion holds, false otherwise |
---|
372 | * @return The filtered list of entities |
---|
373 | */ |
---|
374 | protected List filterEntityList( List entities, List<Criterion> criteria, Closure check ) { |
---|
375 | if( !entities || !criteria || criteria.size() == 0 ) { |
---|
376 | if( searchMode == SearchMode.and ) |
---|
377 | return entities; |
---|
378 | else if( searchMode == SearchMode.or ) |
---|
379 | return [] |
---|
380 | } |
---|
381 | |
---|
382 | return entities.findAll { entity -> |
---|
383 | if( searchMode == SearchMode.and ) { |
---|
384 | for( criterion in criteria ) { |
---|
385 | if( !check( entity, criterion ) ) { |
---|
386 | return false; |
---|
387 | } |
---|
388 | } |
---|
389 | return true; |
---|
390 | } else if( searchMode == SearchMode.or ) { |
---|
391 | for( criterion in criteria ) { |
---|
392 | if( check( entity, criterion ) ) { |
---|
393 | return true; |
---|
394 | } |
---|
395 | } |
---|
396 | return false; |
---|
397 | } |
---|
398 | } |
---|
399 | } |
---|
400 | |
---|
401 | /** |
---|
402 | * Filters the given list of studies on the study criteria |
---|
403 | * @param studies Original list of studies |
---|
404 | * @param entity Name of the entity to check the criteria for |
---|
405 | * @param valueCallback Callback having a study and criterion as input, returning the value of the field to check on |
---|
406 | * @return List with all studies that match the Criteria |
---|
407 | */ |
---|
408 | protected List filterOnTemplateEntityCriteria( List studies, String entityName, Closure valueCallback ) { |
---|
409 | def criteria = getEntityCriteria( entityName ); |
---|
410 | |
---|
411 | def checkCallback = { study, criterion -> |
---|
412 | def value = valueCallback( study, criterion ); |
---|
413 | |
---|
414 | if( value == null ) { |
---|
415 | return false |
---|
416 | } |
---|
417 | |
---|
418 | if( value instanceof Collection ) { |
---|
419 | return criterion.matchAny( value ) |
---|
420 | } else { |
---|
421 | return criterion.match( value ); |
---|
422 | } |
---|
423 | } |
---|
424 | |
---|
425 | return filterEntityList( studies, criteria, checkCallback); |
---|
426 | } |
---|
427 | |
---|
428 | /** |
---|
429 | * Filters the given list of studies on the study criteria |
---|
430 | * @param studies Original list of studies |
---|
431 | * @return List with all studies that match the Study criteria |
---|
432 | */ |
---|
433 | protected List filterOnStudyCriteria( List studies ) { |
---|
434 | def entity = "Study" |
---|
435 | return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) |
---|
436 | } |
---|
437 | |
---|
438 | /** |
---|
439 | * Filters the given list of studies on the subject criteria |
---|
440 | * @param studies Original list of studies |
---|
441 | * @return List with all studies that match the Subject-criteria |
---|
442 | */ |
---|
443 | protected List filterOnSubjectCriteria( List studies ) { |
---|
444 | def entity = "Subject" |
---|
445 | return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) |
---|
446 | } |
---|
447 | |
---|
448 | /** |
---|
449 | * Filters the given list of studies on the sample criteria |
---|
450 | * @param studies Original list of studies |
---|
451 | * @return List with all studies that match the sample-criteria |
---|
452 | */ |
---|
453 | protected List filterOnSampleCriteria( List studies ) { |
---|
454 | def entity = "Sample" |
---|
455 | return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) |
---|
456 | } |
---|
457 | |
---|
458 | /** |
---|
459 | * Filters the given list of studies on the event criteria |
---|
460 | * @param studies Original list of studies |
---|
461 | * @return List with all studies that match the event-criteria |
---|
462 | */ |
---|
463 | protected List filterOnEventCriteria( List studies ) { |
---|
464 | def entity = "Event" |
---|
465 | return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) |
---|
466 | } |
---|
467 | |
---|
468 | /** |
---|
469 | * Filters the given list of studies on the sampling event criteria |
---|
470 | * @param studies Original list of studies |
---|
471 | * @return List with all studies that match the event-criteria |
---|
472 | */ |
---|
473 | protected List filterOnSamplingEventCriteria( List studies ) { |
---|
474 | def entity = "SamplingEvent" |
---|
475 | return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) |
---|
476 | } |
---|
477 | |
---|
478 | /** |
---|
479 | * Filters the given list of studies on the assay criteria |
---|
480 | * @param studies Original list of studies |
---|
481 | * @return List with all studies that match the assay-criteria |
---|
482 | */ |
---|
483 | protected List filterOnAssayCriteria( List studies ) { |
---|
484 | def entity = "Assay" |
---|
485 | return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) |
---|
486 | } |
---|
487 | |
---|
488 | /******************************************************************** |
---|
489 | * |
---|
490 | * Methods for filtering object lists on module criteria |
---|
491 | * |
---|
492 | ********************************************************************/ |
---|
493 | |
---|
494 | /** |
---|
495 | * Filters the given list of entities on the module criteria |
---|
496 | * @param entities Original list of entities. Entities should expose a giveUUID() method to give the token. |
---|
497 | * @return List with all entities that match the module criteria |
---|
498 | */ |
---|
499 | protected List filterOnModuleCriteria( List entities ) { |
---|
500 | // An empty list can't be filtered more than is has been now |
---|
501 | if( !entities || entities.size() == 0 ) |
---|
502 | return []; |
---|
503 | |
---|
504 | // Determine the moduleCommunicationService. Because this object |
---|
505 | // is mocked in the tests, it can't be converted to a ApplicationContext object |
---|
506 | def ctx = ApplicationHolder.getApplication().getMainContext(); |
---|
507 | def moduleCommunicationService = ctx.getBean("moduleCommunicationService"); |
---|
508 | |
---|
509 | switch( searchMode ) { |
---|
510 | case SearchMode.and: |
---|
511 | // Loop through all modules and check whether criteria have been given |
---|
512 | // for that module |
---|
513 | AssayModule.list().each { module -> |
---|
514 | // Remove 'module' from module name |
---|
515 | def moduleName = module.name.replace( 'module', '' ).trim() |
---|
516 | def moduleCriteria = getEntityCriteria( moduleName ); |
---|
517 | |
---|
518 | if( moduleCriteria && moduleCriteria.size() > 0 ) { |
---|
519 | def callUrl = moduleCriteriaUrl( module ); |
---|
520 | def callArgs = moduleCriteriaArguments( module, entities, moduleCriteria ); |
---|
521 | |
---|
522 | try { |
---|
523 | def json = moduleCommunicationService.callModuleMethod( module.url, callUrl, callArgs, "POST" ); |
---|
524 | Closure checkClosure = moduleCriterionClosure( json ); |
---|
525 | entities = filterEntityList( entities, moduleCriteria, checkClosure ); |
---|
526 | } catch( Exception e ) { |
---|
527 | //log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() ) |
---|
528 | e.printStackTrace() |
---|
529 | throw e |
---|
530 | } |
---|
531 | } |
---|
532 | } |
---|
533 | |
---|
534 | return entities; |
---|
535 | case SearchMode.or: |
---|
536 | def resultingEntities = [] |
---|
537 | |
---|
538 | // Loop through all modules and check whether criteria have been given |
---|
539 | // for that module |
---|
540 | AssayModule.list().each { module -> |
---|
541 | // Remove 'module' from module name |
---|
542 | def moduleName = module.name.replace( 'module', '' ).trim() |
---|
543 | def moduleCriteria = getEntityCriteria( moduleName ); |
---|
544 | |
---|
545 | if( moduleCriteria && moduleCriteria.size() > 0 ) { |
---|
546 | def callUrl = moduleCriteriaUrl( module ); |
---|
547 | def callArgs = moduleCriteriaArguments( module, entities, moduleCriteria ); |
---|
548 | |
---|
549 | try { |
---|
550 | def json = moduleCommunicationService.callModuleMethod( module.url, callUrl, callArgs, "POST" ); |
---|
551 | Closure checkClosure = moduleCriterionClosure( json ); |
---|
552 | |
---|
553 | resultingEntities += filterEntityList( entities, moduleCriteria, checkClosure ); |
---|
554 | resultingEntities = resultingEntities.unique(); |
---|
555 | |
---|
556 | } catch( Exception e ) { |
---|
557 | //log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() ) |
---|
558 | e.printStackTrace() |
---|
559 | throw e |
---|
560 | } |
---|
561 | } |
---|
562 | } |
---|
563 | |
---|
564 | println this.resultFields; |
---|
565 | |
---|
566 | return resultingEntities; |
---|
567 | default: |
---|
568 | return []; |
---|
569 | } |
---|
570 | } |
---|
571 | |
---|
572 | /** |
---|
573 | * Returns a closure for determining the value of a module field |
---|
574 | * @param json |
---|
575 | * @return |
---|
576 | */ |
---|
577 | protected Closure moduleCriterionClosure( def json ) { |
---|
578 | return { entity, criterion -> |
---|
579 | // Find the value of the field in this sample. That value is still in the |
---|
580 | // JSON object |
---|
581 | def token = entity.giveUUID() |
---|
582 | def value |
---|
583 | |
---|
584 | if( criterion.field == '*' ) { |
---|
585 | // Collect the values from all fields |
---|
586 | value = []; |
---|
587 | json[ token ].each { field -> |
---|
588 | if( field.value instanceof Collection ) { |
---|
589 | field.value.each { value << it } |
---|
590 | } else { |
---|
591 | value << field.value; |
---|
592 | } |
---|
593 | } |
---|
594 | } else { |
---|
595 | if( !json[ token ] || json[ token ][ criterion.field ] == null ) |
---|
596 | return false; |
---|
597 | |
---|
598 | // Check whether a list or string is given |
---|
599 | value = json[ token ][ criterion.field ]; |
---|
600 | |
---|
601 | // Save the value of this entity for later use |
---|
602 | saveResultField( entity.id, criterion.entity + " " + criterion.field, value ) |
---|
603 | |
---|
604 | if( !( value instanceof Collection ) ) { |
---|
605 | value = [ value ]; |
---|
606 | } |
---|
607 | } |
---|
608 | |
---|
609 | // Convert numbers to a long or double in order to process them correctly |
---|
610 | def values = value.collect { val -> |
---|
611 | val = val.toString(); |
---|
612 | if( val.isLong() ) { |
---|
613 | val = Long.parseLong( val ); |
---|
614 | } else if( val.isDouble() ) { |
---|
615 | val = Double.parseDouble( val ); |
---|
616 | } |
---|
617 | return val; |
---|
618 | } |
---|
619 | |
---|
620 | // Loop through all values and match any |
---|
621 | for( val in values ) { |
---|
622 | if( criterion.match( val ) ) |
---|
623 | return true; |
---|
624 | } |
---|
625 | |
---|
626 | return false; |
---|
627 | } |
---|
628 | } |
---|
629 | |
---|
630 | protected String moduleCriteriaUrl( module ) { |
---|
631 | def callUrl = module.url + '/rest/getQueryableFieldData' |
---|
632 | return callUrl; |
---|
633 | } |
---|
634 | |
---|
635 | protected String moduleCriteriaArguments( module, entities, moduleCriteria ) { |
---|
636 | // Retrieve the data from the module |
---|
637 | def tokens = entities.collect { it.giveUUID() }.unique(); |
---|
638 | def fields = moduleCriteria.collect { it.field }.unique(); |
---|
639 | |
---|
640 | def callUrl = 'entity=' + this.entity |
---|
641 | tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() } |
---|
642 | |
---|
643 | // If all fields are searched, all fields should be retrieved |
---|
644 | if( fields.contains( '*' ) ) { |
---|
645 | |
---|
646 | } else { |
---|
647 | fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() } |
---|
648 | } |
---|
649 | |
---|
650 | return callUrl; |
---|
651 | } |
---|
652 | |
---|
653 | /********************************************************************* |
---|
654 | * |
---|
655 | * These methods are used for saving information about the search results and showing the information later on. |
---|
656 | * |
---|
657 | *********************************************************************/ |
---|
658 | |
---|
659 | /** |
---|
660 | * Saves data about template entities to use later on. This data is copied to a special |
---|
661 | * structure to make it compatible with data fetched from other modules. |
---|
662 | * @see #saveResultField() |
---|
663 | */ |
---|
664 | protected void saveResultFields() { |
---|
665 | if( !results || !criteria ) |
---|
666 | return |
---|
667 | |
---|
668 | criteria.each { criterion -> |
---|
669 | if( criterion.field && criterion.field != '*' ) { |
---|
670 | def valueCallback = valueCallback( criterion.entity ); |
---|
671 | |
---|
672 | if( valueCallback != null ) { |
---|
673 | def name = criterion.entity + ' ' + criterion.field |
---|
674 | |
---|
675 | results.each { result -> |
---|
676 | saveResultField( result.id, name, valueCallback( result, criterion ) ); |
---|
677 | } |
---|
678 | } |
---|
679 | } |
---|
680 | } |
---|
681 | } |
---|
682 | |
---|
683 | /** |
---|
684 | * Saves data about template entities to use later on. This data is copied to a special |
---|
685 | * structure to make it compatible with data fetched from other modules. |
---|
686 | * @param entities List of template entities to find data in |
---|
687 | * @param criteria Criteria to search for |
---|
688 | * @param valueCallback Callback to retrieve a specific field from the entity |
---|
689 | * @see #saveResultField() |
---|
690 | */ |
---|
691 | protected void saveResultFields( entities, criteria, valueCallback ) { |
---|
692 | for( criterion in criteria ) { |
---|
693 | for( entity in entities ) { |
---|
694 | if( criterion.field && criterion.field != '*' ) |
---|
695 | saveResultField( entity.id, criterion.entity + ' ' + criterion.field, valueCallback( entity, criterion ) ) |
---|
696 | } |
---|
697 | } |
---|
698 | } |
---|
699 | |
---|
700 | |
---|
701 | /** |
---|
702 | * Saves a specific field of an object to use later on. Especially useful when looking up data from other modules. |
---|
703 | * @param id ID of the object |
---|
704 | * @param fieldName Field name that has been searched |
---|
705 | * @param value Value of the field |
---|
706 | */ |
---|
707 | protected void saveResultField( id, fieldName, value ) { |
---|
708 | if( resultFields[ id ] == null ) |
---|
709 | resultFields[ id ] = [:] |
---|
710 | |
---|
711 | // Handle special cases |
---|
712 | if( value == null ) |
---|
713 | value = ""; |
---|
714 | |
---|
715 | if( fieldName == "*" ) |
---|
716 | return; |
---|
717 | |
---|
718 | if( value instanceof Collection ) { |
---|
719 | value = value.findAll { it != null } |
---|
720 | } |
---|
721 | |
---|
722 | resultFields[ id ][ fieldName ] = value; |
---|
723 | } |
---|
724 | |
---|
725 | /** |
---|
726 | * Removes all data from the result field map |
---|
727 | */ |
---|
728 | protected void clearResultFields() { |
---|
729 | resultFields = [:] |
---|
730 | } |
---|
731 | |
---|
732 | /** |
---|
733 | * Returns the saved field data that could be shown on screen. This means, the data is filtered to show only data of the query results. |
---|
734 | * |
---|
735 | * Subclasses could filter out the fields they don't want to show on the result screen (e.g. because they are shown regardless of the |
---|
736 | * query.) |
---|
737 | * @return Map with the entity id as a key, and a field-value map as value |
---|
738 | */ |
---|
739 | public Map getShowableResultFields() { |
---|
740 | def resultIds = getResults()*.id; |
---|
741 | return getResultFields().findAll { |
---|
742 | resultIds.contains( it.key ) |
---|
743 | } |
---|
744 | } |
---|
745 | |
---|
746 | /** |
---|
747 | * Returns the field names that are found in the map with showable result fields |
---|
748 | * |
---|
749 | * @param fields Map with showable result fields |
---|
750 | * @see getShowableResultFields |
---|
751 | * @return |
---|
752 | */ |
---|
753 | public List getShowableResultFieldNames( fields ) { |
---|
754 | return fields.values()*.keySet().flatten().unique(); |
---|
755 | } |
---|
756 | |
---|
757 | |
---|
758 | /************************************************************************ |
---|
759 | * |
---|
760 | * Getters and setters |
---|
761 | * |
---|
762 | ************************************************************************/ |
---|
763 | |
---|
764 | /** |
---|
765 | * Returns a list of Criteria |
---|
766 | */ |
---|
767 | public List getCriteria() { return criteria; } |
---|
768 | |
---|
769 | /** |
---|
770 | * Sets a new list of criteria |
---|
771 | * @param c List with criteria objects |
---|
772 | */ |
---|
773 | public void setCriteria( List c ) { criteria = c; } |
---|
774 | |
---|
775 | /** |
---|
776 | * Adds a criterion to this query |
---|
777 | * @param c Criterion |
---|
778 | */ |
---|
779 | public void addCriterion( Criterion c ) { |
---|
780 | if( criteria ) |
---|
781 | criteria << c; |
---|
782 | else |
---|
783 | criteria = [c]; |
---|
784 | } |
---|
785 | |
---|
786 | /** |
---|
787 | * Retrieves the results found using this query. The result is empty is |
---|
788 | * the query has not been executed yet. |
---|
789 | */ |
---|
790 | public List getResults() { return results; } |
---|
791 | |
---|
792 | /** |
---|
793 | * Returns the results found using this query, filtered by a list of ids. |
---|
794 | * @param selectedIds List with ids of the entities you want to return. |
---|
795 | * @return A list with only those results for which the id is in the selectedIds |
---|
796 | */ |
---|
797 | public List filterResults( List selectedIds ) { |
---|
798 | if( !selectedIds || !results ) |
---|
799 | return results |
---|
800 | |
---|
801 | return results.findAll { |
---|
802 | selectedIds.contains( it.id ) |
---|
803 | } |
---|
804 | } |
---|
805 | |
---|
806 | /** |
---|
807 | * Returns a list of fields for the results of this query. The fields returned are those |
---|
808 | * fields that the query searched for. |
---|
809 | */ |
---|
810 | public Map getResultFields() { return resultFields; } |
---|
811 | |
---|
812 | public String toString() { |
---|
813 | return ( this.entity ? this.entity + " search" : "Search" ) + " " + this.id |
---|
814 | } |
---|
815 | |
---|
816 | public boolean equals( Object o ) { |
---|
817 | if( o == null ) |
---|
818 | return false |
---|
819 | |
---|
820 | if( !( o instanceof Search ) ) |
---|
821 | return false |
---|
822 | |
---|
823 | Search s = (Search) o; |
---|
824 | |
---|
825 | return ( searchMode == s.searchMode && |
---|
826 | entity == s.entity && |
---|
827 | criteria.size() == s.criteria.size() && |
---|
828 | s.criteria.containsAll( criteria ) && |
---|
829 | criteria.containsAll( s.criteria ) ); |
---|
830 | } |
---|
831 | } |
---|