source: trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy @ 52

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