diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 630c62ff45..39bae9778e 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1408,6 +1408,20 @@ + + + + + + + search.suggesterService + + + + org.alfresco.service.cmr.search.SuggesterService + + + diff --git a/config/alfresco/subsystems/Search/solr/solr-search-context.xml b/config/alfresco/subsystems/Search/solr/solr-search-context.xml index 400d816552..1717ee07e7 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr/solr-search-context.xml @@ -299,5 +299,14 @@ - + + + + ${solr.suggester.enabled} + + + + + + diff --git a/config/alfresco/subsystems/Search/solr/solr-search.properties b/config/alfresco/subsystems/Search/solr/solr-search.properties index 9c0fbae172..815fd4204f 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search.properties +++ b/config/alfresco/subsystems/Search/solr/solr-search.properties @@ -4,3 +4,7 @@ solr.port.ssl=8443 solr.query.includeGroupsForRoleAdmin=false solr.query.maximumResultsFromUnlimitedQuery=${system.acl.maxPermissionChecks} solr.baseUrl=/solr +# +# Solr Suggester properties +# +solr.suggester.enabled=true \ No newline at end of file diff --git a/config/alfresco/subsystems/Search/solr4/solr-search-context.xml b/config/alfresco/subsystems/Search/solr4/solr-search-context.xml index b90325f37f..90d2d832c4 100644 --- a/config/alfresco/subsystems/Search/solr4/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr4/solr-search-context.xml @@ -298,6 +298,15 @@ - - + + + + + ${solr.suggester.enabled} + + + + + + diff --git a/config/alfresco/subsystems/Search/solr4/solr-search.properties b/config/alfresco/subsystems/Search/solr4/solr-search.properties index 8526fabf6d..9c9a799611 100644 --- a/config/alfresco/subsystems/Search/solr4/solr-search.properties +++ b/config/alfresco/subsystems/Search/solr4/solr-search.properties @@ -4,3 +4,7 @@ solr.port.ssl=8446 solr.query.includeGroupsForRoleAdmin=false solr.query.maximumResultsFromUnlimitedQuery=${system.acl.maxPermissionChecks} solr.baseUrl=/solr4 +# +# Solr Suggester properties +# +solr.suggester.enabled=true \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/lucene/SolrSuggesterResult.java b/source/java/org/alfresco/repo/search/impl/lucene/SolrSuggesterResult.java new file mode 100644 index 0000000000..ba904c4589 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/lucene/SolrSuggesterResult.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.search.impl.lucene; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.alfresco.service.cmr.search.SuggesterResult; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class SolrSuggesterResult implements SuggesterResult +{ + private static final Log logger = LogFactory.getLog(SolrSuggesterResult.class); + + private Long numberFound; + private List> suggestions = new ArrayList<>(); + + public SolrSuggesterResult() + { + } + + public SolrSuggesterResult(JSONObject jsonObject) + { + try + { + processJson(jsonObject); + } + catch (Exception e) + { + logger.info(e.getMessage()); + } + } + + /** + * Parses the json returned from the suggester + * + * @param json the JSON object + * @throws JSONException + */ + @SuppressWarnings("rawtypes") + protected void processJson(JSONObject json) throws JSONException + { + ParameterCheck.mandatory("json", json); + + if (logger.isDebugEnabled()) + { + logger.debug("Suggester JSON response: " + json); + } + + JSONObject suggest = json.getJSONObject("suggest"); + for (Iterator suggestIterator = suggest.keys(); suggestIterator.hasNext(); /**/) + { + String dictionary = (String) suggestIterator.next(); + + JSONObject dictionaryJsonObject = suggest.getJSONObject(dictionary); + for (Iterator dicIterator = dictionaryJsonObject.keys(); dicIterator.hasNext(); /**/) + { + String termStr = (String) dicIterator.next(); + + JSONObject termJsonObject = dictionaryJsonObject.getJSONObject(termStr); + // number found + this.numberFound = termJsonObject.getLong("numFound"); + + // the suggested terms + JSONArray suggestion = termJsonObject.getJSONArray("suggestions"); + for (int i = 0, length = suggestion.length(); i < length; i++) + { + JSONObject data = suggestion.getJSONObject(i); + this.suggestions.add(new Pair(data.getString("term"), data.getInt("weight"))); + } + } + } + } + + @Override + public long getNumberFound() + { + return this.numberFound; + } + + @Override + public List> getSuggestions() + { + return this.suggestions; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(250); + builder.append("SolrSuggesterResult [numberFound=").append(this.numberFound).append(", suggestions=") + .append(this.suggestions).append("]"); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrAdminHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrAdminHTTPClient.java index 30e6557ff1..6b3a650368 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrAdminHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrAdminHTTPClient.java @@ -106,19 +106,28 @@ public class SolrAdminHTTPClient this.httpClientFactory = httpClientFactory; } - public JSONObject execute(HashMapargs) - { + public JSONObject execute(HashMap args) + { + return execute(adminUrl, args); + } + + public JSONObject execute(String relativeHandlerPath, HashMap args) + { + ParameterCheck.mandatory("relativeHandlerPath", relativeHandlerPath); + ParameterCheck.mandatory("args", args); + + String path = getPath(relativeHandlerPath); try - { + { URLCodec encoder = new URLCodec(); StringBuilder url = new StringBuilder(); - - for(String key : args.keySet()) + + for (String key : args.keySet()) { String value = args.get(key); - if(url.length() == 0) + if (url.length() == 0) { - url.append(adminUrl); + url.append(path); url.append("?"); url.append(encoder.encode(key, "UTF-8")); url.append("="); @@ -129,27 +138,27 @@ public class SolrAdminHTTPClient url.append("&"); url.append(encoder.encode(key, "UTF-8")); url.append("="); - url.append(encoder.encode(value, "UTF-8")); + url.append(encoder.encode(value, "UTF-8")); } - + } - - //PostMethod post = new PostMethod(url.toString()); + + // PostMethod post = new PostMethod(url.toString()); GetMethod get = new GetMethod(url.toString()); - + try { httpClient.executeMethod(get); - if(get.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY || get.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) + if (get.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY || get.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) { - Header locationHeader = get.getResponseHeader("location"); - if (locationHeader != null) - { - String redirectLocation = locationHeader.getValue(); - get.setURI(new URI(redirectLocation, true)); - httpClient.executeMethod(get); - } + Header locationHeader = get.getResponseHeader("location"); + if (locationHeader != null) + { + String redirectLocation = locationHeader.getValue(); + get.setURI(new URI(redirectLocation, true)); + httpClient.executeMethod(get); + } } if (get.getStatusCode() != HttpServletResponse.SC_OK) @@ -185,4 +194,20 @@ public class SolrAdminHTTPClient } } + private String getPath(String path) + { + if (path.startsWith(baseUrl)) + { + return path; + } + else if (path.startsWith("/")) + { + return baseUrl + path; + } + else + { + return baseUrl + '/' + path; + } + } + } diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrSuggesterServiceImpl.java b/source/java/org/alfresco/repo/search/impl/solr/SolrSuggesterServiceImpl.java new file mode 100644 index 0000000000..c5ef224992 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrSuggesterServiceImpl.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.search.impl.solr; + +import java.util.HashMap; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.search.impl.lucene.SolrSuggesterResult; +import org.alfresco.service.cmr.search.SuggesterResult; +import org.alfresco.service.cmr.search.SuggesterService; +import org.json.JSONObject; + +/** + * Solr Suggester Service Implementation. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class SolrSuggesterServiceImpl implements SuggesterService +{ + + public static final String SUGGESER_PATH = "/alfresco/suggest"; + + private boolean enabled; + private SolrAdminHTTPClient solrAdminHTTPClient; + + public void setEnabled(boolean isEnabled) + { + this.enabled = isEnabled; + } + + public void setSolrAdminHTTPClient(SolrAdminHTTPClient solrAdminHTTPClient) + { + this.solrAdminHTTPClient = solrAdminHTTPClient; + } + + @Override + public boolean isEnabled() + { + return this.enabled; + } + + @Override + public SuggesterResult getSuggestions(String term, int limit) + { + // if it is not enabled, return an empty result set + if (!enabled) + { + return new SolrSuggesterResult(); + } + try + { + HashMap params = new HashMap<>(3); + params.put("q", term); + if (limit > 0) + { + params.put("suggest.count", Integer.toString(limit)); + } + params.put("wt", "json"); + + JSONObject response = solrAdminHTTPClient.execute(SUGGESER_PATH, params); + return new SolrSuggesterResult(response); + } + catch (Exception e) + { + throw new AlfrescoRuntimeException("SolrSuggester failed.", e); + } + } + +} diff --git a/source/java/org/alfresco/service/cmr/search/SuggesterResult.java b/source/java/org/alfresco/service/cmr/search/SuggesterResult.java new file mode 100644 index 0000000000..2627caaeed --- /dev/null +++ b/source/java/org/alfresco/service/cmr/search/SuggesterResult.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.service.cmr.search; + +import java.util.List; + +import org.alfresco.util.Pair; + +/** + * Term suggestions response object + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public interface SuggesterResult +{ + + /** + * Get the number of suggestions + * + * @return + */ + long getNumberFound(); + + /** + * Get the list of suggestions as ("term", "weight") pairs. Never null. + * + * @return list of suggestions + */ + List> getSuggestions(); +} diff --git a/source/java/org/alfresco/service/cmr/search/SuggesterService.java b/source/java/org/alfresco/service/cmr/search/SuggesterService.java new file mode 100644 index 0000000000..99d8bfc783 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/search/SuggesterService.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.service.cmr.search; + +/** + * A service that returns term suggestions + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public interface SuggesterService +{ + + /** + * Whether the Suggester is enabled (refer to 'solr.suggester.enabled' repository property) or not + * + * @return true if the Suggester is enabled, false otherwise + */ + public boolean isEnabled(); + + /** + * Get suggestions for the specified term + * + * @param term the term to use for the search + * @param limit the number of suggestions for Solr to return + * @return term suggestions result. Never null + */ + public SuggesterResult getSuggestions(String term, int limit); +}