Changeset 75 for trunk


Ignore:
Timestamp:
Nov 28, 2011, 9:18:01 AM (8 years ago)
Author:
robert@…
Message:

Last fixes and extra bugfix in import controller

Location:
trunk/grails-app
Files:
11 edited

Legend:

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

    r74 r75  
    55import grails.converters.*;
    66import nl.tno.massSequencing.*
     7import nl.tno.massSequencing.auth.User
    78
    89class ImportController {
     
    9697
    9798           // Perform the actual computations asynchronously
     99           def loggedInUser = httpSession.user
    98100           runAsync {
    99101                   def entity
     
    105107                                   break;
    106108                           case "assay":
    107                                    entity = getAssay( httpSession.process[ processId ].entityId );
     109                                   entity = getAssay( httpSession.process[ processId ].entityId, loggedInUser );
    108110                                   break;
    109111                           default:
     
    564566        }
    565567
    566         protected Assay getAssay(def assayId) {
     568        protected Assay getAssay(def assayId, User loggedInUser = null) {
    567569                // load assay with id specified by param.id
    568570                def assay
     
    579581                }
    580582               
    581                 if (!assay.study.canRead( session.user ) ) {
     583                if (!assay.study.canRead( loggedInUser ?: session.user ) ) {
    582584                        flash.error = "You don't have the right authorizaton to access assay " + assay.name
    583585                        return null
  • trunk/grails-app/controllers/nl/tno/massSequencing/integration/RestController.groovy

    r63 r75  
    351351        }
    352352       
    353        
    354        
    355353        private def checkAssayToken( def assayToken ) {
    356354                if( !assayToken || assayToken == null ) {
  • trunk/grails-app/controllers/nl/tno/massSequencing/integration/SynchronizeController.groovy

    r51 r75  
    6262                        synchronizationService.sessionToken = session.sessionToken
    6363                        synchronizationService.user = session.user
    64        
     64                       
     65                        def previousEager = synchronizationService.eager;
     66                        synchronizationService.eager = true;
     67                       
    6568                        Study.findAllByTrashcan( false ).each { study ->
     69                                log.debug "Synchronize authrorization: " + study.id + ": " + study.name + " (" + study.token() + ")"
    6670                                synchronizationService.synchronizeAuthorization( study );
    6771                        }
     72                       
     73                        synchronizationService.eager = previousEager
    6874                       
    6975                        if( params.redirectUrl )
  • trunk/grails-app/domain/nl/tno/massSequencing/AssaySample.groovy

    r64 r75  
    11package nl.tno.massSequencing
     2import nl.tno.massSequencing.classification.*
    23
    34import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
     
    1213        // Grails datasource is used for executing custom SQL statement
    1314        def dataSource
     15        def sessionFactory
    1416       
    1517        // To be computed at run time (and saved in cache)
     
    7678         */
    7779        public int numClassifiedSequences() {
    78                 return Sequence.countByAssaySample( this );
     80                int numSeqs = 0;
     81                if( this.sequenceData ) {
     82                        sequenceData.each {
     83                                def sequenceDataNum = Sequence.countBySequenceData( it );
     84                                numSeqs += sequenceDataNum ?: 0;
     85                        }
     86                }
     87               
     88                return numSeqs
    7989        }
    8090       
     
    280290                otherAssaySample.numSequences   = numSequences;
    281291
     292                otherAssaySample.save( flush: true );
     293               
    282294                // Move attached data
    283295                def dataList = [] + sequenceData?.toList()
     
    293305                                        // Clone the sequencedata object
    294306                                        def sd = dataList[ j ]?.clone();
    295                                        
     307
    296308                                        if( sd )
    297309                                                otherAssaySample.addToSequenceData( sd );
    298 
     310                                       
    299311                                        // Copy all sequence classifications to the new sequenceData
    300                                         Sequence.executeUpdate( "UPDATE Sequence s SET s.sequenceData = :new WHERE s.sequenceData = :old ", [ 'old': dataList[ j ], 'new': sd ] )
     312                                        //log.debug "Removing sequences from sequencedata " + dataList[ j ]?.id + " to " + sd?.id
     313                                        //Sequence.executeUpdate( "UPDATE Sequence s SET s.sequenceData = :new WHERE s.sequenceData = :old ", [ 'old': dataList[ j ], 'new': sd ] )
     314                                        //Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s.sequenceData = :old ", [ 'old': dataList[ j ] ] )
    301315                               
    302316                                        // Remove the old sequencedata object
     
    310324                        otherAssaySample.run.removeFromAssaySamples( otherAssaySample );
    311325                }
    312                
     326
    313327                // Remove this sample from the run.
    314328                if( run ) {
     
    319333                        otherAssaySample.run = null;
    320334                }
     335               
    321336        }
    322337       
  • trunk/grails-app/domain/nl/tno/massSequencing/Sequence.groovy

    r59 r75  
    33import nl.tno.massSequencing.classification.Taxon
    44
    5 class Sequence {
     5class Sequence implements Serializable {
    66        // Unique name of this sequence
    77        String name
     
    2222       
    2323        static mapping = {
     24                id composite: ['name']
    2425                name index: 'name_idx'
    2526                classification index: 'taxon_idx'
    26                 sequenceData index: 'sequenceData_idx'
     27                sequenceData index: 'sequenceData_idx', cascade: "all-delete-orphan"
     28               
    2729        }
    2830}
  • trunk/grails-app/domain/nl/tno/massSequencing/classification/Taxon.groovy

    r70 r75  
    169169         * @return                              First taxon that is found and matches the criteria or null if nothing is found
    170170         */
    171         public static Taxon findTaxonByPath( def path, int startLevel = 0 ) {
     171        public static Taxon findTaxonByPath( def path, int startLevel = 0, taxonCache ) {
    172172                if( path.size() == 0 )
    173173                        return null;
     174                       
     175                // Check taxon cache
     176                def cacheTaxon = findTaxonInCache( taxonCache, path );
     177                if( cacheTaxon )
     178                        return cacheTaxon;
    174179                       
    175180                def leafLevel = path.size() - 1 + startLevel;
     
    203208                        }
    204209                       
     210                        storeTaxonInCache( taxonCache, path, leaf );
     211                       
    205212                        return leaf;
    206213                }
    207214               
    208215                return null;
     216        }
     217       
     218        public static emptyTaxonCache() {
     219                return [ "top": [:], "sub": [:] ];
     220        }
     221       
     222        protected static Taxon findTaxonInCache( cache, path ) {
     223                if( path.size() <= 3 ) {
     224                        return cache[ "top" ][ cacheKey( path ) ];
     225                } else {
     226                        def topCacheKey = cacheKey( path[0..2] );
     227                       
     228                        if( !cache[ "sub" ][ topCacheKey ] )
     229                                return null;
     230                       
     231                        return findTaxonInCache( cache[ "sub" ][ topCacheKey ], path[ 3..-1] );
     232                }
     233        }
     234       
     235        protected static void storeTaxonInCache( cache, path, taxon ) {
     236                // Keep a 'layered' cache: the first three levels are kept in this cache. The
     237                // next levels are kept in a separate cache, one map for each subtree, so
     238                // each map will have a size that we can still deal with
     239                //      [
     240                //      "top": [ "bacteria": x, "bacteria;firmicutes": y ],
     241                //      "sub": [ "bacteria;firmicutes;abc":
     242                //                              [ "def;ghi" : z, "def;gkl": m ]
     243                //                      ]
     244                if( path.size() <= 3 ) {
     245                        cache[ "top" ][ cacheKey( path ) ] = taxon;
     246                } else {
     247                        def topCacheKey = cacheKey( path[0..2] );
     248                        def restPath = path[3..-1]
     249                       
     250                        if( cache[ "sub" ][ topCacheKey ] == null ) {
     251                                cache[ "sub" ][ topCacheKey ] = emptyTaxonCache();
     252                        }
     253                       
     254                        storeTaxonInCache( cache[ "sub" ][ topCacheKey ], restPath, taxon )
     255                }
     256               
     257        }
     258       
     259        protected static cacheKey( path ) {
     260                return  path.join( ";" );
    209261        }
    210262       
     
    215267         * @param path                  An array or list with the names of the taxa in the path, ordered by level
    216268         * @param startLevel    Which level is the first entry in the list. Defaults to zero. Can be used in order to find taxa
    217          *                                              without the whole tree being specified (e.g. without the root element)
     269         *                                              without the whole tree being specified (e.g. without the root element)
     270         * @param taxonCache    Hashmap with cached data about taxa found
    218271         * @return                              First taxon that is found and matches the criteria or a new taxon if it didn't exist
    219272         */
    220         static Taxon findOrCreateTaxonByPath( def path, int startLevel = 0 ) {
    221                 def taxon = findTaxonByPath( path, startLevel );
     273        static Taxon findOrCreateTaxonByPath( def path, int startLevel = 0, def taxonCache = null ) {
     274                def taxon = findTaxonByPath( path, startLevel, taxonCache );
    222275               
    223276                if( taxon )
     
    233286                // However, we don't have to check the highest level, so we start at depth - 2
    234287                for( def level = depth - 2; level >= 0 && !found ; level-- ) {
    235                         parentNode = findTaxonByPath( path[ 0 .. level ], startLevel );
     288                        parentNode = findTaxonByPath( path[ 0 .. level ], startLevel, taxonCache );
    236289                       
    237290                        // If this taxon is found, it is the highest level
  • trunk/grails-app/services/nl/tno/massSequencing/ClassificationService.groovy

    r74 r75  
    148148                def i = 0;
    149149                def start = System.currentTimeMillis();
    150                 def lapTime = start;
    151150
    152151                // Create a stateless session in order to speed up inserts
    153152                StatelessSession statelessSession = sessionFactory.openStatelessSession();
     153                def connection = statelessSession.connection()
     154                def statement
    154155               
    155156                // For each line in the taxonomy file, read the sequence and save it
     
    158159                long bytesProcessed = 0;
    159160               
     161                def lapTime = System.currentTimeMillis();
     162
     163                // Store a cache for taxa
     164                def taxonCache = Taxon.emptyTaxonCache();
     165               
    160166                try {
    161167                        while( ( line = taxonomyReader.readLine() ) ) {
     
    169175                                        continue;
    170176                                }
    171        
     177                               
    172178                                sequenceName = parts[ 0 ];
    173179                                classification = parts[ 1 ];
     
    177183                               
    178184                                // Find taxon or create one if needed
    179                                 taxon = Taxon.findOrCreateTaxonByPath( taxa, startLevel );
     185                                taxon = Taxon.findOrCreateTaxonByPath( taxa, startLevel, taxonCache );
    180186       
    181187                                if( taxon ) {
     
    187193       
    188194                                        statelessSession.insert(s);
     195                                       
    189196                                } else {
    190197                                        log.trace( "" + i + " Sequence " + sequenceName + " not imported because it is unclassified." )
    191198                                }
    192        
     199                               
    193200                                if( i % 80 == 0 ) {
    194201                                        onProgress( bytesProcessed );
    195202                                        bytesProcessed = 0;
    196203                                }
    197        
     204                               
    198205                                importedSequences++;
    199206       
     
    213220                        taxonomyReader.close();
    214221                }
    215 
     222               
     223               
     224                log.trace "Processing taxonomy file " + taxonomyFilename + " took: " + ( System.currentTimeMillis() - lapTime ) + "ms for " + importedSequences + " sequences. On average, that is " + ( ( System.currentTimeMillis() - lapTime ) / importedSequences ) + "ms per sequence."
     225               
     226                // Update progress
    216227                onProgress( bytesProcessed );
    217228
     
    236247                Classification.executeUpdate( "DELETE FROM Classification WHERE assaySample = :assaySample", [ 'assaySample': assaySample ] );
    237248
    238                 // Update classification table with data from sequences
    239 
    240                 // Unfortunately, Hibernate (through HQL) can't handle bulk insertion if a table has a
    241                 // composite id (Classification). See http://opensource.atlassian.com/projects/hibernate/browse/HHH-3434
    242                 // For that reason we execute plain SQL
    243                 def connection = sessionFactory.currentSession.connection()
    244                 def statement = connection.createStatement();
    245 
    246                 // This statement searches within the sequences for every taxon that is mentioned (as a leaf-node in the tree)
    247                 // and inserts statistics about those taxons into the classification table.
     249                // The process below might seem a bit complicated. If we just search the sequence table for COUNT(*) for each taxon,
     250                // we would only insert statistics for the leaf nodes in the database.
    248251                //
    249                 // However, if we have the following sequences:
     252                // For example, if we have the following sequences:
    250253                //              sequence1               Bacteria -> Firmicutes -> Clostridia -> (unclassified)
    251254                //              sequence2               Bacteria -> (unclassified)
     
    257260                // and nothing for firmicutes. Of course, these values can be computed at any time, since
    258261                // the taxonomy tree is known. Since these values are used for searching and exporting (e.g. show all samples
    259                 // where firmicutes are present), it is essential that the extra values are also added. This is done in the second statement
    260 
    261                 // Insert statistics for all sequences and (leaf) taxa
    262                 statement.executeUpdate( connection.nativeSQL( """
    263                         INSERT INTO     classification (assay_sample_id, taxon_id, unclassified, total)
    264                         SELECT          sa.id, s.classification_id, count( * ),
    265                                                 (
    266                                                         SELECT          count( * )
    267                                                         FROM            sequence s2
    268                                                         LEFT JOIN       taxonomy t2 ON s2.classification_id = t2.id
    269                                                         LEFT JOIN       sequence_data sd2 ON s2.sequence_data_id = sd2.id
    270                                                         LEFT JOIN       assay_sample sa2 ON sd2.sample_id = sa2.id
    271                                                         WHERE           sa2.id = sa.id AND t2.lft >= t.lft AND t2.rgt <= t.rgt
    272                                                 )
    273                         FROM            sequence s
    274                         LEFT JOIN       taxonomy t ON s.classification_id = t.id
    275                         LEFT JOIN       sequence_data sd ON s.sequence_data_id = sd.id
    276                         LEFT JOIN       assay_sample sa ON sd.sample_id = sa.id
    277                         WHERE           sa.id = """ + assaySample.id + """
    278                         GROUP BY        sa.id, s.classification_id, t.lft, t.rgt
    279            """ ) );
    280 
    281                 // Update the table with missing values
    282                 statement.executeUpdate( connection.nativeSQL( """
    283                 INSERT INTO     classification (assay_sample_id, taxon_id, unclassified, total)
    284                 SELECT * FROM (
    285                         SELECT          a.id as assay_sample_id, t.id as taxon_id, 0 as unclassified,
    286                                                 (
    287                                                         SELECT          sum( c.unclassified )
    288                                                         FROM            classification c
    289                                                         LEFT JOIN       taxonomy t3 ON c.taxon_id = t3.id
    290                                                         WHERE           c.assay_sample_id = a.id
    291                                                                 AND     t3.lft >= t.lft
    292                                                                 AND     t3.rgt <= t.rgt
    293                                                 ) AS total
    294                         FROM            taxonomy t, assay_sample a
    295                         WHERE           a.id = """ + assaySample.id + """
    296                         AND             t.id NOT IN (
    297                                                         SELECT          t2.id
    298                                                         FROM            classification c
    299                                                         LEFT JOIN       taxonomy t2 ON c.taxon_id = t2.id
    300                                                         WHERE           c.assay_sample_id = a.id
    301                                                 )
    302                 ) missingClassifications WHERE missingClassifications.total IS NOT NULL
    303           """ ) );
    304        
     262                // where firmicutes are present), it is essential that the extra values are also added.
     263
     264                // First find the number of sequences that have been classified as a specific taxon or a subtaxon of that taxon
     265                def numTotals = Taxon.executeQuery( "SELECT t.id, count(*) FROM Taxon t, Sequence s LEFT JOIN s.classification t2 WHERE t2.lft >= t.lft AND t2.rgt <= t.rgt AND s.sequenceData.sample = :assaySample GROUP BY t.id ORDER BY t.id", [ "assaySample": assaySample ] );
     266               
     267                // Also find the number of sequences that have been classified as that specific taxon, but not further down (numUnClassified)
     268                def numUnclassifieds = Sequence.executeQuery( "SELECT s.classification.id, count(*) FROM Sequence s WHERE s.sequenceData.sample = :assaySample GROUP BY s.classification.id ORDER BY s.classification.id", [ "assaySample": assaySample ]);
     269
     270                def unClassifiedPointer = 0;
     271                def unClassifiedsSize = numUnclassifieds.size()
     272               
     273                // Combine the two lists. The numTotals list is the full list, and the numUnclassifieds list should be entered in it
     274                def insertQuadriples = []       // assay_sample_id, taxon_id, unclassified, total
     275                numTotals.each { lineTotal ->
     276                        def line = [ assaySample.id, lineTotal[ 0 ], 0, lineTotal[ 1 ] ];
     277                       
     278                        // Check whether we have an entry in numUnclassifieds. If not, the number of unclassified sequences for this taxon is zero
     279                        if( unClassifiedPointer < unClassifiedsSize && numUnclassifieds[ unClassifiedPointer ][ 0 ] == lineTotal[ 0 ] ) {
     280                                line[ 2 ] = numUnclassifieds[ unClassifiedPointer ][ 1 ];
     281                                unClassifiedPointer++;
     282                        }
     283                       
     284                        insertQuadriples << line
     285                }
     286               
     287                // Insert the quadriples into the database
     288                if( insertQuadriples.size() > 0 ) {
     289                        // Unfortunately, Hibernate (through HQL) can't handle bulk insertion if a table has a
     290                        // composite id (Classification). See http://opensource.atlassian.com/projects/hibernate/browse/HHH-3434
     291                        // For that reason we execute plain SQL
     292                        def connection = sessionFactory.currentSession.connection()
     293                        def statement = connection.createStatement();
     294       
     295                        // Insert statistics for all sequences and taxa. All values in the quadriples list are numbers,
     296                        // so no escaping has to be done.
     297                        def sql  = "INSERT INTO classification (assay_sample_id, taxon_id, unclassified, total) VALUES";
     298                            sql += insertQuadriples.collect { "( " + it.join( ", " ) + " )" }.join( ", " );
     299                               
     300                        statement.executeUpdate( connection.nativeSQL( sql ) );
     301                }
    305302        }
    306303
  • trunk/grails-app/services/nl/tno/massSequencing/integration/GscfService.groovy

    r70 r75  
    2626        public String urlAuthRemote( def params, def token, appendParameters = true ) {
    2727                def redirectURL = "${config.gscf.baseURL}/login/auth_remote?moduleURL=${this.moduleURL()}&consumer=${this.consumerID()}&token=${token}&"
    28                 def returnUrl
     28                def returnUrl = config.grails.serverURL
    2929               
    3030                // Append other parameters (but only if this request is a GET request)
     
    3838                        }
    3939
    40                         returnUrl = config.grails.serverURL
    4140                        if (params.controller != null){
    4241                                returnUrl += "/${params.controller}"
  • trunk/grails-app/services/nl/tno/massSequencing/integration/SynchronizationService.groovy

    r74 r75  
    464464                        return Auth.findByUserAndStudy( user, study )
    465465
    466                 if( !performSynchronization() )
    467                         return Auth.findByUserAndStudy( user, study )
    468 
    469466                def gscfAuthorization
    470467                try {
  • trunk/grails-app/services/nl/tno/massSequencing/integration/TrashService.groovy

    r65 r75  
    22
    33import nl.tno.massSequencing.*
     4import nl.tno.massSequencing.classification.*
    45import nl.tno.massSequencing.auth.Auth;
    56
     
    1819                        return
    1920
    20                 saveDataInTrash( study );
     21                //saveDataInTrash( study );
    2122               
    2223                def l = []
     
    3233                // Remove sequence data first, since it gives problems when keeping it
    3334                if( study.assays ) {
    34                         l = [] + study.assays*.assaySamples*.sequenceData?.flatten()
    35                        
    36                         l.each {
    37                                 if( it )
    38                                         it.sample.removeFromSequenceData( it );
    39                         }
     35//                      l = [] + study.assays*.assaySamples*.sequenceData?.flatten()
     36//                     
     37//                      l.each {
     38//                              if( it )
     39//                                      it.sample.removeFromSequenceData( it );
     40//                      }
     41                       
     42                        l = [] + study.assays
     43                        l.each { moveToTrash( it ); }
    4044                }
    4145                study.delete(flush:true);
     
    4751         */
    4852        def moveToTrash( Assay assay ) {
    49                 saveDataInTrash( assay );
     53                //saveDataInTrash( assay );
    5054
    5155                // Remove associations
     
    9397         */
    9498        def moveToTrash( Sample sample ) {
    95                 saveDataInTrash( sample );
     99                //saveDataInTrash( sample );
    96100
    97101                // Remove associations
     
    187191
    188192                def assaySamples = assay.assaySamples.findAll { it.containsData() }
    189 
     193               
    190194                // For every assay sample that contains data, save that data in the trashcan
    191195                if( assaySamples.size() > 0 ) {
     
    195199                        Assay dummyAssay = new Assay( assayToken: newAssayToken, name: assay.name, study: trashcan );
    196200                        trashcan.addToAssays( dummyAssay );
    197                         dummyAssay.save()
    198 
     201                        dummyAssay.save( flush: true )
     202
     203                        // First delete all uploaded sequences for the given assaySamples
     204                        def sequences = Sequence.executeQuery( "FROM Sequence s WHERE s.sequenceData.sample IN (:assaySamples)", [ "assaySamples": assaySamples ] )
     205                        if( sequences )
     206                                Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s IN (:sequences)", [ "sequences": sequences ] )
     207                       
    199208                        assaySamples.each { assaySample ->
     209                                log.debug "Create copy of " + assaySample
    200210                                Sample sample = assaySample.sample
    201211
     
    204214                                Sample dummySample = Sample.cloneSample( sample, newSampleToken, trashcan );
    205215                                trashcan.addToSamples( dummySample );
    206                                 dummySample.save()
    207 
     216                                dummySample.save( flush: true )
     217
     218                                log.debug "copy of sample has been saved";
     219                               
    208220                                // Create dummy assay sample
    209221                                AssaySample dummyAssaySample = new AssaySample( assay: dummyAssay, sample: dummySample, numSequences: assaySample.numSequences );
     
    211223                                dummyAssay.addToAssaySamples( dummyAssaySample );
    212224                                dummySample.addToAssaySamples( dummyAssaySample );
    213                                 dummyAssaySample.save();
     225                                dummyAssaySample.save( flush: true );
     226                               
     227                                log.debug "copy of assaysample has been saved"
    214228
    215229                                // Move data from this assay sample to the trash version of it
    216230                                assaySample.moveValuableDataTo( dummyAssaySample );
     231                               
     232                                log.debug "valuable data has been copied"
    217233                               
    218234                                // Remove the assaySample from its run, since otherwise the statistics of the run (#sequences for example)
     
    220236                                dummyAssaySample.run?.removeFromAssaySamples( dummyAssaySample );
    221237                               
    222                                 dummyAssaySample.save();
     238                                log.debug "old assaySample has been removed from run"
     239                               
     240                                dummyAssaySample.save( flush: true );
    223241                        }
    224242                }
    225243               
    226244                // Remove all assaysamples from this assay from their run
     245                log.debug "removing all assaysamples from their run"
    227246                assay.assaySamples?.each { assaySample ->
    228247                        assaySample.run?.removeFromAssaySamples( assaySample );
    229248                }
     249               
     250               
     251                log.debug "assaysamples removed";
    230252               
    231253                // Remove this assay from the runs, since otherwise the samples will be resaved upon delete
  • trunk/grails-app/views/worker/process.gsp

    r70 r75  
    2525                        });
    2626
    27                         progressInterval = setTimeout(updateProgress, 250);
     27                        progressInterval = setTimeout(updateProgress, 500);
    2828
    2929                });
     
    6767                                                  }
    6868                                        } else {
    69                                                 // Make sure the next progress will be retrieved in 250 ms
    70                                                 progressInterval = setTimeout(updateProgress, 250);
     69                                                // Make sure the next progress will be retrieved in 500 ms
     70                                                progressInterval = setTimeout(updateProgress, 500);
    7171                                        }                                       
    7272                                },
Note: See TracChangeset for help on using the changeset viewer.