ubic.gemma.ontology.OntologyServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.ontology.OntologyServiceImpl.java

Source

/*
 * The Gemma project
 * 
 * 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.ontology;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ubic.basecode.ontology.OntologyLoader;
import ubic.basecode.ontology.model.OntologyIndividual;
import ubic.basecode.ontology.model.OntologyResource;
import ubic.basecode.ontology.model.OntologyTerm;
import ubic.basecode.ontology.providers.AbstractOntologyService;
import ubic.basecode.ontology.providers.BirnLexOntologyService;
import ubic.basecode.ontology.providers.CellTypeOntologyService;
import ubic.basecode.ontology.providers.ChebiOntologyService;
import ubic.basecode.ontology.providers.DiseaseOntologyService;
import ubic.basecode.ontology.providers.ExperimentalFactorOntologyService;
import ubic.basecode.ontology.providers.FMAOntologyService;
import ubic.basecode.ontology.providers.HumanDevelopmentOntologyService;
import ubic.basecode.ontology.providers.HumanPhenotypeOntologyService;
import ubic.basecode.ontology.providers.MammalianPhenotypeOntologyService;
import ubic.basecode.ontology.providers.MouseDevelopmentOntologyService;
import ubic.basecode.ontology.providers.NIFSTDOntologyService;
import ubic.basecode.ontology.providers.ObiService;
import ubic.basecode.ontology.search.OntologySearch;
import ubic.gemma.expression.experiment.service.ExpressionExperimentService;
import ubic.gemma.model.association.GOEvidenceCode;
import ubic.gemma.model.common.description.Characteristic;
import ubic.gemma.model.common.description.CharacteristicService;
import ubic.gemma.model.common.description.ExternalDatabase;
import ubic.gemma.model.common.description.VocabCharacteristic;
import ubic.gemma.model.common.search.SearchSettings;
import ubic.gemma.model.expression.biomaterial.BioMaterial;
import ubic.gemma.model.expression.biomaterial.BioMaterialService;
import ubic.gemma.model.expression.experiment.ExpressionExperiment;
import ubic.gemma.model.genome.Gene;
import ubic.gemma.model.genome.Taxon;
import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject;
import ubic.gemma.ontology.providers.MgedOntologyService;
import ubic.gemma.search.SearchResult;
import ubic.gemma.search.SearchService;

import com.hp.hpl.jena.rdf.model.ModelMaker;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

/**
 * Has a static method for finding out which ontologies are loaded into the system and a general purpose find method
 * that delegates to the many ontology services. NOTE: Logging messages from this service are important for tracking
 * changes to annotations.
 * 
 * @author pavlidis
 * @version $Id: OntologyServiceImpl.java,v 1.18 2013/04/09 21:33:35 cmcdonald Exp $
 */
@Component
public class OntologyServiceImpl implements OntologyService {

    // Private class for sorting Characteristics
    static class TermComparator implements Comparator<Characteristic>, Serializable {

        private static final long serialVersionUID = 1L;
        String comparator;

        public TermComparator(String comparator) {
            super();
            this.comparator = comparator;
        }

        @Override
        public int compare(Characteristic o1, Characteristic o2) {
            String term1 = o1.getValue();
            String term2 = o2.getValue();

            if (term1.equals(term2))
                return 0;

            if (term1.equals(comparator))
                return 1;

            if (term2.equals(comparator))
                return -1;

            if (term1.startsWith(comparator)) {
                if (term2.startsWith(comparator))
                    return 0;
                return 1;
            } else if (term2.startsWith(comparator)) {
                return -1;
            }

            return 0;

        }
    }

    private class CharacteristicComparator implements Comparator<Characteristic> {

