source: trunk/grails-app/controllers/dbnp/studycapturing/AssayController.groovy

Last change on this file was 2162, checked in by m.s.vanvliet@…, 8 years ago

Added option to include/exclude the measurement metadata on exporting

  • Property svn:keywords set to Rev Author Date
File size: 15.5 KB
Line 
1package dbnp.studycapturing
2
3import grails.plugins.springsecurity.Secured
4import grails.converters.JSON
5
6@Secured(['IS_AUTHENTICATED_REMEMBERED'])
7class AssayController {
8
9        def assayService
10        def authenticationService
11        def fileService
12
13        def showByToken = {
14                def assayInstance = Assay.findByAssayUUID(params.id)
15                if (!assayInstance) {
16                        flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'assay.label', default: 'Assay'), params.id])}"
17                        redirect(action: "list")
18                }
19                else {
20                        redirect(action: "show", id: assayInstance.id)
21                }
22        }
23
24        def assayExportFlow = {
25                entry {
26                        action{
27                                def user            = authenticationService.getLoggedInUser()
28                                flow.userStudies    = Study.giveReadableStudies(user)
29
30                                // store Galaxy parameters in the flow. Will be null when not in galaxy mode.
31                                flow.GALAXY_URL = params.GALAXY_URL
32                                flow.tool_id = params.tool_id
33                        }
34                        on("success").to "selectAssay"
35                }
36
37                selectAssay {
38                        on ("submit"){
39                                flow.assay = Assay.get(params.assayId)
40
41                                // check if assay exists
42                                if (!flow.assay) throw new Exception("No assay found with id: ${params.assayId}")
43
44                                // obtain fields for each category
45                                flow.fieldMap = assayService.collectAssayTemplateFields(flow.assay, null)
46
47                                flash.errorMessage = flow.fieldMap.remove('Module Error')
48                                flow.measurementTokens = flow.fieldMap.remove('Module Measurement Data')
49                        }.to "selectFields"
50
51                        on(Exception).to "handleError"
52                }
53
54                selectFields {
55                        on ("submit"){
56
57                                def (fieldMapSelection, measurementTokens) = processExportSelection(flow.assay, flow.fieldMap, params)
58
59                                // interpret the params set and gather the data
60                                flow.rowData = collectAssayData(flow.assay, fieldMapSelection, measurementTokens, flow.assay.samples)
61                               
62                                // save the measurementTokes to the session for use later
63                                session.measurementTokens = measurementTokens
64
65                                // remember the selected file type
66                                flow.exportFileType = params.exportFileType
67                               
68                                // remember is export metadata was selected
69                                flow.exportMetadata = params.exportMetadata
70
71                                // prepare the assay data preview
72                                def previewRows         = Math.min(flow.rowData.size()    as int, 5) - 1
73                                def previewCols         = Math.min(flow.rowData[0].size() as int, 5) - 1
74
75                                flow.assayDataPreview   = flow.rowData[0..previewRows].collect{ it[0..previewCols] as ArrayList }
76
77                        }.to "compileExportData"
78
79                        on("submitToGalaxy") {
80
81                                def (fieldMapSelection, measurementTokens) = processExportSelection(flow.assay, flow.fieldMap, params)
82
83                                // create a random session token that will be used to allow to module to
84                                // sync with gscf prior to presenting the measurement data
85                                def sessionToken = UUID.randomUUID().toString()
86                                def consumer = "galaxy"
87
88                                // put the session token to work
89                                authenticationService.logInRemotely( consumer, sessionToken, authenticationService.getLoggedInUser() )
90
91                                // create a link to the galaxy fetch action where galaxy can fetch the data from
92                                flow.fetchUrl = g.createLink(
93                                        absolute: true,
94                                        controller: "assay",
95                                        action: "fetchGalaxyData",
96                                        params: [
97                                                assayToken: flow.assay.assayUUID,
98                                                sessionToken: sessionToken,
99                                                fieldMapSelection: fieldMapSelection as JSON,
100                                                measurementTokens: measurementTokens as JSON] )
101
102                        }.to "galaxySubmitPage"
103
104                        on(Exception).to "handleError"
105                }
106
107                compileExportData {
108                        on ("ok"){
109                                session.assay = flow.assay
110                                session.rowData = flow.rowData
111                                session.exportFileType = flow.exportFileType
112                                session.exportMetadata = flow.exportMetadata
113
114                        }.to "export"
115                        on ("cancel").to "selectAssay"
116                }
117
118                export {
119                        redirect(action: 'doExport')
120                }
121
122                handleError {
123                        render(view: 'errorPage')
124                }
125
126                // renders a page that directly POSTs a form to galaxy
127                galaxySubmitPage()
128
129        }
130
131        def processExportSelection(assay, fieldMap, params) {
132
133                def fieldMapSelection = [:]
134
135                fieldMap.eachWithIndex { category, categoryIndex ->
136
137                        if (params."cat_$categoryIndex" == 'on') {
138                                fieldMapSelection[category.key] = []
139
140                                category.value.eachWithIndex { field, field_i ->
141
142                                if (params."cat_${categoryIndex}_${field_i}" == 'on')
143                                        fieldMapSelection[category.key] += field
144                                }
145
146                                if (fieldMapSelection[category.key] == [])
147                                        fieldMapSelection.remove(category.key)
148                        }
149                }
150
151                def measurementTokens = []
152
153                if (params."cat_4" == 'on') {
154                        measurementTokens = params.list( "measurementToken" )
155                }
156
157                [fieldMapSelection, measurementTokens]
158        }
159
160        def collectAssayData(assay, fieldMapSelection, measurementTokens, samples, remoteUser = null) {
161                // collect the assay data according to user selection
162                def assayData           = assayService.collectAssayData(assay, fieldMapSelection, measurementTokens, samples, remoteUser)
163
164                flash.errorMessage      = assayData.remove('Module Error')
165
166                assayService.convertColumnToRowStructure(assayData)
167        }
168
169        // This method is accessible for each user. However, he should return with a valid
170        // session token
171        @Secured(['true'])
172        def fetchGalaxyData = {
173
174                def fieldMapSelection = JSON.parse((String) params.fieldMapSelection)
175                def measurementTokens = JSON.parse((String) params.measurementTokens)
176
177                // Check accessibility
178                def consumer = "galaxy"
179                def remoteUser = authenticationService.getRemotelyLoggedInUser( consumer, params.sessionToken )
180                if( !remoteUser ) {
181                        response.status = 401
182                        render "You must be logged in"
183                        return
184                }
185
186                // retrieve assay
187                def assay = Assay.findByAssayUUID( params.assayToken )
188
189                if( !assay ) {
190                        response.status = 404
191                        render "No assay found"
192                        return
193                }
194
195                def rowData = collectAssayData(assay, fieldMapSelection, measurementTokens, [], remoteUser)
196
197                // Invalidate session token
198                authenticationService.logOffRemotely( consumer, params.sessionToken )
199
200                def outputDelimiter = '\t'
201                def outputFileExtension = 'txt'
202
203                def filename = "export.$outputFileExtension"
204                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
205                response.setContentType("application/octet-stream")
206                try {
207                        // Skip first row for now to support the current PLS tool in Galaxy, will change in the future
208                        assayService.exportRowWiseDataToCSVFile(rowData[1..-1], response.outputStream, outputDelimiter, java.util.Locale.US)
209                } catch (Exception e) {
210                        flash.errorMessage = e.message
211                        redirect action: 'errorPage'
212                }
213        }
214
215        /**
216         * Export the row data in session.rowData to the outputStream of the http
217         * response.
218         */
219        def doExport = {
220
221                // make sure we're coming from the export flow, otherwise redirect there
222                if (!(session.rowData && session.exportFileType))
223                        redirect(action: 'assayExportFlow')
224                       
225                def remoteUser = authenticationService.getLoggedInUser()
226                if( !remoteUser ) {
227                        response.status = 401
228                        render "You must be logged in"
229                        return
230                }
231
232                // process requested output file type
233                def outputDelimiter, outputFileExtension, locale = java.util.Locale.US
234
235                switch(session.exportFileType) {
236                        case '2': // Comma delimited csv
237                                outputDelimiter = ','
238                                outputFileExtension = 'csv'
239                                break
240                        case '3': // Semicolon delimited csv
241                                outputDelimiter = ';'
242                                outputFileExtension = 'csv'
243                                locale = java.util.Locale.GERMAN // force use of comma as decimal separator
244                                break
245                        default: // Tab delimited with .txt extension
246                                outputDelimiter = '\t'
247                                outputFileExtension = 'txt'
248                }
249
250                def filename = "export.$outputFileExtension"
251                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
252                response.setContentType("application/octet-stream")
253                try {
254                       
255                        if (session.exportMetadata == '1'){
256                                //merge data with metadata if possible
257                                def metadata = assayService.requestModuleMeasurementMetaDatas(session.assay, session.measurementTokens, remoteUser) ?: null
258                                session.rowData = assayService.mergeModuleDataWithMetadata(session.rowData, metadata)
259                        }
260                               
261                        assayService.exportRowWiseDataToCSVFile(session.rowData, response.outputStream, outputDelimiter, locale)
262
263                        // clear the data from the session
264                        session.removeAttribute('rowData')
265                        session.removeAttribute('measurementTokens')
266                        session.removeAttribute('exportFileType')
267                        session.removeAttribute('exportMetadata')
268
269                } catch (Exception e) {
270
271                        flash.errorMessage = e.message
272                        redirect action: 'errorPage'
273
274                }
275        }
276
277        /**
278         * Method to export one or more assays to excel in separate sheets.
279         *
280         * @param       params.ids              One or more assay IDs to export
281         * @param       params.format   "list" in order to export all assays in one big excel sheet
282         *                                                      "sheets" in order to export every assay on its own sheet (default)
283         */
284        def exportToExcel = {
285                def format = params.get( 'format', 'sheets' );
286                if( format == 'list' ) {
287                        exportToExcelAsList( params );
288                } else {
289                        exportToExcelAsSheets( params );
290                }
291        }
292
293        /**
294         * Method to export one or more assays to excel in separate sheets.
295         *
296         * @param       params.ids              One or more assay IDs to export
297         */
298        def exportToExcelAsSheets = {
299                def assays = getAssaysFromParams( params );
300
301                if( !assays )
302                        return;
303
304                // Send headers to the browser so the user can download the file
305                def filename = 'export.xlsx'
306                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
307                response.setContentType("application/octet-stream")
308
309                try {
310                        // Loop through all assays to collect the data
311                        def rowWiseAssayData = [];
312
313                        assays.each { assay ->
314                                // Determine which fields should be exported for this assay
315                                def fieldMap = assayService.collectAssayTemplateFields(assay, null)
316                                def measurementTokens = fieldMap.remove('Module Measurement Data')
317
318                                // Retrieve row based data for this assay
319                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, [] );
320                                def rowData   = assayService.convertColumnToRowStructure(assayData)
321
322                                // Put each assay on another sheet
323                                rowWiseAssayData << rowData;
324                        }
325
326                        assayService.exportRowWiseDataForMultipleAssaysToExcelFile( rowWiseAssayData, response.getOutputStream() )
327
328                        response.outputStream.flush()
329
330                } catch (Exception e) {
331                        throw e;
332                }
333        }
334
335        /**
336         * Method to export one or more assays to excel.
337         *
338         * @param       params.ids              One or more assay IDs to export
339         */
340        def exportToExcelAsList = {
341                def assays = getAssaysFromParams( params );
342
343                if( !assays )
344                        return;
345
346                // Send headers to the browser so the user can download the file
347                def filename = 'export.csv'
348                response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
349                response.setContentType("application/octet-stream")
350
351                try {
352                        // Loop through all assays to collect the data
353                        def columnWiseAssayData = [];
354
355                        assays.each { assay ->
356                                // Determine which fields should be exported for this assay
357                                def fieldMap = assayService.collectAssayTemplateFields(assay, null)
358                                def measurementTokens = fieldMap.remove('Module Measurement Data')
359
360                                // Retrieve row based data for this assay
361                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, [] );
362
363                                // Prepend study and assay data to the list
364                                assayData = assayService.prependAssayData( assayData, assay, assay.samples?.size() )
365                                assayData = assayService.prependStudyData( assayData, assay, assay.samples?.size() )
366
367                                // Put each assay on another sheet
368                                columnWiseAssayData << assayData;
369                        }
370
371                        // Merge data from all assays
372                        def mergedColumnWiseData = assayService.mergeColumnWiseDataOfMultipleStudies( columnWiseAssayData );
373
374                        def rowData   = assayService.convertColumnToRowStructure(mergedColumnWiseData)
375                        assayService.exportRowWiseDataToCSVFile( rowData, response.getOutputStream() )
376
377                        response.outputStream.flush()
378
379                } catch (Exception e) {
380                        throw e;
381                }
382        }
383
384        /**
385         * Method to export one or more samples to csv in separate sheets.
386         *
387         * @param       params.ids              One or more sample ids to export
388         */
389        def exportSamplesToCsv = {
390                def samples = getSamplesFromParams( params );
391
392                if( !samples ) {
393                        return;
394                }
395
396                // Determine a list of assays these samples have been involved in. That way, we can
397                // retrieve the data for that assay once, and save precious time doing HTTP calls
398                def assays = [:];
399
400                samples.each { sample ->
401                        def thisAssays = sample.getAssays();
402
403                        // Loop through all assays. If it already exists, add the sample it to the list
404                        thisAssays.each { assay ->
405                                if( !assays[ assay.id ] ) {
406                                        assays[ assay.id ] = [ 'assay': assay, 'samples': [] ]
407                                }
408
409                                assays[ assay.id ].samples << sample
410                        }
411                }
412
413                // Now collect data for all assays
414                try {
415                        // Loop through all assays to collect the data
416                        def columnWiseAssayData = [];
417
418                        assays.each { assayInfo ->
419                                def assay = assayInfo.value.assay;
420                                def assaySamples = assayInfo.value.samples;
421
422                                // Determine which fields should be exported for this assay
423                                def fieldMap = assayService.collectAssayTemplateFields(assay, null)
424                                def measurementTokens = fieldMap.remove('Module Measurement Data')
425
426                                // Retrieve row based data for this assay
427                                def assayData = assayService.collectAssayData( assay, fieldMap, measurementTokens, assaySamples );
428
429                                // Prepend study and assay data to the list
430                                assayData = assayService.prependAssayData( assayData, assay, assaySamples.size() )
431                                assayData = assayService.prependStudyData( assayData, assay, assaySamples.size() )
432
433                                // Make sure the assay data can be distinguished later
434                                assayData.put( "Assay data - " + assay.name, assayData.remove( "Assay Data") )
435                                assayData.put( "Module measurement data - " + assay.name, assayData.remove( "Module Measurement Data") )
436
437                                // Add the sample IDs to the list, in order to be able to combine
438                                // data for a sample that has been processed in multiple assays
439                                assayData[ "Sample Data" ][ "id" ] = assaySamples*.id;
440
441                                columnWiseAssayData << assayData;
442                        }
443
444                        def mergedColumnWiseData = assayService.mergeColumnWiseDataOfMultipleStudiesForASetOfSamples( columnWiseAssayData );
445
446                        def rowData   = assayService.convertColumnToRowStructure(mergedColumnWiseData)
447
448                        // Send headers to the browser so the user can download the file
449                        def filename = 'export.csv'
450                        response.setHeader("Content-disposition", "attachment;filename=\"${filename}\"")
451                        response.setContentType("application/octet-stream")
452
453                        assayService.exportRowWiseDataToCSVFile( rowData, response.getOutputStream() )
454
455                        response.outputStream.flush()
456
457                } catch (Exception e) {
458                        throw e;
459                }
460        }
461
462
463        def getAssaysFromParams( params ) {
464                def ids = params.list( 'ids' ).findAll { it.isLong() }.collect { Long.valueOf( it ) };
465                def tokens = params.list( 'tokens' );
466
467                if( !ids && !tokens ) {
468                        flash.errorMessage = "No assay ids given";
469                        redirect( action: "errorPage" );
470                        return [];
471                }
472
473                // Find all assays for the given ids
474                def assays = [];
475                ids.each { id ->
476                        def assay = Assay.get( id );
477                        if( assay )
478                                assays << assay;
479                }
480
481                // Also accept tokens for defining studies
482                tokens.each { token ->
483                        def assay = Assay.findByAssayUUID( token );
484                        if( assay )
485                                assays << assay;
486                }
487
488                if( !assays ) {
489                        flash.errorMessage = "No assays found";
490                        redirect( action: "errorPage" );
491                        return [];
492                }
493
494                return assays.unique();
495        }
496
497        def getSamplesFromParams( params ) {
498                def ids = params.list( 'ids' ).findAll { it.isLong() }.collect { Long.valueOf( it ) };
499                def tokens = params.list( 'tokens' );
500
501                if( !ids && !tokens ) {
502                        flash.errorMessage = "No sample ids given";
503                        redirect( action: "errorPage" );
504                        return [];
505                }
506
507                // Find all assays for the given ids
508                def samples = [];
509                ids.each { id ->
510                        def sample = Sample.get( id );
511                        if( sample )
512                                samples << sample;
513                }
514
515                // Also accept tokens for defining studies
516                tokens.each { token ->
517                        def sample = Sample.findBySampleUUID( token );
518                        if( sample )
519                                samples << sample;
520                }
521
522                if( !samples ) {
523                        flash.errorMessage = "No assays found";
524                        redirect( action: "errorPage" );
525                        return [];
526                }
527
528                return samples.unique();
529        }
530
531        def errorPage = {
532                render(view: 'assayExport/errorPage')
533        }
534}
Note: See TracBrowser for help on using the repository browser.