root/grails-app/controllers/RestController.groovy @ 146

Revision 146, 16.6 KB (checked in by j.saito@…, 4 years ago)

Removed three possible sources for null-pointer exceptions in the CommunicationManager?.

Added a simpel way of handling REST errors using a new RestException? class.

I have tried to introduce Exception handling for errors in the Communication manager. So far, I only committed exceptions for something going wrong. I have been working on a more fine grained distinction between server side errors and client side errors related to REST. however, these errors require some sort of convention for delivering from the server side to the client side. Therefore, they require a convention to be agreed to by NMCDSP and I have not committed these Exceptions.

Line 
1import data.*
2import grails.converters.*
3import java.io.PrintWriter
4import java.io.StringWriter
5import org.codehaus.groovy.grails.web.json.*
6import org.compass.core.engine.SearchEngineQueryParseException
7import org.compass.core.impl.*
8import dbnp.rest.common.CommunicationManager
9import dbnp.rest.*
10
11
12class RestController {
13
14    def searchableService
15
16    def simpleAssayList = []
17    def measurementDataList = []
18
19
20
21    /** Expose the list of features within a certain assay
22     *
23     * @author Adem and Jahn
24     * @since 20100525
25     *
26     * $Rev$
27     *
28     * This class provides a REST-full service for getting and setting the data
29     * in the Simple Assay Module (SAM). The service consists of several
30     * resources listed below. So far, all resources are GET resoruces, i.e. we
31     * do not use PUT, POST or DELETE. Because we are using Grails' web libaries,
32     * each resource corresponds to exactly one action of this controller.
33     *
34     *
35     *
36     * The REST resources implemented in this controller are:
37     *
38     * SAM-host:port/sam/rest/getMeasurements
39     * SAM-host:port/sam/rest/getMeasurementsForValue
40     * SAM-host:port/sam/rest/getMeasurementsForRange
41     * SAM-host:port/sam/rest/getMeasurementsForSample
42     * SAM-host:port/sam/rest/getDataSimple
43     * SAM-host:port/sam/rest/getAvailableBiomarkers
44     *
45     * Where 'SAM-host-url' is the url of the SAM server's host with port number 'port'.
46     *
47     *
48     *
49     * Some of the resources use additional parameters such, e.g. because they Grails'
50     * Searchable plugin. For instance, the getMeasurement resource is addressed as follows:
51     *
52     * SAM-host:port/sam/rest/getMeasurements?submit=Query&ump;q=ldl
53     *
54     * In this example the first paramter is 'submit' with value 'Query' and the second
55     * parameter is 'q' with value 'ldl'. The parameter 'submit' is neccessary to exploit
56     * automatic parsing functionality of the the Searchable plugin. The parameter 'q'
57     * is the keyword requested by the same plugin.
58     */
59
60
61
62
63      /****************************************************************/
64     /* REST resources for providing basic data to the GSCF          */
65    /****************************************************************/
66
67
68
69    /**
70     * Return a list of simple assay measurements matching the querying text.
71     * Uses Searchable plugin.
72     *
73     * @param assayToken
74     * @return list of meassurements for token. Each member of the list is a hash.
75         *                      the hash contains the three keys values pairs: value, sampleToken, and
76         *                      measurementMetadata.
77     *
78     * Example REST call:
79     * http://localhost:8182/sam/rest/getMeasurements/z?assayToken=2
80     *
81     * Resulting JSON object:
82     *
83     * [{"value":135,"sampleToken":"1_A","measurementToken":"total carbon dioxide (tCO)"},
84         * {"value":4.2,"sampleToken":"1_A","measurementToken":"sodium (Na+)"} ]
85     */
86    def getMeasurements = {
87
88                def valid = CommunicationManager.hasValidParams( params, {render it as JSON }, 'assayToken' )
89                if( !valid ) {
90                        return
91                }
92
93
94                def assayToken = params.assayToken
95                def list = []
96                def assay = SimpleAssay.find( "from SimpleAssay as a where a.externalAssayID =? ", [assayToken] )
97                SimpleAssayMeasurement
98                        .findAll( "from SimpleAssayMeasurement as m where m.assay =?", [assay] )
99                                .each{ measurement ->
100                                        def tripple = [:]
101                                tripple['value'] = measurement.value
102                                tripple['sampleToken'] = measurement.sample.externalSampleId
103                                tripple['measurementToken'] = measurement.type.name
104                                        list.push tripple
105                                }
106
107                render list as JSON
108    }
109
110
111    /**
112     * Return measurement metadata for measurement
113     *
114     * @param assayToken
115     * @param measurementTokens
116     * @return list of measurements
117     *
118     * Example REST call:
119         * http://localhost:8182/sam/rest/getMeasurementMetadata/33?assayToken=2
120     *      &measurementToken=total calcium (Ca)
121         *              &measurementToken=glucose
122     *
123     * Resulting JSON object:
124     *
125         * [ {"name":"glucose","unit":"mg/dl","referenceValues":null,"correctionMethod":null,"isDrug":false,
126         *    "isIntake":false,"inSerum":false,"isNew":false},
127         *   {"name":"total calcium (Ca)","unit":"mg/dl",
128         *    "referenceValues":null,"correctionMethod":null,"isDrug":false,"isIntake":false,"inSerum":false,
129         *    "isNew":false} ]
130         */
131    def getMeasurementMetadata = {
132
133                def assayToken = params.assayToken
134                def measurementTokens = params.measurementToken
135
136        if( !assayToken || !measurementTokens ) {
137                        return render [] as JSON
138                }
139
140        def list = []
141        def types = []
142        def assay = SimpleAssay.find( "from SimpleAssay as a where a.externalAssayID =?", [assayToken] )
143
144                if( measurementTokens.class == java.lang.String ) {
145                        measurementTokens = [ measurementTokens ]
146                }
147
148        SimpleAssayMeasurement
149            .findAll( "from SimpleAssayMeasurement as m where m.assay =?", [assay] )
150                .each{ if( !types.contains(it.type)) types.push( it.type ) }
151   
152        types.each { type ->
153                        def isInList = false
154                    measurementTokens.each { if(it==type.name) { isInList = true } }
155                        println type
156                        if(     isInList ) {
157                        def tuple = [:]
158                        tuple['name'] = type.name
159                        tuple['unit'] = type.unit
160                        tuple['referenceValues'] = type.referenceValues
161                        tuple['correctionMethod'] = type.correctionMethod
162                        tuple['isDrug'] = type.isDrug
163                        tuple['isIntake'] = type.isIntake
164                        tuple['inSerum'] = type.inSerum
165                        tuple['isNew'] = type.isNew
166                        list.push tuple
167                }
168        }
169
170        render list as JSON
171    }
172
173
174
175    /*
176     * Return list of metadata fields available for the samples in an assay
177     *
178     * @param assayTokes
179     * @param measurementTokens
180     * @param sampleTokens
181     * @return  table (as hash) with values for given samples and measurements
182         *
183     * Example REST call:
184         * http://localhost:8182/sam/rest/getMeasurementData/doit?assayToken=PPSH-Glu-A
185     *    &measurementToken=total carbon dioxide (tCO)
186         *    &sampleToken=5_A
187         *    &sampleToken=1_A
188         *
189     * Resulting JSON object:
190         * [ {"sampleToken":"1_A","typeToken":"total carbon dioxide (tCO)","value":28},
191         *   {"sampleToken":"5_A","typeToken":"total carbon dioxide (tCO)","value":29} ]
192     */
193    def getMeasurementData = {
194                def assayToken = params.assayToken
195                def measurementTokens = params.measurementToken
196                def sampleTokens = params.sampleToken
197
198        if( !assayToken || !measurementTokens || !sampleTokens ) {
199                        return render [] as JSON
200                }
201
202                if( measurementTokens.class == java.lang.String ) {
203                        measurementTokens = [ measurementTokens ]
204                }
205
206                if( sampleTokens.class == java.lang.String ) {
207                        sampleTokens = [ sampleTokens ]
208                }
209
210        def assay = SimpleAssay.find( "from SimpleAssay as a where a.externalAssayID =?", [assayToken] )
211
212                def data = SimpleAssayMeasurement
213                        .findAll( "from SimpleAssayMeasurement as m where m.assay =?", [assay] )
214
215                def results = []
216                data.each { measurement ->
217                        def type = measurement.type.name
218                        def sample = measurement.sample.externalSampleId
219                        def isMatch = false
220
221                        measurementTokens.each { if(it==type) isMatch=true }
222                        if( isMatch ) {
223                           isMatch=false
224                           sampleTokens.each { if(it==sample) isMatch=true }
225                        }
226                        if( isMatch ) {
227                                results.push( [ 'sampleToken':sample, 'typeToken':type, 'value':measurement.value ] )
228                        }       
229                }
230
231                render results as JSON
232    }
233
234
235
236    /**
237     * Return URL to view an assay.
238     *
239     * @param  assayToken
240     * @return URL to view an assay.
241         *
242     */
243    def getAssayURL = {
244
245                def assayToken = params.assayToken
246                def assay = SimpleAssay.find( "from SimpleAssay as a where a.id = " + assayToken )
247
248        if( !assayToken || !sampleTokens ) {
249                        return render [] as JSON
250                }
251
252            String url = 'CommunicationManager.SAMServerURL' +
253                        '/SimpleAssay/show/' + assay.id
254
255                render url as JSON
256        }
257
258
259
260
261
262      /***************************************************/
263     /* REST resources related to the querying in GSCF  */
264    /***************************************************/
265
266
267    /**
268     * Run searchable on query string for finding hits in SimpleAssays and SimpleAssayMeasurementTypes.
269     *
270     * @param  string: Query string
271     * @return map.    The map contains two keys "studyIds" and "assays". studyIds represent the studies for 
272     *                 which MeasurementTypes were found containing the query string. assayIds are the
273     *                 externalAssayIDs of the SimpleAssays for which a match was found.
274     *                     Example of a returned map:  [ assays:[data.SimpleAssay : 1], studyIds:[PPSH] ].
275     */
276    def getQueryResult = {
277                def query = params['query']
278                def searchResult = querySearchable( query )
279                def results = getResultingObjects( searchResult )       // get assays and studies
280                render results as JSON 
281    }
282
283
284
285    /**
286     * Run searchable on query string and values for finding hits in SimpleAssayMeasurement and
287     * SimpleAssayMeasurementType. (This rest resource is called by the SimpleQuery of GSCF.)
288     *
289     * @param  string: Query string
290     * @return map. The map contains enriched information on SimpleAssayMeasurements.   
291     *                 
292     * Example return value (contained in JSON):                 
293         *              [[externalAssayId:1, externalSampleId:A10_B, type:Glucose, unit:Insulin, value:202.0],
294         *               [externalAssayId:1, externalSampleId:A1_B, type:Insulin, unit:g, value:101.0]]
295     */
296    def getQueryResultWithOperator = {
297                def userQuery = params['query']
298                def operator  = params['operator']
299                def value     = params['value']
300                def dValue = value.toDouble()
301                def searchResult = querySearchable( userQuery )
302
303                def query = "from SimpleAssayMeasurement as m where m.type =:type AND m.value "
304                if( operator=="<" )      { query += " <:value " }
305                else if( operator==">" ) { query += " >:value " }
306                else                                 { query += " = :value " }
307
308                def measurements = []
309                searchResult.each{ result ->
310                        if( result.class == SimpleAssayMeasurementType ) {
311
312                                SimpleAssayMeasurement.findAll( query, [type:result,value:dValue] ).each {
313                                        measurements.add( it.getRestRepresentation() )
314                                }
315                        }
316                }
317
318                render measurements as JSON 
319    }
320
321
322
323
324
325
326
327    /**
328     * Gets measurement for value, return a list of Measurement value matching the querying keyword and value
329     * Uses Searchable plugin.
330     *
331     * @param measurement name
332     * @param value
333     * @return list of measurement values
334     */
335    def getMeasurementsForValue = {
336        try {
337                        render params
338            def measurementName = params.q.getAt(0)
339            def measurementValue = params.q.getAt(1)
340
341            for ( i in SimpleAssayMeasurement.list()){
342                if ( (i.assay.name.equals(measurementName))&&(i.value.equals(measurementValue.toFloat()))){
343                    measurementDataList.add(i)
344                }
345            }
346            render measurementDataList
347        } catch (SearchEngineQueryParseException ex) {
348            return [parseException: true]
349        }
350    }
351
352
353    /**
354     * Return a list of Measurement corresponding to the searched
355     * Measurement with the searched min and max values
356     * Uses Searchable plugin.
357     *
358     * @params measurement name
359     * @params min value
360     * @params max value
361     */
362    def getMeasurementsForRange = {
363        try {
364            def measurementName = params.q.getAt(0)
365            def measurementMinValue = params.q.getAt(1)
366            def measurementMaxValue = params.q.getAt(2)
367
368            for ( i in SimpleAssayMeasurement.list()){
369                if ( (i.assay.name.equals(measurementName))&&(i.value > measurementMinValue.toFloat() )
370                &&(i.value < measurementMaxValue.toFloat() )){
371                    measurementDataList.add(i)
372                }
373            }
374            render measurementDataList
375        } catch (SearchEngineQueryParseException ex) {
376            return [parseException: true]
377        }
378    }
379
380
381
382    /**
383     * Get a measurement for a given sample, return a list of all measurements
384     * that reference the given sample in the study.
385     *
386     * @param sample ID
387     * @return list of Measurements linked to the param
388     */
389
390    def getMeasurementsForSample = {
391    try {
392            def paramId = params.q
393            for ( i in SimpleAssay.list()){
394                if ( i.sampleID.equals(paramId) ){
395                    simpleAssayList.add(i)
396                }
397            }
398            render simpleAssayList
399        } catch (SearchEngineQueryParseException ex) {
400            return [parseException: true]
401        }
402    }
403
404
405    /**
406     * Return measurement value referencing a given measurement.
407     *
408     * @param measurement name
409     * @return list of measurement values
410     */
411    def getDataSimple = {
412         try {
413            def measurementName = params.q
414
415            for ( i in SimpleAssayMeasurement.list()){
416               if ( i.assay.name.equals(measurementName)){
417                   measurementDataList.add(i)
418               }
419            }
420            render measurementDataList
421        } catch (SearchEngineQueryParseException ex) {
422            return [parseException: true]
423        }
424    }
425
426    /**
427     * getDataForSample return the data relatives to a sample
428     * @param sample ID
429     * @return list of Measurements
430     */
431    def getDataForSample = {
432        try {
433            //change this value depending on the GET method
434            def sampleId = params.q
435                   
436            for ( i in SimpleAssay.list()){         
437               if ( i.sampleID.equals(sampleId)){
438                   measurementDataList.add(i.measurements)
439                 render i.measurements
440               }
441            }
442            render measurementDataList
443           
444        } catch (SearchEngineQueryParseException ex) {
445            return [parseException: true]
446        }
447    }
448
449
450    def getAvailableBiomarkers = {
451
452         try {
453            def measurementName = params.q
454            render measurementsList
455        } catch (SearchEngineQueryParseException ex) {
456            return [parseException: true]
457        }
458    }
459
460
461
462
463
464
465    /**  Launch a Searchable query and return its result object
466     *
467     *   @param  searchable query string, e.g. "Insulin"
468     *   @return Resulting searchable object
469     */
470    private Object querySearchable( query ) {
471                def searchResult = []
472                try {
473                        searchResult = searchableService.search( query )
474                        searchResult = searchResult['results']
475                }
476                catch( SearchEngineQueryParseException e) { println "SEQPE: " + e }
477                catch( Exception ex) {
478                        StringWriter sw = new StringWriter();
479                        ex.printStackTrace(new PrintWriter(sw));
480                        String stacktrace = sw.toString();
481                        System.out.println("stacktrace = " + stacktrace);
482                }
483                return searchResult
484    }
485
486
487
488    /**  For SimpleAssayMeasurement objects, collect their correpsonding externalAssayIDs
489     *   in a set (i.e., collection without double entries).
490     *   
491     *   @param  SimpleAssayMeasurement object
492     *   @return List of externalAssayIds corresponding to studies, which contain samples, which
493     *           use the given type.
494     */
495    private Object getAssaysForMeasurementType( type ) {
496                // unfortunately there seems to be no easy way to get the table name for a domain class?
497                // a method via the metaclass is mentioned here: http://taapps-javalibs.blogspot.com/2009/03/grailshow-to-get-database-table-and.html
498                // but if the names are changed, this has to be refactored inside this query
499                // Use 'select distinct a' to get a list of SimpleAssay objects instead of a list of ids
500                def assayQuery  = "select distinct a.externalAssayID from SimpleAssayMeasurement m, SimpleAssay a "
501                        assayQuery += "where m.type.id = ${type.id} and m.assay.id=a.id"
502                def assays = SimpleAssay.executeQuery(assayQuery)
503                return assays
504    }
505
506   
507
508    /** Collect studyIds and SimpleAssay domain objects related to the result of a
509     *  Searchable query.
510     *   
511     *   @param  queryResult: Searchable result (Map).
512     *   @return Map with one key that has a list as value. The key is 'assays'.
513     *           The list contains externalAssayIds of the assays that match the given query string.
514     *           Example of a returned map:  [ assays:[2,3] ].
515     */
516    private Map getResultingObjects( queryResult ) {
517                def results = [assays:[]]
518                queryResult.each { result ->
519                        switch( result ) {
520                                case { it instanceof data.SimpleAssayMeasurementType }:
521                                        def assays = getAssaysForMeasurementType(result)
522                                        results['assays'] = results['assays'].plus(assays)
523                                                break
524                                case { it instanceof data.SimpleAssay }:
525                                        results['assays'] = results['assays'].plus(result.id)
526                                        break
527                        }
528                }
529                return results
530    }
531
532
533
534
535        def test = {
536                render CommunicationManager.getStudies()
537        }
538
539}
540
Note: See TracBrowser for help on using the browser.