source: trunk/grails-app/controllers/nl/tno/massSequencing/AssayController.groovy @ 44

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

Removed mass sample editing (to prevent the edit tags screen opening very slowly). Also added the possibility to add an excel file which matches sequence files to samples (when uploading) (#13). Finally added some 'return false' to onClick events, when dialogs were opened, to prevent the browser from scrolling to the top.

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