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

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

Improved user interface and implemented basic export functionality

File size: 13.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 ) }
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                Assay a = getAssay(params.id);
70                flash.error = "";
71
72                // Create run based on given parameters
73                Run run = new Run();
74
75                run.setPropertiesFromForm( params );
76
77                if( a )
78                        a.addToRuns( run );
79
80                if( !run.save() ) {
81                        flash.message = "Run could not be saved: " + run.getErrors();
82                } else {
83                        flash.message = "Run " + run.name + " has been added to the system."
84                }
85
86                if( a )
87                        redirect( controller: "assay", action: "show", id: a.id )
88                else
89                        redirect( controller: 'run' );
90        }
91
92        def update = {
93                Run run = getRun( params.id );
94
95                if( !run ) {
96                        redirect(controller: 'assay', action: 'show', id: params.assayId)
97                        return
98                }
99
100                // Set properties to the run
101                params.parameterFile = params.editParameterFile
102
103                run.setPropertiesFromForm( params );
104
105                if( run.save() ) {
106                        flash.message = "Run succesfully saved";
107                } else {
108                        flash.error = "Run could not be saved: " + run.getErrors();
109                }
110
111                Assay assay = getAssay(params.assayId);
112                flash.error = "";
113
114                if( assay ) {
115                        redirect( controller: 'assay', action: 'show', id: assay.id)
116                } else {
117                        redirect( controller: 'run', action: 'show', id: run.id )
118                }
119        }
120
121        def delete = {
122                Run run = getRun( params.id );
123
124                if( !run ) {
125                        redirect(controller: 'assay', action: 'show', id: params.assayId)
126                        return
127                }
128
129                // Don't remove runs for which data exists
130                if( run.sequenceData?.size() ) {
131                        flash.message = "Run could not be deleted because samples are associated with it.";
132                        redirect( controller: "assay", action: "show", id: params.assayId )
133                }
134
135                // Remove all associations
136                run.assays.each {
137                        run.removeFromAssays( it );
138                }
139
140                def name = run.name
141                run.delete();
142                flash.message = "Run " + name + " has been deleted from the system."
143
144                redirect( controller: "assay", action: "show", id: params.assayId )
145        }
146
147        /**************************************************************************
148         *
149         * Methods for handling data about the samples in this run
150         *
151         *************************************************************************/
152
153        /**
154         * Downloads an excel sheet with data about the assay samples, to enter data in excel
155         */
156        def downloadTagsExcel = {
157                Run run = getRun( params.id );
158
159                if( !run ) {
160                        redirect(controller: 'study')
161                        return
162                }
163
164                // Make it only possible to update samples writable by the user
165                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
166
167                def filename = "Run " + run.name + "_tags.xls"
168                def wb = sampleExcelService.downloadSampleExcel( assaySamples, false );
169
170                // Make file downloadable
171                log.trace( "Creation for downloading the file " + filename )
172                sampleExcelService.excelService.downloadFile( wb, filename, response )
173        }
174
175
176        /**
177         * Parses an uploaded excel file and shows a form to match columns
178         */
179        def parseTagExcel = {
180                Run run = getRun( params.id );
181
182                if( !run ) {
183                        redirect(controller: 'study')
184                        return
185                }
186
187                def filename = params.filename
188
189                // Security check to prevent accessing files in other directories
190                if( !filename || filename.contains( '..' ) ) {
191                        response.status = 500;
192                        render "Invalid filename given";
193                        return;
194                }
195
196                // Check for existence and readability
197                File file = new File( fileService.getUploadDir(), filename)
198
199                if( !file.exists() || !file.canRead() ) {
200                        response.status = 404;
201                        render "The uploaded file doesn't exist or doesn't work as expected.";
202                        return;
203                }
204
205                // Save the filename in session for later use
206                session.filename = filename;
207                def excelData;
208                try {
209                        excelData = sampleExcelService.parseTagsExcel( file, false );
210                } catch( Throwable e ) { // Catch a throwable here instead of an exception, since the apache poi stuff gives an Error on failure
211                        // Couldn't create a workbook from this file.
212                        response.status = 400 // Bad request
213                        render "Uploaded file is not a valid excel file: " + e.getMessage()
214                        return
215                }
216                session.possibleFields = excelData.possibleFields
217
218                [run: run, headers: excelData.headers, exampleData: excelData.exampleData, filename: filename, possibleFields: [ "Don't import" ] + excelData.possibleFields, bestMatches: excelData.bestMatches]
219        }
220
221        /**
222         * Updates the assay samples based on the given excel file and the column matches
223         */
224        def updateTagsByExcel = {
225                Run run = getRun( params.id );
226
227                if( !run ) {
228                        // Now delete the file, since we don't need it anymore
229                        _deleteUploadedFileFromSession()
230
231                        redirect(controller: 'study')
232                        return
233                }
234
235                if( !session.filename ) {
236                        // Now delete the file, since we don't need it anymore
237                        _deleteUploadedFileFromSession()
238
239                        flash.error = "No excel file found because session timed out. Please try again."
240                        redirect( action: 'show', id: params.id)
241                        return
242                }
243
244                // Determine the match-columns
245                def matchColumns = params[ 'matches'];
246
247                // Now loop through the excel sheet and update all samples with the specified data
248                File file = new File( fileService.getUploadDir(), session.filename );
249
250                if( !file.exists() || !file.canRead() ) {
251                        flash.error = "Excel file has been removed since previous step. Please try again."
252                        redirect( action: 'show', id: params.id)
253                        return
254                }
255
256                // Make it only possible to update samples writable by the user
257                def assaySamples = run.assaySamples.findAll { it.assay.study.canWrite( session.user ) }
258
259                def excelData = sampleExcelService.updateTagsByExcel( matchColumns, session.possibleFields, file, assaySamples );
260
261                // Return a message to the user
262                if( !excelData.success ) {
263                        flash.error = excelData.message
264                } else if( excelData.numSuccesful == 0 ) {
265                        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?"
266                } else {
267                        flash.message = excelData.numSuccesful + " samples have been updated. "
268
269                        if( excelData.failedRows.size() > 0 )
270                                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."
271                }
272                redirect( action: 'show', id: params.id )
273        }
274
275
276        /**
277         * Update the properties of the assay samples manually
278         */
279        def updateTagsManually = {
280                Run run = getRun( params.id );
281
282                if( !run ) {
283                        redirect(controller: 'study')
284                        return
285                }
286
287                // Loop through all assay samples and set data
288                def sampleParams = params.assaySample;
289
290                if( sampleParams ) {
291                        run.assaySamples.each { assaySample ->
292                                def assaySampleParams = sampleParams.get( assaySample.id as String );
293                                if( assaySampleParams ) {
294                                        assaySample.tagName = assaySampleParams.tagName
295                                        assaySample.oligoNumber = assaySampleParams.oligoNumber
296                                        assaySample.tagSequence = assaySampleParams.tagSequence
297
298                                        assaySample.save()
299                                }
300                        }
301                }
302
303                flash.message = "Data about samples is saved."
304                redirect( action: 'show', id: params.id )
305        }
306
307        /**************************************************************************
308         *
309         * Methods for handling data about assays for this run
310         *
311         *************************************************************************/
312
313        /**
314         * Adds existing samples to this run
315         */
316        def addSamples = {
317                Run run = getRun( params.id );
318
319                if( !run ) {
320                        redirect(controller: 'study')
321                        return
322                }
323
324                // Add checked runs to this assay
325                def assaySamples = params.assaySamples
326                if( assaySamples instanceof String ) {
327                        assaySamples = [ assaySamples ]
328                }
329
330                def numAdded = 0;
331                assaySamples.each { assaySampleId ->
332                        try {
333                                def assaySample = AssaySample.findById( assaySampleId as Long )
334                                if( run.assaySamples == null || !run.assaySamples.contains( assaySample ) ) {
335                                        run.addToAssaySamples( assaySample );
336                                        numAdded++;
337                                }
338                        } catch( Exception e ) {}
339                }
340
341                flash.message = numAdded + " samples are added to this run."
342                redirect( action: 'show', id: params.id)
343        }
344
345        /**
346         * Removes sample from this run
347         */
348        def removeSample = {
349                Run run = getRun( params.id );
350
351                if( !run ) {
352                        redirect(controller: 'study')
353                        return
354                }
355
356                if( !params.assaySampleId ) {
357                        flash.message = "No sample id given"
358                        redirect(action: 'show', id: params.id)
359                        return
360                }
361
362                def assaySample
363
364                try {
365                        assaySample = AssaySample.findById( params.assaySampleId as Long )
366                } catch( Exception e ) {
367                        log.error e
368                        flash.message = "Incorrect assaysample id given: " + params.assaySampleId
369                        redirect(action: 'show', id: params.id)
370                        return
371                }
372
373                if( run.assaySamples.contains( assaySample ) ) {
374                        run.removeFromAssaySamples( assaySample );
375                        flash.message = "The sample has been removed from this run."
376                } else {
377                        flash.message = "The given sample was not associated with this run."
378                }
379
380                redirect( action: 'show', id: params.id)
381        }
382
383
384        /**
385         * Adds existing assays to this run
386         */
387        def addAssays = {
388                Run run = getRun( params.id );
389
390                if( !run ) {
391                        redirect(controller: 'study')
392                        return
393                }
394
395                // Add checked runs to this assay
396                def assays = params.assays
397                if( assays instanceof String ) {
398                        assays = [ assays ]
399                }
400
401                def numAdded = 0;
402                assays.each { assay_id ->
403                        try {
404                                def assay = Assay.findById( assay_id as Long )
405                                if( run.assays == null || !run.assays.contains( assay ) ) {
406                                        run.addToAssays( assay );
407                                        numAdded++;
408                                }
409                        } catch( Exception e ) {}
410                }
411
412                flash.message = numAdded + " assays are added to this run."
413                redirect( action: 'show', id: params.id)
414        }
415
416        /**
417         * Removes assay for this run
418         */
419        def removeAssay = {
420                Run run = getRun( params.id );
421
422                if( !run ) {
423                        redirect(controller: 'study')
424                        return
425                }
426
427                if( !params.assay_id ) {
428                        flash.message = "No assay id given"
429                        redirect(action: 'show', id: params.id)
430                        return
431                }
432
433                def assay
434
435                try {
436                        assay = Assay.findById( params.assay_id as Long )
437                } catch( Exception e ) {
438                        throw e
439                        flash.message = "Incorrect assay id given: "
440                        redirect(action: 'show', id: params.id)
441                        return
442                }
443
444                if( run.assays.contains( assay ) ) {
445                        run.removeFromAssays( assay );
446                        flash.message = "The assay has been removed from this run."
447                } else {
448                        flash.message = "The given assay was not associated with this run."
449                }
450
451                redirect( action: 'show', id: params.id)
452        }
453
454        /**
455         * Exports data about one or more runs in fasta format
456         */
457        def exportAsFasta = {
458                def ids = params.list( 'ids' );
459
460                ids = ids.findAll { it.isLong() }.collect { Long.parseLong( it ) }
461
462                if( !ids ) {
463                        def message = "No run ids given"
464                        response.setStatus( 400, message)
465                        render message;
466                        return;
467                }
468
469                def assaySamples = [];
470                def name
471
472                if( ids.size() == 1 )
473                        name = "Run_" + Run.get( ids[ 0 ] )?.name?.replace( ' ', '_' );
474                else
475                        name = "runs";
476
477                // Determine which assaySamples to export
478                ids.each { id ->
479                        def run = Run.get( id );
480                        if( run )
481                                assaySamples += run.assaySamples
482                }
483
484                // Export the sequences and quality scores
485                response.setHeader "Content-disposition", "attachment; filename=${name}.zip"
486                try {
487                        fastaService.export( assaySamples.unique(), response.getOutputStream(), name );
488                        response.outputStream.flush();
489                } catch( Exception e ) {
490                        log.error( "Exception occurred during export of sequences. Probably the user has cancelled the download." );
491                }
492        }
493
494
495        /**
496         * Deletes an uploaded file for which the filename is given in the session.
497         * @return
498         */
499        def _deleteUploadedFileFromSession() {
500                if( !session.filename )
501                        return
502
503                // Now delete the file, since we don't need it anymore
504                fileService.delete( session.filename  )
505                session.filename = ''
506        }
507
508        protected Run getRun(def runId) {
509                // load study with id specified by param.id
510                def run
511                try {
512                        run = Run.get(runId as Long)
513                } catch( Exception e ) {
514                        flash.error = "Incorrect id given: " + runId
515                        return null
516                }
517
518                if (!run) {
519                        flash.error = "No run found with id: " + runId
520                        return null
521                }
522
523                return run
524        }
525
526        protected Assay getAssay(def assayId) {
527                // load study with id specified by param.id
528                def assay
529                try {
530                        assay = Assay.get(assayId as Long)
531                } catch( Exception e ) {
532                        flash.error = "Incorrect id given: " + assayId
533                        return null
534                }
535
536                if (!assay) {
537                        flash.error = "No assay found with id: " + assayId
538                        return null
539                }
540
541                if (!assay.study.canRead( session.user ) ) {
542                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
543                        return null
544                }
545
546                return assay
547        }
548}
Note: See TracBrowser for help on using the repository browser.