package org.genedb.web.mvc.controller;
import org.genedb.db.dao.CvDao;
import org.genedb.db.dao.OrganismDao;
import org.genedb.db.dao.SequenceDao;
import org.genedb.db.taxon.TaxonNameType;
import org.genedb.db.taxon.TaxonNode;
import org.genedb.db.taxon.TaxonNodeList;
import org.genedb.db.taxon.TaxonNodeManager;
import org.genedb.querying.core.NumericQueryVisibility;
import org.genedb.querying.core.QueryException;
import org.genedb.querying.core.QueryFactory;
import org.genedb.querying.tmpquery.ChangedGeneFeaturesQuery;
import org.genedb.querying.tmpquery.GeneSummary;
import org.genedb.querying.tmpquery.IdsToGeneSummaryQuery;
import org.genedb.querying.tmpquery.QuickSearchQuery;
import org.genedb.querying.tmpquery.SuggestQuery;
import org.genedb.querying.tmpquery.QuickSearchQuery.QuickSearchQueryResults;
import org.genedb.querying.tmpquery.TopLevelFeaturesQuery;
import org.genedb.querying.tmpquery.TopLevelFeaturesQuery.TopLevelFeature;
import org.gmod.schema.mapped.CvTerm;
import org.gmod.schema.mapped.Feature;
import org.gmod.schema.mapped.Organism;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
/**
*
* A controller for REST services, initially focused on EupathDB requirements.
*
*
* @author gv1
*
*/
@Controller
@RequestMapping("/service")
public class RestController {
private static final Logger logger = Logger.getLogger(RestController.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
@Qualifier("organismDao")
OrganismDao organismDao;
@Autowired
@Qualifier("sequenceDao")
SequenceDao sequenceDao;
@Autowired
@Qualifier("cvDao")
CvDao cvDao;
private final String viewName = "json:";
@RequestMapping(method=RequestMethod.GET, value={"/test", "/test.*"})
public ModelAndView test(HttpServletRequest request, HttpServletResponse response)
{
ModelAndView mav = new ModelAndView(viewName);
mav.addObject("model", "hello world");
return mav;
}
@RequestMapping(method=RequestMethod.GET, value="/organisms")
public ModelAndView organisms() {
ModelAndView mav = new ModelAndView(viewName);
OrganismResults results = new OrganismResults();
// using the taxon node manager to make sure any organism that isn't properly setup doesn't slip through here
TaxonNodeManager tnm = (TaxonNodeManager) applicationContext.getBean("taxonNodeManager", TaxonNodeManager.class);
TaxonNode taxonNode = tnm.getTaxonNodeForLabel("Root");
List<TaxonNode> childrens = taxonNode.getAllChildren();
for (TaxonNode node : childrens)
{
if (node.isOrganism() && node.isPopulated()) {
results.organisms.add(node.getLabel());
}
}
Collections.sort(results.organisms);
mav.addObject("model", results);
return mav;
}
@RequestMapping(method=RequestMethod.GET, value="/top")
public ModelAndView top(@RequestParam("commonName") String commonName) throws RestException {
ModelAndView mav = new ModelAndView(viewName);
Organism org = organismDao.getOrganismByCommonName(commonName);
TopLevelResults results = new TopLevelResults();
results.organism = commonName;
//TopLevelFeaturesQuery topQuery = (TopLevelFeaturesQuery) queryFactory.retrieveQuery("topLevelFeatures", NumericQueryVisibility.PRIVATE);
TopLevelFeaturesQuery topQuery = (TopLevelFeaturesQuery) applicationContext.getBean("topLevelFeatures", TopLevelFeaturesQuery.class);
TaxonNode taxonNode = getTaxonNode(commonName);
TaxonNodeList taxons = new TaxonNodeList(taxonNode);
topQuery.setTaxons(taxons);
try {
List<TopLevelFeature> tops = topQuery.getResults();
for (TopLevelFeature top : tops) {
results.features.add(top);
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
mav.addObject("model", results);
return mav;
}
@RequestMapping(method=RequestMethod.GET, value={"/changesummary", "/changesummary.*"})
public ModelAndView changesSummary(@RequestParam("since") String since, @RequestParam("taxon") String taxon)
{
ModelAndView mav = new ModelAndView(viewName);
final ChangedFeatureSetResultSummary organismSetResultSummary = new ChangedFeatureSetResultSummary();
organismSetResultSummary.name = "genome/changes";
organismSetResultSummary.since = since;
organismSetResultSummary.count = 0;
mav.addObject("model", organismSetResultSummary);
try {
TaxonNode taxonNode = getTaxonNode(taxon);
Organism o = getOrganism(taxonNode);
if (o == null) {
return mav;
}
Date sinceDate = getSinceDate(since);
ChangedGeneFeaturesQuery changedGeneFeaturesQuery = (ChangedGeneFeaturesQuery) applicationContext.getBean("changedGeneFeatures", ChangedGeneFeaturesQuery.class);
changedGeneFeaturesQuery.setDate(sinceDate);
changedGeneFeaturesQuery.setOrganismId(o.getOrganismId());
// deliberately resetting since to whatever it has been interpreted to be
organismSetResultSummary.since = sinceDate.toString();
organismSetResultSummary.taxonomyID = taxonNode.getTaxonId();
final Hashtable<String,Integer> statistics = new Hashtable<String,Integer>();
changedGeneFeaturesQuery.processCallBack(new RowCallbackHandler(){
public void processRow(ResultSet rs) throws SQLException {
String type = rs.getString("type");
if (! statistics.containsKey(type)) {
statistics.put(type, 0);
}
statistics.put(type, statistics.get(type) + 1);
organismSetResultSummary.count += 1;
}
});
for (String key : statistics.keySet()) {
ChangedFeatureSetResultSummaryStatistic statistic = new ChangedFeatureSetResultSummaryStatistic();
statistic.annotation = key;
statistic.count = statistics.get(key);
organismSetResultSummary.statistics.add(statistic);
logger.info(key + " " + statistics.get(key));
}
} catch (RestException re) {
mav.addObject("model", re.model);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
mav.addObject("model", new ErrorModel(e.getMessage()));
}
return mav;
}
private Organism getOrganism(TaxonNode taxonNode) throws RestException {
return organismDao.getOrganismByCommonName(taxonNode.getLabel());
}
private TaxonNode getTaxonNode(String taxon) throws RestException {
TaxonNodeManager tnm = (TaxonNodeManager) applicationContext.getBean("taxonNodeManager", TaxonNodeManager.class);
TaxonNode taxonNode = tnm.getTaxonNodeForLabel(taxon);
if (taxonNode == null) {
throw new RestException(ErrorType.INVALID_PARAMETER, "Could not find a taxonNode for taxon " + taxon );
}
logger.info(taxonNode);
return taxonNode;
}
private Date getSinceDate(String since) throws RestException {
Date sinceDate = Calendar.getInstance().getTime();
try {
return getDateFromString(since);
} catch (ParseException e) {
throw new RestException(ErrorType.MISSING_PARAMETER, "Please supply a date as 'yyyy-mm-dd'.");
}
}
/**
* Returns all features changed since a certain date, as determined by the DateQuery.
*
* @param since
* @param since
* @return
*/
@RequestMapping(method=RequestMethod.GET, value={"/changes"})
public ModelAndView changes(@RequestParam("since") String since, @RequestParam("taxon") String taxon, @RequestParam(value="type", required=false) String type)
{
logger.info(String.format("Searching for changes in %s since %s : ", taxon, since));
ModelAndView mav = new ModelAndView(viewName);
try {
TaxonNode taxonNode = getTaxonNode(taxon);
Organism o = getOrganism(taxonNode);
Date sinceDate = getSinceDate(since);
final ChangedFeatureSetResult organismSetResult = new ChangedFeatureSetResult();
organismSetResult.since = sinceDate.toString();
organismSetResult.name = "genome/changes";
organismSetResult.count = 0;
mav.addObject("model", organismSetResult);
if (o == null) {
return mav;
}
ChangedGeneFeaturesQuery changedGeneFeaturesQuery = (ChangedGeneFeaturesQuery) applicationContext.getBean("changedGeneFeatures", ChangedGeneFeaturesQuery.class);
changedGeneFeaturesQuery.setDate(sinceDate);
changedGeneFeaturesQuery.setOrganismId(o.getOrganismId());
if (type != null) {
changedGeneFeaturesQuery.setType(type);
}
organismSetResult.taxonomyID = taxonNode.getTaxonId();
changedGeneFeaturesQuery.processCallBack(new RowCallbackHandler(){
public void processRow(ResultSet rs) throws SQLException {
FeatureStatus fs = new FeatureStatus();
fs.type = rs.getString("type");
fs.changedetail = rs.getString("changedetail");
fs.changedate = rs.getString("changedate");
fs.geneuniquename = rs.getString("geneuniquename");
fs.mrnauniquename = rs.getString("mrnauniquename");
fs.transcriptuniquename = rs.getString("transcriptuniquename");
logger.info(fs);
organismSetResult.addResult(fs);
organismSetResult.count += 1;
}
});
} catch (RestException re) {
mav.addObject("model", re.model);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
mav.addObject("model", new ErrorModel(e.getMessage()));
}
return mav;
}
/**
*
* Wraps QuickSearchQuery, and falls back onto a SuggestQuery.
*
* @param term
* @param taxon
* @param max
* @return
* @throws QueryException
*/
@RequestMapping(method=RequestMethod.GET, value={"/search", "/search.*"})
public ModelAndView search ( @RequestParam("term") String term, @RequestParam("taxon") String taxon, @RequestParam("max") int max ) throws QueryException {
logger.info(String.format("Searching %s for %s : ", taxon, term));
ModelAndView mav = new ModelAndView(viewName);
QuickSearchResults qsr = new QuickSearchResults();
qsr.term = term;
qsr.max = max;
qsr.totalHits = 0;
mav.addObject("model", qsr);
if (term.equalsIgnoreCase("*")) {
return mav;
}
try {
QuickSearchQuery query = (QuickSearchQuery) applicationContext.getBean("quickSearch", QuickSearchQuery.class);
query.setSearchText(term);
query.setAllNames(true);
query.setProduct(true);
query.setPseudogenes(true);
TaxonNodeManager tnm = (TaxonNodeManager) applicationContext.getBean("taxonNodeManager", TaxonNodeManager.class);
TaxonNode taxonNode = tnm.getTaxonNodeForLabel(taxon);
if (taxonNode == null) {
throw new RestException(ErrorType.INVALID_PARAMETER, "Could not find a taxonNode for taxon " + taxon );
}
logger.info(taxonNode);
TaxonNodeList taxons = new TaxonNodeList(taxonNode);
query.setTaxons(taxons);
List<String> ids = query.getResults(0, max);
IdsToGeneSummaryQuery idsToGeneSummaryQuery = (IdsToGeneSummaryQuery) applicationContext.getBean("idsToGeneSummary", IdsToGeneSummaryQuery.class);
idsToGeneSummaryQuery.setIds(ids);
logger.info(ids);
logger.info(ids.size());
//QuickSearchQueryResults results = query.getQuickSearchQueryResults(0, max);
List<GeneSummary> geneResults = idsToGeneSummaryQuery.getResultsSummaries(0, max) ; //query.getResultsSummaries(0, max);
logger.info(geneResults);
logger.info(geneResults.size());
qsr.totalHits = query.getTotalResultsSize();
int i = 0;
for (GeneSummary result : geneResults) {
i++;
QuickSearchResult q = new QuickSearchResult();
q.systematicId = result.getSystematicId();
q.product = result.getProduct();
q.displayId = result.getDisplayId();
q.taxonDisplayName = result.getTaxonDisplayName();
q.topLevelFeatureName = result.getTopLevelFeatureName();
qsr.addHit(q);
}
logger.info("Processed " + i + " results");
SuggestQuery squery = (SuggestQuery) applicationContext.getBean("suggest", SuggestQuery.class);
squery.setSearchText(term);
squery.setMax(max);
squery.setTaxons(taxons);
@SuppressWarnings("unchecked")
List<String> sResults = (List<String>) squery.getResults();
for (Object sResult : sResults) {
// logger.debug(sResult);
Suggestion s = new Suggestion();
s.name = (String) sResult;
qsr.addSuggestion(s);
}
} catch (RestException re) {
mav.addObject("model", re.model);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
mav.addObject("model", new ErrorModel(e.getMessage()));
}
logger.info("returning " + mav);
return mav;
}
/*
*
* Private utilities follow.
*
*/
/**
*
* Searches for a taxonnode given a taxonomy id.
*
* @param taxonomyID
* @return
*/
private TaxonNode getTaxonFromTaxonomyID(String taxonomyID)
{
List<Organism> organisms = organismDao.getOrganisms();
for (Organism organism : organisms)
{
logger.debug(organism);
String orgTaxonID = organism.getPropertyValue("genedb_misc", "taxonId");
if (orgTaxonID == null)
continue;
logger.debug(orgTaxonID);
if (orgTaxonID.equals(taxonomyID))
{
String organismTaxonName = organism.getGenus() + " " + organism.getSpecies();
logger.debug(organismTaxonName);
TaxonNode taxon = getTaxonFromID( organismTaxonName );
logger.debug(taxon);
return taxon;
}
}
return null;
}
/**
* The full taxon name is the genus and the species.
* @param fullTaxonName equal to organism.getGenus() + " " + organism.getSpecies()
* @return a taxon node if it finds one
*/
private TaxonNode getTaxonFromID(String fullTaxonName)
{
TaxonNodeManager tnm = (TaxonNodeManager) applicationContext.getBean("taxonNodeManager", TaxonNodeManager.class);
TaxonNode taxonNode = tnm.getTaxonNodeForLabel("Root");
List<TaxonNode> childrens = taxonNode.getAllChildren();
for (TaxonNode node : childrens)
{
if (node.getName(TaxonNameType.FULL).equals(fullTaxonName))
{
return node;
}
}
return null;
}
/**
* Returns a list of taxons.
*/
private List<TaxonNode> getAllTaxons()
{
TaxonNodeManager tnm = (TaxonNodeManager) applicationContext.getBean("taxonNodeManager", TaxonNodeManager.class);
TaxonNode taxonNode = tnm.getTaxonNodeForLabel("Root");
List<TaxonNode> childrens = taxonNode.getAllChildren();
return childrens;
}
/**
* Returns a date from a string, if formatted correctly.
* @param since
* @return
* @throws ParseException
*/
private Date getDateFromString(String since)
throws ParseException
{
Date sinceDate = Calendar.getInstance().getTime();
if (since != null)
{
logger.info("supplied since " + since);
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd" );
sinceDate = dateFormat.parse(since);
}
return sinceDate;
}
}
/**
* A list of error types that services can return. Error codes are autogenerated from the position in this enum.
* @author gv1
*
*/
enum ErrorType
{
MISSING_PARAMETER,
INVALID_PARAMETER,
NO_RESULT,
QUERY_FAILURE,
MISC
}
/*
*
* Following are initial view-model definitions. Eventually these should be moved out.
*
*/
@XStreamAlias("baseResult")
class BaseResult
{
@XStreamAlias("name")
@XStreamAsAttribute
protected String name;
}
@XStreamAlias("results")
class RestResultSet extends BaseResult
{
@XStreamImplicit()
private List<BaseResult> results = new ArrayList<BaseResult>();
public void addResult(BaseResult br)
{
results.add(br);
}
}
@XStreamAlias("results")
class TopLevelResults extends BaseResult {
@XStreamAlias("organism")
@XStreamAsAttribute
public String organism;
@XStreamImplicit()
@XStreamAlias("features")
public List<TopLevelFeature> features = new ArrayList<TopLevelFeature>();
}
@XStreamAlias("results")
class OrganismResults extends BaseResult {
@XStreamAlias("organisms")
public List<String> organisms = new ArrayList<String>();
}
@XStreamAlias("results")
class QuickSearchResults extends BaseResult {
@XStreamAlias("term")
@XStreamAsAttribute
public String term;
@XStreamAlias("max")
@XStreamAsAttribute
public int max;
@XStreamAlias("totalHits")
@XStreamAsAttribute
public int totalHits;
@XStreamImplicit()
private List<BaseResult> suggestions = new ArrayList<BaseResult>();
public void addSuggestion(BaseResult br)
{
suggestions.add(br);
}
@XStreamImplicit()
private List<BaseResult> hits = new ArrayList<BaseResult>();
public void addHit(BaseResult br)
{
suggestions.add(br);
}
}
@XStreamAlias("suggestions")
class Suggestion extends BaseResult { }
@XStreamAlias("hits")
class QuickSearchResult extends BaseResult
{
@XStreamAlias("systematicId")
@XStreamAsAttribute
public String systematicId;
@XStreamAlias("displayId")
@XStreamAsAttribute
public String displayId;
@XStreamAlias("taxonDisplayName")
@XStreamAsAttribute
public String taxonDisplayName;
@XStreamAlias("product")
@XStreamAsAttribute
public String product;
@XStreamAlias("topLevelFeatureName")
@XStreamAsAttribute
public String topLevelFeatureName;
@XStreamAlias("left")
@XStreamAsAttribute
public String left;
}
@XStreamAlias("results")
class ChangedFeatureSetResult extends RestResultSet
{
@XStreamAlias("since")
@XStreamAsAttribute
public String since;
@XStreamAlias("taxonomyID")
@XStreamAsAttribute
public String taxonomyID;
@XStreamAlias("count")
@XStreamAsAttribute
public int count;
}
@XStreamAlias("results")
class ChangedFeatureSetResultSummary extends ChangedFeatureSetResult {
@XStreamAlias("summary")
public List<ChangedFeatureSetResultSummaryStatistic> statistics = new ArrayList<ChangedFeatureSetResultSummaryStatistic>();
}
@XStreamAlias("statistics")
class ChangedFeatureSetResultSummaryStatistic {
@XStreamAlias("Annotation type")
public String annotation;
@XStreamAlias("Count")
public int count;
}
@XStreamAlias("feature")
class FeatureStatus extends BaseResult
{
@XStreamAlias("type")
@XStreamAsAttribute
public String type;
@XStreamAlias("geneuniquename")
@XStreamAsAttribute
public String geneuniquename;
@XStreamAlias("mrnauniquename")
@XStreamAsAttribute
public String mrnauniquename;
@XStreamAlias("transcriptuniquename")
@XStreamAsAttribute
public String transcriptuniquename;
@XStreamAlias("changedate")
@XStreamAsAttribute
public String changedate;
@XStreamAlias("changedetail")
@XStreamAsAttribute
public String changedetail;
}
@XStreamAlias("error")
class ErrorModel
{
@XStreamAlias("type")
@XStreamAsAttribute
public String type = ErrorType.MISC.toString();
@XStreamAlias("code")
@XStreamAsAttribute
public int code = ErrorType.MISC.ordinal();
@XStreamImplicit(itemFieldName="message")
private List<String> msgs = new ArrayList<String>();
public ErrorModel() {
//
}
public ErrorModel(String message) {
addMessage(message);
}
public void addMessage(String msg)
{
msgs.add(msg);
}
}
class RestException extends Exception {
public ErrorModel model = new ErrorModel();;
public RestException(ErrorType type, String[] messages) {
model.type = type.toString();
model.code = type.ordinal() + 1;
for (String message : messages) {
model.addMessage(message);
}
}
public RestException(ErrorType type, String message) {
model.type = type.toString();
model.code = type.ordinal() + 1;
model.addMessage(message);
}
}