Ignore:
Timestamp:
May 23, 2011, 5:52:20 PM (9 years ago)
Author:
robert@…
Message:
  • Improved speed by using ajax calls in pagination
  • Importing taxonomy files now works by adding it to sequenceData objects
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/nl/tno/massSequencing/files/ImportController.groovy

    r58 r59  
    1919         *************************************************************************/
    2020
    21         /** 
    22          * Shows a screen that processing is done
     21        /**
     22         * Shows a screen to indicate that files are being parsed
    2323         */
    24         def showProcessScreen = {
     24        def parseUploadedFiles = {
    2525                def entityType = params.entityType
    26 
     26               
    2727                // Check whether files are given
    2828                def names = params.list( 'sequencefiles' )
     
    3737                        return
    3838                }
    39                        
     39               
     40                // Create a unique process identifier
     41                String processId = UUID.randomUUID().toString();
     42                                       
    4043                // Save filenames in session
    41                 session.processFilenames = names;
     44                if( !session.process )
     45                        session.process = [:]
     46                       
     47                if( !session.process[ processId ] )
     48                        session.process[ processId ] = [:]
     49                         
     50                session.process[ processId ].filenames = names;
     51                session.process[ processId ].entityId = params.id;
     52                session.process[ processId ].entityType = entityType
    4253               
    4354                // Check for total size of the files in order to be able
     
    4758                        filesize += fileService.get( it )?.length()
    4859                }
    49 
    50                 session.processProgress = [
     60               
     61                if( !session.progress )
     62                        session.progress = [:]
     63               
     64                session.progress[ processId ] = [
    5165                        stepNum: 1,
    5266                        numSteps: 2,
     
    5670                        stepTotal: filesize
    5771                ]
    58                        
    59                 [entityId: params.id, entityType: params.entityType, filenames: names, url: createLink( action: 'showProcessResult', id: params.id, params: [entityType: entityType] ) ]
     72                                       
     73                render( view: 'showProcessScreen', model: [
     74                        processUrl: createLink( controller: "import", action: "processUploadedFiles" ),
     75                        processParameters: [ processId: processId, entityId: params.id, entityType: params.entityType ],
     76                        progressUrl: createLink( controller: "import", action: "getProgress", params: [ processId: processId ] ),
     77                        finishUrl: createLink( controller: "import", action: 'parseUploadResult', params: [ processId: processId, id: params.id, entityType: entityType] ),
     78                        errorUrl: createLink( controller: entityType, action: "show", id: params.id ),
     79                        entityId: params.id, entityType: params.entityType] );
    6080        }
    6181       
     
    6383         * Processes uploaded files and tries to combine them with samples
    6484         */
    65         def process = {
     85        def processUploadedFiles = {
     86                def processId = params.processId
    6687                def entity
    67                 def assaySamples
    6888               
    6989                switch( params.entityType ) {
    7090                        case "run":
    71                                 entity = getRun( params.id );
    72                                 assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
     91                                entity = getRun( params.entityId );
    7392                                break;
    7493                        case "assay":
    75                                 entity = getAssay( params.id );
    76                                 assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
     94                                entity = getAssay( params.entityId );
    7795                                break;
    7896                        default:
     
    8199                                return;
    82100                }
    83 
     101               
     102                def assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
     103               
    84104                if (!entity) {
    85105                        response.setStatus( 404, flash.error )
     
    89109
    90110                // Check whether files are given
    91                 def names = session.processFilenames
     111                def names = session.process[ processId ]?.filenames
    92112
    93113                if( !names ) {
     114                        println "Process ID: " + processId
     115                        session.process.each {
     116                                println it.key + " = " + it.value;
     117                        }
    94118                        response.setStatus( 500, "No files uploaded for processing" )
    95119                        render "";
     
    123147                def onProgress = { progress, total ->
    124148                        // Update progress
    125                         httpSession.processProgress.stepTotal = total;
    126                         httpSession.processProgress.stepProgress = progress;
     149                        httpSession.progress[ processId ].stepTotal = total;
     150                        httpSession.progress[ processId ].stepProgress = progress;
    127151                }
    128152                def newStep = { total, description ->
    129153                        // Start a new step
    130                         httpSession.processProgress.stepTotal = total;
    131                         httpSession.processProgress.stepProgress = 0;
    132                        
    133                         httpSession.processProgress.stepDescription = description;
    134                         httpSession.processProgress.stepNum++;
    135                 }
    136 
    137                 def parsedFiles = importService.parseFiles( filenames, onProgress, [progress: 0, total: httpSession.processProgress.stepTotal ], newStep );
    138                
    139                 println "Parsed files success: " + parsedFiles.success
     154                        httpSession.progress[ processId ].stepTotal = total;
     155                        httpSession.progress[ processId ].stepProgress = 0;
     156                       
     157                        httpSession.progress[ processId ].stepDescription = description;
     158                        httpSession.progress[ processId ].stepNum++;
     159                }
     160
     161                def parsedFiles = importService.parseFiles( filenames, onProgress, [progress: 0, total: httpSession.progress[ processId ].stepTotal ], newStep );
    140162               
    141163                // Determine excel matches from the uploaded files
    142164                parsedFiles.success = fastaService.inferExcelMatches( parsedFiles.success );
    143165               
    144                 // Now check whether a taxonomy and groups file are uploaded. If so send them to the classificationService for
    145                 // parsing
    146                 def classificationFiles = parsedFiles.success.findAll { it.type == 'groups' || it.type == 'taxonomy' };
    147                 def excelFiles = parsedFiles.success.findAll { it.type == 'excel' }
    148                 def excelMatches = [:]
    149                
    150                 // Find a match between input (mothur) samples and the samples in the system
    151                 excelFiles.each { excelFile ->
    152                         if( excelFile.matches && excelFile.matches[ 'mothursamples' ] ) {
    153                                 excelFile.matches[ 'mothursamples' ].each { excelMatch ->
    154                                         def foundSample = assaySamples.find { it.sample.name == excelMatch.samplename };
    155                                         if( foundSample )
    156                                                 excelMatches[ excelMatch.mothurname ] = foundSample
    157                                 }
    158                         }
    159                 }
    160                
    161                 if( classificationFiles ) {
    162                         parsedFiles.success -= classificationFiles;
    163 
    164                         // If no excel matches are found, no classifications can be stored
    165                         if( !excelMatches ) {
    166                                 classificationFiles.each {
    167                                         it.success = false
    168                                         it.message = "An excel file which maps the input samples with samples in the system must be provided."
    169                                        
    170                                         parsedFiles.failure << it;
    171                                 }
    172                         } else {
    173                                 // Set progress to a new step, so the user knows that it will take a while
    174                                 long totalSize = classificationFiles.collect { it.filesize }.sum();
    175                                 newStep( totalSize, "Storing classification" )
    176                                 println "Total size: " + totalSize;
    177                                
    178                                 classificationFiles = classificationService.storeClassification( classificationFiles, excelMatches, { bytesProcessed -> httpSession.processProgress.stepProgress += bytesProcessed } );
    179                                
    180                                 classificationFiles.each {
    181                                         if( it.success ) {
    182                                                 parsedFiles.success << it;
    183                                         } else {
    184                                                 parsedFiles.failure << it;
    185                                         }
    186                                 }
    187                         }
    188                 }
    189                
    190166                // Match files with samples in the database
    191167                def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples );
     
    194170                matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename }
    195171
     172                // Retrieve all files that have not been matched
     173                def notMatchedFiles = parsedFiles.success.findAll {
     174                        switch( it.type ) {
     175                                case "fasta":
     176                                        return !matchedFiles*.fasta*.filename.contains( it.filename );
     177                                case "qual":
     178                                        return !matchedFiles*.feasibleQuals.flatten().filename.contains( it.filename );
     179                                case "taxonomy":
     180                                        return !matchedFiles*.feasibleClassifications.flatten().filename.contains( it.filename );
     181                        }
     182                        return false;
     183                }
     184               
    196185                // Saved file matches in session to use them later on
    197                 session.processedFiles = [ parsed: parsedFiles,  matched: matchedFiles ];
     186                session.process[ processId ].processedFiles = [ parsed: parsedFiles,  matched: matchedFiles, notMatched: notMatchedFiles ];
    198187
    199188                render ""
    200189        }
    201        
     190
    202191        def getProgress = {
    203                 if( !session.processProgress ) {
     192                def processId = params.processId;
     193                if( !processId || !session.progress?.getAt( processId ) ) {
    204194                        response.setStatus( 500, "No progress information found" );
    205195                        render ""
     
    207197                }
    208198               
    209                 render session.processProgress as JSON
     199                render session.progress[ processId ] as JSON
    210200        }
    211201       
    212202        /**
    213          * Show result of processing
     203         * Show result of processing uploaded files (step 1)
    214204         */
    215         def showProcessResult = {
     205        def parseUploadResult = {
     206                def processId = params.processId;
    216207                // load study with id specified by param.id
    217208                def entity
     
    229220                                return;
    230221                }
    231 
     222               
     223                def assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
     224               
    232225                if (!entity) {
    233226                        response.setStatus( 404, flash.error )
     
    236229                }
    237230               
    238                 if( !session.processedFiles ) {
     231                if( !session.process[ processId ].processedFiles ) {
    239232                        flash.error = "Processing of files failed. Maybe the session timed out."
    240                         redirect( controller: 'assay', action: 'show', 'id': params.id)
    241                         return
    242                 }
    243                
    244                 [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, classificationFiles: session.processedFiles.parsed.success.findAll { it.type == 'taxonomy' }, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ]
     233                        redirect( controller: params.entityType, action: 'show', 'id': params.id)
     234                        return
     235                }
     236               
     237               
     238                // Find matching sequenceData objects for taxonomyfiles that have not been matched
     239                def notMatchedFiles = session.process[ processId ].processedFiles.notMatched;
     240                def extraClassifications = notMatchedFiles.findAll { it.type == "taxonomy" }
     241                extraClassifications.collect {
     242                        // Find all sequence files that have the correct number of sequences and are in the list of assaySamples
     243                        it[ 'feasibleSequenceData' ] = SequenceData.findAllByNumSequences( it.numLines ).findAll { assaySamples.contains( it.sample ) }
     244                        return it
     245                }
     246               
     247                [       entityType: params.entityType, processId: processId, entity: entity, id: params.id,
     248                        parsedFiles: session.process[ processId ].processedFiles.parsed,
     249                        matchedFiles: session.process[ processId ].processedFiles.matched,
     250                        remainingClassificationFiles: extraClassifications,
     251                        selectedRun: params.selectedRun ]
    245252        }
    246253
     
    249256         */
    250257        def returnWithoutSaving = {
     258                def processId = params.processId;
     259
    251260                // Delete all uploaded files from disk
    252                 session.processedFiles?.parsed?.success?.each {
     261                session.process[ processId ]?.processedFiles?.parsed?.success?.each {
    253262                        fileService.delete( it.filename );
    254263                }
     
    269278        }
    270279       
     280        /**
     281         * Shows a screen with the progress of saving matched files
     282         */
     283        def saveMatchedFiles = {
     284                def entityType = params.entityType
     285                def processId = params.processId
     286               
     287                session.process[ processId ].matchedFiles = params.file
     288                session.process[ processId ].matchedRemainingClassification = params.remainingClassification
     289               
     290                // Check for total size of the classification files in order to be able
     291                // to show a progress bar. The handling of classification files is orders
     292                // of magnitude bigger than the rest, so we only show progress of those files
     293                long filesize = 0;
     294
     295                // Loop through all files. Those are the numeric elements in the 'files' array
     296                def digitRE = ~/^\d+$/;
     297                params.file.findAll { it.key.matches( digitRE ) }.each { file ->
     298                        def filevalue = file.value;
     299                       
     300                        // Check if the file is selected
     301                        if( filevalue.include == "on" ) {
     302                                if( fileService.fileExists( filevalue.fasta ) ) {
     303                                        // Also save classification data for this file, if it is present
     304                                        if( filevalue.classification ) {
     305                                                filesize += fileService.get( filevalue.classification )?.size()
     306                                        }
     307                                }
     308                        }
     309                }
     310                params.remainingClassification.findAll { it.key.matches( digitRE ) }.each { file ->
     311                        def filevalue = file.value;
     312                       
     313                        // Check if the file is selected
     314                        if( filevalue.include == "on" ) {
     315                                if( fileService.fileExists( filevalue.filename ) ) {
     316                                        // Also save classification data for this file, if it is present
     317                                        filesize += fileService.get( filevalue.filename )?.size()
     318                                }
     319                        }
     320                }
     321
     322                if( !session.progress )
     323                        session.progress = [:]
     324                       
     325                session.progress[ processId ] = [
     326                        stepNum: 2,
     327                        numSteps: 2,
     328                        stepDescription: 'Store sequence data and classification',
     329                       
     330                        stepProgress: 0,
     331                        stepTotal: filesize
     332                ]
     333                                       
     334                render( view: 'showProcessScreen', model: [
     335                        processUrl: createLink( controller: "import", action: "processMatchedFiles" ),
     336                        processParameters: [ processId: processId, entityId: params.id, entityType: params.entityType ],
     337                        progressUrl: createLink( controller: "import", action: "getProgress", params: [ processId: processId ] ),
     338                        finishUrl: createLink( controller: "import", action: 'saveMatchedResult', params: [ processId: processId, id: params.id, entityType: entityType] ),
     339                        errorUrl: createLink( controller: entityType, action: "show", id: params.id ),
     340                        entityId: params.id, entityType: params.entityType] );
     341        }
     342       
    271343        /**
    272344         * Saves processed files to the database, based on the selections made by the user
    273345         */
    274         def saveProcessedFiles = {
     346        def processMatchedFiles = {
    275347                // load entity with id specified by param.id
    276                 def entity
    277                
    278                 switch( params.entityType ) {
    279                         case "run":
    280                                 entity = getRun( params.id );
    281                                 break;
    282                         case "assay":
    283                                 entity = getAssay( params.id );
    284                                 break;
    285                         default:
    286                                 response.setStatus( 404, "No entity found" );
    287                                 render "";
    288                                 return;
    289                 }
    290 
    291                 if (!entity) {
    292                         response.setStatus( 404, flash.error )
    293                         render "";
    294                         return
    295                 }
     348                def processId = params.processId;
    296349
    297350                // Check whether files are given
    298                 def files = params.file
    299 
    300                 if( !files ) {
    301                         flash.message = "No files were selected."
    302                         redirect( controller: params.entityType, action: 'show', 'id': params.id)
     351                def files = session.process[ processId ].matchedFiles 
     352                def remainingClassification = session.process[ processId ].matchedRemainingClassification;
     353               
     354                if( !files && !remainingClassification ) {
     355                        flash.message = "No files were selected for import."
     356                        redirect( controller: params.entityType, action: 'show', 'id': params.entityId)
    303357                        return
    304358                }
    305359
    306360                File permanentDir = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir )
     361               
     362                // This closure enables keeping track of the progress
     363                def httpSession = session;
     364                def onProgress = { progress ->
     365                        // Update progress
     366                        httpSession.progress[ processId ].stepProgress += progress;
     367                }
     368               
     369                // Loop through all FASTA files. Those are the numeric elements in the 'files' array
     370                def fastaReturn = saveMatchedFastaFiles( files, session.process[ processId ]?.processedFiles, onProgress );
     371                def classificationReturn = saveRemainingClassificationFiles( remainingClassification, onProgress );
     372               
     373                // Update classification (summary) for updated samples
     374                def samplesClassified = [] + fastaReturn.samplesClassified + classificationReturn.samplesClassified;
     375                classificationService.updateClassificationForAssaySamples( samplesClassified.findAll { it }.unique() )
     376               
     377                def returnStructure = [
     378                        numSequenceFiles: fastaReturn.numSequenceFiles,
     379                        numQualFiles: fastaReturn.numQualFiles,
     380                        numClassificationFiles: fastaReturn.numClassificationFiles,
     381                        numExtraClassificationFiles: classificationReturn.numExtraClassifications,
     382                        numTotal: fastaReturn.numSequenceFiles + classificationReturn.numExtraClassifications,
     383                        errors: [] + fastaReturn.errors + classificationReturn.errors
     384                ]
     385
     386                // Return all files that have not been moved
     387                session.process[ processId ]?.processedFiles?.parsed?.success?.each {
     388                        fileService.delete( it.filename );
     389                }
     390               
     391                session.process[ processId ].result = returnStructure;
     392               
     393                response.contentType = "text/plain"
     394                render "";
     395        }
     396       
     397        def saveMatchedFastaFiles( def files, processedFiles, Closure onProgress ) {
    307398                int numSuccesful = 0;
     399                int numQualFiles = 0;
     400                int numClassificationFiles = 0;
     401                def samplesClassified = [];
    308402                def errors = [];
    309                
    310                 // Loop through all files. Those are the numeric elements in the 'files' array
     403
    311404                def digitRE = ~/^\d+$/;
    312405                files.findAll { it.key.matches( digitRE ) }.each { file ->
     
    317410                                if( fileService.fileExists( filevalue.fasta ) ) {
    318411                                        try {
    319                                                 def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, session.processedFiles );
     412                                                def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, processedFiles );
    320413                                               
    321414                                                // Save the data into the database
     
    335428                                                } else {
    336429                                                        sd.save(flush:true);
     430                                                       
     431                                                        // Also save classification data for this file, if it is present
     432                                                        if( filevalue.classification ) {
     433                                                                classificationService.storeClassification( filevalue.classification, sd, onProgress )
     434                                                                samplesClassified << sample
     435                                                               
     436                                                                numClassificationFiles++;
     437                                                        }
     438                                                       
     439                                                        if( sd.qualityFile )
     440                                                                numQualFiles++;
     441                                                       
     442                                                        numSuccesful++;
    337443                                                }
    338                                                
    339                                                 numSuccesful++;
    340444                                        } catch( Exception e ) {
     445                                                e.printStackTrace();
    341446                                                errors << "an error occurred while saving " + filevalue.fasta + ": " + e.getMessage()
    342447                                        }
     
    347452                        }
    348453                }
    349 
    350                 // Return all files that have not been moved
    351                 session.processedFiles?.parsed?.success?.each {
    352                         fileService.delete( it.filename );
    353                 }
    354                
     454               
     455                return [ numSequenceFiles: numSuccesful, numQualFiles: numQualFiles, numClassificationFiles: numClassificationFiles, errors: errors, samplesClassified: samplesClassified.unique() ]
     456        }
     457       
     458        def saveRemainingClassificationFiles( def files, Closure onProgress ) {
     459                def digitRE = ~/^\d+$/;
     460                def errors = [];
     461                def samplesClassified = [];
     462                def numSuccesful = 0;
     463               
     464                files.findAll { it.key.matches( digitRE ) }.each { file ->
     465                        def filevalue = file.value;
     466                       
     467                        // Check if the file is selected
     468                        if( filevalue.include == "on" ) {
     469                                if( fileService.fileExists( filevalue.filename ) ) {
     470                                        def sequenceDataId = filevalue.sequenceData;
     471                                        try {
     472                                                if( sequenceDataId.toString().isLong() ) {
     473                                                        // Retrieve sequenceData and sample now, because the session will be cleared during import
     474                                                        def sequenceData = SequenceData.get( sequenceDataId.toString().toLong() );
     475                                                        def sample = sequenceData.sample;
     476                                                       
     477                                                        if( sequenceData ) {
     478                                                                classificationService.removeClassificationForSequenceData( sequenceData );
     479                                                                classificationService.storeClassification( filevalue.filename, sequenceData, onProgress )
     480                                                                samplesClassified << sample;
     481                                                        }
     482       
     483                                                        numSuccesful++;
     484                                                } else {
     485                                                        errors << "a wrong ID is entered for classification file " + filevalue.filename;
     486                                                }
     487                                        } catch( Exception e ) {
     488                                                e.printStackTrace();
     489                                                errors << "an error occurred while saving " + filevalue.filename + ": " + e.getMessage()
     490                                        }
     491                                }
     492                        }
     493                       
     494                        // File doesn't need to be included in the system. Delete it from disk.
     495                        fileService.delete( filevalue.filename );
     496                }
     497               
     498                return [ numExtraClassifications: numSuccesful, errors: errors, samplesClassified: samplesClassified.unique()  ]
     499               
     500        }
     501
     502        /**
     503         * Redirects the user back to the start screen with a message about how things went
     504         */
     505        def saveMatchedResult = {
     506                def processId = params.processId
     507               
     508                def result = session.process[ processId ].result
     509
    355510                // Return a message to the user
    356                 if( numSuccesful == 0 ) {
    357                         flash.error = "None of the files were imported, because "
    358                        
    359                         if( errors.size() > 0 ) {
    360                                 errors.each {
     511                if( result.numTotal == 0 ) {
     512                       
     513                        if( result.errors.size() > 0 ) {
     514                                flash.error = "None of the files were imported, because "
     515                                result.errors.each {
    361516                                        flash.error += "<br />- " + it
    362517                                }
    363518                        } else {
    364                                 flash.error = "none of the files were selected for import."
     519                                flash.message = "None of the files were imported, because none of the files were selected for import."
    365520                        }
    366521                } else {
    367                         flash.message = numSuccesful + " files have been added to the system. "
    368 
    369                         if( errors.size() > 0 ) {
    370                                 flash.error += errors.size() + " errors occurred during import: "
    371                                 errors.each {
     522                        flash.message = ""     
     523                        if( result.numSequenceFiles == 1 ) {
     524                                flash.message += result.numSequenceFiles + " sequence file has been added to the system"
     525                        } else if( result.numSequenceFiles > 1 ) {
     526                                flash.message += result.numSequenceFiles + " sequence files have been added to the system"
     527                        }
     528                       
     529                        if( result.numQualFiles > 0 || result.numClassificationFiles > 0 ) {
     530                                flash.message += ", with";
     531                        }
     532                       
     533                        if( result.numQualFiles == 1 ) {
     534                                flash.message += " 1 quality file"
     535                        } else if( result.numQualFiles > 1 ) {
     536                                flash.message += " " + result.numQualFiles + " quality files"
     537                        }
     538                       
     539                        if( result.numQualFiles > 0 && result.numClassificationFiles > 0 ) {
     540                                flash.message += " and";
     541                        }
     542                       
     543                        if( result.numClassificationFiles == 1 ) {
     544                                flash.message += " 1 classification file"
     545                        } else if( result.numClassificationFiles > 1 ) {
     546                                flash.message += " " + result.numClassificationFiles + " classification files"
     547                        }
     548                       
     549                        if( flash.message )
     550                                flash.message += "."
     551
     552                        if( result.numExtraClassificationFiles == 1 ) {
     553                                flash.message += result.numExtraClassificationFiles + " additional classification file has been read. ";
     554                        } else if( result.numExtraClassificationFiles > 1 ) {
     555                                flash.message += result.numExtraClassificationFiles + " additional classification files have been read. ";
     556                        }
     557
     558                        if( result.errors.size() > 0 ) {
     559                                flash.error = "However, " + result.errors.size() + " errors occurred during import: "
     560                                result.errors.each {
    372561                                        flash.error += "<br />- " + it
    373562                                }
     
    375564                }
    376565               
     566                // Clear session
     567                session.process?.remove( processId );
     568                session.progress?.remove( processId );
     569               
     570                // Redirect user
    377571                redirect( controller: params.entityType, action: "show", id: params.id )
    378572        }
Note: See TracChangeset for help on using the changeset viewer.