ubic.gemma.core.ontology.providers.GeneOntologyServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.core.ontology.providers.GeneOntologyServiceImpl.java

Source

/*
    
 *
 * Copyright (c) 2007 University of British Columbia
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package ubic.gemma.core.ontology.providers;

import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.query.*;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jena.larq.IndexLARQ;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ubic.basecode.ontology.OntologyLoader;
import ubic.basecode.ontology.model.AnnotationProperty;
import ubic.basecode.ontology.model.OntologyClassRestriction;
import ubic.basecode.ontology.model.OntologyResource;
import ubic.basecode.ontology.model.OntologyTerm;
import ubic.basecode.ontology.providers.AbstractOntologyService;
import ubic.basecode.ontology.search.OntologyIndexer;
import ubic.basecode.ontology.search.OntologySearch;
import ubic.basecode.ontology.search.SearchIndex;
import ubic.basecode.util.Configuration;
import ubic.gemma.core.genome.gene.service.GeneService;
import ubic.gemma.model.common.description.Characteristic;
import ubic.gemma.model.genome.Gene;
import ubic.gemma.model.genome.GeneOntologyTermValueObject;
import ubic.gemma.model.genome.Taxon;
import ubic.gemma.persistence.service.association.Gene2GOAssociationService;
import ubic.gemma.persistence.util.Settings;

import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Holds a complete copy of the GeneOntology. This gets loaded on startup.
 *
 * @author pavlidis
 */
@SuppressWarnings({ "WeakerAccess", "unused" }) // Possible external use
@Component
public class GeneOntologyServiceImpl implements GeneOntologyService {

    public enum GOAspect {
        BIOLOGICAL_PROCESS, CELLULAR_COMPONENT, MOLECULAR_FUNCTION
    }

    private static final String ALL_ROOT = GeneOntologyService.BASE_GO_URI + "ALL";
    private static boolean enabled = true;
    private final static String GO_URL = "http://purl.obolibrary.org/obo/go.owl";
    private final static boolean LOAD_BY_DEFAULT = true;
    private static final String LOAD_GENE_ONTOLOGY_OPTION = "load.geneOntology";
    private static final Log log = LogFactory.getLog(GeneOntologyServiceImpl.class.getName());
    private static final String PART_OF_URI = "http://purl.obolibrary.org/obo/BFO_0000050";
    private static final AtomicBoolean ready = new AtomicBoolean(false);
    private static final AtomicBoolean running = new AtomicBoolean(false);
    // cache
    private static final Map<String, GOAspect> term2Aspect = new HashMap<>();
    // cache
    private static Map<String, OntologyTerm> uri2Term = new HashMap<>();

    /**
     * @param  term the term
     * @return      Usual formatted GO id, e.g., GO:0039392
     */
    public static String asRegularGoId(Characteristic term) {
        String uri = term.getValue();
        return GeneOntologyServiceImpl.asRegularGoId(uri);
    }

    /**
     * @param  term ontology term
     * @return      Usual formatted GO id, e.g., GO:0039392
     */
    public static String asRegularGoId(OntologyTerm term) {
        if (term == null)
            return null;
        String uri = term.getUri();
        return GeneOntologyServiceImpl.asRegularGoId(uri);
    }

    public static String asRegularGoId(String uri) {
        return uri.replaceAll(".*?/", "").replace("_", ":");
    }

    public static boolean isEnabled() {
        return GeneOntologyServiceImpl.enabled;
    }

    /**
     * @return Turn an id like GO:0038128 into a URI.
     */
    private static String toUri(String goId) {
        String uriTerm = goId.replace(":", "_");
        return GeneOntologyService.BASE_GO_URI + uriTerm;
    }

    /**
     * Cache of go term -> child terms
     */
    private final Map<String, Collection<OntologyTerm>> childrenCache = Collections
            .synchronizedMap(new HashMap<String, Collection<OntologyTerm>>());

    private Gene2GOAssociationService gene2GOAssociationService;

    private GeneService geneService;

    /**
     * Cache of gene -> go terms.
     */
    private final Map<Gene, Collection<OntologyTerm>> goTerms = new HashMap<>();

    private final Collection<SearchIndex> indices = new HashSet<>();

    private OntModel model;

    /**
     * Cache of go term -> parent terms
     */
    private final Map<String, Collection<OntologyTerm>> parentsCache = Collections
            .synchronizedMap(new HashMap<String, Collection<OntologyTerm>>());

