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

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

Improved export of fasta files and added properties to assaysamples

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