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.compara;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.TreeMap;
028import org.apache.ibatis.session.SqlSession;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031import uk.ac.roslin.ensembl.config.EnsemblComparaDivision;
032import uk.ac.roslin.ensembl.dao.compara.HomologyDAO;
033import uk.ac.roslin.ensembl.dao.database.DBBaseDAO;
034import uk.ac.roslin.ensembl.dao.database.DBSpecies;
035import uk.ac.roslin.ensembl.dao.factory.DAOComparaFactory;
036import uk.ac.roslin.ensembl.datasourceaware.compara.DAHomologyPairRelationship;
037import uk.ac.roslin.ensembl.datasourceaware.compara.InverseHomologyPairRelationshipView;
038import uk.ac.roslin.ensembl.datasourceaware.core.DADNASequence;
039import uk.ac.roslin.ensembl.datasourceaware.core.DAGene;
040import uk.ac.roslin.ensembl.exception.DAOException;
041import uk.ac.roslin.ensembl.exception.NonUniqueException;
042import uk.ac.roslin.ensembl.mapper.compara.HomologyPairMapper;
043import uk.ac.roslin.ensembl.model.Coordinate;
044import uk.ac.roslin.ensembl.model.Mapping;
045import uk.ac.roslin.ensembl.model.MappingSet;
046import uk.ac.roslin.ensembl.model.compara.HomologyAlignmentProperties;
047import uk.ac.roslin.ensembl.model.core.DNASequence;
048import uk.ac.roslin.ensembl.model.core.Gene;
049import uk.ac.roslin.ensembl.model.core.Species;
050
051
052public class DBHomologyDAO extends DBBaseDAO implements HomologyDAO {
053
054    final static Logger LOGGER = LoggerFactory.getLogger(DBHomologyDAO.class);
055
056    //If we really want to cache here we will need to use a singleton DBHomologyDAO
057    TreeMap<String, DAGene> localGeneCache = new TreeMap<String, DAGene>();
058
059    EnsemblComparaDivision comparaDivision;
060    String sourceVersion = "";
061
062    public DBHomologyDAO(DAOComparaFactory factory) throws DAOException {
063        super(factory);
064        comparaDivision = factory.getComparaDivision();
065        sourceVersion = this.getFactory().getDBVersion();
066    }
067
068    /**
069     * Data Access method to retrieve all of the homologies
070     * (i.e. to all species) for a given gene with a stableID.
071     * @param g The query Gene
072     * @return List of HomologyPairRelationships
073     * @throws DAOException 
074     */    
075    @Override
076    public List<DAHomologyPairRelationship> getHomologiesForGene(Gene g) throws DAOException {
077
078        DAGene gene = (DAGene) g;
079        
080        //this tests for the field used by mybatis
081        if (gene.getStableID()==null || gene.getStableID().isEmpty()) {
082            throw new DAOException("Failed to get homologies for a gene without a stable EnsemblID");
083        }
084
085        List<DAHomologyPairRelationship> results = new ArrayList<DAHomologyPairRelationship>();
086        SqlSession session = null;
087
088        try {
089            session = this.getFactory().getNewSqlSession();
090            HomologyPairMapper mapper = session.getMapper(HomologyPairMapper.class);
091            results = mapper.getHomologiesForGene(g);
092        } catch (Exception e) {
093            throw new DAOException("Failed to call getHomologiesForGene(g) on HomologyDAO", e);
094        } finally {
095            if (session != null) {
096                session.close();
097            }
098        }
099
100        //do i want to do this - or to throw an exception to indicate something
101        //has gone wrong with the DB call
102        if (results==null) {
103            return new ArrayList<DAHomologyPairRelationship>();
104        }
105
106        for (DAHomologyPairRelationship h : results) {
107
108            Species sp = null;
109            //the DAO must have a factory or it wouldn't get here
110            //and we 'should' always have a registry on a factory
111            //we use this to get the species for the homologous gene (i.e from the registry)
112            //but we can return null here
113
114                try {
115                    sp = this.getFactory().getRegistry().getSpeciesByAlias(h.getTargetProperties().getSpeciesName());
116                } catch (NonUniqueException ex) {
117                    LOGGER.debug(ex.getMessage());
118                    for (Object s : ex.getAllHits()) {
119                        LOGGER.debug(((DBSpecies)s).getDatabaseStyleName());                   
120                    }
121                    //not sure what to do here if the name is not unique....
122                    //
123                    //continue;
124                }
125
126            if (sp == null) {
127                //make a 'dummy species' - this wont have a factory!
128                sp = new DBSpecies();
129                ((DBSpecies) sp).setComparaName(sourceVersion, h.getTargetProperties().getSpeciesName());
130            }
131
132            h.setSource(gene);
133
134            DAGene target = new DAGene();
135            target.setDBVersion( this.getFactory().getDBVersion());
136            target.setStableID(h.getTargetProperties().getGeneID());
137            target.setSpecies(sp);
138
139            //hopefully there will only be one relationship to a given target gene
140            //but just in case
141            if (localGeneCache.containsKey(target.getStableID())) {
142                target = localGeneCache.get(target.getStableID());
143            } else {
144               localGeneCache.put(target.getStableID(),target );
145            }
146
147
148            h.setTarget(target);
149
150            //fill in the sourceProperties that are not filled in by sql
151            //but are needed once we reverse the relationship
152            HomologyAlignmentProperties sourceProperties = h.getSourceProperties();
153
154
155            try {
156                sourceProperties.setSequenceName(
157                        ((DADNASequence)gene.getAnnotationLevelMappings().first().getTarget()).getName());
158                sourceProperties.setCoords(
159                        gene.getAnnotationLevelMappings().first().getTargetCoordinates());
160            } catch (Exception e) {
161                LOGGER.warn("Gene "+gene.getHashID()+ " has no annotation level coordinates.");
162            }
163            sourceProperties.setSpeciesName(
164                    (gene.getSpecies()!= null) ? gene.getSpecies().getComparaName(sourceVersion) : ""
165                    );
166
167//            //reverse the relationship
168//            DAHomologyPairRelationship reverse = new DAHomologyPairRelationship();
169//            reverse.setSource(target);
170//            reverse.setTarget(gene);
171//            //these arent treversable - use the same type??
172//            //i.e the type one2many is not necessarily specified the correct way
173//            reverse.setRelationshipType(h.getRelationshipType());
174//            reverse.setTargetProperties(h.getSourceProperties());
175//            reverse.setSourceProperties(h.getTargetProperties());
176//            reverse.setLastCommonAncestor(h.getLastCommonAncestor());
177//            reverse.setId(h.getId());
178//
179//            //add the reversed relationship
180//            target.addHomology(comparaDivision, reverse);
181
182            //this doesnt reverse the direction of the relationship
183            //maybe i do want to do this - and remember that i have to look at
184            //relationships either way
185            target.addHomology(comparaDivision, new InverseHomologyPairRelationshipView(h));
186
187        }
188        return results;
189    }
190    
191    /**
192     * Data Access method to retrieve all of the homologies
193     * in the specified target species for a List of genes with stableIDs.
194     * @param genes theList of query genes
195     * @param target_sp The target Species
196     * @param chrName The target chromosome name
197     * @return List of HomologyPairRelationships
198     * @throws DAOException 
199     */
200    @Override
201    public List<DAHomologyPairRelationship> 
202            getHomologiesForGenesBySpecies(List<? extends Gene> genes, Species target_sp, String chrName) 
203            throws DAOException {
204
205
206        
207        if (genes==null || genes.isEmpty()) {
208            throw new DAOException("Failed to get homologies: No Genes specified");
209        }
210        
211        HashMap<String,DAGene> geneMap = new HashMap<String,DAGene>();
212        
213        if (target_sp==null ) {
214            throw new DAOException("Failed to get homologies: no target Species specified");
215        }
216        if (target_sp.getComparaName(sourceVersion) == null 
217                || target_sp.getComparaName(sourceVersion).isEmpty()) {
218            throw new DAOException("Failed to get homologies: target Species not identified in Compara source");
219        }
220        
221        HashMap<String, Object> query = new HashMap<String, Object>();
222        
223        query.put("targetSpecies", target_sp.getComparaName(sourceVersion));
224
225        
226        for (Gene g : genes) {
227            if (!((DAGene)g).getStableID().isEmpty()) {
228
229                geneMap.put(((DAGene)g).getStableID(), (DAGene) g);
230            }
231        }
232        
233        if (geneMap.isEmpty()) {
234            throw new DAOException("Failed to get homologies: No Genes specified");
235        }
236        
237        query.put("geneIDs",new ArrayList(geneMap.keySet()) );
238        
239        if (chrName !=null && !chrName.isEmpty()) {
240            query.put("chrName", chrName);
241        }
242        
243        List<DAHomologyPairRelationship> results = new ArrayList<DAHomologyPairRelationship>();
244        SqlSession session = null;
245
246        try {
247            session = this.getFactory().getNewSqlSession();
248            HomologyPairMapper mapper = session.getMapper(HomologyPairMapper.class);
249            results = mapper.getHomologiesForGenesBySpecies(query);
250        } catch (Exception e) {
251            throw new DAOException("Failed to call getSpeciesHomologiesForGenes(HashMap) on HomologyDAO", e);
252        } finally {
253            if (session != null) {
254                session.close();
255            }
256        }
257
258        //do i want to do this - or to throw an exception to indicate something
259        //has gone wrong with the DB call
260        if (results==null) {
261            return new ArrayList<DAHomologyPairRelationship>();
262        }
263
264        for (DAHomologyPairRelationship h : results) {
265
266            DAGene sourceGene = geneMap.get(h.getSourceProperties().getGeneID());
267
268            h.setSource(sourceGene);
269
270            DAGene targetGene = new DAGene();
271            targetGene.setDBVersion( this.getFactory().getDBVersion());
272            targetGene.setStableID(h.getTargetProperties().getGeneID());
273            targetGene.setSpecies(target_sp);
274
275            //hopefully there will only be one relationship to a given target gene
276            //but just in case
277            if (localGeneCache.containsKey(targetGene.getStableID())) {
278                targetGene = localGeneCache.get(targetGene.getStableID());
279            } else {
280               localGeneCache.put(targetGene.getStableID(),targetGene );
281            }
282
283
284            h.setTarget(targetGene);
285
286            //fill in the sourceProperties that are not filled in by sql
287            //but are needed once we reverse the relationship
288            HomologyAlignmentProperties sourceProperties = h.getSourceProperties();
289
290
291            try {
292                sourceProperties.setSequenceName(
293                        ((DADNASequence)sourceGene.getAnnotationLevelMappings().first().getTarget()).getName());
294                sourceProperties.setCoords(
295                        sourceGene.getAnnotationLevelMappings().first().getTargetCoordinates());
296            } catch (Exception e) {
297                LOGGER.warn("Gene "+sourceGene.getHashID()+ " has no annotation level coordinates.");
298            }
299            sourceProperties.setSpeciesName(
300                    (sourceGene.getSpecies()!= null) ? sourceGene.getSpecies().getComparaName(sourceVersion) : ""
301                    );
302
303//            //reverse the relationship
304//            DAHomologyPairRelationship reverse = new DAHomologyPairRelationship();
305//            reverse.setSource(target);
306//            reverse.setTarget(gene);
307//            //these arent treversable - use the same type??
308//            //i.e the type one2many is not necessarily specified the correct way
309//            reverse.setRelationshipType(h.getRelationshipType());
310//            reverse.setTargetProperties(h.getSourceProperties());
311//            reverse.setSourceProperties(h.getTargetProperties());
312//            reverse.setLastCommonAncestor(h.getLastCommonAncestor());
313//            reverse.setId(h.getId());
314//
315//            //add the reversed relationship
316//            target.addHomology(comparaDivision, reverse);
317
318            //this doesnt reverse the direction of the relationship
319            //maybe i do want to do this - and remember that i have to look at
320            //relationships either way
321            
322            //we havent done a complete search for the division for all species
323            //so store result with a specieal key
324            //targetGene.addHomology(comparaDivision, new InverseHomologyPairRelationshipView(h));
325            sourceGene.addHomology(EnsemblComparaDivision.INCOMPLETE_SEARCH,h);
326            targetGene.addHomology(EnsemblComparaDivision.INCOMPLETE_SEARCH, new InverseHomologyPairRelationshipView(h));
327
328        }
329        return results;
330    }
331        
332    /**
333    * Data Access method to retrieve all of the regions on DNASequences
334    * for the specified target species, holding genes with homologies to genes found
335    * in the given region of the source query sequence. 
336    * @param source DNASequence to be queried
337    * @param range Coordinate range of query sequence to be queried
338    * @param target the target species
339    * @return HashMap of Syntenic regions against the set of Mappings holding homologous genes
340    * @throws DAOException 
341    */    
342    @Override
343    public HashMap<DADNASequence, MappingSet> getRegionsOfConservedSynteny(DNASequence source, Coordinate range,
344            Species target) throws DAOException {
345        return this.getRegionsOfConservedSynteny(source, range, target, null);
346    }
347
348   /**
349    * Data Access method to retrieve any potential region of 
350    * conserved synteny on a given Chromosome/fragment 
351    * for the specified target species, holding genes with homologies to genes found
352    * in the given region of the source query sequence. 
353    * @param source DNASequence to be queried
354    * @param range Coordinate range of query sequence to be queried
355    * @param target the target species
356    * @param chrName the target chromosome name
357    * @return HashMap of Syntenic regions against the set of Mappings holding homologous genes
358    * @throws DAOException 
359    */
360    @Override
361    public HashMap<DADNASequence, MappingSet>
362            getRegionsOfConservedSynteny(DNASequence source, Coordinate range, Species target, String chrName) 
363            throws DAOException {
364
365        DADNASequence sChr;
366        DBSpecies tSp;
367        String tChr;
368
369        if (source==null || target==null) {
370            throw new DAOException("Require a source chromosome and a target species to call getRegionsOfConservedSynteny");
371        }
372
373        try {
374            sChr = (DADNASequence) source;
375            tSp = (DBSpecies) target;
376            tChr = chrName;
377        } catch (Exception e) {
378            throw new DAOException("failed to pass valid parameters to get Regions of Conserved Synteny", e);
379        }
380
381        HashMap<DADNASequence, MappingSet> syntenies = new HashMap<DADNASequence, MappingSet>();
382
383        if (range == null || range.getStart() == null || range.getEnd() == null) {
384            range = new Coordinate(sChr.getBioBegin(), sChr.getBioEnd(), 1);
385        }
386
387        List<DAGene> sGenes = sChr.getGenesOnRegion(range);
388
389        if (sGenes == null || sGenes.isEmpty()) {
390            return syntenies;
391        }
392        
393        List<DAHomologyPairRelationship> speciesHomologiesForGenes = this.getHomologiesForGenesBySpecies(sGenes, target, tChr);
394
395        //old way recursed through the genes...
396        //for (DAGene g : sGenes) {
397            //List<DAHomologyPairRelationship> l = g.getHomologies(this.comparaDivision, tSp);
398
399            for (DAHomologyPairRelationship hpr : speciesHomologiesForGenes) {
400                //force conversion to real mappings, re-using cached chromosomes if appropriate
401                MappingSet m = null;
402                try {
403                    //get annotation level mappingsof the target (homologous) gene
404                    //i.e. gene-> chromosome in general?
405                    m = hpr.getTarget().getAnnotationLevelMappings();
406                } catch (DAOException dAOException) {
407                }
408                if (m != null && !m.isEmpty()) {
409                    //probably only one of these?
410                    for (Mapping mp : m) {
411                       DADNASequence dna = (DADNASequence) mp.getTarget();
412                       if (!syntenies.containsKey(dna)) {
413                           //if we are using the gene->chr mappings we need to be sorting on the target coords
414                           //syntenies.put(dna, new MappingSet(Mapping.mappingOnTargetComparator));
415                           //but if we are using the chr->gene mappings we can sort on the source coords (i.e. default comparator)
416                           syntenies.put(dna, new MappingSet());
417                       }
418                       // this adds the mapping hom_gene->chr
419                       //syntenies.get(dna).add(mp);
420                       //but we want the mapping chr->hom_gene
421
422                       //this method is called within the getReverseMapping method if required!
423                       //Mapping.addReverseMapping(mp);
424                       syntenies.get(dna).add(mp.getReverseMapping());
425                    }
426                }
427                // incase there are no mappings for the homologous genes on a chromosome!
428                else
429                {
430                    //what do we do
431                }
432            }
433        //}
434        return syntenies;
435    }
436    
437}