/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package uk.ac.ebi.ep.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
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.bind.annotation.ResponseBody;
import uk.ac.ebi.ep.base.search.EnzymeFinder;
import uk.ac.ebi.ep.common.Field;
import uk.ac.ebi.ep.data.domain.UniprotEntry;
import uk.ac.ebi.ep.data.enzyme.model.ChemicalEntity;
import uk.ac.ebi.ep.data.enzyme.model.CountableMolecules;
import uk.ac.ebi.ep.data.enzyme.model.Enzyme;
import uk.ac.ebi.ep.data.enzyme.model.EnzymeModel;
import uk.ac.ebi.ep.data.enzyme.model.EnzymeReaction;
import uk.ac.ebi.ep.data.enzyme.model.Molecule;
import uk.ac.ebi.ep.data.enzyme.model.ProteinStructure;
import uk.ac.ebi.ep.data.enzyme.model.ReactionPathway;
import uk.ac.ebi.ep.data.exceptions.EnzymeFinderException;
import uk.ac.ebi.ep.data.exceptions.EnzymeRetrieverException;
import uk.ac.ebi.ep.data.search.model.Disease;
import uk.ac.ebi.ep.data.search.model.SearchModel;
import uk.ac.ebi.ep.data.search.model.SearchParams;
import uk.ac.ebi.ep.data.search.model.SearchResults;
import uk.ac.ebi.ep.ebeye.autocomplete.Suggestion;
import uk.ac.ebi.ep.functions.Functions;
import uk.ac.ebi.ep.functions.HtmlUtility;
import uk.ac.ebi.ep.web.utils.KeywordType;
import uk.ac.ebi.xchars.SpecialCharacters;
import uk.ac.ebi.xchars.domain.EncodingType;
/**
*
* @author joseph
*/
@Controller
public class SearchController extends AbstractController {
private static final Logger logger = Logger.getLogger(SearchController.class);
private static final String ENZYME_MODEL = "enzymeModel";
private static final String ERROR = "error";
private static final int ASSOCIATED_PROTEIN_LIMIT = 8_00;
private final Integer maxCitations = 50;
private enum ResponsePage {
ENTRY, ERROR;
@Override
public String toString() {
return name().toLowerCase();
}
}
private boolean startsWithDigit(String data) {
return Character.isDigit(data.charAt(0));
}
/**
* Process the entry page,
*
* @param accession The UniProt accession of the enzyme.
* @param field the requested tab.
* @param model
* @param session
* @return
*/
@RequestMapping(value = "/search/{accession}/{field}", method = RequestMethod.GET)
protected String getEnzymeModel(Model model,
@PathVariable String accession, @PathVariable String field,
HttpSession session) {
Field requestedField = Field.valueOf(field.toUpperCase());
enzymeRetriever.setEnzymePortalService(enzymePortalService);
enzymeRetriever.setLiteratureService(literatureService);
enzymeRetriever.setIntenzAdapter(intenzAdapter);
EnzymeModel enzymeModel = null;
String responsePage = ResponsePage.ENTRY.toString();
try {
switch (requestedField) {
case PROTEINSTRUCTURE:
enzymeModel = enzymeRetriever.getProteinStructure(accession);
break;
case REACTIONSPATHWAYS:
enzymeRetriever.setRheaAdapter(rheaAdapter);
enzymeModel = enzymeRetriever.getReactionsPathways(accession);
break;
case MOLECULES:
enzymeRetriever.setChebiAdapter(chebiAdapter);
enzymeModel = enzymeRetriever.getMolecules(accession);
break;
case DISEASEDRUGS:
enzymeModel = enzymeRetriever.getDiseases(accession);
break;
case LITERATURE:
enzymeModel = enzymeRetriever.getLiterature(accession, maxCitations);
model.addAttribute("maxCitations", maxCitations);
break;
default:
enzymeModel = enzymeRetriever.getEnzyme(accession);
requestedField = Field.ENZYME;
break;
}
if (enzymeModel != null) {
UniprotEntry entry = enzymePortalService.findByAccession(accession);
// If we got here from a bookmark, the summary might not be cached:
String summId = Functions.getSummaryBasketId(entry);
@SuppressWarnings("unchecked")
final Map<String, UniprotEntry> sls = (Map<String, UniprotEntry>) session.getAttribute(Attribute.lastSummaries.name());
if (sls == null) {
setLastSummaries(session, Collections.singletonList(
entry));
} else if (sls.get(summId) == null) {
sls.put(summId, entry);
}
enzymeModel.setRequestedfield(requestedField.name().toLowerCase());
model.addAttribute(ENZYME_MODEL, enzymeModel);
model.addAttribute(ENTRY_VIDEO, ENTRY_VIDEO);
addToHistory(session, accession);
}
} catch (EnzymeRetrieverException ex) {
// FIXME: this is an odd job to signal an error for the JSP!
logger.error("Unable to retrieve the entry!", ex);
if (requestedField.getName().equalsIgnoreCase(Field.DISEASEDRUGS.getName())) {
enzymeModel = new EnzymeModel();
enzymeModel.setName("Diseases");
enzymeModel.setRequestedfield(requestedField.name());
Disease d = new Disease();
d.setName(ERROR);
enzymeModel.getDisease().add(0, d);
model.addAttribute("enzymeModel", enzymeModel);
logger.error("Error in retrieving Disease Information");
}
if (requestedField.getName().equalsIgnoreCase(Field.MOLECULES.getName())) {
enzymeModel = new EnzymeModel();
enzymeModel.setRequestedfield(requestedField.getName());
enzymeModel.setName("Small Molecues");
Molecule molecule = new Molecule();
molecule.setName(ERROR);
ChemicalEntity chemicalEntity = new ChemicalEntity();
chemicalEntity.setDrugs(new CountableMolecules());
chemicalEntity.getDrugs().setMolecule(new ArrayList<>());
chemicalEntity.getDrugs().getMolecule().add(molecule);
enzymeModel.setMolecule(chemicalEntity);
model.addAttribute(ENZYME_MODEL, enzymeModel);
logger.error("Error in retrieving Molecules Information");
}
if (requestedField.getName().equalsIgnoreCase(Field.ENZYME.getName())) {
enzymeModel = new EnzymeModel();
enzymeModel.setRequestedfield(requestedField.getName());
enzymeModel.setName("Enzymes");
Enzyme enzyme = new Enzyme();
enzyme.getEnzymetype().add(0, ERROR);
enzymeModel.setEnzyme(enzyme);
model.addAttribute(ENZYME_MODEL, enzymeModel);
logger.error("Error in retrieving Enzymes");
}
if (requestedField.getName().equalsIgnoreCase(Field.PROTEINSTRUCTURE.getName())) {
enzymeModel = new EnzymeModel();
enzymeModel.setRequestedfield(requestedField.getName());
enzymeModel.setName("Protein Structures");
ProteinStructure structure = new ProteinStructure();
structure.setName(ERROR);
enzymeModel.getProteinstructure().add(0, structure);
model.addAttribute(ENZYME_MODEL, enzymeModel);
logger.error("Error in retrieving ProteinStructure");
}
if (requestedField.getName().equalsIgnoreCase(Field.REACTIONSPATHWAYS.getName())) {
enzymeModel = new EnzymeModel();
enzymeModel.setRequestedfield(requestedField.getName());
enzymeModel.setName("Reactions and Pathways");
ReactionPathway pathway = new ReactionPathway();
EnzymeReaction reaction = new EnzymeReaction();
reaction.setName(ERROR);
pathway.setReaction(reaction);
enzymeModel.getReactionpathway().add(0, pathway);
model.addAttribute(ENZYME_MODEL, enzymeModel);
logger.error("Error in retrieving Reaction Pathways");
}
if (requestedField.getName().equalsIgnoreCase(Field.LITERATURE.getName())) {
enzymeModel = new EnzymeModel();
enzymeModel.setRequestedfield(requestedField.getName());
enzymeModel.setName("Literatures");
enzymeModel.getLiterature().add(0, ERROR);
model.addAttribute(ENZYME_MODEL, enzymeModel);
logger.error("Error in retrieving Literature Information");
}
}
return responsePage;
}
/**
* This method is an entry point that accepts the request when the search
* home page is loaded. It then forwards the request to the search page.
*
* @param model
* @param session
* @return
*/
@RequestMapping(value = "/")
public String viewSearchHome(Model model, HttpSession session) {
SearchModel searchModelForm = new SearchModel();
SearchParams searchParams = new SearchParams();
searchParams.setStart(0);
searchModelForm.setSearchparams(searchParams);
model.addAttribute("searchModel", searchModelForm);
model.addAttribute(HOME_VIDEO, HOME_VIDEO);
clearHistory(session);
return "index";
}
@ModelAttribute("/about")
public SearchModel getabout(Model model) {
SearchModel searchModelForm = searchform();
model.addAttribute("searchModel", searchModelForm);
model.addAttribute(HOME_VIDEO, HOME_VIDEO);
return new SearchModel();
}
@RequestMapping(value = "/faq")
public SearchModel getfaq(Model model) {
SearchModel searchModelForm = searchform();
model.addAttribute("searchModel", searchModelForm);
model.addAttribute(HOME_VIDEO, HOME_VIDEO);
return searchModelForm;
}
@RequestMapping(value = "/search")
public SearchModel getSearch(Model model) {
SearchModel searchModelForm = searchform();
model.addAttribute("searchModel", searchModelForm);
model.addAttribute(SEARCH_VIDEO, SEARCH_VIDEO);
return searchModelForm;
}
/**
* Processes the search request. When user enters a search text and presses
* the submit button the request is processed here.
*
* @param searchModel
* @param model
* @param searchTerm
* @param session
* @param searchId
* @param keywordType
* @param ec
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/search", method = RequestMethod.POST)
public String postSearchResult(SearchModel searchModel, Model model, @RequestParam(required = false, value = "searchTerm") String searchTerm,
@RequestParam(required = false, value = "ec") String ec, @RequestParam(required = false, value = "searchId") String searchId, @RequestParam(required = false, value = "keywordType") String keywordType,
HttpSession session, HttpServletRequest request, HttpServletResponse response) {
String view = "error";
String searchKey = null;
SearchResults results = null;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
try {
// See if it is already there, perhaps we are paginating:
Map<String, SearchResults> prevSearches
= getPreviousSearches(session.getServletContext());
String modelSearchKey = getSearchKey(searchModel.getSearchparams());
searchKey = Jsoup.clean(modelSearchKey, Whitelist.basic());
results = prevSearches.get(searchKey);
if (results == null) {
// New search:
clearHistory(session);
switch (searchModel.getSearchparams().getType()) {
case KEYWORD:
//results = searchKeyword(searchModel.getSearchparams());
results = searchKeyword(ec, searchTerm, searchId, keywordType, ASSOCIATED_PROTEIN_LIMIT);
model.addAttribute(SEARCH_VIDEO, SEARCH_VIDEO);
//LOGGER.warn("keyword search=" + searchModel.getSearchparams().getText());
break;
case SEQUENCE:
//view = searchSequence(model, searchModel);
model.addAttribute(SEQUENCE_VIDEO, SEQUENCE_VIDEO);
break;
case COMPOUND:
results = searchCompound(model, searchModel);
break;
default:
}
}
if (results != null) { // something to show
cacheSearch(session.getServletContext(), searchKey, results);
setLastSummaries(session, results.getSummaryentries());
searchModel.setSearchresults(results);
applyFilters(searchModel, request);
model.addAttribute("searchConfig", searchConfig);
model.addAttribute("searchModel", searchModel);
model.addAttribute("pagination", getPagination(searchModel));
request.setAttribute("searchTerm", searchModel.getSearchparams().getText());
clearHistory(session);
addToHistory(session, searchModel.getSearchparams().getType(),
searchKey, searchId, keywordType);
view = "search";
}
} catch (Exception e) {
logger.error("one of the search params (Text or Sequence is :" + searchKey, e);
}
return view;
}
// @Override
// protected void clearHistory(HttpSession session) {
// @SuppressWarnings("unchecked")
// List<String> history = (LinkedList<String>) session.getAttribute(Attribute.history.getName());
// if (history == null) {
// history = new LinkedList<>();
// session.setAttribute(Attribute.history.getName(), history);
// } else {
// history.clear();
// }
// }
//
// @Override
// protected void addToHistory(HttpSession session, String s) {
// @SuppressWarnings("unchecked")
// List<String> history = (LinkedList<String>) session.getAttribute(Attribute.history.getName());
// if (history == null) {
// history = new LinkedList<>();
// session.setAttribute(Attribute.history.getName(), history);
// }
// if (history.isEmpty() || !history.get(history.size() - 1).equals(s)) {
// String cleanedText = HtmlUtility.cleanText(s);
// history.add(cleanedText);
// }
// }
//
// /**
// * Adds a search to the user history. The history item (String) actually
// * stored depends on the type of search, so that the links can be re-created
// * in the web page properly (see <code>breadcrumbs.jsp</code>).
// *
// * @param session the user session.
// * @param searchType the search type.
// * @param s the text to be added to history.
// */
// @Override
// protected void addToHistory(HttpSession session, SearchParams.SearchType searchType, String s) {
// switch (searchType) {
// case KEYWORD:
// addToHistory(session, "searchparams.text=" + s);
// break;
// case COMPOUND:
// addToHistory(session,
// "searchparams.type=COMPOUND&searchparams.text=" + s);
// break;
// case SEQUENCE:
// addToHistory(session, "searchparams.sequence=" + s);
// break;
// default:
// addToHistory(session, "searchparams.text=" + s);
//
// }
// }
/**
* Retrieves any previous searches stored in the application context.
*
* @param servletContext the application context.
* @return a map of searches to results.
*/
@SuppressWarnings("unchecked")
@Override
protected Map<String, SearchResults> getPreviousSearches(
ServletContext servletContext) {
Map<String, SearchResults> prevSearches = (Map<String, SearchResults>) servletContext.getAttribute(Attribute.prevSearches.name());
if (prevSearches == null) {
// Map implementation which maintains the order of access:
prevSearches = Collections.synchronizedMap(
new LinkedHashMap<String, SearchResults>(
searchConfig.getSearchCacheSize(), 1, true));
servletContext.setAttribute(Attribute.prevSearches.getName(),
prevSearches);
}
return prevSearches;
}
/**
* Processes a string to normalise it to use as a key in the application
* cache.<br> Note that the key for a ChEBI ID depends on the type of
* search: if a keyword search, the prefix will be lowercase (
* <code>chebi:</code>); if a compound structure search, the prefix will be
* uppercase ( <code>CHEBI:</code>).
*
* @param searchParams the search parameters, including the original search
* text from the user.
* @return A normalised string.
*/
@Override
protected String getSearchKey(SearchParams searchParams) {
String key = null;
switch (searchParams.getType()) {
case KEYWORD:
key = searchParams.getText().trim().toLowerCase();
break;
case SEQUENCE:
key = searchParams.getSequence().trim().toUpperCase()
.replaceAll("[\n\r]", "");
break;
case COMPOUND:
key = searchParams.getText().trim().toUpperCase();
break;
default:
key = searchParams.getText().trim().toLowerCase();
}
return key;
}
/**
* Searches by keyword.
*
* @param searchParameters the search parameters.
* @return the search results.
*/
@Override
protected SearchResults searchKeyword(SearchParams searchParameters) {
EnzymeFinder finder = new EnzymeFinder(enzymePortalService, ebeyeRestService);
SearchResults results = finder.getEnzymes(searchParameters);
return results;
}
/**
*
* @param ec the EC
* @param keyword this could be users search term
* @param searchId OMIM number, REACTOME id
* @param keywordType could be KEYWORD, DISEASE, PATHWAYS
* @param limit number of associated proteins to find
* @return searchResults
*/
protected SearchResults searchKeyword(String ec, String keyword, String searchId, String keywordType, int limit) {
String searchTerm = HtmlUtility.cleanText(keyword);
searchTerm = searchTerm.replaceAll(""", "");
SearchResults results = null;
EnzymeFinder finder = new EnzymeFinder(enzymePortalService, ebeyeRestService);
if (keywordType.equalsIgnoreCase(KeywordType.KEYWORD.name())) {
results = finder.getAssociatedProteinsByEcAndFulltextSearch(ec, searchTerm, limit);
}
if (keywordType.equalsIgnoreCase(KeywordType.DISEASE.name())) {
String omimId = searchId;
results = finder.getAssociatedProteinsByOmimIdAndEc(omimId, ec, limit);
}
if (keywordType.equalsIgnoreCase(KeywordType.EC.name())) {
results = finder.getAssociatedProteinsByEc(ec, limit);
}
if (keywordType.equalsIgnoreCase(KeywordType.TAXONOMY.name())) {
String taxId = searchId;
results = finder.getAssociatedProteinsByTaxIdAndEc(taxId, ec, limit);
}
if (keywordType.equalsIgnoreCase(KeywordType.PATHWAYS.name())) {
String pathwayId = searchId;
results = finder.getAssociatedProteinsByPathwayIdAndEc(pathwayId, ec, limit);
}
return results;
}
/**
* Uses a finder to search by compound ID.
*
* @param model the view model.
* @param searchModel the search model, including a compound ID as the
* <code>text</code> parameter.
* @return the search results, or <code>null</code> if nothing found.
* @since 1.0.27
*/
private SearchResults searchCompound(Model model, SearchModel searchModel) {
SearchResults results = null;
EnzymeFinder finder = null;
try {
finder = new EnzymeFinder(enzymePortalService, ebeyeRestService);
results = finder.getEnzymesByCompound(searchModel.getSearchparams());
searchModel.setSearchresults(results);
model.addAttribute("searchModel", searchModel);
model.addAttribute("pagination", getPagination(searchModel));
model.addAttribute("searchConfig", searchConfig);
} catch (EnzymeFinderException e) {
logger.error("Unable to get enzymes by compound", e);
}
return results;
}
/**
* A wrapper of {@code postSearchResult} method, created to accept the
* search request using GET.
*
* @param searchModel
* @param result
* @param model
* @param session
* @param request
* @param response
* @return
*/
// @RequestMapping(value = "/search", method = RequestMethod.GET)
// public String getSearchResults(SearchModel searchModel, BindingResult result,
// Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response) {
// return postSearchResult(searchModel, model, session, request, response);
// }
@RequestMapping(value = "/advanceSearch",
method = RequestMethod.GET)
public String getAdvanceSearch(Model model) {
//model.addAttribute("chebiConfig", chebiConfig);
model.addAttribute(SEQUENCE_VIDEO, SEQUENCE_VIDEO);
return "advanceSearch";
}
@ResponseBody
@RequestMapping("/service/search")
public List<Suggestion> enzymesAutocompleteSearch(@RequestParam(value = "name", required = true) String name) {
if (name != null && name.length() >= 3) {
String keyword = String.format("%%%s%%", name);
String cleanedKeyword = Jsoup.clean(keyword, Whitelist.basic());
List<Suggestion> suggestions = ebeyeSuggestionService.autocompleteSearch(cleanedKeyword.trim());
if (suggestions != null && !suggestions.isEmpty()) {
return suggestions.stream().distinct().collect(Collectors.toList());
} else {
return new ArrayList<>();
}
}
return new ArrayList<>();
}
public String resolveSpecialCharacters(String data) {
SpecialCharacters xchars = SpecialCharacters.getInstance(null);
EncodingType[] encodings = {
EncodingType.CHEBI_CODE,
EncodingType.COMPOSED,
EncodingType.EXTENDED_HTML,
EncodingType.GIF,
EncodingType.HTML,
EncodingType.HTML_CODE,
EncodingType.JPG,
EncodingType.SWISSPROT_CODE,
EncodingType.UNICODE
};
if (!xchars.validate(data)) {
logger.warn("SPECIAL CHARACTER PARSING ERROR : This is not a valid xchars string!" + data);
}
logger.info("available encodings " + Arrays.toString(encodings));
return xchars.xml2Display(data, EncodingType.CHEBI_CODE);
}
@RequestMapping(value = "/underconstruction", method = RequestMethod.GET)
public String getSearchResult(Model model) {
return "underconstruction";
}
@RequestMapping(value = "/about", method = RequestMethod.GET)
public String getAbout(Model model) {
model.addAttribute(HOME_VIDEO, HOME_VIDEO);
return "about";
}
}