        /*
         * (non-Javadoc)
         * 
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        @Override
        public int compare(Characteristic o1, Characteristic o2) {

            if (o1.getValue().length() == o2.getValue().length()) {
                if (o1.getDescription().startsWith(USED)) {
                    if (o2.getDescription().startsWith(USED)) {
                        // both are used, break tie.
                        return compareByUri(o1, o2);
                    }
                    // o1 is used, o2 is not; o1 should be first.
                    return -1;

                } else if (o2.getDescription().startsWith(USED)) {
                    // o2 is used and o1 is not; o2 should be first.
                    return 1;
                } else {
                    // neither is used.
                    return compareByUri(o1, o2);
                }
            }

            // we don't use the actual text, because we've already declared these matches.
            return o1.getValue().length() < o2.getValue().length() ? -1 : 1;
        }

        // break ties by uri and/or value.
        private int compareByUri(Characteristic o1, Characteristic o2) {
            // both are used. Break tie based on whether it has a URI
            if (o1 instanceof VocabCharacteristic && ((VocabCharacteristic) o1).getValueUri() != null) {
                if (!(o2 instanceof VocabCharacteristic) || ((VocabCharacteristic) o2).getValueUri() == null) {
                    return -1;
                }
                // both have URIs, sort by value
                return o1.getValue().toLowerCase().compareTo(o2.getValue().toLowerCase());
            } else if (o2 instanceof VocabCharacteristic && ((VocabCharacteristic) o2).getValueUri() != null) {
                // we know o1 does not have a uri.
                return 1;
            }
            // both not having uris
            return o1.getValue().toLowerCase().compareTo(o2.getValue().toLowerCase());
        }
    }

    static final String USED = " -USED- ";

    private static Log log = LogFactory.getLog(OntologyServiceImpl.class.getName());

    /**
     * List the ontologies that are available in the jena database.
     * 
     * @return
     */
    public static Collection<ubic.gemma.ontology.Ontology> listAvailableOntologies() {

        Collection<ubic.gemma.ontology.Ontology> ontologies = new HashSet<ubic.gemma.ontology.Ontology>();

        ModelMaker maker = OntologyLoader.getRDBMaker();

        try {
            ExtendedIterator<String> iterator = maker.listModels();
            while (iterator.hasNext()) {
                String name = iterator.next();
                ExternalDatabase database = OntologyUtils.ontologyAsExternalDatabase(name);
                ubic.gemma.ontology.Ontology o = new ubic.gemma.ontology.Ontology(database);
                ontologies.add(o);
            }
            return ontologies;
        } finally {
            maker.close();
        }

    }

    @Autowired
    private BioMaterialService bioMaterialService;

    private BirnLexOntologyService birnLexOntologyService;

    private CellTypeOntologyService cellTypeOntologyService;

    @Autowired
    private CharacteristicService characteristicService;

    private ChebiOntologyService chebiOntologyService;

    private DiseaseOntologyService diseaseOntologyService;
    @Autowired
    private ExpressionExperimentService eeService;
    private ExperimentalFactorOntologyService experimentalFactorOntologyService;
    private FMAOntologyService fmaOntologyService;
    private HumanDevelopmentOntologyService humanDevelopmentOntologyService;
    private HumanPhenotypeOntologyService humanPhenotypeOntologyService;
    private MammalianPhenotypeOntologyService mammalianPhenotypeOntologyService;
    private MgedOntologyService mgedOntologyService;
    private MouseDevelopmentOntologyService mouseDevelopmentOntologyService;
    private NIFSTDOntologyService nifstdOntologyService;
    private ObiService obiService;

    private Collection<AbstractOntologyService> ontologyServices = new HashSet<AbstractOntologyService>();

    @Autowired
    private SearchService searchService;