    @Override
    public void afterPropertiesSet() {

        /*
         * If this load.ontologies is NOT configured, we go ahead (per-ontology config will be checked).
         */
        String doLoad = Configuration.getString("load.ontologies");
        if (StringUtils.isBlank(doLoad) || Configuration.getBoolean("load.ontologies")) {
            this.init(false);
        }

    }

    @Override
    public Map<Long, Collection<OntologyTerm>> calculateGoTermOverlap(Gene queryGene, Collection<Long> geneIds) {

        Map<Long, Collection<OntologyTerm>> overlap = new HashMap<>();
        if (queryGene == null)
            return null;
        if (geneIds.size() == 0)
            return overlap;

        Collection<OntologyTerm> queryGeneTerms = this.getGOTerms(queryGene);

        overlap.put(queryGene.getId(), queryGeneTerms); // include the query gene in the list. Clearly 100% overlap
        // with itself!

        Collection<Gene> genes = this.geneService.load(geneIds);

        this.putOverlapGenes(overlap, queryGeneTerms, genes);

        return overlap;
    }

    @Override
    public Collection<OntologyTerm> calculateGoTermOverlap(Gene queryGene1, Gene queryGene2) {

        if (queryGene1 == null || queryGene2 == null)
            return null;

        Collection<OntologyTerm> queryGeneTerms1 = this.getGOTerms(queryGene1);
        Collection<OntologyTerm> queryGeneTerms2 = this.getGOTerms(queryGene2);

        return this.computeOverlap(queryGeneTerms1, queryGeneTerms2);
    }

    @Override
    public Map<Long, Collection<OntologyTerm>> calculateGoTermOverlap(Long queryGene, Collection<Long> geneIds) {
        Map<Long, Collection<OntologyTerm>> overlap = new HashMap<>();
        if (queryGene == null)
            return null;
        if (geneIds.size() == 0)
            return overlap;

        Collection<OntologyTerm> queryGeneTerms = this.getGOTerms(queryGene);

        overlap.put(queryGene, queryGeneTerms); // include the query gene in the list. Clearly 100% overlap
        // with itself!

        Collection<Gene> genes = this.geneService.load(geneIds);

        this.putOverlapGenes(overlap, queryGeneTerms, genes);

        return overlap;
    }

    @Override
    public Collection<OntologyTerm> computeOverlap(Collection<OntologyTerm> masterTerms,
            Collection<OntologyTerm> comparisonTerms) {
        Collection<OntologyTerm> overlapTerms = new HashSet<>(masterTerms);
        overlapTerms.retainAll(comparisonTerms);

        return overlapTerms;
    }

    @Override
    public Collection<OntologyTerm> findTerm(String queryString) {

        if (!this.isReady())
            return new HashSet<>();

        if (GeneOntologyServiceImpl.log.isDebugEnabled())
            GeneOntologyServiceImpl.log.debug("Searching Gene Ontology for '" + queryString + "'");

        // make sure we are all-inclusive
        queryString = queryString.trim();
        queryString = queryString.replaceAll("\\s+", " AND ");

        StopWatch timer = new StopWatch();
        timer.start();
        Collection<OntologyResource> rawMatches = new HashSet<>();
        for (SearchIndex index : this.indices) {
            rawMatches.addAll(OntologySearch.matchIndividuals(model, index, queryString));
        }
        if (timer.getTime() > 100) {
            GeneOntologyServiceImpl.log.info("Find " + rawMatches.size() + " raw go terms from " + queryString
                    + ": " + timer.getTime() + " ms");
        }
        timer.reset();
        timer.start();

        /*
         * Required to make sure the descriptions are filled in.
         */
        Collection<OntologyTerm> matches = new HashSet<>();
        for (OntologyResource r : rawMatches) {
            if (StringUtils.isBlank(r.getUri()))
                continue;
            OntologyTerm termForURI = getTerm(r.getUri());
            if (termForURI == null) {
                GeneOntologyServiceImpl.log.warn("No term for : " + r);
                continue;
            }
            matches.add(termForURI);
        }

        if (timer.getTime() > 100) {
            GeneOntologyServiceImpl.log
                    .info("Convert " + rawMatches.size() + " raw go terms to terms: " + timer.getTime() + " ms");
        }

        return matches;
    }

    @Override
    public Collection<OntologyTerm> getAllChildren(OntologyTerm entry) {
        return this.getAllChildren(entry, false);
    }

