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

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

Improved export of fasta files and added properties to assaysamples

File size: 16.5 KB
Line 
1package nl.tno.metagenomics
2
3import java.util.Date;
4
5import org.codehaus.groovy.grails.commons.ConfigurationHolder
6
7class RunController {
8        def fileService
9        def synchronizationService
10        def sampleExcelService
11        def fastaService
12
13        def index = {
14                [runs: Run.list()]
15        }
16
17        def show = {
18                // load run with id specified by param.id
19                def run = getRun( params.id );
20
21                if (!run) {
22                        redirect(controller: 'study', action: 'index')
23                        return
24                }
25
26                // Make sure the newest data is available
27                synchronizationService.sessionToken = session.sessionToken
28                synchronizationService.user = session.user
29                try { 
30                        synchronizationService.synchronizeStudies();
31                } catch( Exception e ) {
32                        log.error "Exception occurred during synchronization in " + params.controller + ": " + e.getMessage()
33                        redirect( url: synchronizationService.gscfService.urlAuthRemote(params, session.sessionToken) )
34                }
35
36                // Determine runs not used in this assay
37                def otherAssays = Assay.list( sort: "name" ).findAll { !it.runs.contains( run ) && it.study.canRead( session.user ) }
38
39                // Send the assay information to the view
40                [run: run, allRuns: Run.list(), otherAssays: otherAssays, editable: true]
41        }
42
43        /**
44         * Shows a form to edit the specified run in dialog mode
45         */
46        def editForm = {
47                // load run with id specified by param.id
48                Run run = getRun( params.id );
49
50                if (!run) {
51                        render flash.error
52                        return
53                }
54
55                Assay assay = null
56                if( params.assayId ) {
57                        assay = getAssay( params.assayId )
58
59                        if( !assay ) {
60                                render flash.error;
61                                return
62                        }
63                }
64
65                [assay: assay, run: run]
66        }
67
68        def create = {
69                // Retrieve the assay from the database, but don't exit with an error if no assay is found
70                Assay a = getAssay(params.id);
71                flash.error = "";
72
73                // Create run based on given parameters
74                Run run = new Run();
75
76                run.setPropertiesFromForm( params );
77
78                if( a )
79                        a.addToRuns( run );
80
81                if( !run.save() ) {
82                        flash.message = "Run could not be saved: " + run.getErrors();
83                } else {
84                        flash.message = "Run " + run.name + " has been added to the system."
85                }
86
87                if( a )
88                        redirect( controller: "assay", action: "show", id: a.id )
89                else
90                        redirect( controller: 'run' );
91        }
92
93        def update = {
94                Run run = getRun( params.id );
95
96                if( !run ) {
97                        redirect(controller: 'assay', action: 'show', id: params.assayId)
98                        return
99                }
100
101                // Set properties to the run
102                params.parameterFile = params.editParameterFile
103
104                run.setPropertiesFromForm( params );
105
106                if( run.save() ) {
107                        flash.message = "Run succesfully saved";
108                } else {
109                        flash.error = "Run could not be saved: " + run.getErrors();
110                }
111
112                Assay assay = getAssay(params.assayId);
113                flash.error = "";
114
115                if( assay ) {
116                        redirect( controller: 'assay', action: 'show', id: assay.id)
117                } else {
118                        redirect( controller: 'run', action: 'show', id: run.id )
119                }
120        }
121
122        def delete = {
123                Run run = getRun( params.id );
124
125                if( !run ) {
126                        redirect(controller: 'assay', action: 'show', id: params.assayId)
127                        return
128                }
129
130                // Don't remove runs for which data exists
131                if( run.sequenceData?.size() ) {
132                        flash.message = "Run could not be deleted because samples are associated with it.";
133                        redirect( controller: "assay", action: "show", id: params.assayId )
134                }
135
136                // Check whether the user has sufficient privileges to remove the run from all assays
137                def hasPrivileges = true;
138                run.assay.each {
139                        if( !it.study.canWrite( session.user ) ) 
140                                hasPrivileges = false
141                }
142               
143                if( !hasPrivileges ) {
144                        flash.message = "Run could not be deleted because you don't have sufficient privileges to remove the run from all assays.";
145                        redirect( controller: "assay", action: "show", id: params.assayId )
146                }
147               
148                // Remove all associations
149                run.assays.each {
150                        run.removeFromAssays( it );
151                }
152
153                def name = run.name
154                run.delete();
155                flash.message = "Run " + name + " has been deleted from the system."
156
157                redirect( controller: "assay", action: "show", id: params.assayId )
158        }
159
160        /**************************************************************************
161         *
162         * Methods for handling data about the samples in this run
163         *
164         *************************************************************************/
165
166        /**
167         * Downloads an excel sheet with data about the assay samples, to enter data in excel
168         */
169        def downloadTagsExcel = {
170                Run run = getRun( params.id );
171
172                if( !run ) {
173                        redirect(controller: 'study')
174                        return
175                }
176
177                // Make it only possible to update samples writable by the user
178                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
179
180                def filename = "Run " + run.name + "_tags.xls"
181                def wb = sampleExcelService.downloadSampleExcel( assaySamples, false );
182
183                // Make file downloadable
184                log.trace( "Creation for downloading the file " + filename )
185                sampleExcelService.excelService.downloadFile( wb, filename, response )
186        }
187
188
189        /**
190         * Parses an uploaded excel file and shows a form to match columns
191         */
192        def parseTagExcel = {
193                Run run = getRun( params.id );
194
195                if( !run ) {
196                        redirect(controller: 'study')
197                        return
198                }
199
200                def filename = params.filename
201
202                // Security check to prevent accessing files in other directories
203                if( !filename || filename.contains( '..' ) ) {
204                        response.status = 500;
205                        render "Invalid filename given";
206                        return;
207                }
208
209                // Check for existence and readability
210                File file = new File( fileService.getUploadDir(), filename)
211
212                if( !file.exists() || !file.canRead() ) {
213                        response.status = 404;
214                        render "The uploaded file doesn't exist or doesn't work as expected.";
215                        return;
216                }
217
218                // Save the filename in session for later use
219                session.filename = filename;
220                def excelData;
221                try {
222                        excelData = sampleExcelService.parseTagsExcel( file, false );
223                } catch( Throwable e ) { // Catch a throwable here instead of an exception, since the apache poi stuff gives an Error on failure
224                        // Couldn't create a workbook from this file.
225                        response.status = 400 // Bad request
226                        render "Uploaded file is not a valid excel file: " + e.getMessage()
227                        return
228                }
229                session.possibleFields = excelData.possibleFields
230
231                [run: run, headers: excelData.headers, exampleData: excelData.exampleData, filename: filename, possibleFields: [ "Don't import" ] + excelData.possibleFields, bestMatches: excelData.bestMatches]
232        }
233
234        /**
235         * Updates the assay samples based on the given excel file and the column matches
236         */
237        def updateTagsByExcel = {
238                Run run = getRun( params.id );
239
240                if( !run ) {
241                        // Now delete the file, since we don't need it anymore
242                        _deleteUploadedFileFromSession()
243
244                        redirect(controller: 'study')
245                        return
246                }
247
248                if( !session.filename ) {
249                        // Now delete the file, since we don't need it anymore
250                        _deleteUploadedFileFromSession()
251
252                        flash.error = "No excel file found because session timed out. Please try again."
253                        redirect( action: 'show', id: params.id)
254                        return
255                }
256
257                // Determine the match-columns
258                def matchColumns = params[ 'matches'];
259
260                // Now loop through the excel sheet and update all samples with the specified data
261                File file = new File( fileService.getUploadDir(), session.filename );
262
263                if( !file.exists() || !file.canRead() ) {
264                        flash.error = "Excel file has been removed since previous step. Please try again."
265                        redirect( action: 'show', id: params.id)
266                        return
267                }
268
269                // Make it only possible to update samples writable by the user
270                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
271               
272                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assaySamples );
273
274                println excelData
275               
276                // Return a message to the user
277                if( !excelData.success ) {
278                        flash.error = excelData.message
279                } else if( excelData.numSuccesful == 0 ) {
280                        flash.error = "None of the " + excelData.failedRows.size() + " row(s) could be imported, because none of the sample names matched or no samples are writable. Have you provided the right excel file?"
281                } else {
282                        flash.message = excelData.numSuccesful + " samples have been updated. "
283
284                        if( excelData.failedRows.size() > 0 )
285                                flash.message += excelData.failedRows.size() + " row(s) could not be imported, because the sample names could not be found in the database or you don't have the proper permissions to change them."
286                }
287
288                // Now delete the file, since we don't need it anymore
289                _deleteUploadedFileFromSession()
290
291                redirect( action: 'show', id: params.id )
292        }
293
294
295        /**
296         * Update the properties of the assay samples manually
297         */
298        def updateTagsManually = {
299                Run run = getRun( params.id );
300
301                if( !run ) {
302                        redirect(controller: 'study')
303                        return
304                }
305
306                // Loop through all assay samples and set data
307                def sampleParams = params.assaySample;
308
309                if( sampleParams ) {
310                        run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.each { assaySample ->
311                                def assaySampleParams = sampleParams.get( assaySample.id as String );
312                                if( assaySampleParams ) {
313                                        sampleExcelService.variableFields.each { k, v ->
314                                                assaySample[ k ] = assaySampleParams[ k ];
315                                        }
316                                        assaySample.save()
317                                }
318                        }
319                }
320
321                flash.message = "Data about samples is saved."
322                redirect( action: 'show', id: params.id )
323        }
324
325        /**************************************************************************
326         *
327         * Methods for handling data about assays for this run
328         *
329         *************************************************************************/
330
331        /**
332         * Adds existing samples to this run
333         */
334        def addSamples = {
335                Run run = getRun( params.id );
336
337                if( !run ) {
338                        redirect(controller: 'run', action: 'index')
339                        return
340                }
341
342                // Add checked runs to this assay
343                def assaySamples = params.assaySamples
344                if( assaySamples instanceof String ) {
345                        assaySamples = [ assaySamples ]
346                }
347
348                def numAdded = 0;
349                assaySamples.each { assaySampleId ->
350                        try {
351                                def assaySample = AssaySample.findById( assaySampleId as Long )
352                                if( assaySample.assay.study.canWrite( session.user ) ) {
353                                        if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
354                                                run.addToAssaySamples( assaySample );
355                                                numAdded++;
356                                        }
357                                }
358                        } catch( Exception e ) {}
359                }
360
361                flash.message = numAdded + " samples are added to this run."
362                redirect( action: 'show', id: params.id)
363        }
364
365        /**
366         * Removes sample from this run
367         */
368        def removeSample = {
369                Run run = getRun( params.id );
370
371                if( !run ) {
372                        redirect(controller: 'study')
373                        return
374                }
375
376                if( !params.assaySampleId ) {
377                        flash.error = "No sample id given"
378                        redirect(action: 'show', id: params.id)
379                        return
380                }
381
382                def assaySample
383
384                try {
385                        assaySample = AssaySample.findById( params.assaySampleId as Long )
386                } catch( Exception e ) {
387                        log.error e
388                        flash.error = "Incorrect assaysample id given: " + params.assaySampleId
389                        redirect(action: 'show', id: params.id)
390                        return
391                }
392               
393                if( !assaySample.assay.study.canWrite( session.user ) ) {
394                        flash.error = "You don't have sufficient privileges to remove the specified sample from this run."
395                        redirect(action: 'show', id: params.id)
396                        return
397                }
398               
399                if( run.assaySamples.contains( assaySample ) ) {
400                        run.removeFromAssaySamples( assaySample );
401                        flash.message = "The sample has been removed from this run."
402                } else {
403                        flash.message = "The given sample was not associated with this run."
404                }
405
406                redirect( action: 'show', id: params.id)
407        }
408
409
410        /**
411         * Adds existing assays to this run
412         */
413        def addAssays = {
414                Run run = getRun( params.id );
415
416                if( !run ) {
417                        redirect(controller: 'study')
418                        return
419                }
420
421                // Add checked runs to this assay
422                def assays = params.assays
423                if( assays instanceof String ) {
424                        assays = [ assays ]
425                }
426
427                def numAdded = 0;
428                assays.each { assay_id ->
429                        try {
430                                def assay = Assay.findById( assay_id as Long )
431                                if( assay.study.canWrite( session.user ) ) {
432                                        if( run.assays == null || !run.assays.contains( assay ) ) {
433                                                run.addToAssays( assay );
434                                                numAdded++;
435                                        }
436                                }
437                        } catch( Exception e ) {}
438                }
439
440                flash.message = numAdded + " assays are added to this run."
441                redirect( action: 'show', id: params.id)
442        }
443
444        /**
445         * Removes assay for this run
446         */
447        def removeAssay = {
448                Run run = getRun( params.id );
449
450                if( !run ) {
451                        redirect(controller: 'study')
452                        return
453                }
454
455                if( !params.assay_id ) {
456                        flash.message = "No assay id given"
457                        redirect(action: 'show', id: params.id)
458                        return
459                }
460
461                def assay
462
463                try {
464                        assay = Assay.findById( params.assay_id as Long )
465                } catch( Exception e ) {
466                        throw e
467                        flash.message = "Incorrect assay id given: "
468                        redirect(action: 'show', id: params.id)
469                        return
470                }
471
472                if( !assay.study.canWrite( session.user ) ) {
473                        flash.error = "You don't have sufficient privileges to remove the specified assay from this run."
474                        redirect(action: 'show', id: params.id)
475                        return
476                }
477               
478                if( run.assays.contains( assay ) ) {
479                        run.removeFromAssays( assay );
480                        flash.message = "The assay has been removed from this run."
481                } else {
482                        flash.message = "The given assay was not associated with this run."
483                }
484
485                redirect( action: 'show', id: params.id)
486        }
487
488        /**
489         * Exports data about one or more runs in fasta format
490         */
491        def exportAsFasta = {
492                def assaySamples = getAssaySamples( params );
493
494                if( assaySamples == null )
495                        return;
496
497                def name
498
499                if( assaySamples.size() == 0 ) {
500                        flash.error = "No samples found for selected runs";
501                        redirect( action: "list" );
502                        return;
503                } else if( assaySamples*.run.unique().size() == 1 )
504                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
505                else
506                        name = "runs";
507
508                // Export the sequences and quality scores
509                response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
510                try {
511                        fastaService.export( assaySamples.unique(), response.getOutputStream() );
512                        response.outputStream.flush();
513                } catch( Exception e ) {
514                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
515                }
516        }
517       
518        /**
519         * Export metadata of selected samples in excel format
520         */
521        def exportMetaData = {
522                def assaySamples = getAssaySamples( params );
523                def name
524               
525                if( assaySamples == null )
526                        return;
527                       
528                if( assaySamples.size() == 0 ) {
529                        flash.error = "No samples found for selected runs";
530                        redirect( action: "list" );
531                        return;
532                } else if( assaySamples*.run.unique().size() == 1 ) {
533                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
534                } else {
535                        name = "runs";
536                }
537
538                // Export the metadata
539                try {
540                        // The export functionality needs a assaysSample-tag list, but it
541                        // should be empty when only exporting metadata
542                        def tags = [];
543                        assaySamples.unique().each { assaySample ->
544                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
545                        }
546                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
547                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() ) ) {
548                                flash.error = "An error occurred while fetching sample data. Maybe the session has timed out.";
549                                response.setHeader( "Content-disposition", "" );
550                                redirect( action: "index" );
551                        }
552                        response.outputStream.flush();
553                } catch( Exception e ) {
554                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
555                }
556        }
557       
558        protected List getAssaySamples( params ) {
559                def ids = params.list( 'ids' );
560               
561                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
562
563                if( !ids ) {
564                        def message = "No run ids given"
565                        flash.error = message
566                        redirect( action: "index" );
567                        return;
568                }
569
570                def assaySamples = [];
571
572                // Determine which assaySamples to export
573                ids.each { id ->
574                        def run = Run.get( id );
575                        if( run )
576                                assaySamples += run.assaySamples.findAll { it.assay.study.canRead( session.user ) }
577                }
578               
579                return assaySamples;
580        }
581
582        /**
583         * Deletes an uploaded file for which the filename is given in the session.
584         * @return
585         */
586        def _deleteUploadedFileFromSession() {
587                if( !session.filename )
588                        return
589
590                // Now delete the file, since we don't need it anymore
591                fileService.delete( session.filename  )
592                session.filename = ''
593        }
594
595        protected Run getRun(def runId) {
596                // load study with id specified by param.id
597                def run
598                try {
599                        run = Run.get(runId as Long)
600                } catch( Exception e ) {
601                        flash.error = "Incorrect id given: " + runId
602                        return null
603                }
604
605                if (!run) {
606                        flash.error = "No run found with id: " + runId
607                        return null
608                }
609
610                return run
611        }
612
613        protected Assay getAssay(def assayId) {
614                // load study with id specified by param.id
615                def assay
616                try {
617                        assay = Assay.get(assayId as Long)
618                } catch( Exception e ) {
619                        flash.error = "Incorrect id given: " + assayId
620                        return null
621                }
622
623                if (!assay) {
624                        flash.error = "No assay found with id: " + assayId
625                        return null
626                }
627
628                if (!assay.study.canRead( session.user ) ) {
629                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
630                        return null
631                }
632
633                return assay
634        }
635}
Note: See TracBrowser for help on using the repository browser.