001/**
002 * Copyright (C) 2010-2015 The Roslin Institute <contact andy.law@roslin.ed.ac.uk>
003 *
004 * This file is part of JEnsembl: a Java API to Ensembl data sources developed by the
005 * Bioinformatics Group at The Roslin Institute, The Royal (Dick) School of
006 * Veterinary Studies, University of Edinburgh.
007 *
008 * Project hosted at: http://jensembl.sourceforge.net
009 *
010 * This is free software: you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License (version 3) as published by
012 * the Free Software Foundation.
013 *
014 * This software is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * in this software distribution. If not, see: http://opensource.org/licenses/gpl-3.0.html
021 */
022package uk.ac.roslin.ensembl.dao.database.coreaccess;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import org.apache.ibatis.session.SqlSession;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030import uk.ac.roslin.ensembl.config.DBConnection;
031import uk.ac.roslin.ensembl.config.EnsemblCoordSystemType;
032import uk.ac.roslin.ensembl.config.FeatureType;
033import uk.ac.roslin.ensembl.dao.coreaccess.TranscriptDAO;
034import uk.ac.roslin.ensembl.dao.factory.DAOCollectionCoreFactory;
035import uk.ac.roslin.ensembl.dao.factory.DAOCoreFactory;
036import uk.ac.roslin.ensembl.dao.factory.DAOSingleSpeciesCoreFactory;
037import uk.ac.roslin.ensembl.datasourceaware.DAXRef;
038import uk.ac.roslin.ensembl.datasourceaware.core.*;
039import uk.ac.roslin.ensembl.exception.DAOException;
040import uk.ac.roslin.ensembl.mapper.core.TranscriptMapper;
041import uk.ac.roslin.ensembl.mapper.handler.DBResultHandler;
042import uk.ac.roslin.ensembl.mapper.handler.HashMapHolder;
043import uk.ac.roslin.ensembl.mapper.query.FeatureQuery;
044import uk.ac.roslin.ensembl.model.*;
045import uk.ac.roslin.ensembl.model.core.CoordinateSystem;
046import uk.ac.roslin.ensembl.model.core.Gene;
047
048public class DBTranscriptDAO extends DBCoreObjectDAO implements TranscriptDAO {
049
050    //if the meta table tells us that transcript.build.level is 'toplevel'
051    //we can grab the top ranked CS and use this for all genes
052    //there may be other values than 'toplevel' - i dont know what :)
053    //if the build level isn't annotated we have to get the possible CS levels
054    //from meta_coord and use which ever one is approriate!
055    CoordinateSystem transcriptBuildCS = null;
056    final static Logger LOGGER = LoggerFactory.getLogger(DBTranscriptDAO.class);
057
058    public DBTranscriptDAO() {
059        super();
060    }
061
062    public DBTranscriptDAO(DAOSingleSpeciesCoreFactory factory) throws DAOException {
063        super(factory);
064
065        //we want to initialize the GeneDAO so that it knows which COordinate system
066        //it needs to quwery about genes
067
068        transcriptBuildCS = this.ssFactory.getDatabase().getBuildCoordSystem(FeatureType.transcript.toString());
069        //what if this is null or throws an exception?
070        //if it throws an exception we are probably in some sort of bad factory environment
071        if (transcriptBuildCS == null) {
072            transcriptBuildCS = this.ssFactory.getDatabase().getTopLevelCoordSystem();
073        }
074    }
075
076    public DBTranscriptDAO(DAOCollectionCoreFactory factory) throws DAOException {
077        super(factory);
078        transcriptBuildCS = this.collFactory.getDatabase().getBuildCoordSystem(species, FeatureType.transcript.toString());
079        //what if this is null  or throw an exception?
080        //if it throws an exception we are probably in some sort of bad factory environment
081        if (transcriptBuildCS == null) {
082            transcriptBuildCS = this.collFactory.getDatabase().getTopLevelCS(species);
083        }
084    }
085
086    /**
087     * Uses the stableid of an object to fill in missing data 
088     * @param object
089     * @throws DAOException
090     */
091    @Override
092    public void reInitialize(IdentifiableObject object) throws DAOException {
093
094        if (object == null || !(object instanceof DATranscript)) {
095            throw new DAOException("Object not a DATranscript");
096        }
097
098        DATranscript transcript = (DATranscript) object;
099
100        //the DAO method requires a stableID
101        if (transcript.getStableID() == null || transcript.getStableID().isEmpty()) {
102            return;
103        }
104
105        DATranscript temp = this.getTranscriptByStableID(transcript.getStableID());
106
107        if (temp != null) {
108            transcript.setAnalysisID(temp.getAnalysisID());
109            transcript.setDisplayXRef(temp.getDisplayXRef());
110            transcript.setBiotype(temp.getBiotype());
111            transcript.setId(temp.getId());
112            transcript.setCreationDate(temp.getCreationDate());
113            transcript.setModificationDate(temp.getModificationDate());
114            transcript.setDescription(temp.getDescription());
115            transcript.setCanonicalTranslationID(temp.getCanonicalTranslationID());
116            transcript.setCurrent(temp.isCurrent());
117            transcript.setDisplayName(temp.getDisplayName());
118            transcript.setStatus(temp.getStatus());
119            transcript.setGeneID(temp.getGeneID());
120
121
122            for (Mapping tempMapping : temp.getLoadedMappings()) {
123
124                Mapping tempRevMapping = tempMapping.getReverseMapping();
125                if (tempRevMapping != null) {
126                    tempRevMapping.setTarget(transcript);
127                }
128                tempMapping.setSource(transcript);
129                transcript.addMapping(tempMapping);
130
131            }
132        }           
133        transcript.setInitialized(true);
134       
135    }
136
137    /**
138     * @param id
139     * 
140     * @throws DAOException
141     */
142    @Override
143    public DATranscript getTranscriptByID(Integer id) throws DAOException {
144
145        if (id == null) {
146            return null;
147        }
148
149        FeatureQuery q = new FeatureQuery();
150        q.setFeatureID(id);
151        if (!singleSpecies) {
152            try {
153                q.setSpeciesID(species.getDBSpeciesID(this.getFactory().getDBVersion()));
154            } catch (Exception e) {
155                throw new DAOException("No species ID context for this query!", e);
156            }
157        }
158        return this.getTranscript(q);
159    }
160
161    /**
162     * @param stableID
163     * 
164     * @throws DAOException
165     */
166    @Override
167    public DATranscript getTranscriptByStableID(String stableID) throws DAOException {
168        if (stableID == null || stableID.isEmpty()) {
169            return null;
170        }
171
172        FeatureQuery q = new FeatureQuery();
173        if (!singleSpecies) {
174            try {
175                q.setSpeciesID(species.getDBSpeciesID(this.getFactory().getDBVersion()));
176            } catch (Exception e) {
177                throw new DAOException("No species ID context for this query!", e);
178            }
179        }
180        q.setFeatureStableID(stableID.trim());
181        return this.getTranscript(q);
182    }
183
184    @Override
185    public List<DATranscript> getTranscriptsForGene(Gene gene) throws DAOException {
186        if (gene == null || !(gene instanceof DAGene) || gene.getId() == null) {
187            return null;
188        }
189
190        DAGene daGene = (DAGene) gene;
191
192        FeatureQuery q = new FeatureQuery();
193        if (!singleSpecies) {
194            try {
195                q.setSpeciesID(species.getDBSpeciesID(this.getFactory().getDBVersion()));
196            } catch (Exception e) {
197                throw new DAOException("No species ID context for this query!", e);
198            }
199        }
200
201        q.setGeneID(daGene.getId());
202
203        return this.getTranscripts(q, daGene);
204
205    }
206
207    /**
208     * Returns a list of Ensembl Transcripts matching the query VegaID string.
209     * Vega currently curates gene, transcript and protein annotations for a human 
210     * and and a few key regions of other vertebrate species. Calling this method
211     * on EnsemblGenomes (invertebrate) species will return an empty list by default. 
212     * @param id a valid vega ID (these begin 'OTT...')
213     * 
214     * @throws DAOException 
215     */    
216    @Override
217    public List<DATranscript> getTranscriptsForVegaID(String id) throws DAOException {
218
219        //return an empty list rather than null
220        List<DATranscript> out = new ArrayList<DATranscript>();
221        
222        if (id==null || id.isEmpty() 
223                || !this.getFactory().getRegistry().getDatasourceType().equals(DBConnection.DataSource.ENSEMBLDB)) {
224            return out;
225        }        
226        
227        List<HashMapHolder> l = null;
228        TranscriptRowHandler handler = null;
229
230        List<HashMap> result = new ArrayList<HashMap>();
231
232        SqlSession session = null;
233
234        try {
235            session = this.getFactory().getNewSqlSession();
236            TranscriptMapper mapper = session.getMapper(TranscriptMapper.class);
237            l = mapper.getVegaTranscripts(id);
238        } catch (Exception e) {
239            throw new DAOException("Failed to call getTranscriptsForVegaID", e);
240        } finally {
241            if (session != null) {
242                session.close();
243            }
244        }
245        if (l != null) {
246            for (HashMapHolder h : l) {
247
248                result.add(h.getMap());
249
250            }
251
252            if (result != null && !result.isEmpty()) {
253
254                handler = new TranscriptRowHandler(result);
255                handler.handleResult();
256                out = handler.getListResult();
257            }
258        }
259        
260        
261        if (out == null) {
262            out  = new ArrayList<DATranscript>();
263        } 
264
265        /*
266         * NOTE: we don't set the query VegaID on the return gene as this being null 
267         * is the trigger for lazyloading a vega XRef.
268         * NOR can we make a valid Vega XRef at this point as we dont have enough information 
269         * (ie we dont have its database ID). 
270         */
271        
272//        if (!out.isEmpty()) {
273//            ExternalDB edb = this.daoFactory.getDatabase().getExternalDB(ExternalDBType.VegaTranscript.toString());
274//            DAXRef xref = new DAXRef();
275//            xref.setDaoFactory(this.getFactory());
276//            xref.setDB(edb);
277//            xref.setPrimaryAccession(id);
278//            xref.setDisplayID(id);
279//            xref.setDBName(edb.getDBName());
280//
281//            for (DATranscript g : out) {
282//                g.setVegaTranscriptID(id);
283//                g.addTypedXRefs(ExternalDBType.VegaTranscript, new ArrayList<DAXRef>(Arrays.asList(xref)));
284//            }
285//        }
286        return out;
287    }
288   
289    /**
290     * Returns a list of Ensembl Transcripts matching the query CCDS ID string.
291     * CCDS currently curates consensus canonical transcripts for  human and mouse. 
292     * Calling this method
293     * on EnsemblGenomes (invertebrate) species will return an empty list by default. 
294     * @param id a valid CCDS accession or accession.version (these begin 'CCDS...')
295     * 
296     * @throws DAOException 
297     */    
298    @Override 
299    public List<DATranscript> getTranscriptsForCcdsID(String id) throws DAOException {
300
301        //return an empty list rather than null
302        List<DATranscript> out = new ArrayList<DATranscript>();
303        
304        if (id==null || id.isEmpty() 
305                || !this.getFactory().getRegistry().getDatasourceType().equals(DBConnection.DataSource.ENSEMBLDB)) {
306            return out;
307        }        
308        
309        List<HashMapHolder> l = null;
310        TranscriptRowHandler handler = null;
311
312        List<HashMap> result = new ArrayList<HashMap>();
313
314        SqlSession session = null;
315
316        try {
317            session = this.getFactory().getNewSqlSession();
318            TranscriptMapper mapper = session.getMapper(TranscriptMapper.class);
319            l = mapper.getCcdsTranscripts(id);
320        } catch (Exception e) {
321            throw new DAOException("Failed to call getTranscriptsForCcdsID", e);
322        } finally {
323            if (session != null) {
324                session.close();
325            }
326        }
327        if (l != null) {
328            for (HashMapHolder h : l) {
329
330                result.add(h.getMap());
331
332            }
333
334            if (result != null && !result.isEmpty()) {
335
336                handler = new TranscriptRowHandler(result);
337                handler.handleResult();
338                out = handler.getListResult();
339            }
340        }
341        
342        
343        if (out == null) {
344            out  = new ArrayList<DATranscript>();
345        } 
346
347        /*
348         * NOTE: we don't set the query ccdsID on the return gene as this being null 
349         * is the trigger for lazyloading a CCDS XRef.
350         * NOR can we make a valid Ccds XRef at this point as we dont have enough information 
351         * (ie we dont have its database ID). 
352         */
353        
354//        if (!out.isEmpty()) {
355//            ExternalDB edb = this.daoFactory.getDatabase().getExternalDB(ExternalDBType.CCDS.toString());
356//            DAXRef xref = new DAXRef();
357//            xref.setDaoFactory(this.getFactory());
358//            xref.setDB(edb);
359//            xref.setPrimaryAccession(id);
360//            xref.setDisplayID(id);
361//            xref.setDBName(edb.getDBName());
362//
363//            for (DATranscript g : out) {
364//                g.setCCDSTranscriptID(id);
365//                g.addTypedXRefs(ExternalDBType.CCDS, new ArrayList<DAXRef>(Arrays.asList(xref)));
366//            }
367//        }
368        return out;
369    }
370        
371    
372    //*******************************
373    private DATranscript getTranscript(FeatureQuery query) throws DAOException {
374        DATranscript out = null;
375        TranscriptRowHandler handler = null;
376        List<HashMap> result;
377
378        SqlSession session = null;
379
380        try {
381            session = this.getFactory().getNewSqlSession();
382            TranscriptMapper mapper = session.getMapper(TranscriptMapper.class);
383            result = mapper.getTranscript(query);
384        } catch (Exception e) {
385            throw new DAOException("Failed to call getTranscript", e);
386        } finally {
387            if (session != null) {
388                session.close();
389            }
390        }
391
392        if (result != null && !result.isEmpty()) {
393
394            handler = new TranscriptRowHandler(result);
395            handler.handleResult();
396            out = handler.getObjectResult();
397        }
398
399        return out;
400    }
401
402    private List<DATranscript> getTranscripts(FeatureQuery query, DAGene gene) throws DAOException {
403        List<DATranscript> out = null;
404        TranscriptRowHandler handler = null;
405        List<HashMap> result;
406
407        SqlSession session = null;
408
409        try {
410            session = this.getFactory().getNewSqlSession();
411            TranscriptMapper mapper = session.getMapper(TranscriptMapper.class);
412            result = mapper.getTranscript(query);
413        } catch (Exception e) {
414            throw new DAOException("Failed to call getTranscripts", e);
415        } finally {
416            if (session != null) {
417                session.close();
418            }
419        }
420
421        if (result != null && !result.isEmpty()) {
422
423            handler = new TranscriptRowHandler(result, gene);
424            handler.handleResult();
425            out = handler.getListResult();
426        }
427
428        return out;
429    }
430
431    //*********************************
432    //as an inner class it has access to the factory etc of the enclosing class
433    public class TranscriptRowHandler implements DBResultHandler {
434
435        // the magic strings to be used as property keys for HashMap in Ibatis
436        private final String transcript = "transcript";
437        private final String xref = "xref";
438        private final String target = "target";
439        private final String coords = "coords";
440        private final String coordSystemID = "csID";
441//        private final String xdb = "xdb";
442        private DADNASequence parentSeq = null;
443        private DATranscript objectResult = null;
444        private List<DATranscript> listResult = new ArrayList<DATranscript>();
445        private List<HashMap> rawResults = null;
446        private DAGene daGene = null;
447
448        public TranscriptRowHandler(List<HashMap> results, DAGene g) {
449            daGene = g;
450            rawResults = results;
451            if (daGene != null) {
452                try {
453                    MappingSet m = daGene.getTopLevelMappings();
454                    parentSeq = (DADNASequence) m.first().getTarget();
455                } catch (DAOException ex) {
456                    LOGGER.info("DAOException trying to retrieve the Chromosome for the Gene", ex);
457                }
458            }
459
460        }
461
462        public TranscriptRowHandler(List<HashMap> results) {
463            rawResults = results;
464        }
465
466        @Override
467        public List<DATranscript> getListResult() {
468            return listResult;
469        }
470
471        @Override
472        public DATranscript getObjectResult() {
473            return objectResult;
474        }
475
476        public void handleResult() throws DAOException {
477
478            if (rawResults == null || rawResults.isEmpty()) {
479                return;
480            }
481
482            for (HashMap map : rawResults) {
483                handleRow(map);
484            }
485
486        }
487
488        private void handleRow(HashMap result) throws DAOException {
489
490            DATranscript rowTranscript = this.parseResult(result);
491
492            if (rowTranscript != null) {
493                this.objectResult = rowTranscript;
494                this.listResult.add(rowTranscript);
495            }
496
497        }
498
499        private DATranscript parseResult(HashMap result) throws DAOException {
500
501            if (result == null || result.isEmpty()) {
502                return null;
503            }
504
505            DADNASequence pparentSeq = parentSeq;
506            DATranscript ptranscript = null;
507            DADNASequence ptarget = null;
508            Coordinate pcoords = null;
509            DAXRef pxref = null;
510            Integer pcsID = null;
511//            ExternalDB db = null;
512
513            ptranscript = (DATranscript) result.get(this.transcript);
514            pcoords = (Coordinate) result.get(this.coords);
515            ptarget = (DADNASequence) result.get(this.target);
516            pxref = (DAXRef) result.get(this.xref);
517            pcsID = (Integer) result.get(this.coordSystemID);
518//            db = (ExternalDB) result.get(this.xdb);
519
520            if (ptranscript == null
521                    || pcoords == null
522                    || ptarget == null) {
523                return null;
524            }
525            
526//            if (db != null) {
527//                    int originalHashCode = db.originalHashCode();
528//                    db = ((CoreDatabase) daoFactory.getDatabase()).validateExternalDB(db);  
529//                    int checkedHashCode = db.originalHashCode();
530//                    if (originalHashCode-checkedHashCode !=0) {
531//                        System.out.println("*** FAILED TO REUSE EXTERNALDB");
532//                    } else {
533//                        System.out.println("*** successfully reused externaldb");
534//                    }
535//            }            
536
537            //if we havent got a target DNA for the Gene, we can make and use one
538            //for the transcript, beware - may make >1 identically id'd sequence 
539            //here tho cos not caching
540            if (pparentSeq == null) {
541
542                CoordinateSystem targetCS = null;
543
544                if (ptarget.getDaoFactory() == null) {
545                    ptarget.setDaoFactory(daoFactory);
546                }
547
548                targetCS = ptarget.getCoordSystem();
549
550                if (targetCS == null) {
551                    if (singleSpecies) {
552                        ptarget.setCoordSystem(ssFactory.getDatabase().getCSByID(pcsID));
553                    } else {
554                        ptarget.setCoordSystem(collFactory.getDatabase().getCSByID(species, pcsID));
555                    }
556                    targetCS = ptarget.getCoordSystem();
557                }
558
559
560                if (targetCS.isSequenceLevel()) {
561                    ptarget.setCoordSystem(targetCS);
562                } else if (targetCS.getType().equals(EnsemblCoordSystemType.chromosome)) {
563                    ptarget = new DAChromosome((DAOCoreFactory) daoFactory);
564                    ptarget.setId(((DADNASequence) result.get(this.target)).getId());
565                    ptarget.setName(((DADNASequence) result.get(this.target)).getName());
566                    ptarget.setDBSeqLength(((DADNASequence) result.get(this.target)).getDBSeqLength());
567                    ptarget.setSpecies(species);
568                    ptarget.setCoordSystem(targetCS);
569
570                    //loook to see if we have this sequence in cache
571                    ptarget = species.getCachedChromosome((DAChromosome) ptarget);
572
573                } else {
574                    ptarget = new DAAssembledDNASequence((DAOCoreFactory) daoFactory);
575                    ptarget.setId(((DADNASequence) result.get(this.target)).getId());
576                    ptarget.setName(((DADNASequence) result.get(this.target)).getName());
577                    ptarget.setDBSeqLength(((DADNASequence) result.get(this.target)).getDBSeqLength());
578                    ptarget.setCoordSystem(targetCS);
579
580                    //not got a cache of assembled sequences yet to check
581                }
582
583
584            } else {
585                //if the target DNASequence isn't the sequence that the gene is on
586                //throw this result away
587                //ultimately will need to modify this to map from alternate CS/sequences
588                if (!ptarget.getId().equals(pparentSeq.getId())) {
589                    return null;
590                }
591            }
592
593            if (ptranscript.getDaoFactory() == null) {
594                //pgene.setType(FeatureType.transcript);
595                ptranscript.setDaoFactory(daoFactory);
596            }
597
598            if (ptranscript.getDisplayXRef() == null) {
599                pxref = (DAXRef) result.get(this.xref);
600                if (pxref != null) {
601                    pxref.setDaoFactory(daoFactory);
602//                    pxref.setDB(db);
603                    ptranscript.setDisplayXRef(pxref);
604                }
605            }
606
607
608
609
610
611//            //check if the transcript object has somehow been returned from a cache and it
612//            //has a mapping to a seq region with the same id as the parentSeq: if
613//            //so, swap it for the parentSeq
614//            for (Mapping m : pgene.getLoadedMappings()) {
615//                if (m.getTarget().getId().equals(pparentSeq.getId())) {
616//                    if (pparentSeq.getDaoFactory() == null) {
617//                        pparentSeq.setDaoFactory(daoFactory);
618//                    }
619//                    if (pparentSeq.getCoordSystem() == null) {
620//                        if (singleSpecies) {
621//                            pparentSeq.setCoordSystem(ssFactory.getDatabase().getCSByID(pcsID));
622//                        } else {
623//                            pparentSeq.setCoordSystem(collFactory.getDatabase().getCSByID(species, pcsID));
624//                        }
625//                    }
626//                    //do the switch
627//                    m.setTarget(pparentSeq);
628//                    m.setTargetCoordinates(pcoords);
629//                    Mapping.addReverseMapping(m);
630//                    return pgene;
631//                }
632//            }
633//
634//
635            Mapping mapping = new Mapping();
636            mapping.setSource(ptranscript);
637            mapping.setTargetCoordinates(pcoords);
638
639            if (pparentSeq != null) {
640                mapping.setTarget(pparentSeq);
641            } else {
642                mapping.setTarget(ptarget);
643            }
644            if (ptranscript.addMapping(mapping)) {
645                Mapping.addReverseMapping(mapping);
646            }
647            if (daGene != null) {
648                if (daGene.getCanonicalTranscriptID() != null
649                        && ptranscript.getId() != null
650                        && daGene.getCanonicalTranscriptID().compareTo(ptranscript.getId()) == 0) {
651                    ptranscript.setCanonical(true);
652                    daGene.setCanonicalTranscript(ptranscript);
653                }
654                daGene.addTranscript(ptranscript);
655                ptranscript.setGene(daGene);
656            }
657            return ptranscript;
658
659
660        }
661    }
662}