source: trunk/grails-app/controllers/nl/tno/metagenomics/AssayController.groovy @ 14

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

Implemented improved authorization (#16)
Built in select all checkboxes (#25)

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