package org.sakaiproject.search.elasticsearch;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.action.admin.indices.status.IndexStatus;
import org.elasticsearch.action.admin.indices.status.IndicesStatusRequest;
import org.elasticsearch.action.admin.indices.status.IndicesStatusResponse;
import org.elasticsearch.action.count.CountResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.OrFilterBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.node.Node;
import org.elasticsearch.search.SearchHit;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.event.api.NotificationEdit;
import org.sakaiproject.event.api.NotificationService;
import org.sakaiproject.search.api.*;
import org.sakaiproject.search.elasticsearch.filter.SearchItemFilter;
import org.sakaiproject.search.model.SearchBuilderItem;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.util.*;
import static org.sakaiproject.search.elasticsearch.ElasticSearchIndexBuilder.getFieldFromSearchHit;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.index.query.FilterBuilders.*;
import static org.elasticsearch.index.query.FilterBuilders.termsFilter;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.node.NodeBuilder.nodeBuilder;
import static org.elasticsearch.search.facet.FacetBuilders.*;
/**
* <p>Drop in replacement for sakai's legacy search service which uses {@link <a href="http://elasticsearch.org">elasticsearch</a>}.
* This service runs embedded in Sakai it does not require a standalone search server. Like the legacy search service
* docs are indexed by notification sent via the event service. This handoff simply puts the reference and associated
* metadata into ES. A timer task runs and looks for docs that do not have content, and then digests the content
* and stores that in the ES index. This task is necessary to off load this work from any user initiated thread so in the
* case of large files we aren't holding on to user threads for log periods of time. </p>
* <p/>
* <p>Any elasticsearch properties are automatically fed to initialization by looking for properties that start with
* "elasticsearch." For example to enable the REST access set the following in sakai.properties:
* <p/>
* <pre>
* elasticsearch.http.enabled=true
* elasticsearch.http.port=9201
* </pre>
* <p/>
* </p>
*/
public class ElasticSearchService implements SearchService {
private static final Log log = LogFactory.getLog(ElasticSearchService.class);
/* constant config */
public static final String CONFIG_PROPERTY_PREFIX = "elasticsearch.";
public final static String SAKAI_DOC_TYPE = "sakai_doc";
public static final String FACET_NAME = "tag";
/* ElasticSearch handles */
private Node node;
private Client client;
/* injected dependencies */
private NotificationEdit notification;
private List<String> triggerFunctions;
private NotificationService notificationService;
private EventTrackingService eventTrackingService;
private ServerConfigurationService serverConfigurationService;
private ElasticSearchIndexBuilder indexBuilder;
private SiteService siteService;
private boolean localNode = false;
private boolean useSiteFilters = false;
/**
* This property is ignored at the present time it here to preserve backwards capatability.
*
* TODO We could interpret this to mean whether or not this node will hold data indices
* and shards be allocated to it. So setting this to false for this node to only be a search client.
* That would take some rework to assure nodes don't attempt indexing work that will fail.
*/
private boolean searchServer = true;
private boolean useFacetting = true;
private boolean useSuggestions = true;
/**
* dependency
*/
private UserDirectoryService userDirectoryService;
/**
* dependency
*/
private SessionManager sessionManager;
/* injectable configuration */
/**
* The ES node name for this server. Defaults to the serverId config property
*/
private String nodeName;
/**
* The ES cluster name. Defaults to the serverName config property
*/
private String clusterName;
/**
* set to true to force an index rebuild at startup time, defaults to false. This is probably something
* you never want to use, other than in development or testing
*/
private boolean rebuildIndexOnStartup = false;
/**
* the ES indexname defaults 'sakai_index'
*/
private String indexName = "sakai_index";
/**
* max number of suggestions to return when looking for suggestions (this populates the autocomplete drop down in the UI)
*/
private int maxNumberOfSuggestions = 10;
/**
* N most frequent terms
*/
private int facetTermSize = 10;
/**
* used in searchXML() to maintain backwards compatibility
*/
private String sharedKey = null;
private SearchItemFilter filter;
/**
* Register a notification action to listen to events and modify the search
* index
*/
public void init() {
if (!isEnabled()) {
log.info("ElasticSearch is not enabled. Set search.enable=true to change that.");
return;
}
log.info("Initializing ElasticSearch...");
initializeElasticSearch();
log.debug("Register a notification to trigger indexation on new elements");
// register a transient notification for resources
notification = notificationService.addTransientNotification();
// add all the functions that are registered to trigger search index modification
notification.setFunction(SearchService.EVENT_TRIGGER_SEARCH);
for (String function : triggerFunctions) {
notification.addFunction(function);
}
// set the filter to any site related resource
notification.setResourceFilter("/");
// set the action
notification.setAction(new SearchNotificationAction(indexBuilder));
}
protected void initializeElasticSearch() {
Map properties = new HashMap<String, String>();
// load anything set into the ServerConfigurationService that starts with "elasticsearch."
for (ServerConfigurationService.ConfigItem configItem : serverConfigurationService.getConfigData().getItems()) {
if (configItem.getName().startsWith(ElasticSearchService.CONFIG_PROPERTY_PREFIX + "index.")){
continue;
}
if (configItem.getName().startsWith(CONFIG_PROPERTY_PREFIX)) {
properties.put(configItem.getName().replaceFirst(CONFIG_PROPERTY_PREFIX, ""), configItem.getValue());
}
}
// these properties are required at an minimum, assure they are set to reasonable defaults.
if (!properties.containsKey("node.name")) {
properties.put("node.name", serverConfigurationService.getServerId());
}
if (!properties.containsKey("cluster.name")) {
properties.put("cluster.name", serverConfigurationService.getServerName());
}
// ES calls need these, store these away
setNodeName((String) properties.get("node.name"));
setClusterName((String) properties.get("cluster.name"));
if (!properties.containsKey("path.data")) {
properties.put("path.data", serverConfigurationService.getSakaiHomePath() + "/elasticsearch/" + getNodeName());
}
log.info("Setting ElasticSearch storage area to: " + properties.get("path.data"));
// initialize elasticsearch
ImmutableSettings.Builder settings = settingsBuilder().put(properties);
node = nodeBuilder()
.settings(settings)
.data(true).local(localNode).node();
client = node.client();
// initialized indexBuilder with references it needs
indexBuilder.setClient(client);
indexBuilder.setIndexName(indexName);
// init index and kick off rebuild if necessary
if (rebuildIndexOnStartup) {
indexBuilder.rebuildIndex();
} else {
indexBuilder.assureIndex();
}
}
public SearchResponse search(String searchTerms, List<String> siteIds, int start, int end, List<String> references) throws InvalidSearchQueryException {
return search(searchTerms, siteIds, start, end, null, null, references);
}
public SearchResponse search(String searchTerms, List<String> siteIds, int start, int end, String filterName, String sorterName, List<String> references) throws InvalidSearchQueryException {
if (references == null) {
references = new ArrayList();
}
if (siteIds == null) {
siteIds = new ArrayList();
}
BoolQueryBuilder query = boolQuery();
if (searchTerms.contains(":")) {
String[] termWithType = searchTerms.split(":");
String termType = termWithType[0];
String termValue = termWithType[1];
// little fragile but seems like most providers follow this convention, there isn't a nice way to get the type
// without a handle to a reference.
query.must(termQuery(SearchService.FIELD_TYPE, "sakai:" + termType));
query.must(matchQuery(SearchService.FIELD_CONTENTS, termValue));
} else {
query.must(matchQuery(SearchService.FIELD_CONTENTS, searchTerms));
}
if (references.size() > 0){
query.must(termsQuery(SearchService.FIELD_REFERENCE, references.toArray(new String[references.size()])));
}
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(indexName)
.setSearchType(SearchType.QUERY_THEN_FETCH)
.setQuery(query)
.setTypes(SAKAI_DOC_TYPE)
.addFields(SearchService.FIELD_REFERENCE, SearchService.FIELD_SITEID,
SearchService.FIELD_TITLE, SearchService.FIELD_URL, SearchService.FIELD_TYPE, SearchService.FIELD_TOOL)
.setFrom(start).setSize(end - start);
if(useFacetting) {
searchRequestBuilder.addFacet(termsFacet(FACET_NAME).field("contents.lowercase").size(facetTermSize));
}
// if we have sites filter results to include only the sites included
if (siteIds.size() > 0) {
searchRequestBuilder.setRouting(siteIds.toArray(new String[siteIds.size()]));
// creating config whether or not to use filter, there are performance and caching differences that
// maybe implementation decisions
if (useSiteFilters) {
OrFilterBuilder siteFilter = orFilter().add(
termsFilter(SearchService.FIELD_SITEID, siteIds.toArray(new String[siteIds.size()])).execution("bool"));
searchRequestBuilder.setFilter(siteFilter);
} else {
query.must(termsQuery(SearchService.FIELD_SITEID, siteIds.toArray(new String[siteIds.size()])));
}
}
log.debug("search request: " + searchRequestBuilder.toString());
SearchResponse response = searchRequestBuilder.execute().actionGet();
log.debug("search request took: " + response.getTook().format());
eventTrackingService.post(eventTrackingService.newEvent(EVENT_SEARCH,
EVENT_SEARCH_REF + query.toString(), true,
NotificationService.PREF_IMMEDIATE));
return response;
}
@Override
public SearchList search(String searchTerms, List<String> siteIds, int searchStart, int searchEnd) throws InvalidSearchQueryException {
SearchResponse response = search(searchTerms, siteIds, searchStart, searchEnd, null, null, new ArrayList<String>());
return new ElasticSearchList(searchTerms.toLowerCase(), response, this, indexBuilder, FACET_NAME, filter);
}
@Override
public SearchList search(String searchTerms, List<String> siteIds, int start, int end, String filterName, String sorterName) throws InvalidSearchQueryException {
return search(searchTerms, siteIds, start, end);
}
public String searchXML(Map parameterMap) {
String userid = null;
String searchTerms = null;
String checksum = null;
String contexts = null;
String ss = null;
String se = null;
try {
String[] useridA = (String[]) parameterMap.get(REST_USERID);
String[] searchTermsA = (String[]) parameterMap.get(REST_TERMS);
String[] checksumA = (String[]) parameterMap.get(REST_CHECKSUM);
String[] contextsA = (String[]) parameterMap.get(REST_CONTEXTS);
String[] ssA = (String[]) parameterMap.get(REST_START);
String[] seA = (String[]) parameterMap.get(REST_END);
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"?>"); //$NON-NLS-1$
boolean requestError = false;
if (useridA == null || useridA.length != 1) {
requestError = true;
} else {
userid = useridA[0];
}
if (searchTermsA == null || searchTermsA.length != 1) {
requestError = true;
} else {
searchTerms = searchTermsA[0];
}
if (checksumA == null || checksumA.length != 1) {
requestError = true;
} else {
checksum = checksumA[0];
}
if (contextsA == null || contextsA.length != 1) {
requestError = true;
} else {
contexts = contextsA[0];
}
if (ssA == null || ssA.length != 1) {
requestError = true;
} else {
ss = ssA[0];
}
if (seA == null || seA.length != 1) {
requestError = true;
} else {
se = seA[0];
}
if (requestError) {
throw new Exception("Invalid Request"); //$NON-NLS-1$
}
int searchStart = Integer.parseInt(ss);
int searchEnd = Integer.parseInt(se);
String[] ctxa = contexts.split(";"); //$NON-NLS-1$
List<String> ctx = new ArrayList<String>(ctxa.length);
for (int i = 0; i < ctxa.length; i++) {
ctx.add(ctxa[i]);
}
if (sharedKey != null && sharedKey.length() > 0) {
String check = digestCheck(userid, searchTerms);
if (!check.equals(checksum)) {
throw new Exception("Security Checksum is not valid"); //$NON-NLS-1$
}
}
org.sakaiproject.tool.api.Session s = sessionManager.startSession();
User u = userDirectoryService.getUser("admin"); //$NON-NLS-1$
s.setUserId(u.getId());
sessionManager.setCurrentSession(s);
try {
SearchList sl = search(searchTerms, ctx, searchStart, searchEnd);
sb.append("<results "); //$NON-NLS-1$
sb.append(" fullsize=\"").append(sl.getFullSize()) //$NON-NLS-1$
.append("\" "); //$NON-NLS-1$
sb.append(" start=\"").append(sl.getStart()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(" size=\"").append(sl.size()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(" >"); //$NON-NLS-1$
for (Iterator<SearchResult> si = sl.iterator(); si.hasNext(); ) {
SearchResult sr = (SearchResult) si.next();
sr.toXMLString(sb);
}
sb.append("</results>"); //$NON-NLS-1$
return sb.toString();
} finally {
sessionManager.setCurrentSession(null);
}
} catch (Exception ex) {
log.error("Search Service XML response failed ", ex);
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\"?>"); //$NON-NLS-1$
sb.append("<fault>"); //$NON-NLS-1$
sb.append("<request>"); //$NON-NLS-1$
sb.append("<![CDATA["); //$NON-NLS-1$
sb.append(" userid = ").append(StringEscapeUtils.escapeXml(userid)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
sb
.append(" searchTerms = ").append(StringEscapeUtils.escapeXml(searchTerms)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
sb
.append(" checksum = ").append(StringEscapeUtils.escapeXml(checksum)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
sb
.append(" contexts = ").append(StringEscapeUtils.escapeXml(contexts)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(" ss = ").append(StringEscapeUtils.escapeXml(ss)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(" se = ").append(StringEscapeUtils.escapeXml(se)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
sb.append("]]>"); //$NON-NLS-1$
sb.append("</request>"); //$NON-NLS-1$
sb.append("<error>"); //$NON-NLS-1$
sb.append("<![CDATA["); //$NON-NLS-1$
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
pw.flush();
sb.append(sw.toString());
pw.close();
sw.close();
} catch (Exception ex2) {
sb.append("Failed to serialize exception " + ex.getMessage()) //$NON-NLS-1$
.append("\n"); //$NON-NLS-1$
sb.append("Case: " + ex2.getMessage()); //$NON-NLS-1$
}
sb.append("]]>"); //$NON-NLS-1$
sb.append("</error>"); //$NON-NLS-1$
sb.append("</fault>"); //$NON-NLS-1$
return sb.toString();
}
}
private String digestCheck(String userid, String searchTerms)
throws GeneralSecurityException, IOException {
MessageDigest sha1 = MessageDigest.getInstance("SHA1"); //$NON-NLS-1$
String chstring = sharedKey + userid + searchTerms;
return byteArrayToHexStr(sha1.digest(chstring.getBytes("UTF-8"))); //$NON-NLS-1$
}
private static String byteArrayToHexStr(byte[] data) {
char[] chars = new char[data.length * 2];
for (int i = 0; i < data.length; i++) {
byte current = data[i];
int hi = (current & 0xF0) >> 4;
int lo = current & 0x0F;
chars[2 * i] = (char) (hi < 10 ? ('0' + hi) : ('A' + hi - 10));
chars[2 * i + 1] = (char) (lo < 10 ? ('0' + lo) : ('A' + lo - 10));
}
return new String(chars);
}
@Override
public void registerFunction(String function) {
log.info("Register " + function + " as a trigger for the search service");
if (!isEnabled()) {
log.debug("ElasticSearch is not enabled. Set search.enable=true to change that.");
return;
}
notification.addFunction(function);
}
public void refreshInstance() {
indexBuilder.refreshIndex();
}
public void rebuildInstance() {
indexBuilder.rebuildIndex();
}
public void refreshSite(String currentSiteId) {
indexBuilder.refreshIndex(currentSiteId);
}
public void rebuildSite(String currentSiteId) {
indexBuilder.rebuildIndex(currentSiteId);
}
@Override
public void reload() {
}
@Override
public String getStatus() {
indexBuilder.assureIndex();
IndicesStatusResponse response = client.admin().indices().status(new IndicesStatusRequest(indexName)).actionGet();
IndexStatus status = response.getIndices().get(indexName);
StringBuffer sb = new StringBuffer();
long pendingDocs = indexBuilder.getPendingDocuments();
if (pendingDocs != 0) {
sb.append( "active. " + pendingDocs + " pending items in queue. ");
} else {
sb.append("idle. ");
}
sb.append("Index Size: " + roundTwoDecimals(status.getStoreSize().getGbFrac()) + " GB" +
" Refresh Time: " + status.getRefreshStats().getTotalTimeInMillis() + "ms" +
" Flush Time: " + status.getFlushStats().getTotalTimeInMillis() + "ms" +
" Merge Time: " + status.getMergeStats().getTotalTimeInMillis() + "ms");
return sb.toString();
}
@Override
public int getNDocs() {
indexBuilder.assureIndex();
CountResponse response = client.prepareCount(indexName)
.setQuery(filteredQuery(matchAllQuery(),existsFilter(SearchService.FIELD_CONTENTS)))
.execute()
.actionGet();
return (int) response.getCount();
}
private double roundTwoDecimals(double d) {
DecimalFormat twoDForm = new DecimalFormat("#.##");
return Double.valueOf(twoDForm.format(d));
}
@Override
public int getPendingDocs() {
return (int) indexBuilder.getPendingDocuments();
}
@Override
public List<SearchBuilderItem> getAllSearchItems() {
return Collections.emptyList();
}
@Override
public List<SearchBuilderItem> getSiteMasterSearchItems() {
return Collections.emptyList();
}
@Override
public List<SearchBuilderItem> getGlobalMasterSearchItems() {
return Collections.emptyList();
}
@Override
public SearchStatus getSearchStatus() {
final String lastLoad = new Date(indexBuilder.getLastLoad()).toString();
final String loadTime = String.valueOf((double) (0.001 * (indexBuilder.getLastLoad())));
final String pdocs = String.valueOf(indexBuilder.getPendingDocuments());
final String ndocs = String.valueOf(getNDocs());
final NodesInfoResponse nodesInfoResponse = client.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet();
final String[] nodes = new String[nodesInfoResponse.getNodes().length];
int i = 0;
for (NodeInfo nodeInfo : nodesInfoResponse.getNodes()) {
nodes[i++] = nodeInfo.getNode().getName();
}
final NodesStatsResponse nodesStatsResponse = client.admin().cluster().nodesStats(new NodesStatsRequest(nodes)).actionGet();
//TODO will show same status for each node, need to deal with that
return new SearchStatus() {
public String getLastLoad() {
return lastLoad;
}
public String getLoadTime() {
return loadTime;
}
public String getCurrentWorker() {
return getNodeName();
}
public String getCurrentWorkerETC() {
return getNodeName();
}
public List<Object[]> getWorkerNodes() {
List<Object[]> workers = new ArrayList();
for (NodeStats nodeStat : nodesStatsResponse.getNodes()) {
workers.add(new Object[]{nodeStat.getNode().getName() + "(" + nodeStat.getHostname() + ")",
indexBuilder.getStartTime(),
getStatus()});
}
return workers;
}
public String getNDocuments() {
return ndocs;
}
public String getPDocuments() {
return pdocs;
}
};
}
@Override
public boolean removeWorkerLock() {
return true;
}
@Override
public List<Object[]> getSegmentInfo() {
return Collections.singletonList(new Object[]{"Index Segment Info is not implemented", "", ""});
}
@Override
public void forceReload() {
}
@Override
public TermFrequency getTerms(int documentId) throws IOException {
throw new UnsupportedOperationException("ElasticSearch can't does not support this operation at this time.");
}
@Override
public boolean isEnabled() {
return serverConfigurationService.getBoolean("search.enable", false);
}
@Override
public String getDigestStoragePath() {
return null;
}
public String getSearchSuggestion(String searchString) {
String[] suggestions = getSearchSuggestions(searchString, null, true);
if (suggestions != null && suggestions.length > 0) {
for (String suggestion : suggestions) {
if (searchString.equalsIgnoreCase(suggestion)) {
continue;
}
return suggestion;
}
}
return null;
}
/**
* Get all the sites a user has access to.
* @return An array of site IDs.
*/
protected String[] getAllUsersSites(String currentUser) {
List<Site> sites = siteService.getSites(
org.sakaiproject.site.api.SiteService.SelectionType.ACCESS,
null, null, null, null, null);
List<String> siteIds = convertSitesToStringArray(sites);
siteIds.add(siteService.getUserSiteId(currentUser));
return siteIds.toArray(new String[siteIds.size()]);
}
protected List<String> convertSitesToStringArray(List<Site> sites) {
List<String> siteIds = new ArrayList<String>(sites.size());
for (Site site: sites) {
if (site != null && site.getId() != null) {
siteIds.add(site.getId());
}
}
return siteIds;
}
public String[] getSearchSuggestions(String searchString, String currentSite, boolean allMySites) {
if (!useSuggestions) {
return new String[0];
}
String currentUser = "";
User user = userDirectoryService.getCurrentUser();
if (user != null) {
currentUser = user.getId();
}
String[] sites;
if (allMySites || currentSite == null) {
sites = getAllUsersSites(currentUser);
} else {
sites = new String[]{currentSite};
}
TermQueryBuilder query = termQuery("title", searchString);
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(indexName)
.setSearchType(SearchType.QUERY_THEN_FETCH)
.setQuery(query)
.setTypes(SAKAI_DOC_TYPE)
.setSize(maxNumberOfSuggestions)
.setRouting(sites)
// .addHighlightedField(SearchService.FIELD_TITLE, 255, 0)
.addField(SearchService.FIELD_TYPE)
.addField(SearchService.FIELD_REFERENCE)
//.addField(SearchService.FIELD_ID)
.addField(SearchService.FIELD_SITEID)
.addField(SearchService.FIELD_TITLE);
OrFilterBuilder siteFilter = orFilter().add(
termsFilter(SearchService.FIELD_SITEID, sites).execution("bool"));
searchRequestBuilder.setFilter(siteFilter);
log.debug("search request: " + searchRequestBuilder.toString());
SearchResponse response = searchRequestBuilder.execute().actionGet();
log.debug("search request took: " + response.getTook().format());
List<String> suggestions = new ArrayList();
for (SearchHit hit : response.getHits()) {
suggestions.add(getFieldFromSearchHit(SearchService.FIELD_TITLE, hit));
// suggestions.add(hit.getHighlightFields().get(SearchService.FIELD_TITLE).getFragments()[0].string());
}
return suggestions.toArray(new String[suggestions.size()]);
}
@Override
public boolean isSearchServer() {
return true;
}
public void destroy(){
if (node != null) {
node.close();
}
}
//------------------------------------------------------------------------------------------
//As far as I know, this implementation isn't diagnosable, so this is a dummy implementation
//------------------------------------------------------------------------------------------
@Override
public void enableDiagnostics() {
}
@Override
public void disableDiagnostics() {
}
@Override
public boolean hasDiagnostics() {
return false;
}
public void setTriggerFunctions(List<String> triggerFunctions) {
this.triggerFunctions = triggerFunctions;
}
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void setEventTrackingService(EventTrackingService eventTrackingService) {
this.eventTrackingService = eventTrackingService;
}
public void setIndexName(String indexName) {
//elasticsearch wants lowers case index names
this.indexName = indexName.toLowerCase();
}
public void setIndexBuilder(ElasticSearchIndexBuilder indexBuilder) {
this.indexBuilder = indexBuilder;
}
public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
this.serverConfigurationService = serverConfigurationService;
}
public void setRebuildIndexOnStartup(boolean rebuildIndexOnStartup) {
this.rebuildIndexOnStartup = rebuildIndexOnStartup;
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
this.userDirectoryService = userDirectoryService;
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public void setSharedKey(String sharedKey) {
this.sharedKey = sharedKey;
}
public void setSiteService(SiteService siteService) {
this.siteService = siteService;
}
public void setMaxNumberOfSuggestions(int maxNumberOfSuggestions) {
this.maxNumberOfSuggestions = maxNumberOfSuggestions;
}
public void setFacetTermSize(int facetTermSize) {
this.facetTermSize = facetTermSize;
}
public void setLocalNode(boolean localNode) {
this.localNode = localNode;
}
public void setUseSiteFilters(boolean useSiteFilters) {
this.useSiteFilters = useSiteFilters;
}
public boolean getUseFacetting() {
return useFacetting;
}
public void setUseFacetting(boolean useFacetting) {
this.useFacetting = useFacetting;
}
public boolean getUseSuggestions() {
return useSuggestions;
}
public void setUseSuggestions(boolean useSuggestions) {
this.useSuggestions = useSuggestions;
}
public void setFilter(SearchItemFilter filter) {
this.filter = filter;
}
public void setSearchServer(boolean searchServer) {
this.searchServer = searchServer;
}
}