001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.search;
021:
022: import java.io.IOException;
023: import java.util.*;
024:
025: import org.apache.commons.lang.time.StopWatch;
026: import org.apache.log4j.Logger;
027:
028: import com.ecyrd.jspwiki.*;
029: import com.ecyrd.jspwiki.event.WikiEvent;
030: import com.ecyrd.jspwiki.event.WikiEventListener;
031: import com.ecyrd.jspwiki.event.WikiEventUtils;
032: import com.ecyrd.jspwiki.event.WikiPageEvent;
033: import com.ecyrd.jspwiki.filters.BasicPageFilter;
034: import com.ecyrd.jspwiki.filters.FilterException;
035: import com.ecyrd.jspwiki.modules.InternalModule;
036: import com.ecyrd.jspwiki.parser.MarkupParser;
037: import com.ecyrd.jspwiki.providers.ProviderException;
038: import com.ecyrd.jspwiki.rpc.RPCCallable;
039: import com.ecyrd.jspwiki.rpc.json.JSONRPCManager;
040: import com.ecyrd.jspwiki.util.ClassUtil;
041:
042: /**
043: * Manages searching the Wiki.
044: *
045: * @author Arent-Jan Banck
046: * @since 2.2.21.
047: */
048:
049: public class SearchManager extends BasicPageFilter implements
050: InternalModule, WikiEventListener {
051: private static final Logger log = Logger
052: .getLogger(SearchManager.class);
053:
054: private static final String DEFAULT_SEARCHPROVIDER = "com.ecyrd.jspwiki.search.LuceneSearchProvider";
055: public static final String PROP_USE_LUCENE = "jspwiki.useLucene";
056: public static final String PROP_SEARCHPROVIDER = "jspwiki.searchProvider";
057:
058: private SearchProvider m_searchProvider = null;
059:
060: public static final String JSON_SEARCH = "search";
061:
062: public SearchManager(WikiEngine engine, Properties properties)
063: throws WikiException {
064: initialize(engine, properties);
065:
066: WikiEventUtils.addWikiEventListener(m_engine.getPageManager(),
067: WikiPageEvent.PAGE_DELETE_REQUEST, this );
068:
069: JSONRPCManager.registerGlobalObject(JSON_SEARCH,
070: new JSONSearch());
071: }
072:
073: /**
074: * Provides a JSON RPC API to the JSPWiki Search Engine.
075: * @author jalkanen
076: */
077: public class JSONSearch implements RPCCallable {
078: /**
079: * Provides a list of suggestions to use for a page name.
080: * Currently the algorithm just looks into the value parameter,
081: * and returns all page names from that.
082: *
083: * @param wikiName the page name
084: * @param maxLength maximum number of suggestions
085: * @return the suggestions
086: */
087: public List getSuggestions(String wikiName, int maxLength) {
088: StopWatch sw = new StopWatch();
089: sw.start();
090: List list = new ArrayList(maxLength);
091:
092: if (wikiName.length() > 0) {
093: wikiName = MarkupParser.cleanLink(wikiName);
094: wikiName = wikiName.toLowerCase();
095:
096: String oldStyleName = MarkupParser.wikifyLink(wikiName)
097: .toLowerCase();
098:
099: Set allPages = m_engine.getReferenceManager()
100: .findCreated();
101:
102: int counter = 0;
103: for (Iterator i = allPages.iterator(); i.hasNext()
104: && counter < maxLength;) {
105: String p = (String) i.next();
106: String pp = p.toLowerCase();
107: if (pp.startsWith(wikiName)
108: || pp.startsWith(oldStyleName)) {
109: list.add(p);
110: counter++;
111: }
112: }
113: }
114:
115: sw.stop();
116: if (log.isDebugEnabled())
117: log.debug("Suggestion request for " + wikiName
118: + " done in " + sw);
119: return list;
120: }
121:
122: /**
123: * Performs a full search of pages.
124: *
125: * @param searchString The query string
126: * @param maxLength How many hits to return
127: * @return the pages found
128: */
129: public List findPages(String searchString, int maxLength) {
130: StopWatch sw = new StopWatch();
131: sw.start();
132:
133: List list = new ArrayList(maxLength);
134:
135: if (searchString.length() > 0) {
136: try {
137: Collection c;
138:
139: if (m_searchProvider instanceof LuceneSearchProvider)
140: c = ((LuceneSearchProvider) m_searchProvider)
141: .findPages(searchString, 0);
142: else
143: c = m_searchProvider.findPages(searchString);
144:
145: int count = 0;
146: for (Iterator i = c.iterator(); i.hasNext()
147: && count < maxLength; count++) {
148: SearchResult sr = (SearchResult) i.next();
149: HashMap hm = new HashMap();
150: hm.put("page", sr.getPage().getName());
151: hm.put("score", new Integer(sr.getScore()));
152: list.add(hm);
153: }
154: } catch (Exception e) {
155: log.info("AJAX search failed; ", e);
156: }
157: }
158:
159: sw.stop();
160: if (log.isDebugEnabled())
161: log.debug("AJAX search complete in " + sw);
162: return list;
163: }
164: }
165:
166: /**
167: * This particular method starts off indexing and all sorts of various activities,
168: * so you need to run this last, after things are done.
169: *
170: * @param engine the wiki engine
171: * @param properties the properties used to initialize the wiki engine
172: * @throws FilterException if the search provider failed to initialize
173: */
174: public void initialize(WikiEngine engine, Properties properties)
175: throws FilterException {
176: m_engine = engine;
177:
178: loadSearchProvider(properties);
179:
180: try {
181: m_searchProvider.initialize(engine, properties);
182: } catch (NoRequiredPropertyException e) {
183: // TODO Auto-generated catch block
184: e.printStackTrace();
185: } catch (IOException e) {
186: // TODO Auto-generated catch block
187: e.printStackTrace();
188: }
189: }
190:
191: private void loadSearchProvider(Properties properties) {
192: //
193: // See if we're using Lucene, and if so, ensure that its
194: // index directory is up to date.
195: //
196: String useLucene = properties.getProperty(PROP_USE_LUCENE);
197:
198: // FIXME: Obsolete, remove, or change logic to first load searchProvder?
199: // If the old jspwiki.useLucene property is set we use that instead of the searchProvider class.
200: if (useLucene != null) {
201: log.info(PROP_USE_LUCENE + " is deprecated; please use "
202: + PROP_SEARCHPROVIDER
203: + "=<your search provider> instead.");
204: if (TextUtil.isPositive(useLucene)) {
205: m_searchProvider = new LuceneSearchProvider();
206: } else {
207: m_searchProvider = new BasicSearchProvider();
208: }
209: log.debug("useLucene was set, loading search provider "
210: + m_searchProvider);
211: return;
212: }
213:
214: String providerClassName = properties.getProperty(
215: PROP_SEARCHPROVIDER, DEFAULT_SEARCHPROVIDER);
216:
217: try {
218: Class providerClass = ClassUtil.findClass(
219: "com.ecyrd.jspwiki.search", providerClassName);
220: m_searchProvider = (SearchProvider) providerClass
221: .newInstance();
222: } catch (ClassNotFoundException e) {
223: log
224: .warn(
225: "Failed loading SearchProvider, will use BasicSearchProvider.",
226: e);
227: } catch (InstantiationException e) {
228: log
229: .warn(
230: "Failed loading SearchProvider, will use BasicSearchProvider.",
231: e);
232: } catch (IllegalAccessException e) {
233: log
234: .warn(
235: "Failed loading SearchProvider, will use BasicSearchProvider.",
236: e);
237: }
238:
239: if (null == m_searchProvider) {
240: // FIXME: Make a static with the default search provider
241: m_searchProvider = new BasicSearchProvider();
242: }
243: log.debug("Loaded search provider " + m_searchProvider);
244: }
245:
246: public SearchProvider getSearchEngine() {
247: return m_searchProvider;
248: }
249:
250: /**
251: * Sends a search to the current search provider. The query is is whatever native format
252: * the query engine wants to use.
253: *
254: * @param query The query. Null is safe, and is interpreted as an empty query.
255: * @return A collection of WikiPages that matched.
256: */
257: public Collection findPages(String query) throws ProviderException,
258: IOException {
259: if (query == null)
260: query = "";
261: Collection c = m_searchProvider.findPages(query);
262:
263: return c;
264: }
265:
266: /**
267: * Removes the page from the search cache (if any).
268: * @param page The page to remove
269: */
270: public void pageRemoved(WikiPage page) {
271: m_searchProvider.pageRemoved(page);
272: }
273:
274: public void postSave(WikiContext wikiContext, String content) {
275: //
276: // Makes sure that we're indexing the latest version of this
277: // page.
278: //
279: WikiPage p = m_engine.getPage(wikiContext.getPage().getName());
280: reindexPage(p);
281: }
282:
283: /**
284: * Forces the reindex of the given page.
285: *
286: * @param page
287: */
288: public void reindexPage(WikiPage page) {
289: m_searchProvider.reindexPage(page);
290: }
291:
292: public void actionPerformed(WikiEvent event) {
293: if ((event instanceof WikiPageEvent)
294: && (event.getType() == WikiPageEvent.PAGE_DELETE_REQUEST)) {
295: String pageName = ((WikiPageEvent) event).getPageName();
296:
297: WikiPage p = m_engine.getPage(pageName);
298: if (p != null) {
299: pageRemoved(p);
300: }
301: }
302: }
303:
304: }
|