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

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