    @Override
    public Collection<OntologyTerm> getAllChildren(OntologyTerm entry, boolean includePartOf) {
        return this.getDescendants(entry, includePartOf);
    }

    @Override
    public Collection<String> getAllGOTermIds() {
        return GeneOntologyServiceImpl.uri2Term.keySet();
    }

    @Override
    public Collection<OntologyTerm> getAllParents(Collection<OntologyTerm> entries) {
        return this.getAllParents(entries, false);
    }

    @Override
    public Collection<OntologyTerm> getAllParents(Collection<OntologyTerm> entries, boolean includePartOf) {
        if (entries == null)
            return null;
        Collection<OntologyTerm> result = new HashSet<>();
        for (OntologyTerm entry : entries) {
            result.addAll(this.getAncestors(entry, includePartOf));
        }
        return result;
    }

    @Override
    public Collection<OntologyTerm> getAllParents(OntologyTerm entry) {
        return this.getAllParents(entry, true);
    }

    @Override
    public Collection<OntologyTerm> getAllParents(OntologyTerm entry, boolean includePartOf) {
        if (entry == null)
            return new HashSet<>();
        return this.getAncestors(entry, includePartOf);
    }

    @Override
    public Collection<OntologyTerm> getChildren(OntologyTerm entry) {
        return this.getChildren(entry, false);

    }

    @Override
    public Collection<OntologyTerm> getChildren(OntologyTerm entry, boolean includePartOf) {
        if (entry == null)
            return null;
        if (GeneOntologyServiceImpl.log.isDebugEnabled())
            GeneOntologyServiceImpl.log.debug("Getting children of " + entry);
        Collection<OntologyTerm> terms = entry.getChildren(true);

        if (includePartOf)
            terms.addAll(this.getPartsOf(entry));

        return terms;
    }

    @Override
    public Collection<Gene> getGenes(String goId, Taxon taxon) {
        OntologyTerm t = getTermForId(goId);
        if (t == null)
            return null;
        Collection<OntologyTerm> terms = this.getAllChildren(t);
        Collection<Gene> results = new HashSet<>(this.gene2GOAssociationService.findByGOTerm(goId, taxon));

        for (OntologyTerm term : terms) {
            results.addAll(this.gene2GOAssociationService.findByGOTerm(GeneOntologyServiceImpl.asRegularGoId(term),
                    taxon));
        }
        return results;
    }

    @Override
    public Collection<OntologyTerm> getGOTerms(Gene gene) {
        return this.getGOTerms(gene, true, null);
    }

    @Override
    public Collection<OntologyTerm> getGOTerms(Gene gene, boolean includePartOf) {
        return this.getGOTerms(gene, includePartOf, null);
    }

    @Override
    public Collection<OntologyTerm> getGOTerms(Gene gene, boolean includePartOf, GOAspect goAspect) {
        Collection<OntologyTerm> cachedTerms = goTerms.get(gene);
        if (GeneOntologyServiceImpl.log.isTraceEnabled() && cachedTerms != null) {
            this.logIds("found cached GO terms for " + gene.getOfficialSymbol(), goTerms.get(gene));
        }

        if (cachedTerms == null) {
            Collection<OntologyTerm> allGOTermSet = new HashSet<>();

            Collection<Characteristic> annotations = gene2GOAssociationService.findByGene(gene);
            for (Characteristic c : annotations) {
                if (!GeneOntologyServiceImpl.uri2Term.containsKey(c.getValueUri())) {
                    GeneOntologyServiceImpl.log.warn("Term " + c.getValueUri()
                            + " not found in term list can't add to results (ontology not loaded?)");
                    continue;
                }
                allGOTermSet.add(GeneOntologyServiceImpl.uri2Term.get(c.getValueUri()));
            }

            allGOTermSet.addAll(this.getAllParents(allGOTermSet, includePartOf));

            cachedTerms = Collections.unmodifiableCollection(allGOTermSet);
            if (GeneOntologyServiceImpl.log.isTraceEnabled())
                this.logIds("caching GO terms for " + gene.getOfficialSymbol(), allGOTermSet);
            goTerms.put(gene, cachedTerms);
        }

        if (goAspect != null) {

            Collection<OntologyTerm> finalTerms = new HashSet<>();

            for (OntologyTerm ontologyTerm : cachedTerms) {
                GOAspect term = this.getTermAspect(ontologyTerm);
                if (term != null && term.equals(goAspect)) {
                    finalTerms.add(ontologyTerm);
                }
            }

            return finalTerms;
        }

        return cachedTerms;
    }

