source: trunk/grails-app/controllers/nl/tno/metagenomics/AssayController.groovy @ 13

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

Improved user interface and implemented basic export functionality

File size: 10.2 KB
Line 
1package nl.tno.metagenomics
2
3import org.codehaus.groovy.grails.commons.ConfigurationHolder
4
5class AssayController {
6        def synchronizationService
7        def gscfService
8        def fuzzySearchService
9
10        def fileService
11        def excelService
12        def sampleExcelService
13        def fastaService
14
15        def index = {
16                // Filter studies for the ones the user is allowed to see
17                def studies = Study.list();
18                [studies: studies.findAll { it.canRead( session.user ) },
19                        gscfAddUrl: gscfService.urlAddStudy() ]
20        }
21       
22        def show = {
23                def assay = getAssay( params.id );
24                if( !assay )
25                        return
26
27                // Make sure the newest data is available
28                synchronizationService.sessionToken = session.sessionToken
29                synchronizationService.synchronizeAssay( assay );
30
31                // Determine runs not used in this assay
32                def otherRuns = Run.list( sort: "name" ).findAll { !it.assays.contains( assay ) }
33
34                // Send the assay information to the view
35                [assay: assay, editable: assay.study.canWrite( session.user ), otherRuns: otherRuns]
36        }
37
38        def showByToken = {
39                // load study with token specified by param.id
40                def assay = Assay.findByAssayToken(params.id as String)
41
42                if (!assay) {
43                        // Initialize synchronizationService
44                        synchronizationService.sessionToken = session.sessionToken
45                        synchronizationService.user = session.user
46
47                        // If the assay is not found, synchronize studies and try again
48                        synchronizationService.synchronizeStudies();
49
50                        assay = Assay.findByAssayToken(params.id as String)
51
52                        if (!assay) {
53                                // If the assay is not found, synchronize all studies and try again, since we might be out of sync
54                                synchronizationService.eager = true
55                                synchronizationService.synchronizeStudies();
56
57                                assay = Assay.findByAssayToken(params.id as String)
58
59                                // If after synchronization still no assay is found, show an error
60                                if( !assay ) {
61                                        flash.message = "No assay found with token: $params.id"
62                                        redirect(controller: 'study')
63                                }
64                        }
65                }
66
67                redirect( action: "show", id: assay.id );
68        }
69
70        /**************************************************************************
71         *
72         * Method for handling data about samples for this assay
73         *
74         *************************************************************************/
75
76        /**
77         * Downloads an excel sheet with data about the assay samples, to enter data in excel
78         */
79        def downloadTagsExcel = {
80                // load study with id specified by param.id
81                def assay = getAssay( params.id );
82                if( !assay )
83                        return
84
85                def filename = assay.study.name + "_" + assay.name + "_tags.xls"
86                def wb = sampleExcelService.downloadSampleExcel( assay.assaySamples );
87
88                // Make file downloadable
89                log.trace( "Creation for downloading the file " + filename )
90                sampleExcelService.excelService.downloadFile( wb, filename, response )
91        }
92
93        /**
94         * Parses an uploaded excel file and shows a form to match columns
95         */
96        def parseTagExcel = {
97                def assay = getAssay( params.id );
98                if( !assay )
99                        return
100
101                def filename = params.filename
102
103                // Security check to prevent accessing files in other directories
104                if( !filename || filename.contains( '..' ) ) {
105                        response.status = 500;
106                        render "Invalid filename given";
107                        return;
108                }
109
110                // Check for existence and readability
111                File file = new File( fileService.getUploadDir(), filename)
112
113                if( !file.exists() || !file.canRead() ) {
114                        response.status = 404;
115                        render "The uploaded file doesn't exist or doesn't work as expected.";
116                        return;
117                }
118
119                // Save the filename in session for later use
120                session.filename = filename;
121                def excelData;
122                try {
123                        excelData = sampleExcelService.parseTagsExcel( file );
124                } catch( Throwable e ) { // Catch a throwable here instead of an exception, since the apache poi stuff gives an Error on failure
125                        // Couldn't create a workbook from this file.
126                        response.status = 400 // Bad request
127                        render "Uploaded file is not a valid excel file: " + e.getMessage()
128                        return
129                }
130                session.possibleFields = excelData.possibleFields
131               
132                [assay: assay, headers: excelData.headers, exampleData: excelData.exampleData, filename: filename, possibleFields: [ "Don't import" ] + excelData.possibleFields, bestMatches: excelData.bestMatches]
133        }
134
135        /**
136         * Updates the assay samples based on the given excel file and the column matches
137         */
138        def updateTagsByExcel = {
139                def assay = getAssay( params.id );
140                if( !assay ) {
141                        // Now delete the file, since we don't need it anymore
142                        _deleteUploadedFileFromSession()
143                        return;
144                }
145
146                if( !session.filename ) {
147                        // Now delete the file, since we don't need it anymore
148                        _deleteUploadedFileFromSession()
149
150                        flash.error = "No excel file found because session timed out. Please try again."
151                        redirect( action: 'show', id: params.id)
152                        return
153                }
154
155                // Determine the match-columns
156                def matchColumns = params[ 'matches'];
157
158                // Now loop through the excel sheet and update all samples with the specified data
159                File file = new File( fileService.getUploadDir(), session.filename );
160               
161                if( !file.exists() || !file.canRead() ) {
162                        flash.error = "Excel file has been removed since previous step. Please try again."
163                        redirect( action: 'show', id: params.id)
164                        return
165                }
166               
167                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assay.assaySamples );
168
169                // Return a message to the user
170                if( !excelData.success ) {
171                        flash.error = excelData.message
172                } else if( excelData.numSuccesful == 0 ) {
173                        flash.error = "None of the " + excelData.failedRows.size() + " row(s) could be imported, because none of the sample names matched. Have you provided the right excel file?"
174                } else {
175                        flash.message = excelData.numSuccesful + " samples have been updated. "
176
177                        if( excelData.failedRows.size() > 0 )
178                                flash.message += excelData.failedRows.size() + " row(s) could not be imported, because the sample names could not be found in the database."
179                }
180                redirect( action: 'show', id: params.id )
181        }
182
183        /**
184         * Update the properties of the assay samples manually
185         */
186        def updateTagsManually = {
187                def assay = getAssay( params.id );
188                if( !assay )
189                        return
190
191                // Loop through all assay samples and set data
192                def sampleParams = params.assaySample;
193
194                if( sampleParams ) {
195                        assay.assaySamples.each { assaySample ->
196                                def assaySampleParams = sampleParams.get( assaySample.id as String );
197
198                                if( assaySampleParams ) {
199                                        assaySample.tagName = assaySampleParams.tagName
200                                        assaySample.oligoNumber = assaySampleParams.oligoNumber
201                                        assaySample.tagSequence = assaySampleParams.tagSequence
202
203                                        try {
204                                                assaySample.run = Run.get( assaySampleParams.run as Long );
205                                        } catch( Exception e ) {}
206
207                                        assaySample.save()
208                                }
209                        }
210                }
211
212                flash.message = "Data about samples is saved."
213                redirect( action: 'show', id: params.id )
214        }
215
216        /**************************************************************************
217         *
218         * Methods for handling data about runs for this assay
219         *
220         *************************************************************************/
221
222        /**
223         * Adds existing runs to this assay
224         */
225        def addExistingRuns = {
226                def assay = getAssay( params.id );
227                if( !assay )
228                        return
229
230                // Add checked runs to this assay
231                def runs = params.runs
232                if( runs instanceof String ) {
233                        runs = [ runs ]
234                }
235
236                def numAdded = 0;
237                runs.each { run_id ->
238                        try {
239                                def run = Run.findById( run_id as Long )
240                                if( run.assays == null || !run.assays.contains( assay ) ) {
241                                        run.addToAssays( assay );
242                                        numAdded++;
243                                }
244                        } catch( Exception e ) {}
245                }
246
247                flash.message = numAdded + " runs are added to this assay."
248                redirect( action: 'show', id: params.id)
249        }
250
251        /**
252         * Adds existing runs to this assay
253         */
254        def removeRun = {
255                def assay = getAssay( params.id );
256                if( !assay )
257                        return
258
259                if( !params.run_id ) {
260                        flash.message = "No run id given"
261                        redirect(action: 'show', id: params.id)
262                        return
263                }
264
265                def run
266
267                try {
268                        run = Run.findById( params.run_id as Long )
269                } catch( Exception e ) {
270                        throw e
271                        flash.message = "Incorrect run id given: "
272                        redirect(action: 'show', id: params.id)
273                        return
274                }
275
276                if( assay.runs.contains( run ) ) {
277                        assay.removeFromRuns( run );
278                        flash.message = "The run has been removed from this assay."
279                } else {
280                        flash.message = "The given run was not associated with this assay."
281                }
282
283                redirect( action: 'show', id: params.id)
284        }
285
286        def errorPage = {
287                render "An error has occured. $flash.message"
288        }
289
290        /**
291         * Deletes an uploaded file for which the filename is given in the session.
292         * @return
293         */
294        def _deleteUploadedFileFromSession() {
295                if( !session.filename )
296                        return
297
298                // Now delete the file, since we don't need it anymore
299                fileService.delete( session.filename  )
300                session.filename = ''
301        }
302       
303        protected Assay getAssay(def assayId) {
304                // load study with id specified by param.id
305                def assay
306                try {
307                        assay = Assay.get(assayId as Long)
308                } catch( Exception e ) {
309                        flash.error = "Incorrect id given: " + assayId
310                        redirect(controller: 'study')
311                        return null
312                }
313
314                if (!assay) {
315                        flash.error = "No assay found with id: " + assayId
316                        redirect(controller: 'study')
317                        return null
318                }
319               
320                if (!assay.study.canRead( session.user ) ) {
321                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
322                        redirect(controller: 'study')
323                        return null
324                }
325               
326                return assay
327        }
328       
329        /**
330         * Exports data about one or more assays in fasta format
331         */
332        def exportAsFasta = {
333                def tokens = params.list( 'tokens' );
334                def ids = params.list( 'ids' );
335                def name;
336               
337                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
338               
339                if( !tokens && !ids ) {
340                        def message = "No assay tokens or ids given"
341                        response.setStatus( 400, message)
342                        render message;
343                        return;
344                }
345                               
346                def assaySamples = [];
347               
348                // Determine which assaySamples to export
349                def assay;
350                tokens.each { token ->
351                        assay = Assay.findByAssayToken( token );
352                        if( assay )
353                                assaySamples += assay.assaySamples
354                }
355                ids.each { id ->
356                        assay = Assay.get( id );
357                        if( assay )
358                                assaySamples += assay.assaySamples
359                }
360
361                if( ( ids.size() + tokens.size() ) == 1 && assay )
362                        name = "Assay_" + assay?.name?.replace( ' ', '_' );
363                else
364                        name = "assays";
365                       
366                // Export the sequences and quality scores
367                response.setHeader "Content-disposition", "attachment; filename=" + name.trim() + ".zip"
368                try {
369                        fastaService.export( assaySamples.unique(), response.getOutputStream(), name );
370                        response.outputStream.flush();
371                } catch( Exception e ) {
372                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
373                }
374        }
375}
Note: See TracBrowser for help on using the repository browser.