/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1870 $ by $Author: david@nixbioinf.org $ on $Date:: 2010-02-23 #$
*/
package org.eurocarbdb.action.core;
import static org.eurocarbdb.dataaccess.Eurocarb.getEntityManager;
import static org.eurocarbdb.dataaccess.Eurocarb.getHqlQuery;
import static org.eurocarbdb.util.StringUtils.join;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eurocarbdb.action.EurocarbAction;
import org.eurocarbdb.action.ParameterChecking;
import org.eurocarbdb.dataaccess.core.Disease;
import org.eurocarbdb.dataaccess.core.Perturbation;
import org.eurocarbdb.dataaccess.core.Taxonomy;
import org.eurocarbdb.dataaccess.core.TissueTaxonomy;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
/* class Autocompleter
*
* Class to produce lists of strings using like queries for various data sources
*
*
*
*/
@ParameterChecking( whitelist = {"queryType","queryString"})
public class Autocompleter extends EurocarbAction
{
private String queryType;
private String queryString;
private final String[] PARAMETERS_WHITELIST = new String[]{"queryType","queryString"};
public String[] parametersWhitelist() {
return PARAMETERS_WHITELIST;
}
/**
* Inner class for the Autocomplete results. Works as a struct containing
* the information that will be returned in a list to the autocomplete
* result rendering template
*/
public class AutocompleteResult
{
public String description = null;
public String synonyms = null;
public String entrySource = null;
public String supplemental = "";
public Object entry = null;
public String getDescription() {
return description;
}
public String getSynonyms() {
return synonyms;
}
/*
* Entry source for the autocomplete result
* Usually set to either "user" or nothing
*/
public String getEntrySource() {
return entrySource;
}
public String getSupplemental() {
return supplemental;
}
public int hashCode() {
if (this.entry != null) {
return this.entry.hashCode();
}
int hashCode = 1;
if (this.description != null) {
hashCode *= this.description.hashCode();
}
if (this.synonyms != null) {
hashCode *= this.synonyms.hashCode();
}
return hashCode;
}
public boolean equals(Object x) {
if ( this == x )
return true;
if ( (x == null) || ! (x instanceof AutocompleteResult) )
return false;
// objects are the same class
AutocompleteResult r = (AutocompleteResult) x;
if (this.entry != null) {
return this.entry.equals(r.entry);
}
return (( this.description == null && r.description == null ) || (this.description != null && this.description.equals(r.description)))
&& (( this.synonyms == null && r.synonyms == null ) || (this.synonyms != null && this.synonyms.equals(r.synonyms)))
;
}
}
protected Set<AutocompleteResult> results = new LinkedHashSet<AutocompleteResult>();
public String getQueryType()
{
return this.queryType;
}
public void setQueryType(String queryType)
{
this.queryType = queryType;
}
public String getQueryString()
{
return this.queryString;
}
public void setQueryString(String queryString)
{
this.queryString = queryString;
}
public Set<AutocompleteResult> getResults()
{
return this.results;
}
public String execute()
{
if (queryType == null || queryString == null)
{
return SUCCESS;
}
queryString = queryString.toLowerCase();
if (queryType.equals("taxonomy_name"))
{
findTaxonomiesMatchingString(queryString);
}
else if (queryType.equals("tissue_name"))
{
findTissuesMatchingString(queryString);
}
else if (queryType.equals("disease_name"))
{
findDiseasesMatchingString(queryString);
}
else if (queryType.equals("perturbation_name"))
{
findPerturbationsMatchingString(queryString);
}
return SUCCESS;
}
protected AutocompleteResult addResult(String description, Object entry)
{
return addResult(description,"","",entry);
}
protected AutocompleteResult addResult(String description, String synonyms, Object entry)
{
return addResult(description,synonyms,"",entry);
}
protected AutocompleteResult addResult(String description, String synonyms, String entrySource, Object entry)
{
AutocompleteResult result = new AutocompleteResult();
result.description = description;
result.synonyms = synonyms;
result.entrySource = entrySource;
result.entry = entry;
results.add(result);
return result;
}
protected void findTissuesMatchingString(String query)
{
log.debug("Querying tissue for " + query);
for ( TissueTaxonomy tissue : TissueTaxonomy.lookupNameOrSynonym( query ))
{
AutocompleteResult res = addResult(tissue.getName(),tissue);
res.supplemental = join( " > ", tissue.getAllParentTissueTaxonomies());
}
}
protected void findDiseasesMatchingString(String query)
{
log.debug("Querying disease for " + query);
for ( Disease disease : Disease.lookupNameOrSynonym( query ))
{
AutocompleteResult res = addResult(disease.getDiseaseName(),disease);
res.supplemental = join( " > ", disease.getAllParentDiseases());
}
}
protected void findPerturbationsMatchingString(String query)
{
log.debug("Querying perturbations for " + query);
for ( Perturbation perturbation : Perturbation.lookupNameOrSynonym( query ))
{
addResult(perturbation.getPerturbationName(),perturbation);
}
}
private List<Taxonomy> runTaxonomyQuery(String query)
{
MatchMode[] modes = {MatchMode.EXACT, MatchMode.START, MatchMode.ANYWHERE};
LinkedHashSet<Taxonomy> results = new LinkedHashSet<Taxonomy>();
for ( MatchMode mode : modes ) {
log.info("Running match mode: "+mode);
List<Taxonomy> taxes = runTaxonomyQuery(query,mode);
results.addAll(taxes);
if(results.size() >= 10){
log.info("Exiting because of results.size() being >= 10");
}else if(results.size() > 0 && mode == MatchMode.EXACT){
log.info("Exiting because of exact match");
}
if ( (results.size() >= 10) || (results.size() > 0 && mode == MatchMode.EXACT) ) {
log.info("Results size:"+results.size());
return new ArrayList<Taxonomy>(results);
}
}
return new ArrayList<Taxonomy>(results);
}
private List<Taxonomy> runTaxonomyQueryProteomeRanked(String query)
{
log.debug("Fetching matching taxonomies---");
Query queryA;
if(query.matches("^[0-9]+$")){
log.debug("Found integer taxonomic search: "+query);
queryA=getHqlQuery("SELECT a FROM Taxonomy AS a, TaxonomyProteomeSkRanked c " +
"WHERE CAST(a.ncbiId as string) LIKE ? AND " +
"a.ncbiId=c.ncbiId " +
"ORDER BY c.rank ");
queryA.setString(0, ""+query+"%");
}else{
log.debug("Found string taxonomic search: "+query);
queryA=getHqlQuery("SELECT a FROM Taxonomy as a " +
"LEFT JOIN FETCH a.taxonomySynonyms b, TaxonomyProteomeSkRanked c " +
"WHERE (lower(b.synonym) LIKE ? OR lower(a.taxon) LIKE ?) and " +
"a.ncbiId=c.ncbiId order by c.rank ");
queryA.setString(0, ""+query+"%");
queryA.setString(1, ""+query+"%");
}
queryA.setMaxResults(10);
List<Taxonomy> ids=queryA.list();
ArrayList<Taxonomy> uniqueList=new ArrayList<Taxonomy>();
HashSet<String> uniqueTaxons=new HashSet<String>();
for(Taxonomy c:ids){
if(!uniqueTaxons.contains(c.getTaxon())){
uniqueTaxons.add(c.getTaxon());
uniqueList.add(c);
}
}
return uniqueList;
}
/*
* Run the taxonomy query, searching within taxonomies and
* their synonyms for the query string.
*
* re.relativeImportance is given by the formula "right_index - left_index" within
* "core-api/src/org/eurocarbdb/dataaccess/core/TaxonomyRelations.hbm.xml". Relationships
* are stored so that they can be retrieved using a modified preorder tree traversal
* algorithm. There's an example below of a tree with left and right numbers annotated;
* taken from http://www.sitepoint.com/print/hierarchical-data-database/.
*
* [1] Food [18]
* |
* -----------------------------
* | |
* [2] Fruit [7] ...............
* |
* ---------------
* | |
* [3] Red [4] [5] Blue [6]
*
* Therefore to find all descendants of Fruit you look for all nodes with an Lvalue greater than
* 2 and an Rvalue less than 7. The difference between the Lvalue and Rvalue indicates how many
* nodes are children of the given node. The actual number of child nodes is given by
* (Rvalue-Lvalue-1)/2; we must minus one to reset the Rvalue to that of the last child to prevent
* counting the parent node, and we must divide by two because each node increments the counter
* by two.
*/
@SuppressWarnings("unchecked")
private List<Taxonomy> runTaxonomyQuery(String query,MatchMode matchMode)
{
Criteria criteria = getEntityManager().createQuery(Taxonomy.class);
criteria.createAlias("taxonomySynonyms","syn",Criteria.LEFT_JOIN);
criteria.createAlias("relations","rel",Criteria.INNER_JOIN);
criteria.add(
Restrictions.or(
Restrictions.like( "taxon", query, matchMode ),
Restrictions.like( "syn.synonym", query, matchMode )
)
);
criteria.addOrder(Order.asc("rel.relativeImportance"));
//criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.setMaxResults(10);
return criteria.list();
}
protected void findTaxonomiesMatchingString(String query)
{
this.results.clear();
//log.setLevel(Level.ALL);
log.debug("Querying taxonomy for " + query);
for (Taxonomy tax : runTaxonomyQueryProteomeRanked(query))
{
List<String> synonyms = tax.getSynonyms();
String synonymString = "";
if ( synonyms.size() > 0 ) {
synonymString = "("
+ join(",",synonyms)
+ ")" ;
}
addResult(tax.getName(),synonymString,tax);
}
}
}