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

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

Renamed module to massSequencing

File size: 17.6 KB
Line 
1package nl.tno.massSequencing
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(), user: session.user]
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.assaySamples*.sequenceData.flatten().size() ) {
132                        flash.message = "Run could not be deleted because samples with data 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                def a = [] + run.assays
150                a.each {
151                        run.removeFromAssays( it );
152                }
153
154                def name = run.name
155                run.delete();
156                flash.message = "Run " + name + " has been deleted from the system."
157
158                redirect( controller: "assay", action: "show", id: params.assayId )
159        }
160
161        def deleteRun = {
162                Run run = getRun( params.id );
163
164                if( !run ) {
165                        redirect(controller: 'run', action: 'index')
166                        return
167                }
168
169                // Don't remove runs for which data exists
170                if( run.assaySamples*.sequenceData.flatten().size() ) {
171                        flash.message = "Run could not be deleted because samples with data are associated with it.";
172                        redirect(controller: 'run', action: 'index')
173                }
174
175                // Check whether the user has sufficient privileges to remove the run from all assays
176                def hasPrivileges = true;
177                run.assays.each {
178                        if( !it.study.canWrite( session.user ) )
179                                hasPrivileges = false
180                }
181               
182                if( !hasPrivileges ) {
183                        flash.message = "Run could not be deleted because you don't have sufficient privileges to remove the run from all assays.";
184                        redirect(controller: 'run', action: 'index')
185                }
186               
187                // Remove all associations
188                def a = [] + run.assays
189                a.each {
190                        run.removeFromAssays( it );
191                }
192
193                def name = run.name
194                run.delete();
195                flash.message = "Run " + name + " has been deleted from the system."
196
197                redirect(controller: 'run', action: 'index')
198        }
199
200        /**************************************************************************
201         *
202         * Methods for handling data about the samples in this run
203         *
204         *************************************************************************/
205
206        /**
207         * Downloads an excel sheet with data about the assay samples, to enter data in excel
208         */
209        def downloadTagsExcel = {
210                Run run = getRun( params.id );
211
212                if( !run ) {
213                        redirect(controller: 'study')
214                        return
215                }
216
217                // Make it only possible to update samples writable by the user
218                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
219
220                def filename = "Run " + run.name + "_tags.xls"
221                def wb = sampleExcelService.downloadSampleExcel( assaySamples, false );
222
223                // Make file downloadable
224                log.trace( "Creation for downloading the file " + filename )
225                sampleExcelService.excelService.downloadFile( wb, filename, response )
226        }
227
228
229        /**
230         * Parses an uploaded excel file and shows a form to match columns
231         */
232        def parseTagExcel = {
233                Run run = getRun( params.id );
234
235                if( !run ) {
236                        redirect(controller: 'study')
237                        return
238                }
239
240                def filename = params.filename
241
242                // Security check to prevent accessing files in other directories
243                if( !filename || filename.contains( '..' ) ) {
244                        response.status = 500;
245                        render "Invalid filename given";
246                        return;
247                }
248
249                // Check for existence and readability
250                File file = new File( fileService.getUploadDir(), filename)
251
252                if( !file.exists() || !file.canRead() ) {
253                        response.status = 404;
254                        render "The uploaded file doesn't exist or doesn't work as expected.";
255                        return;
256                }
257
258                // Save the filename in session for later use
259                session.filename = filename;
260                def excelData;
261                try {
262                        excelData = sampleExcelService.parseTagsExcel( file, false );
263                } catch( Throwable e ) { // Catch a throwable here instead of an exception, since the apache poi stuff gives an Error on failure
264                        // Couldn't create a workbook from this file.
265                        response.status = 400 // Bad request
266                        render "Uploaded file is not a valid excel file: " + e.getMessage()
267                        return
268                }
269                session.possibleFields = excelData.possibleFields
270
271                [run: run, headers: excelData.headers, exampleData: excelData.exampleData, filename: filename, possibleFields: [ "Don't import" ] + excelData.possibleFields, bestMatches: excelData.bestMatches]
272        }
273
274        /**
275         * Updates the assay samples based on the given excel file and the column matches
276         */
277        def updateTagsByExcel = {
278                Run run = getRun( params.id );
279
280                if( !run ) {
281                        // Now delete the file, since we don't need it anymore
282                        _deleteUploadedFileFromSession()
283
284                        redirect(controller: 'study')
285                        return
286                }
287
288                if( !session.filename ) {
289                        // Now delete the file, since we don't need it anymore
290                        _deleteUploadedFileFromSession()
291
292                        flash.error = "No excel file found because session timed out. Please try again."
293                        redirect( action: 'show', id: params.id)
294                        return
295                }
296
297                // Determine the match-columns
298                def matchColumns = params[ 'matches'];
299
300                // Now loop through the excel sheet and update all samples with the specified data
301                File file = new File( fileService.getUploadDir(), session.filename );
302
303                if( !file.exists() || !file.canRead() ) {
304                        flash.error = "Excel file has been removed since previous step. Please try again."
305                        redirect( action: 'show', id: params.id)
306                        return
307                }
308
309                // Make it only possible to update samples writable by the user
310                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
311               
312                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assaySamples );
313
314                println excelData
315               
316                // Return a message to the user
317                if( !excelData.success ) {
318                        flash.error = excelData.message
319                } else if( excelData.numSuccesful == 0 ) {
320                        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?"
321                } else {
322                        flash.message = excelData.numSuccesful + " samples have been updated. "
323
324                        if( excelData.failedRows.size() > 0 )
325                                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."
326                }
327
328                // Now delete the file, since we don't need it anymore
329                _deleteUploadedFileFromSession()
330
331                redirect( action: 'show', id: params.id )
332        }
333
334
335        /**
336         * Update the properties of the assay samples manually
337         */
338        def updateTagsManually = {
339                Run run = getRun( params.id );
340
341                if( !run ) {
342                        redirect(controller: 'study')
343                        return
344                }
345
346                // Loop through all assay samples and set data
347                def sampleParams = params.assaySample;
348
349                if( sampleParams ) {
350                        run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.each { assaySample ->
351                                def assaySampleParams = sampleParams.get( assaySample.id as String );
352                                if( assaySampleParams ) {
353                                        sampleExcelService.variableFields.each { k, v ->
354                                                assaySample[ k ] = assaySampleParams[ k ];
355                                        }
356                                        assaySample.save()
357                                }
358                        }
359                }
360
361                flash.message = "Data about samples is saved."
362                redirect( action: 'show', id: params.id )
363        }
364
365        /**************************************************************************
366         *
367         * Methods for handling data about assays for this run
368         *
369         *************************************************************************/
370
371        /**
372         * Adds existing samples to this run
373         */
374        def addSamples = {
375                Run run = getRun( params.id );
376
377                if( !run ) {
378                        redirect(controller: 'run', action: 'index')
379                        return
380                }
381
382                // Add checked runs to this assay
383                def assaySamples = params.assaySamples
384                if( assaySamples instanceof String ) {
385                        assaySamples = [ assaySamples ]
386                }
387
388                def numAdded = 0;
389                assaySamples.each { assaySampleId ->
390                        try {
391                                def assaySample = AssaySample.findById( assaySampleId as Long )
392                                if( assaySample.assay.study.canWrite( session.user ) ) {
393                                        if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
394                                                run.addToAssaySamples( assaySample );
395                                                numAdded++;
396                                        }
397                                }
398                        } catch( Exception e ) {}
399                }
400
401                flash.message = numAdded + " samples are added to this run."
402                redirect( action: 'show', id: params.id)
403        }
404
405        /**
406         * Removes sample from this run
407         */
408        def removeSample = {
409                Run run = getRun( params.id );
410
411                if( !run ) {
412                        redirect(controller: 'study')
413                        return
414                }
415
416                if( !params.assaySampleId ) {
417                        flash.error = "No sample id given"
418                        redirect(action: 'show', id: params.id)
419                        return
420                }
421
422                def assaySample
423
424                try {
425                        assaySample = AssaySample.findById( params.assaySampleId as Long )
426                } catch( Exception e ) {
427                        log.error e
428                        flash.error = "Incorrect assaysample id given: " + params.assaySampleId
429                        redirect(action: 'show', id: params.id)
430                        return
431                }
432               
433                if( !assaySample.assay.study.canWrite( session.user ) ) {
434                        flash.error = "You don't have sufficient privileges to remove the specified sample from this run."
435                        redirect(action: 'show', id: params.id)
436                        return
437                }
438               
439                if( run.assaySamples.contains( assaySample ) ) {
440                        run.removeFromAssaySamples( assaySample );
441                        flash.message = "The sample has been removed from this run."
442                } else {
443                        flash.message = "The given sample was not associated with this run."
444                }
445
446                redirect( action: 'show', id: params.id)
447        }
448
449
450        /**
451         * Adds existing assays to this run
452         */
453        def addAssays = {
454                Run run = getRun( params.id );
455
456                if( !run ) {
457                        redirect(controller: 'study')
458                        return
459                }
460
461                // Add checked runs to this assay
462                def assays = params.assays
463                if( assays instanceof String ) {
464                        assays = [ assays ]
465                }
466
467                def numAdded = 0;
468                assays.each { assay_id ->
469                        try {
470                                def assay = Assay.findById( assay_id as Long )
471                                if( assay.study.canWrite( session.user ) ) {
472                                        if( run.assays == null || !run.assays.contains( assay ) ) {
473                                                run.addToAssays( assay );
474                                                numAdded++;
475                                        }
476                                }
477                        } catch( Exception e ) {}
478                }
479
480                flash.message = numAdded + " assays are added to this run."
481                redirect( action: 'show', id: params.id)
482        }
483
484        /**
485         * Removes assay for this run
486         */
487        def removeAssay = {
488                Run run = getRun( params.id );
489
490                if( !run ) {
491                        redirect(controller: 'study')
492                        return
493                }
494
495                if( !params.assay_id ) {
496                        flash.message = "No assay id given"
497                        redirect(action: 'show', id: params.id)
498                        return
499                }
500
501                def assay
502
503                try {
504                        assay = Assay.findById( params.assay_id as Long )
505                } catch( Exception e ) {
506                        throw e
507                        flash.message = "Incorrect assay id given: "
508                        redirect(action: 'show', id: params.id)
509                        return
510                }
511
512                if( !assay.study.canWrite( session.user ) ) {
513                        flash.error = "You don't have sufficient privileges to remove the specified assay from this run."
514                        redirect(action: 'show', id: params.id)
515                        return
516                }
517               
518                if( run.assays.contains( assay ) ) {
519                        run.removeFromAssays( assay );
520                        flash.message = "The assay has been removed from this run."
521                } else {
522                        flash.message = "The given assay was not associated with this run."
523                }
524
525                redirect( action: 'show', id: params.id)
526        }
527
528        /**
529         * Exports data about one or more runs in fasta format
530         */
531        def exportAsFasta = {
532                def assaySamples = getAssaySamples( params );
533
534                if( assaySamples == null )
535                        return;
536
537                def name
538
539                if( assaySamples.size() == 0 ) {
540                        flash.error = "No samples found for selected runs";
541                        redirect( action: "list" );
542                        return;
543                } else if( assaySamples*.run.unique().size() == 1 )
544                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
545                else
546                        name = "runs";
547
548                // Export the sequences and quality scores
549                response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
550                try {
551                        fastaService.export( assaySamples.unique(), response.getOutputStream() );
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        /**
559         * Export metadata of selected samples in excel format
560         */
561        def exportMetaData = {
562                def assaySamples = getAssaySamples( params );
563                def name
564               
565                if( assaySamples == null )
566                        return;
567                       
568                if( assaySamples.size() == 0 ) {
569                        flash.error = "No samples found for selected runs";
570                        redirect( action: "list" );
571                        return;
572                } else if( assaySamples*.run.unique().size() == 1 ) {
573                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
574                } else {
575                        name = "runs";
576                }
577
578                // Export the metadata
579                try {
580                        // The export functionality needs a assaysSample-tag list, but it
581                        // should be empty when only exporting metadata
582                        def tags = [];
583                        assaySamples.unique().each { assaySample ->
584                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
585                        }
586                        response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
587                        if( !sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() ) ) {
588                                flash.error = "An error occurred while fetching sample data. Maybe the session has timed out.";
589                                response.setHeader( "Content-disposition", "" );
590                                redirect( action: "index" );
591                        }
592                        response.outputStream.flush();
593                } catch( Exception e ) {
594                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
595                }
596        }
597       
598        protected List getAssaySamples( params ) {
599                def ids = params.list( 'ids' );
600               
601                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
602
603                if( !ids ) {
604                        def message = "No run ids given"
605                        flash.error = message
606                        redirect( action: "index" );
607                        return;
608                }
609
610                def assaySamples = [];
611
612                // Determine which assaySamples to export
613                ids.each { id ->
614                        def run = Run.get( id );
615                        if( run )
616                                assaySamples += run.assaySamples.findAll { it.assay.study.canRead( session.user ) }
617                }
618               
619                return assaySamples;
620        }
621
622        /**
623         * Deletes an uploaded file for which the filename is given in the session.
624         * @return
625         */
626        def _deleteUploadedFileFromSession() {
627                if( !session.filename )
628                        return
629
630                // Now delete the file, since we don't need it anymore
631                fileService.delete( session.filename  )
632                session.filename = ''
633        }
634
635        protected Run getRun(def runId) {
636                // load study with id specified by param.id
637                def run
638                try {
639                        run = Run.get(runId as Long)
640                } catch( Exception e ) {
641                        flash.error = "Incorrect id given: " + runId
642                        return null
643                }
644
645                if (!run) {
646                        flash.error = "No run found with id: " + runId
647                        return null
648                }
649
650                return run
651        }
652
653        protected Assay getAssay(def assayId) {
654                // load study with id specified by param.id
655                def assay
656                try {
657                        assay = Assay.get(assayId as Long)
658                } catch( Exception e ) {
659                        flash.error = "Incorrect id given: " + assayId
660                        return null
661                }
662
663                if (!assay) {
664                        flash.error = "No assay found with id: " + assayId
665                        return null
666                }
667
668                if (!assay.study.canRead( session.user ) ) {
669                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
670                        return null
671                }
672
673                return assay
674        }
675}
Note: See TracBrowser for help on using the repository browser.