source: trunk/grails-app/controllers/RestController.groovy @ 1968

Last change on this file since 1968 was 1968, checked in by robert@…, 8 years ago

Updated rest controller so anonymous users (i.e. without sessionToken) can also retrieve their data. If the user is anonymous, he will only get data that is public (where study.publicstudy = true and study.published = true)

  • Property svn:keywords set to Rev Author Date
File size: 22.1 KB
Line 
1/**
2 * RestController
3 *
4 * This controler provides a REST service.
5 * The names of the RESET resources are the same as the names of this
6 * controller's actions. E.g., the resources called getStudies simply
7 * corresponds to the action getStudies. Some of the resources are parameterized.
8 * The parameters are passed as parameters in the url and are available in the
9 * params respecting Grails' conventions. In this file, we adher to the javadoc 
10 * convention for describing parameters ("@param"), but actually we mean
11 * key-value pairs in the params object of each Grails action we comment on.
12 *
13 * @author      Jahn-Takeshi Saito
14 * @since       20100601
15 *
16 */
17
18import dbnp.studycapturing.Study
19import dbnp.studycapturing.Assay
20import dbnp.authentication.SecUser
21import grails.converters.*
22import nl.metabolomicscentre.dsp.http.BasicAuthentication
23import dbnp.rest.common.CommunicationManager
24import org.springframework.security.core.context.SecurityContextHolder;
25
26class RestController {
27
28        /**************************************************/
29        /** Rest resources for Simple Assay Module (SAM) **/
30        /**************************************************/
31
32        def authenticationService
33        def beforeInterceptor = [action:this.&auth,except:["isUser"]]
34        def credentials
35        def requestUser
36
37        /**
38         * Authorization closure, which is run before executing any of the REST resource actions
39         * It fetches a consumer/token combination from the url and checks whether
40         * that is a correct and known combination
41         *
42         * @param       consumer        consumer name of the calling module
43         * @param       token           token for the authenticated user (e.g. session_id)
44         * @return  true if the user is remotely logged in, false otherwise
45         */
46        private def auth() {
47                if( !authenticationService.isRemotelyLoggedIn( params.consumer, params.token ) ) {
48                        response.sendError(403)
49                        return false
50                } else {
51                        return true
52                }
53        }
54
55        /**
56         * REST resource for data modules.
57         * Consumer and token should be supplied via URL parameters.
58         * Determines whether the given user/password combination is a valid GSCF account.
59         *
60         * @param       consumer        consumer name of the calling module
61         * @param       token           token for the authenticated user (e.g. session_id)
62         * @return bool {"authenticated":true} when user/password is a valid GSCF account, {"authenticated":false} otherwise.
63         */
64        def isUser = {
65                boolean isUser = authenticationService.isRemotelyLoggedIn( params.consumer, params.token )
66                def reply = ['authenticated':isUser]
67
68                // set output header to json
69                response.contentType = 'application/json'
70
71                render reply as JSON
72        }
73
74        /**
75         * REST resource for data modules.
76         * Consumer and token should be supplied via URL parameters.
77         * Provides the details of the user that has logged in
78         *
79         * @param       consumer        consumer name of the calling module
80         * @param       token           token for the authenticated user (e.g. session_id)
81         * @return bool {"username": "...", "id": ... } when user/password is logged in.
82         */
83        def getUser = {
84                if( !auth() )
85                        return;
86               
87                SecUser user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
88                def reply = [username: user.username, id: user.id, isAdministrator: user.hasAdminRights() ]
89
90                // set output header to json
91                response.contentType = 'application/json'
92
93                render reply as JSON
94        }
95
96        /**
97         * REST resource for data modules.
98         * Consumer and token should be supplied via URL parameters.
99         * Provide a list of all studies owned by the supplied user.
100         *
101         * @param       studyToken  optional parameter. If no studyToken is given, all studies available to user are returned.
102         *                      Otherwise, the studies for which the studyTokens are given are be returned.
103         * @param       consumer        consumer name of the calling module
104         * @param       token           token for the authenticated user (e.g. session_id)
105         * @return  JSON object list containing 'studyToken', and 'name' (title) for each study
106         *
107         * If one study is requested, a 404 error might occur if the study doesn't exist, and a 401 error if the user is not
108         * authorized to access this study. If multiple studies are requrested, non-existing studies or studies for which the
109         * user is not authorized are not returned in the list (so the list might be empty).
110         *
111         * Example 1. REST call without studyToken.
112         *
113         * Call: http://localhost:8080/gscf/rest/getStudies/query
114         *
115         * Result: [{"title":"NuGO PPS3 mouse study leptin module","studyToken":"PPS3_leptin_module",
116         *                      "startDate":"2008-01-01T23:00:00Z","published":false,"Description":"C57Bl/6 mice were fed a high fat (45 en%)
117         *                      or low fat (10 en%) diet after a four week run-in on low fat diet.","Objectives":null,"Consortium":null,
118         *                      "Cohort name":null,"Lab id":null,"Institute":null,"Study protocol":null},
119         *                      {"title":"NuGO PPS human study","studyToken":"PPSH","startDate":"2008-01-13T23:00:00Z","published":false,
120         *                      "Description":"Human study performed at RRI; centres involved: RRI, IFR, TUM, Maastricht U.","Objectives":null,
121         *                      "Consortium":null,"Cohort name":null,"Lab id":null,"Institute":null,"Study protocol":null}]
122         *
123         *
124         * Example 2. REST call with one studyToken.
125         *
126         * Call: http://localhost:8080/gscf/rest/getStudies/query?studyToken=PPSH
127         *
128         * Result: [{"title":"NuGO PPS human study","studyToken":"PPSH","startDate":"2008-01-13T23:00:00Z",
129         *              "published":false,"Description":"Human study performed at RRI; centres involved: RRI, IFR, TUM, Maastricht U.",
130         *              "Objectives":null,"Consortium":null,"Cohort name":null,"Lab id":null,"Institute":null,"Study protocol":null}]
131         *
132         *
133         *
134         * Example 2. REST call with two studyTokens.
135         *
136         * http://localhost:8080/gscf/rest/getStudies/query?studyToken=PPSH&studyToken=PPS3_leptin_module
137         *
138         * Result: same as result of Example 1.
139         */
140        def getStudies = {
141                def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
142               
143                List returnStudies = []
144                List studies = []
145
146                if( !params.studyToken ) {
147                        studies = Study.findAll()
148                }
149                else if( params.studyToken instanceof String ) {
150                        def study = Study.findByStudyUUID( params.studyToken )
151                        if( study ) {
152                                if( !study.canRead(user) ) {
153                                        response.sendError(401)
154                                        return false
155                                }
156
157                                studies.push study
158                        } else {
159                                response.sendError(404)
160                                return false
161                        }
162
163                }
164                else {
165                        params.studyToken.each{ studyToken ->
166                                def study = Study.findByStudyUUID( studyToken );
167                                if( study )
168                                        studies.push study
169                        }
170                }
171
172
173                studies.each { study ->
174                        if(study) {
175                                // Check whether the person is allowed to read the data of this study
176                                if( study.canRead(user)) {
177
178                                        def items = [studyToken:study.giveUUID(), 'public': study.publicstudy]
179                                        study.giveFields().each { field ->
180                                                def name = field.name
181                                                def value = study.getFieldValue( name )
182                                                items[name] = value
183                                        }
184
185                                        // Add study version number
186                                        items['version'] = study.version;
187
188                                        returnStudies.push items
189                                }
190                        }
191                }
192
193                // set output header to json
194                response.contentType = 'application/json'
195
196                render returnStudies as JSON
197        }
198
199        /**
200         * REST resource for data modules.
201         * Consumer and token should be supplied via URL parameters.
202         * Provides the version number of the specified study
203         *
204         * @param       studyToken  optional parameter. If no studyToken is given, a 400 error is given
205         * @param       consumer        consumer name of the calling module
206         * @param       token           token for the authenticated user (e.g. session_id)
207         * @return  JSON object list containing 'studyToken', and 'version'
208         *
209         * A 404 error might occur if the study doesn't exist, and a 401 error if the user is not
210         * authorized to access this study.
211         *
212         * Example. REST call with one studyToken.
213         *
214         * Call: http://localhost:8080/gscf/rest/getStudyVersion?studyToken=PPSH
215         *
216         * Result: {"studyToken":"PPSH","version":31}
217         */
218        def getStudyVersion = {
219                def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
220               
221                def versionInfo = [:];
222                def study
223
224                if( !params.studyToken || !(params.studyToken instanceof String)) {
225                        response.sendError(400)
226                        return false
227                } else {
228                        study = Study.findByStudyUUID( params.studyToken )
229                        if( study ) {
230                                if( !study.canRead(user) ) {
231                                        response.sendError(401)
232                                        return false
233                                }
234                        } else {
235                                response.sendError(404)
236                                return false
237                        }
238                }
239
240                versionInfo[ 'studyToken' ] = params.studyToken;
241                versionInfo[ 'version' ] = study.version;
242
243                // set output header to json
244                response.contentType = 'application/json'
245
246                render versionInfo as JSON
247        }
248
249        /**
250        * REST resource for data modules.
251        * Consumer and token should be supplied via URL parameters.
252        * Provides the version number of all studies readable by this user
253        *
254        * @param        consumer        consumer name of the calling module
255        * @param        token           token for the authenticated user (e.g. session_id)
256        * @return  JSON object list containing studies with 'studyToken', and 'version'
257        *
258        * A 404 error might occur if the study doesn't exist, and a 401 error if the user is not
259        * authorized to access this study.
260        *
261        * Example. REST call with one studyToken.
262        *
263        * Call: http://localhost:8080/gscf/rest/getStudyVersions
264        *
265        * Result: [{"studyToken":"PPSH","version":31},{"studyToken":"Other study", "version":3}]
266        */
267   def getStudyVersions = {
268           // Check which user has been logged in
269           def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
270           
271           def jsonList = []
272           
273                Study.giveReadableStudies( user ).each { study ->
274                        if(study) {
275                                jsonList << [studyToken:study.giveUUID(), version: study.version]
276                        }
277                }
278
279                // set output header to json
280                response.contentType = 'application/json'
281
282                render jsonList as JSON
283   }
284
285       
286        /**
287         * REST resource for data modules.
288         * Consumer and token should be supplied via URL parameters.
289         * Provide a list of all subjects belonging to a study.
290         *
291         * If the user is not allowed to read the study contents, a 401 error is given. If the study doesn't exist, a 404 error is given
292         *
293         * @param       studyToken      String The external study id (code) of the target GSCF Study object
294         * @param       consumer        consumer name of the calling module
295         * @param       token           token for the authenticated user (e.g. session_id)
296         * @return JSON object list of subject names
297         */
298        def getSubjects = {
299                // Check which user has been logged in
300                def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
301 
302                List subjects = []
303                if( params.studyToken ) {
304                        def study = Study.findByStudyUUID( params.studyToken)
305
306                        if(study) {
307                                // Check whether the person is allowed to read the data of this study
308                                if( !study.canRead(user) ) {
309                                        response.sendError(401)
310                                        return false
311                                }
312
313                                study.subjects.each { subjects.push it.name }
314                        } else {
315                                response.sendError(404)
316                                return false
317                        }
318                }
319
320                // set output header to json
321                response.contentType = 'application/json'
322
323                render subjects as JSON
324        }
325
326
327        /**
328         * REST resource for data modules.
329         * Consumer and token should be supplied via URL parameters.
330         * Provide a list of all assays for a given study.
331         *
332         * If the user is not allowed to read the study contents, a 401 error is given. If the study doesn't exist, a 404 error is given
333         *
334         * @param       studyToken      String The external study id (code) of the target GSCF Study object
335         * @param       consumer        consumer name of the calling module
336         * @return list of assays in the study as JSON object list, filtered to only contain assays
337         *         for the specified module, with 'assayToken' and 'name' for each assay
338         *
339         *
340         * Example 1. REST call without assayToken
341         *            http://localhost:8080/gscf/rest/getAssays/aas?studyToken=PPSH
342         *                              &consumer=http://localhost:8182/sam
343         *
344         * Result: [{"name":"Glucose assay after",
345         *                      "module":{"class":"dbnp.studycapturing.AssayModule","id":1,"name":"SAM module for clinical data",
346         *                              "platform":"clinical measurements","url":"http://localhost:8182/sam"},
347         *                      "externalAssayID":"PPSH-Glu-A", "Description":null,"parentStudyToken":"PPSH"},
348         *                      {"name":"Glucose assay before",
349         *                              "module":{"class":"dbnp.studycapturing.AssayModule","id":1,"name":"SAM module for clinical data",
350         *                              "platform":"clinical measurements","url":"http://localhost:8182/sam"},
351         *                              "externalAssayID":"PPSH-Glu-B","Description":null,"parentStudyToken":"PPSH"}]
352         *
353         *
354         * Example 2. REST call with one assayToken
355         *                        http://localhost:8080/gscf/rest/getAssays/queryOneTokenz?studyToken=PPSH
356         *                              &consumer=http://localhost:8182/sam&assayToken=PPSH-Glu-A
357         *
358         * Result: [{"name":"Glucose assay after","module":{"class":"dbnp.studycapturing.AssayModule","id":1,
359         *                      "name":"SAM module for clinical data","platform":"clinical measurements","url":"http://localhost:8182/sam"},
360         *                      "externalAssayID":"PPSH-Glu-A","Description":null,"parentStudyToken":"PPSH"}]
361         *
362         *
363         * Example 3. REST call with two assayTokens.
364         *
365         * Result: Same as result in Example 1.
366         */
367        def getAssays = {
368                // Check which user has been logged in
369                def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
370 
371                // set output header to json
372                response.contentType = 'application/json'
373
374                List returnList = []    // return list of hashes each containing fields and values belonging to an assay
375
376                // Check if required parameters are present
377                def validCall = CommunicationManager.hasValidParams( params, "consumer" )
378                if( !validCall ) {
379                        response.status = 500;
380                        render "Error. Wrong or insufficient parameters." as JSON
381                        return
382                }
383               
384                def assays = []
385               
386                if( params.studyToken ) {
387
388                        def study = Study.findByStudyUUID(params.studyToken)
389
390                        if(study) {
391                                // Check whether the person is allowed to read the data of this study
392                                if( !study.canRead(user)) {
393                                        response.sendError(401)
394                                        return false
395                                }
396
397                                if(params.assayToken==null) {
398                                        assays = study.assays
399                                }
400                                else if( params.assayToken instanceof String ) {
401                                        def assay = study.assays.find{ it.giveUUID() == params.assayToken }
402                                        if( assay ) {
403                                                assays.push assay
404                                        }
405                                }
406                                else {                                                                                                  // there are multiple assayTokens instances
407                                        params.assayToken.each { assayToken ->
408                                                def assay = study.assays.find{ it.giveUUID() == assayToken }
409                                                if(assay) {
410                                                        assays.push assay
411                                                }
412                                        }
413                                }
414
415                        } else {
416                                response.sendError(404)
417                                return false
418                        }
419
420                } else {
421                        // Return all assays for the given module
422                        assays = Assay.list().findAll{ it.parent.canRead( user ) }
423                }
424
425                // Create data for all assays
426                assays.each{ assay ->
427                        if (assay.module?.url && assay.module.url.equals(params.moduleURL)) {
428                                if(assay) {
429                                        def map = [assayToken : assay.giveUUID()]
430                                        assay.giveFields().each { field ->
431                                                def name = field.name
432                                                def value = assay.getFieldValue( name )
433                                                map[name] = value
434                                        }
435                                        map["parentStudyToken"] = assay.parent.giveUUID()
436                                        returnList.push( map )
437                                }
438                        }
439                }
440
441                render returnList as JSON
442        }
443
444        /**
445         * REST resource for data modules.
446         * Provide all samples of a given Assay. The result is an enriched list with additional information for each sample.
447         *
448         * If the user is not allowed to read the study contents, a 401 error is given. If the assay doesn't exist, a 404 error is given
449         *
450         * @param       assayToken      String (assayToken of some Assay in GSCF)
451         * @param       sampleToken Optional parameter. One or more sampleTokens to specify what sample to give exectly.
452         *                      If not given, return all samples for specified assay.
453         * @param       consumer        consumer name of the calling module
454         * @param       token           token for the authenticated user (e.g. session_id)
455         * @return As a JSON object list, for each sample in that assay:
456         * @return 'name' (Sample name, which is unique)
457         * @return 'material' (Sample material)
458         * @return 'subject' (The name of the subject from which the sample was taken)
459         * @return 'event' (the name of the template of the SamplingEvent describing the sampling)
460         * @return 'startTime' (the time the sample was taken relative to the start of the study, as a string)
461         * @return additional template fields are returned
462         *
463         *
464         *
465         * Example 1: no sampleTokens given.
466         * Query:
467         * http://localhost:8080/gscf/rest/getSamples/query?assayToken=PPSH-Glu-A
468         *
469         * Result:
470         * [{"sampleToken":"5_A","material":"blood plasma","subject":"5","event":"Blood extraction","startTime":"4 days, 6 hours"},
471         * {"sampleToken":"6_A","mateauthenticationService.getRemotelyLoggedInUser( params.consumer, params.token )rial":"blood plasma","subject":"6","event":"Blood extraction","startTime":"4 days, 6 hours"},
472         * {"sampleToken":"10_A","material":"blood plasma","subject":"10","event":"Blood extraction","startTime":"4 days, 6 hours"},
473         * {"sampleToken":"2_A","material":"blood plasma","subject":"2","event":"Blood extraction","startTime":"4 days, 6 hours"},
474         * {"sampleToken":"11_A","material":"blood plasma","subject":"11","event":"Blood extraction","startTime":"4 days, 6 hours"},
475         * {"sampleToken":"1_A","material":"blood plasma","subject":"1","event":"Blood extraction","startTime":"4 days, 6 hours"},
476         * {"sampleToken":"9_A","material":"blood plasma","subject":"9","event":"Blood extraction","startTime":"4 days, 6 hours"},
477         * {"sampleToken":"4_A","material":"blood plasma","subject":"4","event":"Blood extraction","startTime":"4 days, 6 hours"},
478         * {"sampleToken":"8_A","material":"blood plasma","subject":"8","event":"Blood extraction","startTime":"4 days, 6 hours"},
479         * {"sampleToken":"7_A","material":"blood plasma","subject":"7","event":"Blood extraction","startTime":"4 days, 6 hours"},
480         * {"sampleToken":"3_A","material":"blood plasma","subject":"3","event":"Blood extraction","startTime":"4 days, 6 hours"}]
481         *
482         *
483         *
484         * Example 2: one sampleToken given.
485         * Query:
486         * http://localhost:8080/gscf/rest/getSamples/query?assayToken=PPSH-Glu-A&sampleToken=5_A
487         *
488         * Result:
489         * [{"sampleToken":"5_A","material":"blood plasma","subject":"5","event":"Blood extraction","startTime":"4 days, 6 hours"}]
490         *
491         *
492         *
493         * Example 3: two sampleTokens given.
494         * Query:
495         * http://localhost:8080/gscf/rest/getSamples/query?assayToken=PPSH-Glu-A&sampleToken=5_A&sampleToken=6_A
496         *
497         * Result:
498         * [{"sampleToken":"5_A","material":"blood plasma","subject":"5","event":"Blood extraction","startTime":"4 days, 6 hours"},
499         *  {"sampleToken":"6_A","material":"blood plasma","subject":"6","event":"Blood extraction","startTime":"4 days, 6 hours"}]
500         *
501         *
502         * Example 4: no assaytoken given
503         * Query:
504         * http://localhost:8080/gscf/rest/getSamples/query?sampleToken=5_A&sampleToken=6_A
505         *
506         * Result:
507         * [{"sampleToken":"5_A","material":"blood plasma","subject":"5","event":"Blood extraction","startTime":"4 days, 6 hours"},
508         *  {"sampleToken":"6_A","material":"blood plasma","subject":"6","event":"Blood extraction","startTime":"4 days, 6 hours"}]
509         *
510         */
511        def getSamples = {
512                // Check which user has been logged in
513                def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token )
514
515                def items = []
516                def samples
517                if( params.assayToken ) {
518                        def assay = Assay.findByAssayUUID( params.assayToken );
519
520                        if( assay )  {
521                                // Check whether the person is allowed to read the data of this study
522                                if( !assay.parent.canRead( user )) {
523                                        response.sendError(401)
524                                        return false
525                                }
526
527                                samples = assay.getSamples() // on all samples
528                        } else {
529                                // Assay not found
530                                response.sendError(404)
531                                return false
532                        }
533                } else {
534                        // Find all samples from studies the user can read
535                        def studies = Study.list().findAll { it.canRead( user ) };
536                        samples = studies*.getSamples().flatten();
537                }
538
539                // Check whether only a subset of samples should be returned
540                if( params.sampleToken ) {
541                        def sampleTokens = params.list( "sampleToken" );
542                        samples = samples.findAll { sampleTokens.contains( it.giveUUID() ) }
543                }
544
545                samples.each { sample ->
546
547                        def item = [
548                                                'sampleToken' : sample.giveUUID(),
549                                                'material'        : sample.material?.name,
550                                                'subject'         : sample.parentSubject?.name,
551                                                'event'           : sample.parentEvent?.template?.name,
552                                                'startTime'       : sample.parentEvent?.getStartTimeString()
553                                        ]
554
555                        sample.giveFields().each { field ->
556                                def name = field.name
557                                def value = sample.getFieldValue( name )
558                                if(name!='material')
559                                {
560                                        item[name]=value
561                                }
562                        }
563
564                        if(sample.parentEvent) {
565                                def parentEvent = sample.parentEvent
566                                def eventHash = [:]
567                                parentEvent.giveFields().each { field ->
568                                        def name = field.name
569                                        if( name !='sampleTemplate' && name != 'fields') {
570                                                def value = parentEvent.getFieldValue( name )
571                                                eventHash[name]=value
572                                        }
573                                }
574                                item['eventObject'] = eventHash
575                        }
576
577                        if(sample.parentSubject) {
578                                def parentSubject = sample.parentSubject
579                                def subject = [:]
580                                parentSubject.giveFields().each { field ->
581                                        def name = field.name
582                                        if( name!='fields') {
583                                                def value = parentSubject.getFieldValue( name )
584                                                subject[name]=value
585                                        }
586                                }
587                                item['subjectObject'] = subject
588                        }
589
590                        items.push item
591                }
592
593                // set output header to json
594                response.contentType = 'application/json'
595
596                render items as JSON
597        }
598
599        /**
600         * Returns the authorization level the user has for a given study or assay.
601         *
602         * If no studyToken or assayToken is given, a 400 (Bad Request) error is given.
603         * If both a studyToken and assayToken are given, the studyToken is used and the assayToken is ignored.
604         * If the given assay or study doesn't exist, a 404 (Not found) error is given.
605         *
606         * @param       consumer        consumer name of the calling module
607         * @param       token           token for the authenticated user (e.g. session_id)
608         * @param       studyToken      token of the study for which the authorization is asked
609         * @param       assayToken      token of the study for which the authorization is asked
610         * @return      JSON Object
611         * @return  { isOwner: true/false, 'canRead': true/false, 'canWrite': true/false }
612         */
613        def getAuthorizationLevel = {
614                def study
615               
616                if( params.studyToken ) {
617                        study = Study.findByStudyUUID(params.studyToken);
618                } else if( params.assayToken ) {
619                        study = Assay.findByAssayUUID(params.assayToken)?.parent;
620                } else {
621                        response.sendError(400)
622                        return false
623                }
624
625                if( !study ) {
626                        response.sendError(404)
627                        return false
628                }
629
630                def user = authenticationService.getRemotelyLoggedInUser( params.consumer, params.token );
631                def auth = ['isOwner': study.isOwner(user), 'canRead': study.canRead(user), 'canWrite': study.canWrite(user)];
632                log.trace "Authorization for study " + study.title + " and user " + user?.username + ": " + auth
633
634                // set output header to json
635                response.contentType = 'application/json'
636
637                render auth as JSON;
638        }
639}
Note: See TracBrowser for help on using the repository browser.