    @Override
    public Collection<OntologyTerm> getGOTerms(Long geneId) {
        return this.getGOTerms(geneId, true, null);
    }

    /**
     * @param  gene          gene
     * @param  goAspect      go aspect
     * @param  includePartOf include part of
     * @return               collection of ontology terms
     */
    public Collection<OntologyTerm> getGOTerms(Long gene, boolean includePartOf, GOAspect goAspect) {
        return this.getGOTerms(geneService.load(gene), includePartOf, goAspect);
    }

    @Override
    public Collection<OntologyTerm> getParents(OntologyTerm entry) {
        return this.getParents(entry, false);
    }

    @Override
    public Collection<OntologyTerm> getParents(OntologyTerm entry, boolean includePartOf) {
        Collection<OntologyTerm> parents = entry.getParents(true);
        Collection<OntologyTerm> results = new HashSet<>();
        for (OntologyTerm term : parents) {
            // The isRoot() returns true for the MolecularFunction, BiologicalProcess, CellularComponent
            if (term.isRoot())
                continue;
            if (term.getUri().equalsIgnoreCase(GeneOntologyServiceImpl.ALL_ROOT))
                continue;
            //noinspection StatementWithEmptyBody // Better readability
            if (term instanceof OntologyClassRestriction) {
                // don't keep it. - for example, "ends_during" RO_0002093
            } else {
                results.add(term);
            }
        }

        if (includePartOf)
            results.addAll(this.getIsPartOf(entry));

        return results;
    }

    /**
     * @param  uri uri
     * @return     null if not found
     */
    @Override
    public OntologyTerm getTerm(String uri) {
        if (GeneOntologyServiceImpl.uri2Term == null || !GeneOntologyServiceImpl.uri2Term.containsKey(uri))
            return null;
        return GeneOntologyServiceImpl.uri2Term.get(uri);
    }

    @Override
    public GOAspect getTermAspect(Characteristic goId) {
        String string = GeneOntologyServiceImpl.asRegularGoId(goId);
        return this.getTermAspect(string);
    }

    @Override
    public GOAspect getTermAspect(String goId) {
        OntologyTerm term = getTermForId(goId);
        if (term == null)
            return null;
        return this.getTermAspect(term);
    }

    @Override
    public String getTermDefinition(String goId) {
        OntologyTerm t = getTermForId(goId);
        assert t != null;
        Collection<AnnotationProperty> annotations = t.getAnnotations();
        for (AnnotationProperty annot : annotations) {
            GeneOntologyServiceImpl.log.info(annot.getProperty());
            if (annot.getProperty().equals("hasDefinition")) {
                return annot.getContents();
            }
        }
        return null;
    }

    /**
     * @param  goId e.g. GO:0001312
     * @return      null if not found
     */
    @Override
    public OntologyTerm getTermForId(String goId) {
        if (GeneOntologyServiceImpl.uri2Term == null)
            return null;

        if (!GeneOntologyServiceImpl.uri2Term.containsKey(GeneOntologyServiceImpl.toUri(goId))) {
            GeneOntologyServiceImpl.log.warn("GOID " + goId + " not recognized?");
        }

        return GeneOntologyServiceImpl.uri2Term.get(GeneOntologyServiceImpl.toUri(goId));
    }

    @Override
    public String getTermName(String goId) {

        OntologyTerm t = getTermForId(goId);
        if (t == null)
            return "[Not available]"; // not ready yet?
        return t.getTerm();
    }

    @Override
    public GeneOntologyTermValueObject getValueObject(OntologyTerm term) {
        return new GeneOntologyTermValueObject(GeneOntologyServiceImpl.asRegularGoId(term), term);
    }

    @Override
    public Collection<GeneOntologyTermValueObject> getValueObjects(Collection<OntologyTerm> terms) {
        Collection<GeneOntologyTermValueObject> vos = new ArrayList<>(terms.size());
        for (OntologyTerm term : terms) {
            vos.add(this.getValueObject(term));
        }
        return vos;
    }

    @Override
    public Collection<GeneOntologyTermValueObject> getValueObjects(Gene gene) {
        return gene == null ? null : this.getValueObjects(this.getGOTerms(gene));
    }

