source: trunk/grails-app/controllers/dbnp/query/AdvancedQueryController.groovy @ 2068

Last change on this file since 2068 was 2068, checked in by taco@…, 11 years ago

AdvancedQueryController?.groovy, small fix to prevent a bunch of error messages showing up when searching with the '[Any field in any object] *' field option. To render an empty list as JSON, one should use a variable.

  • Property svn:keywords set to Rev Author Date
File size: 23.6 KB
Line 
1package dbnp.query
2
3import dbnp.modules.*
4import org.dbnp.gdt.*
5import dbnp.studycapturing.*;
6import grails.converters.JSON
7
8/**
9 * Basic web interface for searching within studies
10 *
11 * @author Robert Horlings (robert@isdat.nl)
12 */
13class AdvancedQueryController {
14        def moduleCommunicationService;
15        def authenticationService
16
17        def entitiesToSearchFor = [ 'Study': 'Studies', 'Sample': 'Samples', 'Assay': 'Assays']
18
19        /**
20         * Shows search screen
21         */
22        def index = {
23                // Check whether criteria have been given before
24                def criteria = [];
25                if( params.criteria ) {
26                        criteria = parseCriteria( params.criteria, false )
27                }
28                [searchModes: SearchMode.values(), entitiesToSearchFor: entitiesToSearchFor, searchableFields: getSearchableFields(), criteria: criteria, previousSearches: session.queries ]
29        }
30
31        /**
32         * Searches for studies or samples based on the user parameters.
33         *
34         * @param       entity          The entity to search for ( 'Study' or 'Sample' )
35         * @param       criteria        HashMap with the values being hashmaps with field, operator and value.
36         *                                              [ 0: [ field: 'Study.name', operator: 'equals', value: 'term' ], 1: [..], .. ]
37         */
38        def search = {
39                if( !params.criteria ) {
40                        flash.error = "No criteria given to search for. Please try again.";
41                        redirect( action: 'index' )
42                }
43
44                if( !params.entity || !entitiesToSearchFor*.key.contains( params.entity ) ) {
45                        flash.error = "No or incorrect entity given to search for. Please try again.";
46                        redirect( action: 'index', params: [ criteria: parseCriteria( params.criteria ) ] )
47                }
48
49                // Create a search object and let it do the searching
50                Search search = determineSearch( params.entity );
51                String view = determineView( params.entity );
52
53                // Choose between AND and OR search. Default is given by the Search class itself.
54                switch( params.operator?.toString()?.toLowerCase() ) {
55                        case "or":
56                                search.searchMode = SearchMode.or;
57                                break;
58                        case "and":
59                                search.searchMode = SearchMode.and;
60                                break;
61                }
62
63                search.execute( parseCriteria( params.criteria ) );
64
65                // Save search in session
66                def queryId = saveSearch( search );
67                render( view: view, model: [search: search, queryId: queryId, actions: determineActions(search)] );
68        }
69
70        /**
71         * Removes a specified search from session
72         * @param       id      queryId of the search to discard
73         */
74        def discard = {
75                def queryIds = params.list( 'id' );
76                queryIds = queryIds.findAll { it.isInteger() }.collect { Integer.valueOf( it ) }
77
78                if( queryIds.size() == 0 ) {
79                        flash.error = "Incorrect search ID given to discard"
80                        redirect( action: "index" );
81                        return
82                }
83
84                queryIds.each { queryId ->
85                        discardSearch( queryId );
86                }
87
88                if( queryIds.size() > 1 ) {
89                        flash.message = "Searches have been discarded"
90                } else {
91                        flash.message = "Search has been discarded"
92                }
93                redirect( action: "list" );
94        }
95
96        /**
97         * Shows a specified search from session
98         * @param       id      queryId of the search to show
99         */
100        def show = {
101                def queryId = params.int( 'id' );
102
103                if( !queryId ) {
104                        flash.error = "Incorrect search ID given to show"
105                        redirect( action: "index" );
106                        return
107                }
108
109                // Retrieve the search from session
110                Search s = retrieveSearch( queryId );
111                if( !s ) {
112                        flash.message = "Specified search could not be found"
113                        redirect( action: "index" );
114                        return;
115                }
116
117                // Attach all objects to the current hibernate thread, because the
118                // object might be attached to an old thread, since the results are
119                // saved in session
120                s.getResults().each {
121                        it.attach();
122                }
123
124                // Determine which view to show
125                def view = determineView( s.entity );
126                render( view: view, model: [search: s, queryId: queryId, actions: determineActions(s)] );
127        }
128
129        /**
130         * Shows a list of searches that have been saved in session
131         * @param       id      queryId of the search to show
132         */
133        def list = {
134                def searches = listSearches();
135
136                if( !searches || searches.size() == 0 ) {
137                        flash.message = "No previous searches found";
138                        redirect( action: "index" );
139                        return;
140                }
141                [searches: searches]
142        }
143
144        /**
145         * Shows a search screen where the user can search within the results of another search
146         * @param       id      queryId of the search to search in
147         */
148        def searchIn = {
149                def queryIds = params.list( 'id' );
150                queryIds = queryIds.findAll { it.isInteger() }.collect { Integer.valueOf( it ) }
151
152                if( queryIds.size() == 0 ) {
153                        flash.error = "Incorrect search ID given to search in"
154                        redirect( action: "list" );
155                        return
156                }
157
158                // Retrieve the searches from session
159                def params = [:]
160                queryIds.eachWithIndex { queryId, idx ->
161                        Search s = retrieveSearch( queryId );
162                        if( !s ) {
163                                flash.message = "Specified search " + queryId + " could not be found"
164                                return;
165                        } else {
166                                params[ "criteria." + idx + ".entityfield" ] = s.entity;
167                                params[ "criteria." + idx + ".operator" ] = "in";
168                                params[ "criteria." + idx + ".value" ] = queryId;
169                        }
170                }
171
172                redirect( action: "index", params: params)
173        }
174
175        /**
176         * Combines the results of multiple searches
177         * @param       id      queryIds of the searches to combine
178         */
179        def combine = {
180                def queryIds = params.list( 'id' );
181                queryIds = queryIds.findAll { it.isInteger() }.collect { Integer.valueOf( it ) }
182
183                if( queryIds.size() == 0 ) {
184                        flash.error = "Incorrect search ID given to combine"
185                        redirect( action: "index" );
186                        return
187                }
188
189                // First determine whether the types match
190                def searches = [];
191                def type = "";
192                flash.error = "";
193                queryIds.eachWithIndex { queryId, idx ->
194                        Search s = retrieveSearch( queryId );
195                        if( !s ) {
196                                return;
197                        }
198
199                        if( type ) {
200                                if( type != s.entity ) {
201                                        flash.error = type + " and " + s.entity.toLowerCase() + " queries can't be combined. Selected queries of one type.";
202                                        return
203                                }
204                        } else {
205                                type = s.entity
206                        }
207                }
208
209                if( flash.error ) {
210                        redirect( action: "list" );
211                        return;
212                }
213
214                if( !type ) {
215                        flash.error = "No correct query ids were given."
216                        redirect( action: "list" );
217                        return;
218                }
219
220                // Retrieve the searches from session
221                Search combined = determineSearch( type );
222                combined.searchMode = SearchMode.or;
223
224                queryIds.eachWithIndex { queryId, idx ->
225                        Search s = retrieveSearch( queryId );
226                        if( s ) {
227                                combined.addCriterion( new Criterion( entity: type, field: null, operator: Operator.insearch, value: s ) );
228                        }
229                }
230
231                // Execute search to combine the results
232                combined.execute();
233
234                def queryId = saveSearch( combined );
235                redirect( action: "show", id: queryId );
236        }
237
238        /**
239         * Registers a search from a module with GSCF, in order to be able to refine the searches
240         */
241        def refineExternal = {
242                // Determine parameters and retrieve objects based on the tokens
243                def name = params.name
244                def url = params.url
245                def entity = params.entity
246                def tokens = params.list( 'tokens' );
247                def results
248
249                switch( entity ) {
250                        case "Study":
251                                results = Study.findAll( "from Study s where s.studyUUID IN (:tokens)", [ 'tokens': tokens ] )
252                                break;
253                        case "Assay":
254                                results = Assay.findAll( "from Assay a where a.assayUUID IN (:tokens)", [ 'tokens': tokens ] )
255                                break;
256                        case "Sample":
257                                results = Sample.findAll( "from Sample s where s.sampleUUID IN (:tokens)", [ 'tokens': tokens ] )
258                                break;
259                        default:
260                                response.sendError( 400 );
261                                render "The given entity is not supported. Choose one of Study, Assay or Sample"
262                                return;
263                }
264
265                // Register and save search
266                Search s = Search.register( name, url, entity, results );
267                int searchId = saveSearch( s );
268
269                // Redirect to the search screen
270                def params = [
271                                        "criteria.0.entityfield": s.entity,
272                                        "criteria.0.operator": "in",
273                                        "criteria.0.value": searchId
274                                ];
275
276                redirect( action: "index", params: params)
277        }
278
279        /**
280         * Retrieves a list of distinct values that have been entered for the given field.
281         */
282        def getFieldValues = {
283                def entityField = params.entityfield;
284                entityField = entityField.split( /\./ );
285                def entity = entityField[ 0 ];
286                def field = entityField[ 1 ];
287               
288                def term = params.term
289                def termLike = "%" + term + "%"
290               
291                // Skip searching all fields
292                if( entity == "*" || field == "*" ) {
293                        def emptyList = []
294                        render emptyList as JSON
295                        return;
296                }
297               
298                def entityClass = TemplateEntity.parseEntity( 'dbnp.studycapturing.' + entity)
299               
300                // Domain fields can be easily found
301                if( field == "Template" ) {
302                        render Template.executeQuery( "select distinct t.name FROM Template t where t.entity = :entity AND t.name LIKE :term", [ "entity": entityClass, "term": termLike ] ) as JSON
303                        return;
304                }
305               
306                // Determine domain fields of the entity
307                def domainFields = entityClass.giveDomainFields();
308                def domainField = domainFields.find { it.name == field };
309               
310                // The values of a domainfield can be determined easily
311                if( domainField ) {
312                        render entityClass.executeQuery( "select distinct e." + field + " FROM " + entity + " e WHERE e." + field + " LIKE :term", [ "term": termLike ] ) as JSON
313                        return;
314                }
315               
316                // Find all fields with this name and entity, in order to determine the type of the field
317                def fields = TemplateField.findAll( "FROM TemplateField t WHERE t.name = :name AND t.entity = :entity", [ "name": field, "entity": entityClass ] );
318
319                // If the field is not found, return an empty list
320                def listValues = [];
321                if( !fields ) {
322                        render listValues as JSON
323                }
324
325                // Determine the type (or types) of the field
326                def fieldTypes = fields*.type.unique()*.casedName;
327               
328                // Now create a list of possible values, based on the fieldType(s)
329               
330                // Several types of fields are handled differently.
331                // The 'simple' types (string, double) are handled by searching in the associated 'templateXXXXFields' table
332                // The 'complex' types (stringlist, template etc., referencing another database table) can't be
333                // handled correctly (the same way), since the HQL INDEX() function doesn't work on those relations.
334                // We do a search for these types to see whether any field with that type fits this criterion, in order to
335                // filter out false positives later on.
336                fieldTypes.each { type ->
337                        // Determine field name
338                        def fieldName = "template" + type + 'Fields'
339
340                        switch( type ) {
341                                case 'String':
342                                case 'Text':
343                                case 'File':
344                                        // 'Simple' field types (string values)
345                                        listValues += entityClass.executeQuery( "SELECT DISTINCT f FROM " + entity + " s left join s." + fieldName + " f WHERE index(f) = :field AND f LIKE :term", [ "field": field, "term": termLike ] );
346                                        break;
347                                       
348                                case 'Date':
349                                case 'Double':
350                                case 'Long':
351                                        // 'Simple' field types that can be converted to string and compared
352                                        listValues += entityClass.executeQuery( "SELECT DISTINCT f FROM " + entity + " s left join s." + fieldName + " f WHERE index(f) = :field AND str(f) LIKE :term", [ "field": field, "term": termLike ] );
353                                        break;
354                                case 'Boolean':
355                                        // Simple field types that don't support like
356                                        listValues += entityClass.executeQuery( "SELECT DISTINCT f FROM " + entity + " s left join s." + fieldName + " f WHERE index(f) = :field", [ "field": field ] );
357                                        break;
358                               
359                                case 'RelTime':
360                                        // RelTime values should be formatted before returning
361                                        def reltimes = entityClass.executeQuery( "SELECT DISTINCT f FROM " + entity + " s left join s." + fieldName + " f WHERE index(f) = :field", [ "field": field ] );
362                                        listValues += reltimes.collect { def rt = new RelTime( it ); return rt.toString(); }
363                                        break;
364
365                                case 'StringList':
366                                case 'ExtendableStringList':
367                                case 'Term':
368                                case 'Template':
369                                case 'Module':
370                                        // 'Complex' field types: select all possible names for the given field, that have ever been used
371                                        // (i.e. all ontologies that have ever been used in any field). We have to do it this way, because the HQL
372                                        // index() function (see simple fields) doesn't work on many-to-many relations
373                                        listValues += entityClass.executeQuery( "SELECT DISTINCT f.name FROM " + entity + " s left join s." + fieldName + " f WHERE f.name LIKE :term", [ "term": termLike ] );
374                                default:
375                                        break;
376                        }
377                }
378               
379                render listValues as JSON
380        }
381
382        protected String determineView( String entity ) {
383                switch( entity ) {
384                        case "Study":   return "studyresults";  break;
385                        case "Sample":  return "sampleresults"; break;
386                        case "Assay":   return "assayresults";  break;
387                        default:                return "results"; break;
388                }
389        }
390
391        /**
392         * Returns the search object used for searching
393         */
394        protected Search determineSearch( String entity ) {
395                switch( entity ) {
396                        case "Study":   return new StudySearch();
397                        case "Sample":  return new SampleSearch();
398                        case "Assay":   return new AssaySearch();
399
400                        // This exception will only be thrown if the entitiesToSearchFor contains more entities than
401                        // mentioned in this switch structure.
402                        default:                throw new Exception( "Can't search for entities of type " + entity );
403                }
404        }
405
406        /**
407         * Returns a map of entities with the names of the fields the user can search on
408         * @return
409         */
410        protected def getSearchableFields() {
411                def fields = [ '*' : [ '*' ] ]; // Searches for all fields in all objects
412
413                // Retrieve all local search fields
414                getEntities().each {
415                        def entity = getEntity( 'dbnp.studycapturing.' + it );
416
417                        if( entity ) {
418                                def domainFields = entity.giveDomainFields();
419                                def templateFields = TemplateField.findAllByEntity( entity )
420
421                                def fieldNames = ( domainFields + templateFields ).collect { it.name }.unique() + 'Template' + '*'
422
423                                fields[ it ] = fieldNames.sort { a, b ->
424                                        def aUC = a.size() > 1 ? a[0].toUpperCase() + a[1..-1] : a;
425                                        def bUC = b.size() > 1 ? b[0].toUpperCase() + b[1..-1] : b;
426                                        aUC <=> bUC
427                                };
428                        }
429                }
430
431                // Loop through all modules and check which fields are searchable
432                // Right now, we just combine the results for different entities
433                AssayModule.list().each { module ->
434                        def callUrl = module.url + '/rest/getQueryableFields'
435                        try {
436                                def json = moduleCommunicationService.callModuleMethod( module.url, callUrl );
437                                def moduleFields = [];
438                                entitiesToSearchFor.each { entity ->
439                                        if( json[ entity.key ] ) {
440                                                json[ entity.key ].each { field ->
441                                                        moduleFields << field.toString();
442                                                }
443                                        }
444                                }
445
446                                // Remove 'module' from module name
447                                def moduleName = module.name.replace( 'module', '' ).trim()
448
449                                fields[ moduleName ] = moduleFields.unique() + '*';
450                        } catch( Exception e ) {
451                                log.error( "Error while retrieving queryable fields from " + module.name + ": " + e.getMessage() )
452                        }
453                }
454
455                return fields;
456        }
457
458        /**
459         * Parses the criteria from the query form given by the user
460         * @param       c       Data from the input form and had a form like
461         *
462         *      [
463         *              0: [entityfield:a.b, operator: b, value: c],
464         *              0.entityfield: a.b,
465         *              0.operator: b,
466         *              0.field: c
467         *              1: [entityfield:f.q, operator: e, value: d],
468         *              1.entityfield: f.q,
469         *              1.operator: e,
470         *              1.field: d
471         *      ]
472         * @param parseSearchIds        Determines whether searches are returned instead of their ids
473         * @return                                      List with Criterion objects
474         */
475        protected List parseCriteria( def formCriteria, def parseSearchIds = true ) {
476                ArrayList list = [];
477                flash.error = "";
478
479                // Loop through all keys of c and remove the non-numeric ones
480                for( c in formCriteria ) {
481                        if( c.key ==~ /[0-9]+/ && c.value.entityfield ) {
482                                def formCriterion = c.value;
483
484                                Criterion criterion = new Criterion();
485
486                                // Split entity and field
487                                def field = formCriterion.entityfield?.split( /\./ );
488                                if( field.size() > 1 ) {
489                                        criterion.entity = field[0].toString();
490                                        criterion.field = field[1].toString();
491                                } else {
492                                        criterion.entity = field[0];
493                                        criterion.field = null;
494                                }
495
496                                // Convert operator string to Operator-enum field
497                                try {
498                                        criterion.operator = Criterion.parseOperator( formCriterion.operator );
499                                } catch( Exception e) {
500                                        log.debug "Operator " + formCriterion.operator + " could not be parsed: " + e.getMessage();
501                                        flash.error += "Criterion could not be used: operator " + formCriterion.operator + " is not valid.<br />\n";
502                                        continue;
503                                }
504
505                                // Special case of the 'in' operator
506                                if( criterion.operator == Operator.insearch ) {
507                                        Search s
508                                        try {
509                                                s = retrieveSearch( Integer.parseInt( formCriterion.value ) );
510                                        } catch( Exception e ) {}
511
512                                        if( !s ) {
513                                                flash.error += "Can't search within previous query: query not found";
514                                                continue;
515                                        }
516
517                                        if( parseSearchIds ) {
518                                                criterion.value = s
519                                        } else {
520                                                criterion.value = s.id
521                                        }
522                                } else {
523                                        // Copy value
524                                        criterion.value = formCriterion.value;
525                                }
526
527                                list << criterion;
528                        }
529                }
530
531                return list;
532        }
533
534        /**
535         * Returns all entities for which criteria can be entered
536         * @return
537         */
538        protected def getEntities() {
539                return [ 'Study', 'Subject', 'Sample', 'Event', 'SamplingEvent', 'Assay' ]
540        }
541
542        /**
543         * Creates an object of the given entity.
544         *
545         * @return False if the entity is not a subclass of TemplateEntity
546         */
547        protected def getEntity( entityName ) {
548                // Find the templates
549                def entity
550                try {
551                        entity = Class.forName(entityName, true, this.getClass().getClassLoader())
552
553                        // succes, is entity an instance of TemplateEntity?
554                        if (entity.superclass =~ /TemplateEntity$/ || entity.superclass.superclass =~ /TemplateEntity$/) {
555                                return entity;
556                        } else {
557                                return false;
558                        }
559                } catch( ClassNotFoundException e ) {
560                        log.error "Class " + entityName + " not found: " + e.getMessage()
561                        return null;
562                }
563
564        }
565
566
567        /***************************************************************************
568         *
569         * Methods for saving results in session
570         *
571         ***************************************************************************/
572
573        /**
574         * Saves the given search in session. Any search with the same criteria will be overwritten
575         * 
576         * @param s             Search to save
577         * @return              Id of the search for later reference
578         */
579        protected int saveSearch( Search s ) {
580                if( !session.queries )
581                        session.queries = [:]
582
583                // First check whether a search with the same criteria is already present
584                def previousSearch = retrieveSearch( s );
585
586                def id
587                if( previousSearch ) {
588                        id = previousSearch.id;
589                } else {
590                        // Determine unique id
591                        id = ( session.queries*.key.max() ?: 0 ) + 1;
592                }
593
594                s.id = id;
595
596                if( !s.url )
597                        s.url = g.createLink( controller: "advancedQuery", action: "show", id: id, absolute: true );
598
599                session.queries[ id ] = s;
600
601                return id;
602        }
603
604        /**
605         * Retrieves a search from session with the same criteria as given
606         * @param s                     Search that is used as an example to search for
607         * @return                      Search that has this criteria, or null if no such search is found.
608         */
609        protected Search retrieveSearch( Search s ) {
610                if( !session.queries )
611                        return null
612
613                for( query in session.queries ) {
614                        def value = query.value;
615
616                        if( s.equals( value ) )
617                                return value
618                }
619
620                return null;
621        }
622
623
624        /**
625         * Retrieves a search from session
626         * @param id    Id of the search
627         * @return              Search that belongs to this ID or null if no search is found
628         */
629        protected Search retrieveSearch( int id ) {
630                if( !session.queries || !session.queries[ id ] )
631                        return null
632
633                if( !( session.queries[ id ] instanceof Search ) )
634                        return null;
635
636                return (Search) session.queries[ id ]
637        }
638
639        /**
640         * Removes a search from session
641         * @param id    Id of the search
642         * @return      Search that belonged to this ID or null if no search is found
643         */
644        protected Search discardSearch( int id ) {
645                if( !session.queries || !session.queries[ id ] )
646                        return null
647
648                def sessionSearch = session.queries[ id ];
649
650                session.queries.remove( id );
651
652                if( !( sessionSearch instanceof Search ) )
653                        return null;
654
655                return (Search) sessionSearch
656        }
657
658        /**
659         * Retrieves a list of searches from session
660         * @return      List of searches from session
661         */
662        protected List listSearches() {
663                if( !session.queries )
664                        return []
665
666                return session.queries*.value.toList()
667        }
668
669        /**
670         * Determine a list of actions that can be performed on specific entities
671         * @param entity                Name of the entity that the actions could be performed on
672         * @param selectedIds   List with ids of the selected items to perform an action on
673         * @return
674         */
675        protected List determineActions( Search s, def selectedIds = null ) {
676                return gscfActions( s, selectedIds ) + moduleActions( s, selectedIds );
677        }
678
679        /**
680         * Determine a list of actions that can be performed on specific entities by GSCF
681         * @param entity        Name of the entity that the actions could be performed on
682         * @param selectedTokens        List with tokens (UUID) of the selected items to perform an action on
683         */
684        protected List gscfActions(Search s, def selectedTokens = null) {
685                switch(s.entity) {
686                        case "Study":
687                                def ids = []
688                                s.filterResults(selectedTokens).each {
689                                        ids << it.id
690                                }
691
692                                def paramString = ids.collect { return 'ids=' + it }.join( '&' );
693
694                                return [[
695                                                module: "gscf",
696                                                name:"simpletox",
697                                                type: "export",
698                                                description: "Export as SimpleTox",
699                                                url: createLink( controller: "exporter", action: "export", params: [ 'format': 'list', 'ids' : ids ] ),
700                                                submitUrl: createLink( controller: "exporter", action: "export", params: [ 'format': 'list' ] ),
701                                                paramString: paramString
702                                        ], [
703                                                module: "gscf",
704                                                name:"excel",
705                                                type: "export",
706                                                description: "Export as CSV",
707                                                url: createLink( controller: "study", action: "exportToExcel", params: [ 'format': 'list', 'ids' : ids ] ),
708                                                submitUrl: createLink( controller: "study", action: "exportToExcel", params: [ 'format': 'list' ] ),
709                                                paramString: paramString
710                                        ]]
711                        case "Assay":
712                                def ids = []
713                                s.filterResults(selectedTokens).each {
714                                        ids << it.id
715                                }
716
717                                def paramString = ids.collect { return 'ids=' + it }.join( '&' );
718
719                                return [[
720                                                module: "gscf",
721                                                name:"excel",
722                                                type: "export",
723                                                description: "Export as CSV",
724                                                url: createLink( controller: "assay", action: "exportToExcel", params: [ 'format': 'list', 'ids' : ids ] ),
725                                                submitUrl: createLink( controller: "assay", action: "exportToExcel", params: [ 'format': 'list' ] ),
726                                                paramString: paramString
727                                        ]]
728                        case "Sample":
729                                def ids = []
730                                s.filterResults(selectedTokens).each {
731                                        ids << it.id
732                                }
733
734                                def paramString = ids.collect { return 'ids=' + it }.join( '&' );
735
736                                return [[
737                                                module: "gscf",
738                                                name:"excel",
739                                                type: "export",
740                                                description: "Export as CSV",
741                                                url: createLink( controller: "assay", action: "exportToSamplesToCsv", params: [ 'ids' : ids ] ),
742                                                submitUrl: createLink( controller: "assay", action: "exportSamplesToCsv" ),
743                                                paramString: paramString
744                                        ]]
745                        default:
746                                return [];
747                }
748        }
749
750        /**
751         * Determine a list of actions that can be performed on specific entities by other modules
752         * @param entity        Name of the entity that the actions could be performed on
753         */
754        protected List moduleActions(Search s, def selectedTokens = null) {
755                def actions = []
756
757                if( !s.getResults() || s.getResults().size() == 0 )
758                        return []
759
760                // Loop through all modules and check which actions can be performed on the
761                AssayModule.list().each { module ->
762                        // Remove 'module' from module name
763                        def moduleName = module.name.replace( 'module', '' ).trim()
764                        try {
765                                def callUrl = module.url + "/rest/getPossibleActions?entity=" + s.entity
766                                def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl );
767
768                                // Check whether the entity is present in the return value
769                                if( json[ s.entity ] ) {
770                                        json[ s.entity ].each { action ->
771                                                def baseUrl = action.url ?: module.url + "/action/" + action.name
772                                                def paramString = s.filterResults(selectedTokens).collect { "tokens=" + it.giveUUID() }.join( "&" )
773
774                                                def url = baseUrl;
775
776                                                if( url.find( /\?/ ) )
777                                                        url += "&"
778                                                else
779                                                        url += "?"
780
781                                                paramString += "&entity=" + s.entity
782
783                                                actions << [
784                                                                        module: moduleName,
785                                                                        name: action.name,
786                                                                        type: action.type ?: 'default',
787                                                                        description: action.description + " (" + moduleName + ")",
788                                                                        url: url + "&" + paramString,
789                                                                        submitUrl: baseUrl,
790                                                                        paramString: paramString
791                                                                ];
792                                        }
793                                }
794                        } catch( Exception e ) {
795                                // Exception is thrown when the call to the module fails. No problems though.
796                                log.error "Error while fetching possible actions from " + module.name + ": " + e.getMessage()
797                        }
798                }
799
800                return actions;
801        }
802
803}
Note: See TracBrowser for help on using the repository browser.