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

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

Fixed issue #11: export of excel sample data is now column wise

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                println "Matchcolumns: " + matchColumns
273                println "Possible fields: " + session.possibleFields
274                println "Assay samples: " + assaySamples.sample.name
275               
276                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assaySamples );
277
278                println excelData
279               
280                // Return a message to the user
281                if( !excelData.success ) {
282                        flash.error = excelData.message
283                } else if( excelData.numSuccesful == 0 ) {
284                        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?"
285                } else {
286                        flash.message = excelData.numSuccesful + " samples have been updated. "
287
288                        if( excelData.failedRows.size() > 0 )
289                                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."
290                }
291
292                // Now delete the file, since we don't need it anymore
293                _deleteUploadedFileFromSession()
294
295                redirect( action: 'show', id: params.id )
296        }
297
298
299        /**
300         * Update the properties of the assay samples manually
301         */
302        def updateTagsManually = {
303                Run run = getRun( params.id );
304
305                if( !run ) {
306                        redirect(controller: 'study')
307                        return
308                }
309
310                // Loop through all assay samples and set data
311                def sampleParams = params.assaySample;
312
313                if( sampleParams ) {
314                        run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }.each { assaySample ->
315                                def assaySampleParams = sampleParams.get( assaySample.id as String );
316                                if( assaySampleParams ) {
317                                        assaySample.tagName = assaySampleParams.tagName
318                                        assaySample.oligoNumber = assaySampleParams.oligoNumber
319                                        assaySample.tagSequence = assaySampleParams.tagSequence
320
321                                        assaySample.save()
322                                }
323                        }
324                }
325
326                flash.message = "Data about samples is saved."
327                redirect( action: 'show', id: params.id )
328        }
329
330        /**************************************************************************
331         *
332         * Methods for handling data about assays for this run
333         *
334         *************************************************************************/
335
336        /**
337         * Adds existing samples to this run
338         */
339        def addSamples = {
340                Run run = getRun( params.id );
341
342                if( !run ) {
343                        redirect(controller: 'run', action: 'index')
344                        return
345                }
346
347                // Add checked runs to this assay
348                def assaySamples = params.assaySamples
349                if( assaySamples instanceof String ) {
350                        assaySamples = [ assaySamples ]
351                }
352
353                def numAdded = 0;
354                assaySamples.each { assaySampleId ->
355                        try {
356                                def assaySample = AssaySample.findById( assaySampleId as Long )
357                                if( assaySample.assay.study.canWrite( session.user ) ) {
358                                        if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
359                                                run.addToAssaySamples( assaySample );
360                                                numAdded++;
361                                        }
362                                }
363                        } catch( Exception e ) {}
364                }
365
366                flash.message = numAdded + " samples are added to this run."
367                redirect( action: 'show', id: params.id)
368        }
369
370        /**
371         * Removes sample from this run
372         */
373        def removeSample = {
374                Run run = getRun( params.id );
375
376                if( !run ) {
377                        redirect(controller: 'study')
378                        return
379                }
380
381                if( !params.assaySampleId ) {
382                        flash.error = "No sample id given"
383                        redirect(action: 'show', id: params.id)
384                        return
385                }
386
387                def assaySample
388
389                try {
390                        assaySample = AssaySample.findById( params.assaySampleId as Long )
391                } catch( Exception e ) {
392                        log.error e
393                        flash.error = "Incorrect assaysample id given: " + params.assaySampleId
394                        redirect(action: 'show', id: params.id)
395                        return
396                }
397               
398                if( !assaySample.assay.study.canWrite( session.user ) ) {
399                        flash.error = "You don't have sufficient privileges to remove the specified sample from this run."
400                        redirect(action: 'show', id: params.id)
401                        return
402                }
403               
404                if( run.assaySamples.contains( assaySample ) ) {
405                        run.removeFromAssaySamples( assaySample );
406                        flash.message = "The sample has been removed from this run."
407                } else {
408                        flash.message = "The given sample was not associated with this run."
409                }
410
411                redirect( action: 'show', id: params.id)
412        }
413
414
415        /**
416         * Adds existing assays to this run
417         */
418        def addAssays = {
419                Run run = getRun( params.id );
420
421                if( !run ) {
422                        redirect(controller: 'study')
423                        return
424                }
425
426                // Add checked runs to this assay
427                def assays = params.assays
428                if( assays instanceof String ) {
429                        assays = [ assays ]
430                }
431
432                def numAdded = 0;
433                assays.each { assay_id ->
434                        try {
435                                def assay = Assay.findById( assay_id as Long )
436                                if( assay.study.canWrite( session.user ) ) {
437                                        if( run.assays == null || !run.assays.contains( assay ) ) {
438                                                run.addToAssays( assay );
439                                                numAdded++;
440                                        }
441                                }
442                        } catch( Exception e ) {}
443                }
444
445                flash.message = numAdded + " assays are added to this run."
446                redirect( action: 'show', id: params.id)
447        }
448
449        /**
450         * Removes assay for this run
451         */
452        def removeAssay = {
453                Run run = getRun( params.id );
454
455                if( !run ) {
456                        redirect(controller: 'study')
457                        return
458                }
459
460                if( !params.assay_id ) {
461                        flash.message = "No assay id given"
462                        redirect(action: 'show', id: params.id)
463                        return
464                }
465
466                def assay
467
468                try {
469                        assay = Assay.findById( params.assay_id as Long )
470                } catch( Exception e ) {
471                        throw e
472                        flash.message = "Incorrect assay id given: "
473                        redirect(action: 'show', id: params.id)
474                        return
475                }
476
477                if( !assay.study.canWrite( session.user ) ) {
478                        flash.error = "You don't have sufficient privileges to remove the specified assay from this run."
479                        redirect(action: 'show', id: params.id)
480                        return
481                }
482               
483                if( run.assays.contains( assay ) ) {
484                        run.removeFromAssays( assay );
485                        flash.message = "The assay has been removed from this run."
486                } else {
487                        flash.message = "The given assay was not associated with this run."
488                }
489
490                redirect( action: 'show', id: params.id)
491        }
492
493        /**
494         * Exports data about one or more runs in fasta format
495         */
496        def exportAsFasta = {
497                def assaySamples = getAssaySamples( params );
498
499                if( assaySamples == null )
500                        return;
501
502                def name
503
504                if( assaySamples.size() == 0 ) {
505                        flash.error = "No samples found for selected runs";
506                        redirect( action: "list" );
507                        return;
508                } else if( assaySamples*.run.unique().size() == 1 )
509                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
510                else
511                        name = "runs";
512
513                // Export the sequences and quality scores
514                response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
515                try {
516                        fastaService.export( assaySamples.unique(), response.getOutputStream(), name );
517                        response.outputStream.flush();
518                } catch( Exception e ) {
519                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
520                }
521        }
522       
523        /**
524         * Export metadata of selected samples in excel format
525         */
526        def exportMetaData = {
527                def assaySamples = getAssaySamples( params );
528                def name
529               
530                if( assaySamples == null )
531                        return;
532                       
533                if( assaySamples.size() == 0 ) {
534                        flash.error = "No samples found for selected runs";
535                        redirect( action: "list" );
536                        return;
537                } else if( assaySamples*.run.unique().size() == 1 ) {
538                        name = "Run_" + assaySamples[0].run?.name?.replace( ' ', '_' );
539                } else {
540                        name = "runs";
541                }
542
543                // Export the metadata
544                response.setHeader "Content-disposition", "attachment; filename=${name}.xls"
545                try {
546                        // The export functionality needs a assaysSample-tag list, but it
547                        // should be empty when only exporting metadata
548                        def tags = [];
549                        assaySamples.unique().each { assaySample ->
550                                tags << [assaySampleId: assaySample.id, sampleName: assaySample.sample.name, assayName: assaySample.assay.name, studyName: assaySample.assay.study.name, tag: "-"]
551                        }
552                        sampleExcelService.exportExcelSampleData( assaySamples.unique(), tags, response.getOutputStream() );
553                        response.outputStream.flush();
554                } catch( Exception e ) {
555                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
556                        throw e
557                }
558        }
559       
560        protected List getAssaySamples( params ) {
561                def ids = params.list( 'ids' );
562               
563                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
564
565                if( !ids ) {
566                        def message = "No run ids given"
567                        flash.error = message
568                        redirect( action: "index" );
569                        return;
570                }
571
572                def assaySamples = [];
573
574                // Determine which assaySamples to export
575                ids.each { id ->
576                        def run = Run.get( id );
577                        if( run )
578                                assaySamples += run.assaySamples.findAll { it.assay.study.canRead( session.user ) }
579                }
580               
581                return assaySamples;
582        }
583
584        /**
585         * Deletes an uploaded file for which the filename is given in the session.
586         * @return
587         */
588        def _deleteUploadedFileFromSession() {
589                if( !session.filename )
590                        return
591
592                // Now delete the file, since we don't need it anymore
593                fileService.delete( session.filename  )
594                session.filename = ''
595        }
596
597        protected Run getRun(def runId) {
598                // load study with id specified by param.id
599                def run
600                try {
601                        run = Run.get(runId as Long)
602                } catch( Exception e ) {
603                        flash.error = "Incorrect id given: " + runId
604                        return null
605                }
606
607                if (!run) {
608                        flash.error = "No run found with id: " + runId
609                        return null
610                }
611
612                return run
613        }
614
615        protected Assay getAssay(def assayId) {
616                // load study with id specified by param.id
617                def assay
618                try {
619                        assay = Assay.get(assayId as Long)
620                } catch( Exception e ) {
621                        flash.error = "Incorrect id given: " + assayId
622                        return null
623                }
624
625                if (!assay) {
626                        flash.error = "No assay found with id: " + assayId
627                        return null
628                }
629
630                if (!assay.study.canRead( session.user ) ) {
631                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
632                        return null
633                }
634
635                return assay
636        }
637}
Note: See TracBrowser for help on using the repository browser.