root/trunk/grails-app/controllers/RestController.groovy @ 1579

Revision 1579, 19.9 KB (checked in by robert@…, 3 years ago)

Using params.moduleURL instead of params.consumer for checking the assay module

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