NIHVIVO-646 Work on autocomplete in add authors to publication form
This commit is contained in:
parent
c1bdd477f0
commit
86394e3cf2
8 changed files with 479 additions and 12 deletions
|
@ -349,6 +349,11 @@ public class FreeMarkerHttpServlet extends VitroHttpServlet {
|
|||
writeTemplate(templateName, root);
|
||||
}
|
||||
|
||||
protected void ajaxWrite(String templateName, Map<String, Object> map) {
|
||||
templateName = "ajax/" + templateName;
|
||||
writeTemplate(templateName, map);
|
||||
}
|
||||
|
||||
protected void writeTemplate(String templateName, Map<String, Object> map) {
|
||||
StringWriter sw = mergeToTemplate(templateName, map);
|
||||
write(sw);
|
||||
|
|
|
@ -36,6 +36,19 @@ public class TestController extends FreeMarkerHttpServlet {
|
|||
Date now = cal.getTime();
|
||||
body.put("now", now);
|
||||
// In template: ${now?date}, ${now?datetime}, ${now?time}
|
||||
|
||||
// You can add to a collection AFTER putting it in the template data model
|
||||
List<String> fruit = new ArrayList<String>();
|
||||
fruit.add("apples");
|
||||
fruit.add("bananas");
|
||||
body.put("fruit", fruit);
|
||||
fruit.add("oranges");
|
||||
|
||||
// But you cannot modify a scalar after putting it in the data model - the
|
||||
// template still gets the old value
|
||||
String animal = "elephant";
|
||||
body.put("animal", animal);
|
||||
animal = "camel";
|
||||
|
||||
// Create the template to see the examples live.
|
||||
String bodyTemplate = "test.ftl";
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.webapp.search.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.lucene.analysis.Analyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.search.WildcardQuery;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.store.FSDirectory;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreeMarkerHttpServlet;
|
||||
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
|
||||
import edu.cornell.mannlib.vitro.webapp.flags.PortalFlag;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.SearchException;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.beans.Searcher;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.beans.VitroHighlighter;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.beans.VitroQuery;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.beans.VitroQueryFactory;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.lucene.Entity2LuceneDoc;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.lucene.LuceneIndexer;
|
||||
import edu.cornell.mannlib.vitro.webapp.search.lucene.LuceneSetup;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.FlagMathUtils;
|
||||
|
||||
/**
|
||||
* AutocompleteController is used to generate autocomplete and select element content
|
||||
* through a Lucene search. The search logic is copied from PagedSearchController.
|
||||
*/
|
||||
|
||||
/* rjy7 We should have a SearchController that is subclassed by both PagedSearchController
|
||||
* and AjaxSearchController, so the methods don't all have to be copied into both places.
|
||||
* The parent SearchController should extend FreeMarkerHttpServlet. Can only be done
|
||||
* once PagedSearchController has been moved to FreeMarker.
|
||||
*/
|
||||
public class AutocompleteController extends FreeMarkerHttpServlet implements Searcher{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Log log = LogFactory.getLog(AutocompleteController.class.getName());
|
||||
|
||||
private IndexSearcher searcher = null;
|
||||
String NORESULT_MSG = "";
|
||||
private String QUERY_PARAMETER_NAME = "term";
|
||||
private int defaultHitsPerPage = 25;
|
||||
private int defaultMaxSearchSize= 1000;
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
LuceneIndexer indexer=(LuceneIndexer)getServletContext()
|
||||
.getAttribute(LuceneIndexer.class.getName());
|
||||
indexer.addSearcher(this);
|
||||
|
||||
try{
|
||||
String indexDir = getIndexDir(getServletContext());
|
||||
getIndexSearcher(indexDir);
|
||||
}catch(Exception ex){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
doGet(request, response);
|
||||
}
|
||||
|
||||
public void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String templateName = request.getServletPath().equals("/autocomplete") ? "autocompleteResults.ftl" : "selectResults.ftl";
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
doSetup(request, response);
|
||||
|
||||
PortalFlag portalFlag = vreq.getPortalFlag();
|
||||
|
||||
// make sure an IndividualDao is available
|
||||
if( vreq.getWebappDaoFactory() == null
|
||||
|| vreq.getWebappDaoFactory().getIndividualDao() == null ){
|
||||
log.error("makeUsableBeans() could not get IndividualDao ");
|
||||
doSearchError(templateName, map);
|
||||
return;
|
||||
}
|
||||
IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao();
|
||||
|
||||
int maxHitSize = defaultMaxSearchSize;
|
||||
|
||||
String indexDir = getIndexDir(getServletContext());
|
||||
|
||||
String qtxt = vreq.getParameter(QUERY_PARAMETER_NAME);
|
||||
Analyzer analyzer = getAnalyzer(getServletContext());
|
||||
Query query = getQuery(vreq, portalFlag, analyzer, indexDir, qtxt);
|
||||
log.debug("query for '" + qtxt +"' is " + query.toString());
|
||||
|
||||
if (query == null ) {
|
||||
doNoQuery(templateName, map);
|
||||
return;
|
||||
}
|
||||
|
||||
IndexSearcher searcherForRequest = getIndexSearcher(indexDir);
|
||||
|
||||
TopDocs topDocs = null;
|
||||
try{
|
||||
topDocs = searcherForRequest.search(query,null,maxHitSize);
|
||||
}catch(Throwable t){
|
||||
log.error("in first pass at search: " + t);
|
||||
// this is a hack to deal with odd cases where search and index threads interact
|
||||
try{
|
||||
wait(150);
|
||||
topDocs = searcherForRequest.search(query,null,maxHitSize);
|
||||
}catch (Exception ex){
|
||||
log.error(ex);
|
||||
doFailedSearch(templateName, map);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if( topDocs == null || topDocs.scoreDocs == null){
|
||||
log.error("topDocs for a search was null");
|
||||
doFailedSearch(templateName, map);
|
||||
return;
|
||||
}
|
||||
|
||||
int hitsLength = topDocs.scoreDocs.length;
|
||||
if ( hitsLength < 1 ){
|
||||
doFailedSearch(templateName, map);
|
||||
return;
|
||||
}
|
||||
log.debug("found "+hitsLength+" hits");
|
||||
|
||||
List<SearchResult> results = new ArrayList<SearchResult>();
|
||||
for(int i=0; i<topDocs.scoreDocs.length ;i++){
|
||||
try{
|
||||
Document doc = searcherForRequest.doc(topDocs.scoreDocs[i].doc);
|
||||
String uri = doc.get(Entity2LuceneDoc.term.URI);
|
||||
Individual ind = iDao.getIndividualByURI(uri);
|
||||
if (ind != null) {
|
||||
String name = ind.getName();
|
||||
SearchResult result = new SearchResult(name, uri);
|
||||
results.add(result);
|
||||
}
|
||||
} catch(Exception e){
|
||||
log.error("problem getting usable Individuals from search " +
|
||||
"hits" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(results);
|
||||
map.put("results", results);
|
||||
ajaxWrite(templateName, map);
|
||||
|
||||
} catch (Throwable e) {
|
||||
log.error("AutocompleteController(): " + e);
|
||||
doSearchError(templateName, map);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private String getIndexDir(ServletContext servletContext) throws SearchException {
|
||||
Object obj = servletContext.getAttribute(LuceneSetup.INDEX_DIR);
|
||||
if( obj == null || !(obj instanceof String) )
|
||||
throw new SearchException("Could not get IndexDir for lucene index");
|
||||
else
|
||||
return (String)obj;
|
||||
}
|
||||
|
||||
private Analyzer getAnalyzer(ServletContext servletContext) throws SearchException {
|
||||
Object obj = servletContext.getAttribute(LuceneSetup.ANALYZER);
|
||||
if( obj == null || !(obj instanceof Analyzer) )
|
||||
throw new SearchException("Could not get anlyzer");
|
||||
else
|
||||
return (Analyzer)obj;
|
||||
}
|
||||
|
||||
private Query getQuery(VitroRequest request, PortalFlag portalState,
|
||||
Analyzer analyzer, String indexDir, String querystr ) throws SearchException{
|
||||
Query query = null;
|
||||
try{
|
||||
if( querystr == null){
|
||||
log.error("There was no Parameter '"+ QUERY_PARAMETER_NAME
|
||||
+"' in the request.");
|
||||
return null;
|
||||
}else if( querystr.length() > MAX_QUERY_LENGTH ){
|
||||
log.debug("The search was too long. The maximum " +
|
||||
"query length is " + MAX_QUERY_LENGTH );
|
||||
return null;
|
||||
}
|
||||
|
||||
// The way the analyzer is set up, name:Sm* returns no results,
|
||||
// but name:sm* does.
|
||||
querystr = querystr.toLowerCase();
|
||||
|
||||
{
|
||||
BooleanQuery boolQuery = new BooleanQuery();
|
||||
boolQuery.add(
|
||||
new WildcardQuery(new Term(Entity2LuceneDoc.term.NAME, querystr+'*')),
|
||||
BooleanClause.Occur.MUST);
|
||||
Object param = request.getParameter("type");
|
||||
boolQuery.add( new TermQuery(
|
||||
new Term(Entity2LuceneDoc.term.RDFTYPE,
|
||||
(String)param)),
|
||||
BooleanClause.Occur.MUST);
|
||||
query = boolQuery;
|
||||
}
|
||||
|
||||
//check if this is classgroup filtered
|
||||
// Object param = request.getParameter("classgroup");
|
||||
// if( param != null && !"".equals(param)){
|
||||
// BooleanQuery boolQuery = new BooleanQuery();
|
||||
// boolQuery.add( query, BooleanClause.Occur.MUST);
|
||||
// boolQuery.add( new TermQuery(
|
||||
// new Term(Entity2LuceneDoc.term.CLASSGROUP_URI,
|
||||
// (String)param)),
|
||||
// BooleanClause.Occur.MUST);
|
||||
// query = boolQuery;
|
||||
// }
|
||||
|
||||
//if we have a flag/portal query then we add
|
||||
//it by making a BooelanQuery.
|
||||
Query flagQuery = makeFlagQuery( portalState );
|
||||
if( flagQuery != null ){
|
||||
BooleanQuery boolQuery = new BooleanQuery();
|
||||
boolQuery.add( query, BooleanClause.Occur.MUST);
|
||||
boolQuery.add( flagQuery, BooleanClause.Occur.MUST);
|
||||
query = boolQuery;
|
||||
}
|
||||
|
||||
|
||||
}catch (Exception ex){
|
||||
throw new SearchException(ex.getMessage());
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a flag based query clause. This is where searches can filtered
|
||||
* by portal.
|
||||
*
|
||||
* If you think that search is not working correctly with protals and
|
||||
* all that kruft then this is a method you want to look at.
|
||||
*
|
||||
* It only takes into account "the portal flag" and flag1Exclusive must
|
||||
* be set. Where does that stuff get set? Look in vitro.flags.PortalFlag
|
||||
*
|
||||
* One thing to keep in mind with portal filtering and search is that if
|
||||
* you want to search a portal that is different then the portal the user
|
||||
* is 'in' then the home parameter should be set to force the user into
|
||||
* the new portal.
|
||||
*
|
||||
* Ex. Bob requests the search page for vivo in portal 3. You want to
|
||||
* have a drop down menu so bob can search the all CALS protal, id 60.
|
||||
* You need to have a home=60 on your search form. If you don't set
|
||||
* home=60 with your search query, then the search will not be in the
|
||||
* all portal AND the WebappDaoFactory will be filtered to only show
|
||||
* things in portal 3.
|
||||
*
|
||||
* Notice: flag1 as a parameter is ignored. bdc34 2009-05-22.
|
||||
*/
|
||||
@SuppressWarnings("static-access")
|
||||
private Query makeFlagQuery( PortalFlag flag){
|
||||
if( flag == null || !flag.isFilteringActive()
|
||||
|| flag.getFlag1DisplayStatus() == flag.SHOW_ALL_PORTALS )
|
||||
return null;
|
||||
|
||||
// make one term for each bit in the numeric flag that is set
|
||||
Collection<TermQuery> terms = new LinkedList<TermQuery>();
|
||||
int portalNumericId = flag.getFlag1Numeric();
|
||||
Long[] bits = FlagMathUtils.numeric2numerics(portalNumericId);
|
||||
for (Long bit : bits) {
|
||||
terms.add(new TermQuery(new Term(Entity2LuceneDoc.term.PORTAL, Long
|
||||
.toString(bit))));
|
||||
}
|
||||
|
||||
// make a boolean OR query for all of those terms
|
||||
BooleanQuery boolQuery = new BooleanQuery();
|
||||
if (terms.size() > 0) {
|
||||
for (TermQuery term : terms) {
|
||||
boolQuery.add(term, BooleanClause.Occur.SHOULD);
|
||||
}
|
||||
return boolQuery;
|
||||
} else {
|
||||
//we have no flags set, so no flag filtering
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized IndexSearcher getIndexSearcher(String indexDir) {
|
||||
if( searcher == null ){
|
||||
try {
|
||||
Directory fsDir = FSDirectory.getDirectory(indexDir);
|
||||
searcher = new IndexSearcher(fsDir);
|
||||
} catch (IOException e) {
|
||||
log.error("LuceneSearcher: could not make indexSearcher "+e);
|
||||
log.error("It is likely that you have not made a directory for the lucene index. "+
|
||||
"Create the directory indicated in the error and set permissions/ownership so"+
|
||||
" that the tomcat server can read/write to it.");
|
||||
//The index directory is created by LuceneIndexer.makeNewIndex()
|
||||
}
|
||||
}
|
||||
return searcher;
|
||||
}
|
||||
|
||||
|
||||
private void doNoQuery(String templateName, Map<String, Object> map) {
|
||||
ajaxWrite(templateName, map);
|
||||
}
|
||||
|
||||
private void doFailedSearch(String templateName, Map<String, Object> map) {
|
||||
ajaxWrite(templateName, map);
|
||||
}
|
||||
|
||||
private void doSearchError(String templateName, Map<String, Object> map) {
|
||||
ajaxWrite(templateName, map);
|
||||
}
|
||||
|
||||
public static final int MAX_QUERY_LENGTH = 500;
|
||||
|
||||
public class SearchResult implements Comparable<Object> {
|
||||
private String label;
|
||||
private String uri;
|
||||
|
||||
SearchResult(String label, String value) {
|
||||
this.label = label;
|
||||
this.uri = value;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return "{ \"label\": \"" + label + "\", " + "\"uri\": \"" + uri + "\" }";
|
||||
}
|
||||
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
if ( !(o instanceof SearchResult) ) {
|
||||
throw new ClassCastException("Error in SearchResult.compareTo(): expected SearchResult object.");
|
||||
}
|
||||
SearchResult sr = (SearchResult) o;
|
||||
return label.compareTo(sr.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Need to accept notification from indexer that the index has been changed.
|
||||
*/
|
||||
public void close() {
|
||||
searcher = null;
|
||||
}
|
||||
|
||||
public VitroHighlighter getHighlighter(VitroQuery q) {
|
||||
throw new Error("PagedSearchController.getHighlighter() is unimplemented");
|
||||
}
|
||||
|
||||
public VitroQueryFactory getQueryFactory() {
|
||||
throw new Error("PagedSearchController.getQueryFactory() is unimplemented");
|
||||
}
|
||||
|
||||
public List search(VitroQuery query) throws SearchException {
|
||||
throw new Error("PagedSearchController.search() is unimplemented");
|
||||
}
|
||||
|
||||
}
|
|
@ -75,7 +75,7 @@ import edu.cornell.mannlib.vitro.webapp.utils.Html2Text;
|
|||
|
||||
/**
|
||||
* PagedSearchController is the new search controller that interacts
|
||||
* directly with the lucene API and returns paged, relivance ranked results.
|
||||
* directly with the lucene API and returns paged, relevance ranked results.
|
||||
*
|
||||
* @author bdc34
|
||||
*
|
||||
|
@ -114,7 +114,7 @@ public class PagedSearchController extends VitroHttpServlet implements Searcher{
|
|||
Portal portal = vreq.getPortal();
|
||||
PortalFlag portalFlag = vreq.getPortalFlag();
|
||||
|
||||
//make sure a IndividualDao is available
|
||||
//make sure an IndividualDao is available
|
||||
if( vreq.getWebappDaoFactory() == null
|
||||
|| vreq.getWebappDaoFactory().getIndividualDao() == null ){
|
||||
log.error("makeUsableBeans() could not get IndividualDao ");
|
||||
|
@ -152,7 +152,7 @@ public class PagedSearchController extends VitroHttpServlet implements Searcher{
|
|||
|
||||
String qtxt = vreq.getParameter(VitroQuery.QUERY_PARAMETER_NAME);
|
||||
Analyzer analyzer = getAnalyzer(getServletContext());
|
||||
Query query = getQuery(vreq, portalFlag, analyzer, indexDir);
|
||||
Query query = getQuery(vreq, portalFlag, analyzer, indexDir, qtxt);
|
||||
log.debug("query for '" + qtxt +"' is " + query.toString());
|
||||
|
||||
if (query == null ) {
|
||||
|
@ -428,10 +428,10 @@ public class PagedSearchController extends VitroHttpServlet implements Searcher{
|
|||
}
|
||||
|
||||
private Query getQuery(VitroRequest request, PortalFlag portalState,
|
||||
Analyzer analyzer, String indexDir ) throws SearchException{
|
||||
Analyzer analyzer, String indexDir, String querystr ) throws SearchException{
|
||||
Query query = null;
|
||||
try{
|
||||
String querystr = request.getParameter(VitroQuery.QUERY_PARAMETER_NAME);
|
||||
//String querystr = request.getParameter(VitroQuery.QUERY_PARAMETER_NAME);
|
||||
if( querystr == null){
|
||||
log.error("There was no Parameter '"+VitroQuery.QUERY_PARAMETER_NAME
|
||||
+"' in the request.");
|
||||
|
@ -487,6 +487,9 @@ public class PagedSearchController extends VitroHttpServlet implements Searcher{
|
|||
boolQuery.add( flagQuery, BooleanClause.Occur.MUST);
|
||||
query = boolQuery;
|
||||
}
|
||||
|
||||
log.debug("Query: " + query);
|
||||
|
||||
}catch (Exception ex){
|
||||
throw new SearchException(ex.getMessage());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue