ubic.gemma.core.genome.gene.service.GeneSearchServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.core.genome.gene.service.GeneSearchServiceImpl.java

Source

/*
 * The Gemma project
 *
 * Copyright (c) 2009 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.genome.gene.service;

import gemma.gsec.SecurityService;
import gemma.gsec.util.SecurityUtil;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import ubic.gemma.core.genome.gene.*;
import ubic.gemma.core.ontology.providers.GeneOntologyService;
import ubic.gemma.core.search.GeneSetSearch;
import ubic.gemma.core.search.SearchResult;
import ubic.gemma.core.search.SearchResultDisplayObject;
import ubic.gemma.core.search.SearchService;
import ubic.gemma.model.common.search.SearchSettings;
import ubic.gemma.model.common.search.SearchSettingsImpl;
import ubic.gemma.model.genome.Gene;
import ubic.gemma.model.genome.Taxon;
import ubic.gemma.model.genome.gene.*;
import ubic.gemma.persistence.service.genome.taxon.TaxonService;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;

/**
 * Service for searching genes (and gene sets)
 *
 * @author tvrossum
 */
@Service
public class GeneSearchServiceImpl implements GeneSearchService {

    private static final Log log = LogFactory.getLog(GeneSearchServiceImpl.class);
    private static final int MAX_GENES_PER_QUERY = 1000;
    private static final int MAX_GO_TERMS_TO_PROCESS = 20;
    private static final int MAX_GO_GROUP_SIZE = 200;

    private SearchService searchService;
    private SecurityService securityService;
    private TaxonService taxonService;
    private GeneSetSearch geneSetSearch;
    private GeneSetService geneSetService;
    private GeneService geneService;
    private GeneOntologyService geneOntologyService;
    private GeneSetValueObjectHelper geneSetValueObjectHelper;

    public GeneSearchServiceImpl() {
    }

    @Autowired
    public GeneSearchServiceImpl(SearchService searchService, SecurityService securityService,
            TaxonService taxonService, GeneSetSearch geneSetSearch, GeneSetService geneSetService,
            GeneService geneService, GeneOntologyService geneOntologyService,
            GeneSetValueObjectHelper geneSetValueObjectHelper) {
        this.searchService = searchService;
        this.securityService = securityService;
        this.taxonService = taxonService;
        this.geneSetSearch = geneSetSearch;
        this.geneSetService = geneSetService;
        this.geneService = geneService;
        this.geneOntologyService = geneOntologyService;
        this.geneSetValueObjectHelper = geneSetValueObjectHelper;
    }

    @Override
    public Collection<GeneValueObject> getGenesByGOId(String goId, Long taxonId) {

        Taxon tax = taxonService.load(taxonId);

        if (!StringUtils.isBlank(goId) && tax != null && goId.toUpperCase().startsWith("GO")) {

            Collection<Gene> results = geneOntologyService.getGenes(goId, tax);
            if (results != null) {
                results = geneService.thawLite(results);
                return geneService.loadValueObjects(results);
            }
        }

        return new HashSet<>();

    }

    @Override
    public Collection<Gene> getGOGroupGenes(String goQuery, Taxon taxon) {
        StopWatch timer = new StopWatch();
        timer.start();
        Collection<Taxon> taxaForPhenotypeAssoc = new ArrayList<>();
        Collection<Gene> genes = new ArrayList<>();
        // if taxon isn't set, get go groups for each possible taxon
        if (taxon == null) {
            taxaForPhenotypeAssoc.addAll(taxonService.loadAllTaxaWithGenes());
        } else {
            taxaForPhenotypeAssoc.add(taxon);
        }

        for (Taxon taxonForPA : taxaForPhenotypeAssoc) {
            for (GeneSet geneSet : geneSetSearch.findByGoTermName(goQuery, taxonForPA,
                    GeneSearchServiceImpl.MAX_GO_TERMS_TO_PROCESS, GeneSearchServiceImpl.MAX_GO_GROUP_SIZE)) {
                // don't bother adding empty groups
                if (geneSet.getMembers() != null && geneSet.getMembers().size() != 0) {
                    for (GeneSetMember geneMember : geneSet.getMembers()) {
                        genes.add(geneMember.getGene());
                    }
                }
            }
        }
        GeneSearchServiceImpl.log.info("GO search: " + timer.getTime() + "ms");
        return genes;
    }

    @Override
    public Collection<SearchResultDisplayObject> searchGenesAndGeneGroups(String query, Long taxonId) {
        Taxon taxon = null;
        String taxonName = "";
        if (taxonId != null) {
            taxon = taxonService.load(taxonId);
            if (taxon == null) {
                GeneSearchServiceImpl.log.warn("No such taxon with id=" + taxonId);
            } else {
                taxonName = taxon.getCommonName();
            }
        }

        List<SearchResultDisplayObject> displayResults = new ArrayList<>();

        // if query is blank, return list of auto generated sets, user-owned sets (if logged in) and user's recent
        // session-bound sets
        if (StringUtils.isBlank(query)) {

            return this.searchGenesAndGeneGroupsBlankQuery(taxonId);

        }

        Integer maxGeneralSearchResults = 500;

        /*
         * GET GENES AND GENE SETS
         */
        // SearchSettings settings = SearchSettings.geneSearch( query, taxon );
        SearchSettings settings = SearchSettings.Factory.newInstance();
        settings.setQuery(query);
        settings.noSearches();
        settings.setSearchGenes(true); // add searching for genes
        settings.setSearchGeneSets(true); // add searching for geneSets
        settings.setMaxResults(maxGeneralSearchResults);
        if (taxon != null)
            settings.setTaxon(taxon); // this doesn't work yet

        GeneSearchServiceImpl.log.debug("getting results from searchService for " + query);

        Map<Class<?>, List<SearchResult>> results = searchService.speedSearch(settings);

        List<SearchResult> geneSetSearchResults = new ArrayList<>();
        List<SearchResult> geneSearchResults = new ArrayList<>();

        boolean exactGeneSymbolMatch = false;
        if (!results.isEmpty()) {
            if (results.get(GeneSet.class) != null) {
                geneSetSearchResults.addAll(results.get(GeneSet.class));
            }
            if (results.get(Gene.class) != null) {
                geneSearchResults.addAll(results.get(Gene.class));
            }

            // Check to see if we have an exact match, if so, return earlier abstaining from doing other searches
            for (SearchResult geneResult : results.get(Gene.class)) {
                Gene g = (Gene) geneResult.getResultObject();
                // aliases too?
                if (g != null && g.getOfficialSymbol() != null && g.getOfficialSymbol().startsWith(query.trim())) {
                    exactGeneSymbolMatch = true;
                    break;
                }
            }
        }

        Collection<SearchResultDisplayObject> genes = new ArrayList<>();
        Collection<SearchResultDisplayObject> geneSets;

        Map<Long, Boolean> isSetOwnedByUser = new HashMap<>();

        if (taxon != null) { // filter search results by taxon

            List<SearchResult> taxonCheckedGenes = this.retainGenesOfThisTaxon(taxonId, geneSearchResults);

            // convert result object to a value object to a SearchResultDisplayObject
            for (SearchResult sr : taxonCheckedGenes) {
                genes.add(new SearchResultDisplayObject(sr));
            }

            List<SearchResult> taxonCheckedSets = this.retainGeneSetsOfThisTaxon(taxonId, geneSetSearchResults,
                    isSetOwnedByUser);

            // convert result object to a value object
            for (SearchResult sr : taxonCheckedSets) {
                GeneSet g = (GeneSet) sr.getResultObject();
                DatabaseBackedGeneSetValueObject gsVo = geneSetValueObjectHelper.convertToValueObject(g);
                sr.setResultObject(gsVo);
            }
            geneSets = SearchResultDisplayObject.convertSearchResults2SearchResultDisplayObjects(taxonCheckedSets);

            for (SearchResultDisplayObject srDo : geneSets) {
                // geneSets were filtered by taxon above:
                // if taxonId for geneSet != taxonId param, then gene set was already removed
                srDo.setTaxonId(taxonId);
                srDo.setTaxonName(taxonName);
            }
        } else { // set the taxon values

            for (SearchResult sr : geneSearchResults) {
                genes.add(new SearchResultDisplayObject(sr));
            }

            geneSets = new ArrayList<>();
            SearchResultDisplayObject srDo;
            for (SearchResult sr : geneSetSearchResults) {
                GeneSet gs = (GeneSet) sr.getResultObject();
                isSetOwnedByUser.put(gs.getId(), securityService.isOwnedByCurrentUser(gs));

                taxon = geneSetService.getTaxon((GeneSet) sr.getResultObject());
                GeneSetValueObject gsVo = geneSetValueObjectHelper.convertToValueObject(gs);
                srDo = new SearchResultDisplayObject(gsVo);
                srDo.setTaxonId(taxon.getId());
                srDo.setTaxonName(taxon.getCommonName());
                geneSets.add(srDo);
            }
            taxon = null;
        }

        // if a geneSet is owned by the user, mark it as such (used for giving it a special background colour in
        // search results)
        this.setUserOwnedForGeneSets(geneSets, isSetOwnedByUser);

        if (exactGeneSymbolMatch) {
            // get summary results
            GeneSearchServiceImpl.log.info("getting Summary results for " + query);

            List<SearchResultDisplayObject> summaries = this.addEntryForAllResults(query, genes, geneSets,
                    new ArrayList<SearchResultDisplayObject>(), new ArrayList<SearchResultDisplayObject>());
            displayResults.addAll(summaries);
            displayResults.addAll(genes);
            displayResults.addAll(geneSets);
            return displayResults;
        }

        List<SearchResultDisplayObject> srDos;
        // get GO group results
        GeneSearchServiceImpl.log.debug("Getting GO group results for " + query);
        srDos = this.getGOGroupResults(query, taxon);

        List<SearchResultDisplayObject> phenotypeSrDos = new ArrayList<>();

        // only do phenotype search if there is no results at all
        // if ( ( genes.size() < 1 ) ) {

        if (!query.toUpperCase().startsWith("GO")) {
            GeneSearchServiceImpl.log.info("getting Phenotype Association results for " + query);
            phenotypeSrDos = this.getPhenotypeAssociationSearchResults(query, taxon);
        }

        // }

        // get summary results
        GeneSearchServiceImpl.log.debug("Getting Summary results for " + query);
        List<SearchResultDisplayObject> summaryEntries = this.addEntryForAllResults(query, genes, geneSets, srDos,
                phenotypeSrDos);

        // add all results, keeping order of result types
        displayResults.addAll(summaryEntries);
        displayResults.addAll(geneSets);
        displayResults.addAll(srDos);
        displayResults.addAll(phenotypeSrDos);
        displayResults.addAll(genes);

        if (displayResults.isEmpty()) {
            GeneSearchServiceImpl.log.info("No results for search: " + query + " taxon="
                    + ((taxon == null) ? null : taxon.getCommonName()));
            return new HashSet<>();
        }
        GeneSearchServiceImpl.log.info("Results for search: " + query + ", size=" + displayResults.size());

        return displayResults;
    }

    @Override
    public Collection<GeneValueObject> searchMultipleGenes(String query, Long taxonId) throws IOException {

        BufferedReader reader = new BufferedReader(new StringReader(query));
        String line;
        Collection<String> queries = new ArrayList<>();
        while ((line = reader.readLine()) != null) {
            if (StringUtils.isBlank(line))
                continue;
            if (queries.size() > GeneSearchServiceImpl.MAX_GENES_PER_QUERY) {
                GeneSearchServiceImpl.log.warn("Too many genes, stopping");
            }
            queries.add(line);
        }

        Map<String, GeneValueObject> geneMap = this.searchMultipleGenesGetMap(queries, taxonId);

        return geneMap.values();
    }

    @Override
    public Map<String, GeneValueObject> searchMultipleGenesGetMap(Collection<String> query, Long taxonId) {
        Taxon taxon = taxonService.load(taxonId);

        if (taxon == null)
            throw new IllegalArgumentException("No such taxon with id=" + taxonId);

        // this deals with the simple cases. For remainder we look a little harder
        Map<String, GeneValueObject> queryToGenes = geneService.findByOfficialSymbols(query, taxonId);

        for (String line : query) {
            line = StringUtils.strip(line);

            if (StringUtils.isBlank(line)) {
                continue;
            }

            String queryAsKey = line.toLowerCase();
            if (queryToGenes.containsKey(queryAsKey)) {
                // already found.
                continue;
            }

            if (queryToGenes.size() >= GeneSearchServiceImpl.MAX_GENES_PER_QUERY) {
                GeneSearchServiceImpl.log
                        .warn("Too many genes, stopping (limit=" + GeneSearchServiceImpl.MAX_GENES_PER_QUERY + ')');
                break;
            }

            // searching one gene at a time is a bit slow; we do a quick search for symbols.
            SearchSettings settings = SearchSettingsImpl.geneSearch(line, taxon);
            List<SearchResult> geneSearchResults = searchService.speedSearch(settings).get(Gene.class);

            if (geneSearchResults == null || geneSearchResults.isEmpty()) {
                // an empty set is an indication of no results.
                queryToGenes.put(queryAsKey, null);
            } else if (geneSearchResults.size() == 1) { // Just one result so add it
                Gene g = (Gene) geneSearchResults.iterator().next().getResultObject();
                queryToGenes.put(queryAsKey, new GeneValueObject(g));
            } else { // Multiple results need to find best one
                // Usually if there is more than 1 results the search term was a official symbol and picked up matches
                // like grin1, grin2, grin3, grin (given the search term was grin)
                for (SearchResult sr : geneSearchResults) {
                    Gene srGene = (Gene) sr.getResultObject();
                    if (srGene.getTaxon().equals(taxon) && srGene.getOfficialSymbol().equalsIgnoreCase(line)) {
                        queryToGenes.put(queryAsKey, new GeneValueObject(srGene));
                        break; // found so done
                    }
                }

            }
        }

        return queryToGenes;
    }

    private void setUserOwnedForGeneSets(Collection<SearchResultDisplayObject> geneSets,
            Map<Long, Boolean> isSetOwnedByUser) {
        if (SecurityUtil.isUserLoggedIn()) {
            for (SearchResultDisplayObject srDo : geneSets) {
                Long id = (srDo.getResultValueObject() instanceof DatabaseBackedGeneSetValueObject)
                        ? ((GeneSetValueObject) srDo.getResultValueObject()).getId()
                        : new Long(-1);
                srDo.setUserOwned(isSetOwnedByUser.get(id));
            }
        }
    }

    private List<SearchResult> retainGeneSetsOfThisTaxon(Long taxonId, List<SearchResult> geneSetSearchResults,
            Map<Long, Boolean> isSetOwnedByUser) {
        List<SearchResult> taxonCheckedSets = new ArrayList<>();
        for (SearchResult sr : geneSetSearchResults) {
            GeneSet gs = (GeneSet) sr.getResultObject();
            GeneSetValueObject gsVo = geneSetValueObjectHelper.convertToValueObject(gs);

            isSetOwnedByUser.put(gs.getId(), securityService.isOwnedByCurrentUser(gs));

            if (Objects.equals(gsVo.getTaxonId(), taxonId)) {
                taxonCheckedSets.add(sr);
            }
        }
        return taxonCheckedSets;
    }

    private List<SearchResult> retainGenesOfThisTaxon(Long taxonId, List<SearchResult> geneSearchResults) {
        List<SearchResult> taxonCheckedGenes = new ArrayList<>();
        for (SearchResult sr : geneSearchResults) {
            Gene gene = (Gene) sr.getResultObject();
            if (gene.getTaxon() != null && gene.getTaxon().getId().equals(taxonId)) {
                taxonCheckedGenes.add(sr);
            }
        }
        return taxonCheckedGenes;
    }

    /**
     * updates goSets & srDos with GO results
     *
     * @param query query
     * @param taxon taxon
     * @return list of search result display objects
     */
    private List<SearchResultDisplayObject> getGOGroupResults(String query, Taxon taxon) {
        StopWatch timer = new StopWatch();
        timer.start();
        List<SearchResultDisplayObject> srDos = new ArrayList<>();

        if (taxon != null) {

            if (query.toUpperCase().startsWith("GO")) {
                GeneSearchServiceImpl.log
                        .debug("Getting results from geneSetSearch.findByGoId for GO prefixed query: " + query);
                GeneSet goSet = geneSetSearch.findByGoId(query, taxon);
                if (goSet != null && goSet.getMembers() != null && goSet.getMembers().size() > 0) {
                    SearchResultDisplayObject sdo = this.makeGoGroupSearchResult(goSet, query, query, taxon);
                    srDos.add(sdo);
                }
            } else {
                GeneSearchServiceImpl.log.debug("Getting results from geneSetSearch.findByGoTermName for " + query);
                for (GeneSet geneSet : geneSetSearch.findByGoTermName(query, taxon,
                        GeneSearchServiceImpl.MAX_GO_TERMS_TO_PROCESS, GeneSearchServiceImpl.MAX_GO_GROUP_SIZE)) {
                    // don't bother adding empty GO groups
                    // (should probably do this check elsewhere in case it speeds things up)
                    if (geneSet.getMembers() != null && geneSet.getMembers().size() != 0) {
                        SearchResultDisplayObject sdo = this.makeGoGroupSearchResult(geneSet, null, query, taxon);
                        srDos.add(sdo);
                    }
                }
            }
        } else {// taxon is null, search without taxon as a constraint and bag up the results based on taxon

            GeneSearchServiceImpl.log.debug("getting results from geneSetSearch.findByGoId for GO prefixed query: "
                    + query + " with null taxon");
            if (query.toUpperCase().startsWith("GO")) {
                GeneSet goSet = geneSetSearch.findByGoId(query, null);
                if (goSet == null) {
                    return srDos;
                }

                // this gene set has genes from all the different taxons, organize them
                Collection<GeneSet> taxonSpecificSets = this.organizeMultiTaxaSetIntoTaxonSpecificSets(goSet);

                for (GeneSet taxonGeneSet : taxonSpecificSets) {

                    if (taxonGeneSet != null && taxonGeneSet.getMembers() != null
                            && taxonGeneSet.getMembers().size() > 0) {
                        SearchResultDisplayObject sdo = this.makeGoGroupSearchResult(taxonGeneSet, query, query,
                                taxonGeneSet.getMembers().iterator().next().getGene().getTaxon());
                        srDos.add(sdo);
                    }
                }
            } else {
                GeneSearchServiceImpl.log.debug(
                        "getting results from geneSetSearch.findByGoTermName for " + query + " with null taxon");
                for (GeneSet geneSet : geneSetSearch.findByGoTermName(query, null,
                        GeneSearchServiceImpl.MAX_GO_TERMS_TO_PROCESS, GeneSearchServiceImpl.MAX_GO_GROUP_SIZE)) {

                    // geneSet will have genes from different taxons inside, organize them.
                    Collection<GeneSet> taxonSpecificSets = this.organizeMultiTaxaSetIntoTaxonSpecificSets(geneSet);

                    for (GeneSet taxonGeneSet : taxonSpecificSets) {

                        if (geneSet.getMembers() != null && taxonGeneSet.getMembers().size() != 0) {
                            SearchResultDisplayObject sdo = this.makeGoGroupSearchResult(taxonGeneSet, null, query,
                                    taxonGeneSet.getMembers().iterator().next().getGene().getTaxon());
                            srDos.add(sdo);
                        }
                    }
                }
            }

        }

        Collections.sort(srDos);
        if (timer.getTime() > 500)
            GeneSearchServiceImpl.log.info("GO search: " + srDos.size() + " results in " + timer.getTime() + "ms");
        return srDos;
    }

    private Collection<GeneSet> organizeMultiTaxaSetIntoTaxonSpecificSets(GeneSet gs) {

        HashMap<Long, GeneSet> taxonToGeneSetMap = new HashMap<>();

        for (GeneSetMember geneMember : gs.getMembers()) {

            Long id = geneMember.getGene().getTaxon().getId();
            if (taxonToGeneSetMap.get(id) == null) {

                GeneSet newTaxonSet = GeneSet.Factory.newInstance();

                newTaxonSet.setName(gs.getName());
                newTaxonSet.setDescription(gs.getDescription());
                Collection<GeneSetMember> members = new ArrayList<>();
                members.add(geneMember);

                newTaxonSet.setMembers(members);

                taxonToGeneSetMap.put(id, newTaxonSet);

            } else {
                GeneSet existingTaxonSet = taxonToGeneSetMap.get(id);

                existingTaxonSet.getMembers().add(geneMember);
            }

        }

        return taxonToGeneSetMap.values();
    }

    /**
     * updates goSets & srDos with GO results
     *
     * @param query query
     * @param taxon taxon
     * @return list of search result display objects
     */
    private List<SearchResultDisplayObject> getPhenotypeAssociationSearchResults(String query, Taxon taxon) {

        List<SearchResultDisplayObject> phenotypeSrDos = new ArrayList<>();
        // if taxon==null then it grabs results for all taxons
        Collection<GeneSetValueObject> geneSets = geneSetSearch.findByPhenotypeName(query, taxon);
        for (GeneSetValueObject geneSet : geneSets) {
            // don't bother adding empty groups
            // (should probably do this check elsewhere in case it speeds things up)
            if (geneSet.getGeneIds() != null && geneSet.getGeneIds().size() != 0) {
                SearchResultDisplayObject sdo = this.makePhenotypeAssociationGroupSearchResult(geneSet, query,
                        taxon);
                phenotypeSrDos.add(sdo);
                // phenotypeSets.add( geneSet );
            }
        }

        Collections.sort(phenotypeSrDos);
        return phenotypeSrDos;
    }

    private SearchResultDisplayObject makeGoGroupSearchResult(GeneSet goSet, String goId, String query,
            Taxon taxonForGo) {
        GOGroupValueObject ggVo = geneSetValueObjectHelper.convertToGOValueObject(goSet, goId, query);
        return this.getSearchResultForSessionBoundGroupValueObject(taxonForGo, ggVo);
    }

    private SearchResultDisplayObject makePhenotypeAssociationGroupSearchResult(GeneSetValueObject geneSet,
            String query, Taxon taxonForGS) {
        PhenotypeGroupValueObject pgVo = PhenotypeGroupValueObject.convertFromGeneSetValueObject(geneSet, query);
        return this.getSearchResultForSessionBoundGroupValueObject(taxonForGS, pgVo);
    }

    private SearchResultDisplayObject getSearchResultForSessionBoundGroupValueObject(Taxon taxonForGS,
            SessionBoundGeneSetValueObject sbGsVo) {

        if (taxonForGS != null) {
            // GO groups don't seem to have there sbGsVo's taxon info populated
            sbGsVo.setTaxonId(taxonForGS.getId());
            sbGsVo.setTaxonName(taxonForGS.getCommonName());

        } else {
            sbGsVo.setTaxonId(sbGsVo.getTaxonId());
            sbGsVo.setTaxonName(sbGsVo.getTaxonName());
        }
        SearchResultDisplayObject sdo = new SearchResultDisplayObject(sbGsVo);
        sdo.setUserOwned(false);
        sdo.setTaxonId(sbGsVo.getTaxonId());
        sdo.setTaxonName(sbGsVo.getTaxonName());
        return sdo;
    }

    /**
     * Get a list of SearchResultDisplayObjects that summarise all the results found (one per taxon), so at front end
     * user can "select all"
     *
     * @param query          query
     * @param genes          genes
     * @param geneSets       gene sets
     * @param phenotypeSrDos phenotype display objects
     * @param srDos          display objects
     * @return list of search result display objects
     */
    private List<SearchResultDisplayObject> addEntryForAllResults(String query,
            Collection<SearchResultDisplayObject> genes, Collection<SearchResultDisplayObject> geneSets,
            List<SearchResultDisplayObject> srDos, List<SearchResultDisplayObject> phenotypeSrDos) {

        List<SearchResultDisplayObject> summaryResultEntries = new ArrayList<>();

        /*
         * ALL RESULTS BY TAXON GROUPS
         */

        // if >1 result, add a group whose members are all genes returned from search
        if ((genes.size() + geneSets.size() + srDos.size()) > 1) {

            Map<Long, Set<Long>> geneIdsByTaxonId = new HashMap<>();

            // add every individual gene to the set
            for (SearchResultDisplayObject srDo : genes) {
                if (!geneIdsByTaxonId.containsKey(srDo.getTaxonId())) {
                    geneIdsByTaxonId.put(srDo.getTaxonId(), new HashSet<Long>());
                }
                Long id = (srDo.getResultValueObject() instanceof GeneValueObject)
                        ? ((GeneValueObject) srDo.getResultValueObject()).getId()
                        : new Long(-1);
                if (id != -1) {
                    geneIdsByTaxonId.get(srDo.getTaxonId()).add(id);
                }

            }

            // if there's a group, get the number of members
            this.updateGeneIdsByTaxonId(geneSets, geneIdsByTaxonId);
            this.updateGeneIdsByTaxonId(srDos, geneIdsByTaxonId);
            this.updateGeneIdsByTaxonId(phenotypeSrDos, geneIdsByTaxonId);

            // make an entry for each taxon
            Long taxonId;
            Taxon taxon;
            for (Map.Entry<Long, Set<Long>> entry : geneIdsByTaxonId.entrySet()) {
                taxonId = entry.getKey();
                assert taxonId != null;
                taxon = taxonService.load(taxonId);

                // don't make groups for 1 gene
                if (taxon != null && entry.getValue().size() > 1) {
                    FreeTextGeneResultsValueObject byTaxFtVo = new FreeTextGeneResultsValueObject(
                            "All " + taxon.getCommonName() + " results for '" + query + "'",
                            "All " + taxon.getCommonName() + " genes found for your query", taxon.getId(),
                            taxon.getCommonName(), entry.getValue(), query);
                    summaryResultEntries.add(new SearchResultDisplayObject(byTaxFtVo));
                }
            }
        }
        return summaryResultEntries;
    }

    private void updateGeneIdsByTaxonId(Collection<SearchResultDisplayObject> searchResultDisplayObject,
            Map<Long, Set<Long>> geneIdsByTaxonId) {
        for (SearchResultDisplayObject srDo : searchResultDisplayObject) {
            // get the ids of the gene members
            if (!geneIdsByTaxonId.containsKey(srDo.getTaxonId())) {
                geneIdsByTaxonId.put(srDo.getTaxonId(), new HashSet<Long>());
            }

            Object resultValueObject = srDo.getResultValueObject();

            if (resultValueObject instanceof GeneValueObject) {
                geneIdsByTaxonId.get(srDo.getTaxonId()).add(((GeneValueObject) resultValueObject).getId());

            } else if (resultValueObject instanceof GeneSetValueObject) {
                geneIdsByTaxonId.get(srDo.getTaxonId())
                        .addAll(((GeneSetValueObject) resultValueObject).getGeneIds());

            } else {
                throw new UnsupportedOperationException(
                        "Unknown search result type: " + resultValueObject.getClass().getName());
            }

        }
    }

    /**
     * if query is blank, return list of public sets, user-owned sets (if logged in) and user's recent session-bound
     * sets called by ubic.gemma.web.controller.genome.gene.GenePickerController.searchGenesAndGeneGroups(String, Long)
     *
     * @param taxonId taxon id
     * @return Collection<SearchResultDisplayObject>
     */
    private Collection<SearchResultDisplayObject> searchGenesAndGeneGroupsBlankQuery(Long taxonId) {

        Taxon taxon = null;
        if (taxonId != null) {
            taxon = taxonService.load(taxonId);
            if (taxon == null) {
                GeneSearchServiceImpl.log.warn("No such taxon with id=" + taxonId);
            }
        }

        // if query is blank, return list of auto generated sets, user-owned sets (if logged in) and user's recent
        // session-bound sets

        // right now, no public gene sets are useful so we don't want to prompt them

        StopWatch watch = new StopWatch();
        watch.start();

        // get all public sets
        // filtered by security.
        Collection<GeneSet> sets = new ArrayList<>();
        if (!SecurityUtil.isUserLoggedIn()) {
            try {
                sets = geneSetService.loadAll(taxon);
                if (watch.getTime() > 100)
                    GeneSearchServiceImpl.log.info(
                            sets.size() + " sets loaded for taxon =" + taxon + " took: " + watch.getTime() + "ms");
            } catch (AccessDeniedException e) {
                // okay, they just aren't allowed to see those.
            }
        } else if (SecurityUtil.isUserLoggedIn()) {
            /*
             * actually, loadMyGeneSets and loadAll point to the same method (they just use different spring security
             * filters)
             */
            sets = (taxon != null) ? geneSetService.loadMyGeneSets(taxon) : geneSetService.loadMyGeneSets();
            if (watch.getTime() > 100)
                GeneSearchServiceImpl.log.info("Loading the user's gene sets took: " + watch.getTime() + "ms");
        }

        if (sets.isEmpty()) {
            return new ArrayList<>();
        }

        // separate these out because they go at the top of the list
        List<SearchResultDisplayObject> displayResultsPrivate = new ArrayList<>();
        List<SearchResultDisplayObject> displayResultsPublic = new ArrayList<>();
        SearchResultDisplayObject newSrDo;

        List<DatabaseBackedGeneSetValueObject> valueObjects = geneSetValueObjectHelper.convertToValueObjects(sets,
                false);
        if (watch.getTime() > 500)
            GeneSearchServiceImpl.log.info("Database stage done: " + watch.getTime() + "ms");

        for (DatabaseBackedGeneSetValueObject set : valueObjects) {
            newSrDo = new SearchResultDisplayObject(set);
            newSrDo.setTaxonId(((GeneSetValueObject) newSrDo.getResultValueObject()).getTaxonId());
            newSrDo.setTaxonName(((GeneSetValueObject) newSrDo.getResultValueObject()).getTaxonName());
            newSrDo.setUserOwned(!set.getIsPublic());
            ((GeneSetValueObject) newSrDo.getResultValueObject()).setIsPublic(!newSrDo.isUserOwned());
            if (newSrDo.isUserOwned()) {
                displayResultsPrivate.add(newSrDo);
            } else {
                displayResultsPublic.add(newSrDo);
            }
        }

        // keep sets in proper order (user's groups first, then public ones)
        Collections.sort(displayResultsPrivate);
        Collections.sort(displayResultsPublic);

        List<SearchResultDisplayObject> displayResults = new ArrayList<>();

        displayResults.addAll(displayResultsPrivate);
        displayResults.addAll(displayResultsPublic);

        GeneSearchServiceImpl.log
                .info("Results for blank query: " + displayResults.size() + " items, " + watch.getTime() + "ms");

        return displayResults;

    }
}