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

Last change on this file since 1487 was 1487, checked in by robert@…, 9 years ago
  • Fixed StudySearch? unit tests (#303)
  • Changed css error message to class="errormessage" in order to avoid breaking the study wizard (see r1485)
  • Property svn:keywords set to Rev Author Date
File size: 10.6 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: 1487 $
13 * $Author: robert@isdat.nl $
14 * $Date: 2011-02-03 09:51:16 +0000 (do, 03 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
32class Search {
33        public String entity;
34        public SecUser user;
35        public Date executionDate;
36        public int id;  // Is only used when this query is saved in session
37
38        protected List criteria;
39        protected List results;
40        protected Map resultFields = [:];
41
42        public List getCriteria() { return criteria; }
43        public void setCriteria( List c ) { criteria = c; }
44
45        public List getResults() { return results; }
46        public void setResults( List r ) { results = r; }
47       
48        public Map getResultFields() { return resultFields; }
49        public void setResultFields( Map r ) { resultFields = r; }
50
51        public Search() {
52                def ctx = ApplicationHolder.getApplication().getMainContext();
53                def authenticationService = ctx.getBean("authenticationService");
54                def sessionUser = authenticationService?.getLoggedInUser();
55
56                if( sessionUser )
57                        this.user = sessionUser;
58                else
59                        this.user = null
60        }
61       
62        /**
63         * Returns the number of results found by this search
64         * @return
65         */
66        public int getNumResults() {
67                if( results )
68                        return results.size();
69
70                return 0;
71        }
72
73        /**
74         * Executes a search based on the given criteria. Should be filled in by
75         * subclasses searching for a specific entity
76         *
77         * @param       c       List with criteria to search on
78         */
79        public void execute( List c ) {
80                setCriteria( c );
81                execute();
82        }
83
84        /**
85         * Executes a search based on the given criteria. Should be filled in by
86         * subclasses searching for a specific entity
87         */
88        public void execute() {
89                this.executionDate = new Date();
90        }
91
92        /**
93         * Returns a list of criteria targeted on the given entity
94         * @param entity        Entity to search criteria for
95         * @return                      List of criteria
96         */
97        protected List getEntityCriteria( String entity ) {
98                return criteria?.findAll { it.entity == entity }
99        }
100       
101        /**
102         * Filters a list with entities, based on the given criteria and a closure to check whether a criterion is matched
103         *
104         * @param entities      Original list with entities to check for these criteria
105         * @param criteria      List with criteria to match on
106         * @param check         Closure to see whether a specific entity matches a criterion. Gets two arguments:
107         *                                              element         The element to check
108         *                                              criterion       The criterion to check on.
109         *                                      Returns true if the criterion holds, false otherwise
110         * @return                      The filtered list of entities
111         */
112        protected List filterEntityList( List entities, List<Criterion> criteria, Closure check ) {
113                if( !entities || !criteria || criteria.size() == 0 ) {
114                        return entities;
115                }
116
117                return entities.findAll { entity ->
118                        for( criterion in criteria ) {
119                                if( !check( entity, criterion ) ) {
120                                        return false;
121                                }
122                        }
123                        return true;
124                }
125        }
126       
127        /**
128         * Prepares a value from a template entity for comparison, by giving it a correct type
129         *
130         * @param value         Value of the field
131         * @param type          TemplateFieldType       Type of the specific field
132         * @return                      The value of the field in the correct entity
133         */
134        public static def prepare( def value, TemplateFieldType type ) {
135                switch (type) {
136                        case TemplateFieldType.DATE:
137                                try {
138                                        return new SimpleDateFormat( "yyyy-MM-dd" ).parse( value )
139                                } catch( Exception e ) {
140                                        return value.toString();
141                                }
142                        case TemplateFieldType.RELTIME:
143                                try {
144                                        if( value instanceof Number ) {
145                                                return new RelTime( value );
146                                        } else if( value.toString().isNumber() ) {
147                                                return new RelTime( Long.parseLong( value.toString() ) ) 
148                                        } else {
149                                                return new RelTime( value );
150                                        }
151                                } catch( Exception e ) {
152                                        try {
153                                                return Long.parseLong( value )
154                                        } catch( Exception e2 ) {
155                                                return value.toString();
156                                        }
157                                }
158                        case TemplateFieldType.DOUBLE:
159                                try {
160                                        return Double.valueOf( value )
161                                } catch( Exception e ) {
162                                        return value.toString();
163                                }
164                        case TemplateFieldType.BOOLEAN:
165                                try {
166                                        return Boolean.valueOf( value )
167                                } catch( Exception e ) {
168                                        return value.toString();
169                                }
170                        case TemplateFieldType.LONG:
171                                try {
172                                        return Long.valueOf( value )
173                                } catch( Exception e ) {
174                                        return value.toString();
175                                }
176                        case TemplateFieldType.STRING:
177                        case TemplateFieldType.TEXT:
178                        case TemplateFieldType.STRINGLIST:
179                        case TemplateFieldType.TEMPLATE:
180                        case TemplateFieldType.MODULE:
181                        case TemplateFieldType.FILE:
182                        case TemplateFieldType.ONTOLOGYTERM:
183                        default:
184                                return value.toString();
185                }
186
187        }
188       
189       
190        /**
191         * Filters the given list of studies on the study criteria
192         * @param studies               Original list of studies
193         * @param entity                Name of the entity to check the criteria for
194         * @param valueCallback Callback having a study and criterion as input, returning the value of the field to check on
195         * @return                              List with all studies that match the Criteria
196         */
197        protected List filterOnTemplateEntityCriteria( List studies, String entityName, Closure valueCallback ) {
198                def criteria = getEntityCriteria( entityName );
199               
200                def checkCallback = { study, criterion ->
201                        def value = valueCallback( study, criterion );
202                       
203                        if( value == null )
204                                return false
205                        if( value instanceof Collection ) {
206                                return criterion.matchAny( value )
207                        } else {
208                                return criterion.match( value );
209                        }
210                }
211
212                // Save the value of this entity for later use
213                saveResultFields( studies, criteria, valueCallback );
214               
215                return filterEntityList( studies, criteria, checkCallback);
216        }
217
218        /**
219        * Filters the given list of entities on the module criteria
220        * @param entities       Original list of entities. Entities should expose a giveUUID() method to give the token.
221        * @return                       List with all entities that match the module criteria
222        */
223        protected List filterOnModuleCriteria( List entities ) {
224                // An empty list can't be filtered more than is has been now
225                if( !entities || entities.size() == 0 )
226                        return [];
227                       
228                // Determine the moduleCommunicationService. Because this object
229                // is mocked in the tests, it can't be converted to a ApplicationContext object
230                def ctx = ApplicationHolder.getApplication().getMainContext();
231                def moduleCommunicationService = ctx.getBean("moduleCommunicationService");
232                       
233                // Loop through all modules and check whether criteria have been given
234                // for that module
235                AssayModule.list().each { module ->
236                        // Remove 'module' from module name
237                        def moduleName = module.name.replace( 'module', '' ).trim()
238                        def moduleCriteria = getEntityCriteria( moduleName );
239                       
240                        if( moduleCriteria && moduleCriteria.size() > 0 ) {
241                                // Retrieve the data from the module
242                                def tokens = entities.collect { it.giveUUID() }.unique();
243                                def fields = moduleCriteria.collect { it.field }.unique();
244                               
245                                def callUrl = module.url + '/rest/getQueryableFieldData?entity=' + this.entity
246                                tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() }
247                                fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() }
248                               
249                                try {
250                                        def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
251
252                                        // The data has been retrieved. Now walk through all criteria to filter the samples
253                                        entities = filterEntityList( entities, moduleCriteria, { entity, criterion ->
254                                                // Find the value of the field in this sample. That value is still in the
255                                                // JSON object
256                                                def token = entity.giveUUID()
257                                                if( !json[ token ] || json[ token ][ criterion.field ] == null )
258                                                        return false;
259
260                                                // Check whether a list or string is given
261                                                def value = json[ token ][ criterion.field ];
262                                               
263                                                // Save the value of this entity for later use
264                                                saveResultField( entity.id, criterion.entity + " " + criterion.field, value )
265
266                                                if( !( value instanceof Collection ) ) {
267                                                        value = [ value ];
268                                                }
269                                               
270                                                // Loop through all values and match any
271                                                for( val in value ) {
272                                                        // Convert numbers to a long or double in order to process them correctly
273                                                        val = val.toString();
274                                                        if( val.isLong() ) {
275                                                                val = Long.parseLong( val );
276                                                        } else if( val.isDouble() ) {
277                                                                val = Double.parseDouble( val );
278                                                        }
279                                                       
280                                                        if( criterion.match( val ) )
281                                                                return true;
282                                                }
283                                               
284                                                return false;
285                                        });
286                                                                               
287                                } catch( Exception e ) {
288                                        log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() )
289                                }
290                        }
291                }
292               
293                return entities;
294        }
295       
296        /**
297         * Saves data about template entities to use later on. This data is copied to a special
298         * structure to make it compatible with data fetched from other modules. Ses also saveResultField() method
299         * @param entities                      List of template entities to find data in
300         * @param criteria                      Criteria to search for
301         * @param valueCallback         Callback to retrieve a specific field from the entity
302         */
303        protected void saveResultFields( entities, criteria, valueCallback ) {
304                for( criterion in criteria ) {
305                        for( entity in entities ) {
306                                if( criterion.field )
307                                        saveResultField( entity.id, criterion.entity + ' ' + criterion.field, valueCallback( entity, criterion ) )
308                        }
309                }
310        }
311
312       
313        /**
314         * Saves a specific field of an object to use later on. Especially useful when looking up data from other modules.
315         * @param id            ID of the object
316         * @param fieldName     Field name that has been searched
317         * @param value         Value of the field
318         */
319        protected void saveResultField( id, fieldName, value ) {
320                if( resultFields[ id ] == null )
321                        resultFields[ id ] = [:]
322               
323                resultFields[ id ][ fieldName ] = value;
324        }
325       
326        /**
327         * Removes all data from the result field map
328         */
329        protected void clearResultFields() {
330                resultFields = [:]
331        }
332       
333        /**
334         * 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.
335         *
336         * 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
337         * query.)
338         * @return      Map with the entity id as a key, and a field-value map as value
339         */
340        public Map getShowableResultFields() {
341                def resultIds = getResults()*.id;
342                return getResultFields().findAll {
343                        resultIds.contains( it.key )
344                }
345        }
346       
347        public String toString() {
348                return ( this.entity ? this.entity + " search" : "Search" ) + " " + this.id
349        }
350}
Note: See TracBrowser for help on using the repository browser.