    @Override
    public synchronized void init(boolean force) {

        if (GeneOntologyServiceImpl.running.get()) {
            GeneOntologyServiceImpl.log.warn("Gene Ontology initialization is already running");
            return;
        }

        boolean loadOntology = Settings.getBoolean(GeneOntologyServiceImpl.LOAD_GENE_ONTOLOGY_OPTION,
                GeneOntologyServiceImpl.LOAD_BY_DEFAULT);

        if (!force && !loadOntology) {
            GeneOntologyServiceImpl.log.info("Loading Gene Ontology is disabled (both force and "
                    + GeneOntologyServiceImpl.LOAD_GENE_ONTOLOGY_OPTION + " options were false.)");
            GeneOntologyServiceImpl.enabled = false;
            return;
        }

        this.initializeGeneOntology();
    }

    @Override
    public Boolean isAChildOf(OntologyTerm parent, OntologyTerm potentialChild) {
        return this.isAParentOf(potentialChild, parent);
    }

    @Override
    public Boolean isAParentOf(OntologyTerm child, OntologyTerm potentialParent) {
        if (potentialParent.isRoot())
            return true; // well....
        Collection<OntologyTerm> parents = this.getAllParents(child);
        return parents.contains(potentialParent);
    }

    @Override
    public Boolean isAValidGOId(String goId) {
        if (!this.isReady()) {
            throw new UnsupportedOperationException("Gene ontology isn't ready so cannot check validity of IDs");
        }
        return GeneOntologyServiceImpl.uri2Term.containsKey(GeneOntologyServiceImpl.toUri(goId))
                || GeneOntologyServiceImpl.uri2Term
                        .containsKey(GeneOntologyServiceImpl.toUri(goId.replaceFirst("_", ":")));
    }

    @Override
    public boolean isBiologicalProcess(OntologyTerm term) {

        GOAspect nameSpace = this.getTermAspect(term);
        if (nameSpace == null) {
            GeneOntologyServiceImpl.log.debug("No namespace for " + term + ", assuming not Biological Process");
            return false;
        }

        return nameSpace.equals(GOAspect.BIOLOGICAL_PROCESS);
    }

    @Override
    public synchronized boolean isGeneOntologyLoaded() {

        return GeneOntologyServiceImpl.ready.get();
    }

    @Override
    public synchronized boolean isReady() {
        return GeneOntologyServiceImpl.ready.get();
    }

    @Override
    public synchronized boolean isRunning() {
        return GeneOntologyServiceImpl.running.get();
    }

    @Override
    public Collection<OntologyTerm> listTerms() {
        return GeneOntologyServiceImpl.uri2Term.values();
    }

    @Override
    public void loadTermsInNameSpace(InputStream is) {
        this.model = OntologyLoader.loadMemoryModel(is, null, OntModelSpec.OWL_MEM);
        Collection<OntologyResource> terms = OntologyLoader.initialize(null, model);
        this.indices.add(OntologyIndexer.indexOntology("GeneOntology", model));
        GeneOntologyServiceImpl.uri2Term.clear();
        this.addTerms(terms);
        GeneOntologyServiceImpl.ready.set(true);
    }

    @Autowired
    public void setGene2GOAssociationService(Gene2GOAssociationService gene2GOAssociationService) {
        this.gene2GOAssociationService = gene2GOAssociationService;
    }

    @Autowired
    public void setGeneService(GeneService geneService) {
        this.geneService = geneService;
    }

    @Override
    public void shutDown() {
        if (this.isReady()) {
            try {
                this.goTerms.clear();
                this.childrenCache.clear();
                this.parentsCache.clear();
                GeneOntologyServiceImpl.term2Aspect.clear();
                for (IndexLARQ l : indices) {
                    l.close();
                }

                this.model.close();
                this.model = null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                GeneOntologyServiceImpl.ready.set(false);
                GeneOntologyServiceImpl.running.set(false);
            }

        }

    }

    protected void loadTermsInNameSpace() {
        this.model = OntologyLoader.loadMemoryModel(GeneOntologyServiceImpl.GO_URL, OntModelSpec.OWL_MEM);
        Collection<OntologyResource> terms = OntologyLoader.initialize(GeneOntologyServiceImpl.GO_URL, model);
        this.indices.add(OntologyIndexer
                .indexOntology(GeneOntologyServiceImpl.GO_URL.replaceFirst(".*/", "").replace(".owl", ""), model));
        this.addTerms(terms);
    }

