/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.web.api.services; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.alfresco.web.api.APIException; import org.alfresco.web.api.APIRequest; import org.alfresco.web.api.APIResponse; import org.alfresco.web.api.APIService; import org.alfresco.web.api.APIRequest.HttpMethod; import org.alfresco.web.api.APIRequest.RequiredAuthentication; import org.springframework.context.ApplicationContext; /** * Alfresco Text (simple) Search Service * * @author davidc */ public class TextSearch implements APIService { // NOTE: startPage and startIndex are 1 offset. // search parameters // TODO: allow configuration of these private static final StoreRef searchStore = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); private static final int itemsPerPage = 10; // dependencies private String uri; private ServiceRegistry serviceRegistry; private SearchService searchService; private TemplateService templateService; /** * Sets the Http URI * * @param uri */ public void setHttpUri(String uri) { this.uri = uri; } /** * @param serviceRegistry */ public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; } /** * @param searchService */ public void setSearchService(SearchService searchService) { this.searchService = searchService; } /** * @param templateService */ public void setTemplateService(TemplateService templateService) { this.templateService = templateService; } /* (non-Javadoc) * @see org.alfresco.web.api.APIService#getRequiredAuthentication() */ public RequiredAuthentication getRequiredAuthentication() { return APIRequest.RequiredAuthentication.User; } /* (non-Javadoc) * @see org.alfresco.web.api.APIService#getHttpMethod() */ public HttpMethod getHttpMethod() { return APIRequest.HttpMethod.GET; } /* (non-Javadoc) * @see org.alfresco.web.api.APIService#getHttpUri() */ public String getHttpUri() { return this.uri; } /* (non-Javadoc) * @see org.alfresco.web.api.APIService#execute(org.alfresco.web.api.APIRequest, org.alfresco.web.api.APIResponse) */ public void execute(APIRequest req, APIResponse res) throws IOException { // // execute the search // String searchTerms = req.getParameter("q"); String startPageArg = req.getParameter("p"); int startPage = 1; try { startPage = new Integer(startPageArg); } catch(NumberFormatException e) { // NOTE: use default startPage } SearchResult results = search(searchTerms, startPage); // // render the results // String contentType = APIResponse.HTML_TYPE; String template = HTML_TEMPLATE; // TODO: data-drive this String format = req.getParameter("format"); if (format != null) { if (format.equals("atom")) { contentType = APIResponse.ATOM_TYPE; template = ATOM_TEMPLATE; } } // execute template Map searchModel = new HashMap(7, 1.0f); searchModel.put("request", req); searchModel.put("search", results); res.setContentType(contentType + ";charset=UTF-8"); templateService.processTemplateString(null, template, searchModel, res.getWriter()); } /** * Execute the search * * @param searchTerms * @param startPage * @return */ private SearchResult search(String searchTerms, int startPage) { SearchResult searchResult = null; ResultSet results = null; try { // Construct search statement String[] terms = searchTerms.split(" "); Map statementModel = new HashMap(7, 1.0f); statementModel.put("terms", terms); String query = templateService.processTemplateString(null, QUERY_STATEMENT, statementModel); results = searchService.query(searchStore, SearchService.LANGUAGE_LUCENE, query); int totalResults = results.length(); int totalPages = (totalResults / itemsPerPage); totalPages += (totalResults % itemsPerPage != 0) ? 1 : 0; // are we out-of-range if (totalPages != 0 && (startPage < 1 || startPage > totalPages)) { throw new APIException("Start page " + startPage + " is outside boundary of " + totalPages + " pages"); } searchResult = new SearchResult(); searchResult.setSearchTerms(searchTerms); searchResult.setItemsPerPage(itemsPerPage); searchResult.setStartPage(startPage); searchResult.setTotalPages(totalPages); searchResult.setTotalResults(totalResults); searchResult.setStartIndex(((startPage -1) * itemsPerPage) + 1); searchResult.setTotalPageItems(Math.min(itemsPerPage, totalResults - searchResult.getStartIndex() + 1)); TemplateNode[] nodes = new TemplateNode[searchResult.getTotalPageItems()]; for (int i = 0; i < searchResult.getTotalPageItems(); i++) { nodes[i] = new TemplateNode(results.getNodeRef(i + searchResult.getStartIndex() - 1), serviceRegistry, null); } searchResult.setResults(nodes); return searchResult; } finally { if (results != null) { results.close(); } } } /** * Search Result * * @author davidc */ public static class SearchResult { private String id; private String searchTerms; private int itemsPerPage; private int totalPages; private int totalResults; private int totalPageItems; private int startPage; private int startIndex; private TemplateNode[] results; public int getItemsPerPage() { return itemsPerPage; } /*package*/ void setItemsPerPage(int itemsPerPage) { this.itemsPerPage = itemsPerPage; } public TemplateNode[] getResults() { return results; } /*package*/ void setResults(TemplateNode[] results) { this.results = results; } public int getStartIndex() { return startIndex; } /*package*/ void setStartIndex(int startIndex) { this.startIndex = startIndex; } public int getStartPage() { return startPage; } /*package*/ void setStartPage(int startPage) { this.startPage = startPage; } public int getTotalPageItems() { return totalPageItems; } /*package*/ void setTotalPageItems(int totalPageItems) { this.totalPageItems = totalPageItems; } public int getTotalPages() { return totalPages; } /*package*/ void setTotalPages(int totalPages) { this.totalPages = totalPages; } public int getTotalResults() { return totalResults; } /*package*/ void setTotalResults(int totalResults) { this.totalResults = totalResults; } public String getSearchTerms() { return searchTerms; } /*package*/ void setSearchTerms(String searchTerms) { this.searchTerms = searchTerms; } public String getId() { if (id == null) { id = GUID.generate(); } return id; } } // TODO: place into accessible file private final static String ATOM_TEMPLATE = "<#assign dateformat=\"yyyy-MM-dd\">" + "<#assign timeformat=\"HH:mm:sszzz\">" + "\n" + "\n" + " Alfresco Search: ${search.searchTerms}\n" + " 2003-12-13T18:30:02Z\n" + // TODO: " \n" + " Alfresco\n" + // TODO: Issuer of search? " \n" + " urn:uuid:${search.id}\n" + " ${search.totalResults}\n" + " ${search.startIndex}\n" + " ${search.itemsPerPage}\n" + " \n" + " \n" + " \n" + " \n" + "<#if search.startPage > 1>" + " \n" + "" + "<#if search.startPage < search.totalPages>" + " \n" + "" + " \n" + " \n" + "<#list search.results as row>" + " \n" + " ${row.name}\n" + " \n" + " urn:uuid:${row.id}\n" + " ${row.properties.modified?string(dateformat)}T${row.properties.modified?string(timeformat)}\n" + " ${row.properties.description}\n" + " \n" + "" + ""; // TODO: place into accessible file private final static String HTML_TEMPLATE = "\n" + "\n" + "\n" + " \n" + " Alfresco Text Search: ${search.searchTerms}\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "

Alfresco Text Search

\n" + " Results ${search.startIndex} - ${search.startIndex + search.totalPageItems - 1} of ${search.totalResults} for ${search.searchTerms}.\n" + "
    \n" + "<#list search.results as row>" + "
  • \n" + " \n" + " ${row.name}\n" + " \n" + "
    \n" + " ${row.properties.description}\n" + "
    \n" + "
  • \n" + "" + "
\n" + " first" + "<#if search.startPage > 1>" + " previous" + "" + " ${search.startPage}" + "<#if search.startPage < search.totalPages>" + " next" + "" + " last" + " \n" + "\n"; // TODO: place into accessible file private final static String QUERY_STATEMENT = "( " + " TYPE:\"{http://www.alfresco.org/model/content/1.0}content\" AND " + " (" + " (" + "<#list 1..terms?size as i>" + " @\\{http\\://www.alfresco.org/model/content/1.0\\}name:${terms[i - 1]}" + "<#if (i < terms?size)>" + " OR " + "" + "" + " ) " + " ( " + "<#list 1..terms?size as i>" + " TEXT:${terms[i - 1]}" + "<#if (i < terms?size)>" + " OR " + "" + "" + " )" + " )" + ")"; /** * Simple test that can be executed outside of web context * * TODO: Move to test harness * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { ApplicationContext context = ApplicationContextHelper.getApplicationContext(); TextSearch method = new TextSearch(); method.setServiceRegistry((ServiceRegistry)context.getBean(ServiceRegistry.SERVICE_REGISTRY)); method.setTemplateService((TemplateService)context.getBean(ServiceRegistry.TEMPLATE_SERVICE.getLocalName())); method.setSearchService((SearchService)context.getBean(ServiceRegistry.SEARCH_SERVICE.getLocalName())); method.setHttpUri("/search/text"); method.test(); } /** * Simple test that can be executed outside of web context * * TODO: Move to test harness */ private void test() { SearchResult result = search("alfresco tutorial", 1); Map searchModel = new HashMap(7, 1.0f); Map request = new HashMap(); request.put("servicePath", "http://localhost:8080/alfresco/service"); request.put("path", "http://localhost:8080/alfresco"); searchModel.put("request", request); searchModel.put("search", result); StringWriter rendition = new StringWriter(); PrintWriter writer = new PrintWriter(rendition); templateService.processTemplateString(null, HTML_TEMPLATE, searchModel, writer); System.out.println(rendition.toString()); } }