source: trunk/src/groovy/dbnp/query/Search.groovy @ 1501

Last change on this file since 1501 was 1501, checked in by robert@…, 10 years ago
  • Number of seconds for the rest controller to keep data in cache is now a configuration option
  • After searching, it is possible to choose which action to perform on the search results.
  • Property svn:keywords set to Rev Author Date
File size: 12.3 KB
Line 
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: 1501 $
13 * $Author: robert@isdat.nl $
14 * $Date: 2011-02-07 15:07:54 +0000 (ma, 07 feb 2011) $
15 */
16package dbnp.query
17
18import nl.grails.plugins.gdt.*
19import java.util.List;
20import java.text.DateFormat;
21import java.text.SimpleDateFormat
22import java.util.List;
23
24import org.springframework.context.ApplicationContext
25import org.springframework.web.context.request.RequestContextHolder;
26import org.codehaus.groovy.grails.commons.ApplicationHolder;
27
28import dbnp.authentication.*
29
30import org.dbnp.gdt.*
31
32/**
33* Available boolean operators for searches
34* @author robert
35*
36*/
37enum SearchMode {
38   and, or
39}
40
41class Search {
42        public SecUser user;
43        public Date executionDate;
44        public int id;  // Is only used when this query is saved in session
45
46        public String entity;
47        public SearchMode searchMode = SearchMode.and
48       
49        protected List criteria;
50        protected List results;
51        protected Map resultFields = [:];
52
53        public List getCriteria() { return criteria; }
54        public void setCriteria( List c ) { criteria = c; }
55        public void addCriterion( Criterion c ) {
56                if( criteria ) 
57                        criteria << c;
58                else
59                        criteria = [c];
60        }
61
62        public List getResults() { return results; }
63        public void setResults( List r ) { results = r; }
64        public List filterResults( List selectedIds ) {
65                if( !selectedIds || !results )
66                        return results
67                       
68                return results.findAll {
69                        selectedIds.contains( it.id )
70                }
71        }
72       
73       
74        public Map getResultFields() { return resultFields; }
75        public void setResultFields( Map r ) { resultFields = r; }
76
77        public Search() {
78                def ctx = ApplicationHolder.getApplication().getMainContext();
79                def authenticationService = ctx.getBean("authenticationService");
80                def sessionUser = authenticationService?.getLoggedInUser();
81
82                if( sessionUser )
83                        this.user = sessionUser;
84                else
85                        this.user = null
86        }
87       
88        /**
89         * Returns the number of results found by this search
90         * @return
91         */
92        public int getNumResults() {
93                if( results )
94                        return results.size();
95
96                return 0;
97        }
98
99        /**
100         * Executes a search based on the given criteria. Should be filled in by
101         * subclasses searching for a specific entity
102         *
103         * @param       c       List with criteria to search on
104         */
105        public void execute( List c ) {
106                setCriteria( c );
107                execute();
108        }
109
110        /**
111         * Executes a search based on the given criteria.
112         */
113        public void execute() {
114                this.executionDate = new Date();
115               
116                switch( searchMode ) {
117                        case SearchMode.and:
118                                executeAnd();
119                                break;
120                        case SearchMode.or:
121                                executeOr();
122                                break;
123                }
124        }
125       
126        /**
127         * Executes an inclusive (AND) search based on the given criteria. Should be filled in by
128         * subclasses searching for a specific entity
129         */
130        public void executeAnd() {
131               
132        }
133       
134        /**
135        * Executes an exclusive (OR) search based on the given criteria. Should be filled in by
136        * subclasses searching for a specific entity
137        */
138   public void executeOr() {
139           
140   }
141
142        /**
143         * Returns a list of criteria targeted on the given entity
144         * @param entity        Entity to search criteria for
145         * @return                      List of criteria
146         */
147        protected List getEntityCriteria( String entity ) {
148                return criteria?.findAll { it.entity == entity }
149        }
150       
151        /**
152         * Filters a list with entities, based on the given criteria and a closure to check whether a criterion is matched
153         *
154         * @param entities      Original list with entities to check for these criteria
155         * @param criteria      List with criteria to match on
156         * @param check         Closure to see whether a specific entity matches a criterion. Gets two arguments:
157         *                                              element         The element to check
158         *                                              criterion       The criterion to check on.
159         *                                      Returns true if the criterion holds, false otherwise
160         * @return                      The filtered list of entities
161         */
162        protected List filterEntityList( List entities, List<Criterion> criteria, Closure check ) {
163                if( !entities || !criteria || criteria.size() == 0 ) {
164                        if( searchMode == SearchMode.and )
165                                return entities;
166                        else if( searchMode == SearchMode.or )
167                                return []
168                }
169               
170                return entities.findAll { entity ->
171                        if( searchMode == SearchMode.and ) {
172                                for( criterion in criteria ) {
173                                        if( !check( entity, criterion ) ) {
174                                                return false;
175                                        }
176                                }
177                                return true;
178                        } else if( searchMode == SearchMode.or ) {
179                                for( criterion in criteria ) {
180                                        if( check( entity, criterion ) ) {
181                                                return true;
182                                        }
183                                }
184                                return false;
185                        }
186                }
187        }
188       
189        /**
190         * Prepares a value from a template entity for comparison, by giving it a correct type
191         *
192         * @param value         Value of the field
193         * @param type          TemplateFieldType       Type of the specific field
194         * @return                      The value of the field in the correct entity
195         */
196        public static def prepare( def value, TemplateFieldType type ) {
197                if( value == null )
198                        return value
199                       
200                switch (type) {
201                        case TemplateFieldType.DATE:
202                                try {
203                                        return new SimpleDateFormat( "yyyy-MM-dd" ).parse( value )
204                                } catch( Exception e ) {
205                                        return value.toString();
206                                }
207                        case TemplateFieldType.RELTIME:
208                                try {
209                                        if( value instanceof Number ) {
210                                                return new RelTime( value );
211                                        } else if( value.toString().isNumber() ) {
212                                                return new RelTime( Long.parseLong( value.toString() ) ) 
213                                        } else {
214                                                return new RelTime( value );
215                                        }
216                                } catch( Exception e ) {
217                                        try {
218                                                return Long.parseLong( value )
219                                        } catch( Exception e2 ) {
220                                                return value.toString();
221                                        }
222                                }
223                        case TemplateFieldType.DOUBLE:
224                                try {
225                                        return Double.valueOf( value )
226                                } catch( Exception e ) {
227                                        return value.toString();
228                                }
229                        case TemplateFieldType.BOOLEAN:
230                                try {
231                                        return Boolean.valueOf( value )
232                                } catch( Exception e ) {
233                                        return value.toString();
234                                }
235                        case TemplateFieldType.LONG:
236                                try {
237                                        return Long.valueOf( value )
238                                } catch( Exception e ) {
239                                        return value.toString();
240                                }
241                        case TemplateFieldType.STRING:
242                        case TemplateFieldType.TEXT:
243                        case TemplateFieldType.STRINGLIST:
244                        case TemplateFieldType.TEMPLATE:
245                        case TemplateFieldType.MODULE:
246                        case TemplateFieldType.FILE:
247                        case TemplateFieldType.ONTOLOGYTERM:
248                        default:
249                                return value.toString();
250                }
251
252        }       
253       
254        /**
255         * Filters the given list of studies on the study criteria
256         * @param studies               Original list of studies
257         * @param entity                Name of the entity to check the criteria for
258         * @param valueCallback Callback having a study and criterion as input, returning the value of the field to check on
259         * @return                              List with all studies that match the Criteria
260         */
261        protected List filterOnTemplateEntityCriteria( List studies, String entityName, Closure valueCallback ) {
262                def criteria = getEntityCriteria( entityName );
263               
264                def checkCallback = { study, criterion ->
265                        def value = valueCallback( study, criterion );
266                       
267                        if( value == null ) {
268                                return false
269                        }
270
271                        if( value instanceof Collection ) {
272                                return criterion.matchAny( value )
273                        } else {
274                                return criterion.match( value );
275                        }
276                }
277
278                // Save the value of this entity for later use
279                saveResultFields( studies, criteria, valueCallback );
280               
281                return filterEntityList( studies, criteria, checkCallback);
282        }
283
284        /**
285        * Filters the given list of entities on the module criteria
286        * @param entities       Original list of entities. Entities should expose a giveUUID() method to give the token.
287        * @return                       List with all entities that match the module criteria
288        */
289        protected List filterOnModuleCriteria( List entities ) {
290                // An empty list can't be filtered more than is has been now
291                if( !entities || entities.size() == 0 )
292                        return [];
293                       
294                // Determine the moduleCommunicationService. Because this object
295                // is mocked in the tests, it can't be converted to a ApplicationContext object
296                def ctx = ApplicationHolder.getApplication().getMainContext();
297                def moduleCommunicationService = ctx.getBean("moduleCommunicationService");
298               
299                def allEntities = [] 
300                if( searchMode == SearchMode.or ) {
301                        allEntities += entities;
302                        entities = [];
303                } 
304               
305                // Loop through all modules and check whether criteria have been given
306                // for that module
307                AssayModule.list().each { module ->
308                        // Remove 'module' from module name
309                        def moduleName = module.name.replace( 'module', '' ).trim()
310                        def moduleCriteria = getEntityCriteria( moduleName );
311                       
312                        if( moduleCriteria && moduleCriteria.size() > 0 ) {
313                                // Retrieve the data from the module
314                                def tokens = entities.collect { it.giveUUID() }.unique();
315                                def fields = moduleCriteria.collect { it.field }.unique();
316                               
317                                def callUrl = module.url + '/rest/getQueryableFieldData?entity=' + this.entity
318                                tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() }
319                                fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() }
320                               
321                                try {
322                                        def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
323
324                                        Closure checkClosure = { entity, criterion ->
325                                                // Find the value of the field in this sample. That value is still in the
326                                                // JSON object
327                                                def token = entity.giveUUID()
328                                                if( !json[ token ] || json[ token ][ criterion.field ] == null )
329                                                        return false;
330
331                                                // Check whether a list or string is given
332                                                def value = json[ token ][ criterion.field ];
333                                               
334                                                // Save the value of this entity for later use
335                                                saveResultField( entity.id, criterion.entity + " " + criterion.field, value )
336
337                                                if( !( value instanceof Collection ) ) {
338                                                        value = [ value ];
339                                                }
340                                               
341                                                // Convert numbers to a long or double in order to process them correctly
342                                                def values = value.collect { val -> 
343                                                        val = val.toString();
344                                                        if( val.isLong() ) {
345                                                                val = Long.parseLong( val );
346                                                        } else if( val.isDouble() ) {
347                                                                val = Double.parseDouble( val );
348                                                        }
349                                                        return val;
350                                                }
351
352                                                // Loop through all values and match any
353                                                for( val in values ) {
354                                                        if( criterion.match( val ) )
355                                                                return true;
356                                                }
357                                               
358                                                return false;
359                                        }
360                                       
361                                        // The data has been retrieved. Now walk through all criteria to filter the samples
362                                        if( searchMode == SearchMode.and ) {
363                                                entities = filterEntityList( entities, moduleCriteria, checkClosure );
364                                        } else if( searchMode == SearchMode.or ) {
365                                                entities += filterEntityList( allEntities - entities, moduleCriteria, checkClosure );
366                                                entities = entities.unique();
367                                        } 
368                                                                               
369                                } catch( Exception e ) {
370                                        log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
371                                }
372                        }
373                }
374               
375                return entities;
376        }
377       
378        /**
379         * Saves data about template entities to use later on. This data is copied to a special
380         * structure to make it compatible with data fetched from other modules. Ses also saveResultField() method
381         * @param entities                      List of template entities to find data in
382         * @param criteria                      Criteria to search for
383         * @param valueCallback         Callback to retrieve a specific field from the entity
384         */
385        protected void saveResultFields( entities, criteria, valueCallback ) {
386                for( criterion in criteria ) {
387                        for( entity in entities ) {
388                                if( criterion.field )
389                                        saveResultField( entity.id, criterion.entity + ' ' + criterion.field, valueCallback( entity, criterion ) )
390                        }
391                }
392        }
393
394       
395        /**
396         * Saves a specific field of an object to use later on. Especially useful when looking up data from other modules.
397         * @param id            ID of the object
398         * @param fieldName     Field name that has been searched
399         * @param value         Value of the field
400         */
401        protected void saveResultField( id, fieldName, value ) {
402                if( resultFields[ id ] == null )
403                        resultFields[ id ] = [:]
404               
405                // Handle special cases
406                if( value == null )
407                        value = "";
408               
409                if( value instanceof Collection ) {
410                        value = value.findAll { it != null }
411                }
412               
413                resultFields[ id ][ fieldName ] = value;
414        }
415       
416        /**
417         * Removes all data from the result field map
418         */
419        protected void clearResultFields() {
420                resultFields = [:]
421        }
422       
423        /**
424         * 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.
425         *
426         * 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
427         * query.)
428         * @return      Map with the entity id as a key, and a field-value map as value
429         */
430        public Map getShowableResultFields() {
431                def resultIds = getResults()*.id;
432                return getResultFields().findAll {
433                        resultIds.contains( it.key )
434                }
435        }
436       
437        public String toString() {
438                return ( this.entity ? this.entity + " search" : "Search" ) + " " + this.id
439        }
440}
Note: See TracBrowser for help on using the repository browser.