    private void addTerms(Collection<OntologyResource> newTerms) {

        for (OntologyResource term : newTerms) {
            if (term.getUri() == null)
                continue;
            if (term instanceof OntologyTerm) {
                OntologyTerm ontTerm = (OntologyTerm) term;
                GeneOntologyServiceImpl.uri2Term.put(term.getUri(), ontTerm);
                for (String alternativeID : ontTerm.getAlternativeIds()) {
                    GeneOntologyServiceImpl.log.debug(GeneOntologyServiceImpl.toUri(alternativeID));
                    GeneOntologyServiceImpl.uri2Term.put(GeneOntologyServiceImpl.toUri(alternativeID), ontTerm);
                }
            }
        }
    }

    private synchronized Collection<OntologyTerm> getAncestors(OntologyTerm entry, boolean includePartOf) {

        if (entry == null) {
            return new HashSet<>();
        }

        Collection<OntologyTerm> ancestors = parentsCache.get(entry.getUri());
        if (ancestors == null) {
            ancestors = new HashSet<>();

            Collection<OntologyTerm> parents = this.getParents(entry, includePartOf);
            if (parents != null) {
                for (OntologyTerm parent : parents) {
                    ancestors.add(parent);
                    ancestors.addAll(this.getAncestors(parent, includePartOf));
                }
            }

            ancestors = Collections.unmodifiableCollection(ancestors);
            parentsCache.put(entry.getUri(), ancestors);
        }
        return new HashSet<>(ancestors);
    }

    /**
     * @return Given an ontology term recursively determines all the children and adds them to a cache (same as
     *         getAllParents but the recursive code is a little cleaner and doesn't use and accumulator)
     */
    private synchronized Collection<OntologyTerm> getDescendants(OntologyTerm entry, boolean includePartOf) {

        Collection<OntologyTerm> descendants = childrenCache.get(entry.getUri());
        if (descendants == null) {
            descendants = new HashSet<>();

            Collection<OntologyTerm> children = this.getChildren(entry, includePartOf);
            if (children != null) {
                for (OntologyTerm child : children) {
                    descendants.add(child);
                    descendants.addAll(this.getDescendants(child, includePartOf));
                }
            }

            descendants = Collections.unmodifiableCollection(descendants);
            childrenCache.put(entry.getUri(), descendants);
        }
        return new HashSet<>(descendants);

    }

    /**
     * Return terms to which the given term has a part_of relation (it is "part_of" them).
     */
    private Collection<OntologyTerm> getIsPartOf(OntologyTerm entry) {
        Collection<OntologyTerm> r = new HashSet<>();
        String u = entry.getUri();
        String queryString = "SELECT ?x WHERE {  <" + u
                + ">  <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?v . "
                + "?v <http://www.w3.org/2002/07/owl#onProperty>  <" + GeneOntologyServiceImpl.PART_OF_URI + "> . "
                + "?v <http://www.w3.org/2002/07/owl#someValuesFrom> ?x . }";
        Query q = QueryFactory.create(queryString);
        QueryExecution qexec = QueryExecutionFactory.create(q, (Model) entry.getModel());
        try {
            ResultSet results = qexec.execSelect();
            while (results.hasNext()) {
                QuerySolution soln = results.nextSolution();
                Resource x = soln.getResource("x");
                if (x.isAnon())
                    continue; // some reasoners will return these.
                String uri = x.getURI();
                if (GeneOntologyServiceImpl.log.isDebugEnabled())
                    GeneOntologyServiceImpl.log
                            .debug(entry + " is part of " + GeneOntologyServiceImpl.uri2Term.get(uri));
                r.add(GeneOntologyServiceImpl.uri2Term.get(uri));
            }
        } finally {
            qexec.close();
        }
        return r;
    }