    @Override
    public void afterPropertiesSet() {

        this.birnLexOntologyService = new BirnLexOntologyService();
        this.nifstdOntologyService = new NIFSTDOntologyService();
        this.chebiOntologyService = new ChebiOntologyService();
        this.fmaOntologyService = new FMAOntologyService();
        this.diseaseOntologyService = new DiseaseOntologyService();
        this.humanDevelopmentOntologyService = new HumanDevelopmentOntologyService();
        this.cellTypeOntologyService = new CellTypeOntologyService();
        this.mouseDevelopmentOntologyService = new MouseDevelopmentOntologyService();
        this.mammalianPhenotypeOntologyService = new MammalianPhenotypeOntologyService();
        this.mgedOntologyService = new MgedOntologyService();
        this.humanPhenotypeOntologyService = new HumanPhenotypeOntologyService();
        this.experimentalFactorOntologyService = new ExperimentalFactorOntologyService();
        this.obiService = new ObiService();

        this.ontologyServices.add(this.birnLexOntologyService);
        this.ontologyServices.add(this.nifstdOntologyService);
        this.ontologyServices.add(this.chebiOntologyService);
        this.ontologyServices.add(this.fmaOntologyService);
        this.ontologyServices.add(this.diseaseOntologyService);
        this.ontologyServices.add(this.mgedOntologyService);
        this.ontologyServices.add(this.mouseDevelopmentOntologyService);
        this.ontologyServices.add(this.humanDevelopmentOntologyService);
        this.ontologyServices.add(this.cellTypeOntologyService);
        this.ontologyServices.add(this.mammalianPhenotypeOntologyService);
        this.ontologyServices.add(this.humanPhenotypeOntologyService);
        this.ontologyServices.add(this.obiService);
        this.ontologyServices.add(this.experimentalFactorOntologyService);

        for (AbstractOntologyService serv : this.ontologyServices) {
            serv.startInitializationThread(false);
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#findExactTerm(java.lang.String, java.lang.String,
     * ubic.gemma.model.genome.Taxon)
     */
    @Override
    public Collection<Characteristic> findExactTerm(String givenQueryString, String categoryUri, Taxon taxon) {

        if (StringUtils.isBlank(givenQueryString))
            return null;

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

        String queryString = OntologySearch.stripInvalidCharacters(givenQueryString);
        if (StringUtils.isBlank(queryString)) {
            log.warn("The query was not valid (ended up being empty): " + givenQueryString);
            return new HashSet<Characteristic>();
        }

        if (log.isDebugEnabled()) {
            log.debug("starting findExactTerm for " + queryString + ". Timing information begins from here");
        }

        Collection<OntologyResource> results;
        Collection<Characteristic> searchResults = new HashSet<Characteristic>();

        // Add the matching individuals
        Collection<Characteristic> individualResults = new HashSet<Characteristic>();
        if (StringUtils.isNotBlank(categoryUri) && !categoryUri.equals("{}")) {
            results = new HashSet<OntologyResource>(mgedOntologyService.getTermIndividuals(categoryUri.trim()));
            if (results.size() > 0)
                individualResults.addAll(filter(results, queryString));
        }
        if (log.isDebugEnabled())
            log.debug("found " + individualResults.size() + " individuals from ontology term " + categoryUri
                    + " in " + watch.getTime() + " ms");

        Collection<String> foundValues = new HashSet<String>();

        Collection<Characteristic> previouslyUsedInSystem = new HashSet<Characteristic>();

        // this should be very fast.
        Collection<Characteristic> foundChars = characteristicService.findByValue(queryString);

        /*
         * remove duplicates, don't want to redefine == operator for Characteristics for this use consider if the value
         * = then its a duplicate.
         */
        if (foundChars != null) {
            for (Characteristic characteristic : foundChars) {
                if (!foundValues.contains(foundValueKey(characteristic))) {
                    /*
                     * Want to flag in the web interface that these are already used by Gemma Didn't want to make a
                     * characteristic value object just to hold a boolean flag for used....
                     */
                    characteristic.setDescription(USED + characteristic.getDescription());
                    previouslyUsedInSystem.add(characteristic);
                    foundValues.add(foundValueKey(characteristic));
                }
            }
        }
        if (log.isDebugEnabled() || watch.getTime() > 100)
            log.info("found " + previouslyUsedInSystem.size() + " matching characteristics used in the database"
                    + " in " + watch.getTime() + " ms");

        searchForGenes(queryString, categoryUri, taxon, searchResults);

        for (AbstractOntologyService serv : this.ontologyServices) {
            results = serv.findResources(queryString);
            if (log.isDebugEnabled())
                log.debug("found " + results.size() + " in " + watch.getTime() + " ms");
            searchResults.addAll(filter(results, queryString));
        }

        // Sort the individual results.
        Collection<Characteristic> sortedResults = sort(individualResults, previouslyUsedInSystem, searchResults,
                queryString, foundValues);

        if (watch.getTime() > 1000) {
            log.info("Ontology term query for: " + givenQueryString + ": " + watch.getTime() + "ms");
        }

        return sortedResults;

    }

    @Override
    public Collection<CharacteristicValueObject> findExactTermValueObject(String givenQueryString,
            String categoryUri, Taxon taxon) {
        Collection<Characteristic> terms = findExactTerm(givenQueryString, categoryUri, taxon);
        return CharacteristicValueObject.characteristic2CharacteristicVO(terms);

    }

    /**
     * Using the ontology and values in the database, for a search searchQuery given by the client give an ordered list
     * of possible choices
     */
    @Override
    public Collection<CharacteristicValueObject> findExperimentsCharacteristicTags(String searchQueryString,
            boolean useNeuroCartaOntology) {

        String searchQuery = OntologySearch.stripInvalidCharacters(searchQueryString);

        if (searchQuery == null || searchQuery.length() < 3) {
            return new HashSet<CharacteristicValueObject>();
        }

        // this will do like %search%
        Collection<CharacteristicValueObject> characteristicsFromDatabase = CharacteristicValueObject
                .characteristic2CharacteristicVO(this.characteristicService.findByValue("%" + searchQuery));

        Map<String, CharacteristicValueObject> characteristicFromDatabaseWithValueUri = new HashMap<String, CharacteristicValueObject>();
        Collection<CharacteristicValueObject> characteristicFromDatabaseFreeText = new HashSet<CharacteristicValueObject>();

        for (CharacteristicValueObject characteristicInDatabase : characteristicsFromDatabase) {

            // flag to let know that it was found in the database
            characteristicInDatabase.setAlreadyPresentInDatabase(true);

            if (characteristicInDatabase.getValueUri() != null
                    && !characteristicInDatabase.getValueUri().equals("")) {
                characteristicFromDatabaseWithValueUri.put(characteristicInDatabase.getValueUri(),
                        characteristicInDatabase);
            } else {
                // free txt, no value uri
                characteristicFromDatabaseFreeText.add(characteristicInDatabase);
            }
        }

        // search the ontology for the given searchTerm, but if already found in the database dont add it again
        Collection<CharacteristicValueObject> characteristicsFromOntology = findCharacteristicsFromOntology(
                searchQuery, useNeuroCartaOntology, characteristicFromDatabaseWithValueUri);

        // order to show the the term: 1-exactMatch, 2-startWith, 3-substring and 4- no rule
        // order to show values for each List : 1-From database with Uri, 2- from Ontology, 3- from from database with
        // no Uri
        Collection<CharacteristicValueObject> characteristicsWithExactMatch = new ArrayList<CharacteristicValueObject>();
        Collection<CharacteristicValueObject> characteristicsStartWithQuery = new ArrayList<CharacteristicValueObject>();
        Collection<CharacteristicValueObject> characteristicsSubstring = new ArrayList<CharacteristicValueObject>();
        Collection<CharacteristicValueObject> characteristicsNoRuleFound = new ArrayList<CharacteristicValueObject>();

        // from the database with a uri
        putCharacteristicsIntoSpecificList(searchQuery, characteristicFromDatabaseWithValueUri.values(),
                characteristicsWithExactMatch, characteristicsStartWithQuery, characteristicsSubstring,
                characteristicsNoRuleFound);
        // from the ontology
        putCharacteristicsIntoSpecificList(searchQuery, characteristicsFromOntology, characteristicsWithExactMatch,
                characteristicsStartWithQuery, characteristicsSubstring, characteristicsNoRuleFound);
        // from the database with no uri
        putCharacteristicsIntoSpecificList(searchQuery, characteristicFromDatabaseFreeText,
                characteristicsWithExactMatch, characteristicsStartWithQuery, characteristicsSubstring,
                characteristicsNoRuleFound);

        List<CharacteristicValueObject> allCharactersticsFound = new ArrayList<CharacteristicValueObject>();
        allCharactersticsFound.addAll(characteristicsWithExactMatch);
        allCharactersticsFound.addAll(characteristicsStartWithQuery);
        allCharactersticsFound.addAll(characteristicsSubstring);
        allCharactersticsFound.addAll(characteristicsNoRuleFound);

        // limit the size of the returned phenotypes to 100 terms
        if (allCharactersticsFound.size() > 100) {
            return allCharactersticsFound.subList(0, 100);
        }

        return allCharactersticsFound;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#findIndividuals(java.lang.String)
     */
    @Override
    public Collection<OntologyIndividual> findIndividuals(String givenSearch) {

        String query = OntologySearch.stripInvalidCharacters(givenSearch);
        Collection<OntologyIndividual> results = new HashSet<OntologyIndividual>();

        for (AbstractOntologyService ontology : ontologyServices) {
            Collection<OntologyIndividual> found = ontology.findIndividuals(query);
            if (found != null)
                results.addAll(found);
        }

        return results;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#findTermAsCharacteristic(java.lang.String)
     */
    @Override
    public Collection<VocabCharacteristic> findTermAsCharacteristic(String search) {

        String query = OntologySearch.stripInvalidCharacters(search);
        Collection<VocabCharacteristic> results = new HashSet<VocabCharacteristic>();

        if (StringUtils.isBlank(query)) {
            return results;
        }

        for (AbstractOntologyService ontology : ontologyServices) {
            Collection<OntologyTerm> found = ontology.findTerm(query);
            if (found != null)
                results.addAll(convert(new HashSet<OntologyResource>(found)));
        }

        return results;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#findTerms(java.lang.String)
     */
    @Override
    public Collection<OntologyTerm> findTerms(String search) {

        String query = OntologySearch.stripInvalidCharacters(search);

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

        if (StringUtils.isBlank(query)) {
            return results;
        }

        for (AbstractOntologyService ontology : ontologyServices) {
            if (ontology.isOntologyLoaded()) {
                Collection<OntologyTerm> found = ontology.findTerm(query);
                if (found != null)
                    results.addAll(found);
            }
        }

        return results;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getBirnLexOntologyService()
     */
    @Override
    public BirnLexOntologyService getBirnLexOntologyService() {
        return birnLexOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getChebiOntologyService()
     */
    @Override
    public ChebiOntologyService getChebiOntologyService() {
        return chebiOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getDiseaseOntologyService()
     */
    @Override
    public DiseaseOntologyService getDiseaseOntologyService() {
        return diseaseOntologyService;
    }

    @Override
    public ExperimentalFactorOntologyService getExperimentalFactorOntologyService() {
        return experimentalFactorOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getFmaOntologyService()
     */
    @Override
    public FMAOntologyService getFmaOntologyService() {
        return fmaOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getHumanPhenotypeOntologyService()
     */
    @Override
    public HumanPhenotypeOntologyService getHumanPhenotypeOntologyService() {
        return humanPhenotypeOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getMammalianPhenotypeOntologyService()
     */
    @Override
    public MammalianPhenotypeOntologyService getMammalianPhenotypeOntologyService() {
        return mammalianPhenotypeOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getMgedOntologyService()
     */
    @Override
    public MgedOntologyService getMgedOntologyService() {
        return mgedOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getNifstfOntologyService()
     */
    @Override
    public NIFSTDOntologyService getNifstfOntologyService() {
        return nifstdOntologyService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getObiService()
     */
    @Override
    public ObiService getObiService() {
        return obiService;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getResource(java.lang.String)
     */
    @Override
    public OntologyResource getResource(String uri) {
        for (AbstractOntologyService ontology : ontologyServices) {
            OntologyResource resource = ontology.getResource(uri);
            if (resource != null)
                return resource;
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#getTerm(java.lang.String)
     */
    @Override
    public OntologyTerm getTerm(String uri) {
        for (AbstractOntologyService ontology : ontologyServices) {
            OntologyTerm term = ontology.getTerm(uri);
            if (term != null)
                return term;
        }
        return null;
    }

    /**
     * @param vc
     * @return true if the valudUri isa ObsoleteClass. This will only work if the ontology in question is loaded.
     */
    @Override
    public boolean isObsolete(String uri) {
        OntologyTerm t = this.getTerm(uri);
        if (t != null && t.isTermObsolete())
            return true;

        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#reinitializeAllOntologies()
     */
    @Override
    public void reinitializeAllOntologies() {
        for (AbstractOntologyService serv : this.ontologyServices) {
            serv.startInitializationThread(true);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#removeBioMaterialStatement(java.util.Collection, java.util.Collection)
     */
    @Override
    public void removeBioMaterialStatement(Collection<Long> characterIds, Collection<Long> bmIdList) {

        log.debug("Vocab Characteristic: " + characterIds);
        log.debug("biomaterial ID List: " + bmIdList);

        Collection<BioMaterial> bms = bioMaterialService.loadMultiple(bmIdList);

        for (BioMaterial bm : bms) {

            Collection<Characteristic> current = bm.getCharacteristics();
            if (current == null)
                continue;

            Collection<Characteristic> found = new HashSet<Characteristic>();

            for (Characteristic characteristic : current) {
                if (characterIds.contains(characteristic.getId()))
                    found.add(characteristic);

            }
            if (found.size() == 0)
                continue;

            current.removeAll(found);

            for (Characteristic characteristic : found) {
                log.info("Removing characteristic from " + bm + " : " + characteristic);
            }

            bm.setCharacteristics(current);
            bioMaterialService.update(bm);

        }

        for (Long id : characterIds) {
            characteristicService.delete(id);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#removeBioMaterialStatement(java.lang.Long,
     * ubic.gemma.model.expression.biomaterial.BioMaterial)
     */
    @Override
    public void removeBioMaterialStatement(Long characterId, BioMaterial bm) {
        Characteristic vc = characteristicService.load(characterId);
        if (vc == null)
            throw new IllegalArgumentException("No characteristic with id=" + characterId + " was foundF");
        bm.getCharacteristics().remove(vc);
        characteristicService.delete(characterId);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * ubic.gemma.ontology.OntologyService#saveBioMaterialStatement(ubic.gemma.model.common.description.Characteristic,
     * ubic.gemma.model.expression.biomaterial.BioMaterial)
     */
    @Override
    public void saveBioMaterialStatement(Characteristic vc, BioMaterial bm) {

        log.debug("Vocab Characteristic: " + vc);

        vc.setEvidenceCode(GOEvidenceCode.IC); // manually added characteristic
        Set<Characteristic> chars = new HashSet<Characteristic>();
        chars.add(vc);

        Collection<Characteristic> current = bm.getCharacteristics();
        if (current == null)
            current = new HashSet<Characteristic>(chars);
        else
            current.addAll(chars);

        for (Characteristic characteristic : chars) {
            log.info("Adding characteristic to " + bm + " : " + characteristic);
        }

        bm.setCharacteristics(current);
        bioMaterialService.update(bm);

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * ubic.gemma.ontology.OntologyService#saveBioMaterialStatement(ubic.gemma.model.common.description.Characteristic,
     * java.util.Collection)
     */
    @Override
    public void saveBioMaterialStatement(Characteristic vc, Collection<Long> bioMaterialIdList) {

        log.debug("Vocab Characteristic: " + vc);
        log.debug("Biomaterial ID List: " + bioMaterialIdList);

        Collection<BioMaterial> biomaterials = bioMaterialService.loadMultiple(bioMaterialIdList);

        for (BioMaterial bioM : biomaterials) {
            saveBioMaterialStatement(vc, bioM);
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#saveExpressionExperimentStatement(ubic.gemma.model.common.description.
     * Characteristic, ubic.gemma.model.expression.experiment.ExpressionExperiment)
     */
    @Override
    public void saveExpressionExperimentStatement(Characteristic vc, ExpressionExperiment ee) {
        if (vc == null) {
            throw new IllegalArgumentException("Null characteristic");
        }
        if (StringUtils.isBlank(vc.getCategory())) {
            throw new IllegalArgumentException("Must provide a category");
        }

        if (StringUtils.isBlank(vc.getValue())) {
            throw new IllegalArgumentException("Must provide a value");
        }

        if (vc.getEvidenceCode() == null) {
            vc.setEvidenceCode(GOEvidenceCode.IC); // assume: manually added
            // characteristic
        }

        if (vc instanceof VocabCharacteristic && isObsolete(((VocabCharacteristic) vc).getValueUri())) {
            log.info(vc + " is obsolete, not saving");
            return;
        }

        if (ee == null)
            throw new IllegalArgumentException("Experiment cannot be null");

        log.info("Adding characteristic '" + vc.getValue() + "' to " + ee.getShortName() + " (ID=" + ee.getId()
                + ") : " + vc);

        ee.getCharacteristics().add(vc);
        eeService.update(ee);

    }

    /*
     * (non-Javadoc)
     * 
     * @see ubic.gemma.ontology.OntologyService#saveExpressionExperimentStatements(java.util.Collection,
     * ubic.gemma.model.expression.experiment.ExpressionExperiment)
     */
    @Override
    public void saveExpressionExperimentStatements(Collection<Characteristic> vc, ExpressionExperiment ee) {
        for (Characteristic characteristic : vc) {
            // load necessary to make sure we are dealing with the persistent version.
            saveExpressionExperimentStatement(characteristic, eeService.thawLite(eeService.load(ee.getId())));
        }
    }

    /**
     * @param sortedResultsExact
     */
    @Override
    public void sort(List<Characteristic> sortedResultsExact) {
        Collections.sort(sortedResultsExact, new CharacteristicComparator());
    }

    /**
     * Given a collection of ontology terms converts them to a collection of VocabCharacteristics
     * 
     * @param terms
     * @param filterTerm
     * @return
     */
    private Collection<VocabCharacteristic> convert(final Collection<OntologyResource> resources) {

        Collection<VocabCharacteristic> converted = new HashSet<VocabCharacteristic>();

        if ((resources == null) || (resources.isEmpty()))
            return converted;

        for (OntologyResource res : resources) {
            VocabCharacteristic vc = VocabCharacteristic.Factory.newInstance();

            // If there is no URI we don't want to send it back (ie useless)
            if ((res.getUri() == null) || StringUtils.isEmpty(res.getUri()))
                continue;

            if (res instanceof OntologyTerm) {
                OntologyTerm term = (OntologyTerm) res;
                vc.setValue(term.getTerm());
                vc.setValueUri(term.getUri());
                vc.setDescription(term.getComment());
            }
            if (res instanceof OntologyIndividual) {
                OntologyIndividual indi = (OntologyIndividual) res;
                vc.setValue(indi.getLabel());
                vc.setValueUri(indi.getUri());
                vc.setDescription("Individual");
            }

            converted.add(vc);
        }

        return converted;
    }

    /**
     * Given a collection of ontology terms will filter out all the terms that don't have the filter term in their
     * label.
     * 
     * @param terms
     * @param filterTerm
     * @return
     */
    private Collection<VocabCharacteristic> filter(final Collection<OntologyResource> terms, final String filter) {

        Collection<VocabCharacteristic> filtered = new HashSet<VocabCharacteristic>();

        if ((terms == null) || (terms.isEmpty()))
            return filtered;

        String caseInsensitiveFilter = filter.toLowerCase().trim();

        for (OntologyResource res : terms) {
            if (StringUtils.isNotEmpty(res.getLabel())
                    && res.getLabel().toLowerCase().indexOf(caseInsensitiveFilter) > -1) {
                VocabCharacteristic vc = VocabCharacteristic.Factory.newInstance();
                if (res instanceof OntologyTerm) {
                    OntologyTerm term = (OntologyTerm) res;
                    vc.setValue(term.getTerm());
                    vc.setValueUri(term.getUri());
                    vc.setDescription(term.getComment());
                } else if (res instanceof OntologyIndividual) {
                    OntologyIndividual indi = (OntologyIndividual) res;
                    vc.setValue(indi.getLabel());
                    vc.setValueUri(indi.getUri());
                    vc.setDescription("Individual");
                }

                filtered.add(vc);
            }
        }
        log.debug("returning " + filtered.size() + " terms after filter");

        return filtered;
    }

    /** given a collection of characteristics add them to the correct List */
    private Collection<CharacteristicValueObject> findCharacteristicsFromOntology(String searchQuery,
            boolean useNeuroCartaOntology,
            Map<String, CharacteristicValueObject> characteristicFromDatabaseWithValueUri) {

        Collection<CharacteristicValueObject> characteristicsFromOntology = new HashSet<CharacteristicValueObject>();

        // in neurocarta we dont need to search all Ontologies
        Collection<AbstractOntologyService> ontologyServicesToUse = new HashSet<AbstractOntologyService>();

        if (useNeuroCartaOntology) {
            ontologyServicesToUse.add(this.nifstdOntologyService);
            ontologyServicesToUse.add(this.fmaOntologyService);
            ontologyServicesToUse.add(this.obiService);

        } else {
            ontologyServicesToUse = this.ontologyServices;
        }

        // search all Ontology
        for (AbstractOntologyService ontologyService : ontologyServicesToUse) {

            Collection<OntologyTerm> ontologyTerms = ontologyService.findTerm(searchQuery);

            for (OntologyTerm ontologyTerm : ontologyTerms) {

                // if the ontology term wasnt already found in the database
                if (characteristicFromDatabaseWithValueUri.get(ontologyTerm.getUri()) == null) {

                    CharacteristicValueObject phenotype = new CharacteristicValueObject(
                            ontologyTerm.getLabel().toLowerCase(), ontologyTerm.getUri());

                    characteristicsFromOntology.add(phenotype);
                }
            }
        }

        return characteristicsFromOntology;
    }

    /**
     * @param c
     * @return
     */
    private String foundValueKey(Characteristic c) {
        if (c instanceof VocabCharacteristic) {
            return ((VocabCharacteristic) c).getValueUri();
        }
        return c.getValue();
    }

    /**
     * Allow us to store gene information as a characteristic associated with our entities. This doesn't work so well
     * for non-ncbi genes.
     * 
     * @param g
     * @return
     */
    private Characteristic gene2Characteristic(Gene g) {
        VocabCharacteristic vc = VocabCharacteristic.Factory.newInstance();
        vc.setCategory("gene");
        vc.setCategoryUri("http://purl.org/commons/hcls/gene");
        vc.setValue(g.getOfficialSymbol() + " [" + g.getTaxon().getCommonName() + "]" + " " + g.getOfficialName());
        vc.setDescription(g.toString());
        if (g.getNcbiGeneId() != null) {
            vc.setValueUri("http://purl.org/commons/record/ncbi_gene/" + g.getNcbiGeneId());
        }
        return vc;
    }

    /** given a collection of characteristics add them to the correct List */
    private void putCharacteristicsIntoSpecificList(String searchQuery,
            Collection<CharacteristicValueObject> characteristics,
            Collection<CharacteristicValueObject> characteristicsWithExactMatch,
            Collection<CharacteristicValueObject> characteristicsStartWithQuery,
            Collection<CharacteristicValueObject> characteristicsSubstring,
            Collection<CharacteristicValueObject> characteristicsNoRuleFound) {

        for (CharacteristicValueObject cha : characteristics) {
            // Case 1, exact match
            if (cha.getValue().equalsIgnoreCase(searchQuery)) {
                characteristicsWithExactMatch.add(cha);
            }
            // Case 2, starts with a substring of the word
            else if (cha.getValue().toLowerCase().startsWith(searchQuery.toLowerCase())) {
                characteristicsStartWithQuery.add(cha);
            }
            // Case 3, contains a substring of the word
            else if (cha.getValue().toLowerCase().indexOf(searchQuery.toLowerCase()) != -1) {
                characteristicsSubstring.add(cha);
            } else {
                characteristicsNoRuleFound.add(cha);
            }
        }
    }

    /**
     * @param queryString
     * @param categoryUri
     * @param taxon okay if null
     * @param searchResults
     */
    private void searchForGenes(String queryString, String categoryUri, Taxon taxon,
            Collection<Characteristic> searchResults) {
        if (categoryUri == null)
            return;

        if (categoryUri.equals("http://mged.sourceforge.net/ontologies/MGEDOntology.owl#GeneticModification")
                || categoryUri.equals(
                        "http://mged.sourceforge.net/ontologies/MGEDOntology.owl#IndividualGeneticCharacteristics")
                || categoryUri.equals("http://mged.sourceforge.net/ontologies/MGEDOntology.owl#Genotype")) {

            /*
             * Kick into a special search for genes. The user will have to deal with choosing one from the right taxon.
             */
            SearchSettings ss = SearchSettings.Factory.newInstance();
            ss.setQuery(queryString);
            ss.noSearches();
            ss.setTaxon(taxon);
            ss.setSearchGenes(true);
            Map<Class<?>, List<SearchResult>> geneResults = this.searchService.search(ss, true, false);

            if (geneResults.containsKey(Gene.class)) {
                for (SearchResult sr : geneResults.get(Gene.class)) {
                    Gene g = (Gene) sr.getResultObject();
                    log.debug("Search for " + queryString + " returned: " + g);
                    searchResults.add(gene2Characteristic(g));
                }
            }
        }
    }

    /**
     * @param individualResults
     * @param alreadyUsedResults
     * @param searchResults
     * @param searchTerm
     * @param foundValues
     * @return
     */
    private Collection<Characteristic> sort(Collection<Characteristic> individualResults,
            Collection<Characteristic> alreadyUsedResults, Collection<Characteristic> searchResults,
            String searchTerm, Collection<String> foundValues) {

        /*
         * Organize the list into 3 parts. Want to get the exact match showing up on top But close matching
         * individualResults and alreadyUsedResults should get priority over jena's search results. Each result's order
         * should be preserved.
         */

        List<Characteristic> sortedResultsExact = new ArrayList<Characteristic>();
        List<Characteristic> sortedResultsStartsWith = new ArrayList<Characteristic>();
        List<Characteristic> sortedResultsBottom = new ArrayList<Characteristic>();

        for (Characteristic characteristic : alreadyUsedResults) {
            if (characteristic.getValue().equalsIgnoreCase(searchTerm)) {
                sortedResultsExact.add(characteristic);
            } else if (characteristic.getValue().startsWith(searchTerm)) {
                sortedResultsStartsWith.add(characteristic);
            } else {
                sortedResultsBottom.add(characteristic);
            }
        }

        for (Characteristic characteristic : individualResults) {
            String key = foundValueKey(characteristic);
            if (foundValues.contains(key))
                continue;

            foundValues.add(key);

            if (characteristic.getValue().equalsIgnoreCase(searchTerm)) {
                sortedResultsExact.add(characteristic);
            } else if (characteristic.getValue().startsWith(searchTerm)) {
                sortedResultsStartsWith.add(characteristic);
            } else {
                sortedResultsBottom.add(characteristic);
            }
        }

        for (Characteristic characteristic : searchResults) {
            String key = foundValueKey(characteristic);
            if (foundValues.contains(key))
                continue;
            foundValues.add(key);
            if (characteristic.getValue().equalsIgnoreCase(searchTerm)) {
                sortedResultsExact.add(characteristic);
            } else if (characteristic.getValue().startsWith(searchTerm)) {
                sortedResultsStartsWith.add(characteristic);
            } else {
                sortedResultsBottom.add(characteristic);
            }
        }

        sort(sortedResultsExact);
        sort(sortedResultsStartsWith);
        sort(sortedResultsBottom);

        List<Characteristic> sortedTerms = new ArrayList<Characteristic>(foundValues.size());
        sortedTerms.addAll(sortedResultsExact);
        sortedTerms.addAll(sortedResultsStartsWith);
        sortedTerms.addAll(sortedResultsBottom);

        return sortedTerms;
    }

}