    /**
     * Return terms which have "part_of" relation with the given term (they are "part_of" the given term).
     */
    private Collection<OntologyTerm> getPartsOf(OntologyTerm entry) {
        Collection<OntologyTerm> r = new HashSet<>();
        String u = entry.getUri();
        String queryString = "SELECT ?x WHERE {" + "?x <http://www.w3.org/2000/01/rdf-schema#subClassOf> ?v . "
                + "?v <http://www.w3.org/2002/07/owl#onProperty> <" + GeneOntologyServiceImpl.PART_OF_URI + ">  . "
                + "?v <http://www.w3.org/2002/07/owl#someValuesFrom> <" + u + "> . }";
        Query q = QueryFactory.create(queryString);
        QueryExecution qexec = QueryExecutionFactory.create(q, (Model) entry.getModel());
        try {
            ResultSet results = qexec.execSelect();
            while (results.hasNext()) {
                QuerySolution soln = results.nextSolution();
                Resource x = soln.getResource("x");
                String uri = x.getURI();
                if (x.isAnon())
                    continue; // some reasoners will return these.
                if (GeneOntologyServiceImpl.log.isDebugEnabled())
                    GeneOntologyServiceImpl.log
                            .debug(GeneOntologyServiceImpl.uri2Term.get(uri) + " is part of " + entry);
                r.add(GeneOntologyServiceImpl.uri2Term.get(uri));
            }
        } finally {
            qexec.close();
        }
        return r;
    }

    private GOAspect getTermAspect(OntologyTerm term) {
        assert term != null;
        String goId = term.getTerm();

        if (GeneOntologyServiceImpl.term2Aspect.containsKey(goId)) {
            return GeneOntologyServiceImpl.term2Aspect.get(goId);
        }

        String nameSpace = null;
        for (AnnotationProperty annot : term.getAnnotations()) {
            /*
             * Why they changed this, I can't say. It used to be hasOBONamespace but now comes through as
             * has_obo_namespace.
             */
            if (annot.getProperty().equals("hasOBONamespace") || annot.getProperty().equals("has_obo_namespace")) {
                nameSpace = annot.getContents();
                break;
            }
        }

        if (nameSpace == null) {
            GeneOntologyServiceImpl.log.warn("aspect could not be determined for: " + term);
            return null;
        }

        GOAspect aspect = GOAspect.valueOf(nameSpace.toUpperCase());
        GeneOntologyServiceImpl.term2Aspect.put(goId, aspect);

        return aspect;
    }

    private synchronized void initializeGeneOntology() {
        if (GeneOntologyServiceImpl.running.get())
            return;

        Thread loadThread = new Thread(new Runnable() {
            @Override
            public void run() {
                GeneOntologyServiceImpl.running.set(true);
                GeneOntologyServiceImpl.uri2Term = new HashMap<>();
                GeneOntologyServiceImpl.log.info("Loading Gene Ontology...");
                StopWatch loadTime = new StopWatch();
                loadTime.start();

                try {
                    GeneOntologyServiceImpl.this.loadTermsInNameSpace();

                    GeneOntologyServiceImpl.log
                            .info("Gene Ontology loaded, total of " + GeneOntologyServiceImpl.uri2Term.size()
                                    + " items in " + loadTime.getTime() / 1000 + "s");
                    GeneOntologyServiceImpl.ready.set(true);
                    GeneOntologyServiceImpl.running.set(false);

                    GeneOntologyServiceImpl.log.info("Done loading GO");
                    loadTime.stop();
                } catch (Throwable e) {
                    GeneOntologyServiceImpl.log.error(e, e);
                    GeneOntologyServiceImpl.ready.set(false);
                    GeneOntologyServiceImpl.running.set(false);
                }
            }

        });

        loadThread.start();

    }

    private void logIds(String prefix, Collection<OntologyTerm> terms) {
        StringBuilder buf = new StringBuilder(prefix);
        buf.append(": [ ");
        Iterator<OntologyTerm> i = terms.iterator();
        while (i.hasNext()) {
            buf.append(GeneOntologyServiceImpl.asRegularGoId(i.next()));
            if (i.hasNext())
                buf.append(", ");
        }
        buf.append(" ]");
        GeneOntologyServiceImpl.log.trace(buf.toString());
    }

    private void putOverlapGenes(Map<Long, Collection<OntologyTerm>> overlap,
            Collection<OntologyTerm> queryGeneTerms, Collection<Gene> genes) {
        for (Object obj : genes) {
            Gene gene = (Gene) obj;
            if (queryGeneTerms.isEmpty()) {
                overlap.put(gene.getId(), new HashSet<OntologyTerm>());
                continue;
            }

            Collection<OntologyTerm> comparisonOntos = this.getGOTerms(gene);

            if (comparisonOntos == null || comparisonOntos.isEmpty()) {
                overlap.put(gene.getId(), new HashSet<OntologyTerm>());
                continue;
            }

            overlap.put(gene.getId(), this.computeOverlap(queryGeneTerms, comparisonOntos));
        }
    }

}