001: /**
002: * Copyright (C) 2001 Yasna.com. All rights reserved.
003: *
004: * ===================================================================
005: * The Apache Software License, Version 1.1
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by
022: * Yasna.com (http://www.yasna.com)."
023: * Alternately, this acknowledgment may appear in the software itself,
024: * if and wherever such third-party acknowledgments normally appear.
025: *
026: * 4. The names "Yazd" and "Yasna.com" must not be used to
027: * endorse or promote products derived from this software without
028: * prior written permission. For written permission, please
029: * contact yazd@yasna.com.
030: *
031: * 5. Products derived from this software may not be called "Yazd",
032: * nor may "Yazd" appear in their name, without prior written
033: * permission of Yasna.com.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL YASNA.COM OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of Yasna.com. For more information
051: * on Yasna.com, please see <http://www.yasna.com>.
052: */
053:
054: /**
055: * Copyright (C) 2000 CoolServlets.com. All rights reserved.
056: *
057: * ===================================================================
058: * The Apache Software License, Version 1.1
059: *
060: * Redistribution and use in source and binary forms, with or without
061: * modification, are permitted provided that the following conditions
062: * are met:
063: *
064: * 1. Redistributions of source code must retain the above copyright
065: * notice, this list of conditions and the following disclaimer.
066: *
067: * 2. Redistributions in binary form must reproduce the above copyright
068: * notice, this list of conditions and the following disclaimer in
069: * the documentation and/or other materials provided with the
070: * distribution.
071: *
072: * 3. The end-user documentation included with the redistribution,
073: * if any, must include the following acknowledgment:
074: * "This product includes software developed by
075: * CoolServlets.com (http://www.coolservlets.com)."
076: * Alternately, this acknowledgment may appear in the software itself,
077: * if and wherever such third-party acknowledgments normally appear.
078: *
079: * 4. The names "Jive" and "CoolServlets.com" must not be used to
080: * endorse or promote products derived from this software without
081: * prior written permission. For written permission, please
082: * contact webmaster@coolservlets.com.
083: *
084: * 5. Products derived from this software may not be called "Jive",
085: * nor may "Jive" appear in their name, without prior written
086: * permission of CoolServlets.com.
087: *
088: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
089: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
090: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
091: * DISCLAIMED. IN NO EVENT SHALL COOLSERVLETS.COM OR
092: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
093: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
094: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
095: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
096: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
097: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
098: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
099: * SUCH DAMAGE.
100: * ====================================================================
101: *
102: * This software consists of voluntary contributions made by many
103: * individuals on behalf of CoolServlets.com. For more information
104: * on CoolServlets.com, please see <http://www.coolservlets.com>.
105: */package com.Yasna.forum.database;
106:
107: import java.util.Date;
108: import java.util.Iterator;
109: import java.io.IOException;
110: import java.io.File;
111:
112: import org.apache.lucene.analysis.*;
113: import org.apache.lucene.analysis.standard.*;
114: import org.apache.lucene.document.*;
115: import org.apache.lucene.store.*;
116: import org.apache.lucene.search.*;
117: import org.apache.lucene.queryParser.*;
118: import org.apache.lucene.index.*;
119:
120: import com.Yasna.forum.*;
121:
122: /**
123: * Database implementation of the Query interface using Lucene.
124: */
125: public class DbQuery implements com.Yasna.forum.Query {
126:
127: /**
128: * The query String to use for searching. Set it the empty String by
129: * default so that if the user fails to set a query String, there won't
130: * be errors.
131: */
132: private String queryString = "";
133: private java.util.Date beforeDate = null;
134: private java.util.Date afterDate = null;
135: private User user = null;
136: private ForumThread thread = null;
137:
138: /**
139: * The results of the query as an array of message ID's.
140: */
141: private int[] results = null;
142:
143: private Forum forum;
144: private DbForumFactory factory;
145:
146: /**
147: * The maximum number of results to return with a search.
148: */
149: private static final int MAX_RESULTS_SIZE = 500;
150:
151: private static String indexPath = null;
152: private static IndexReader reader;
153: private static Searcher searcher;
154: private static Directory searchDirectory = null;
155: private static long indexLastModified;
156: private static Analyzer analyzer = new StandardAnalyzer();
157:
158: public DbQuery(Forum forum, DbForumFactory factory) {
159: this .forum = forum;
160: this .factory = factory;
161: }
162:
163: public DbQuery(DbForumFactory factory) {
164: this .factory = factory;
165: this .forum = null;
166: }
167:
168: public void setQueryString(String queryString) {
169: this .queryString = queryString;
170: //reset results
171: results = null;
172: }
173:
174: public String getQueryString() {
175: return queryString;
176: }
177:
178: public void setBeforeDate(java.util.Date beforeDate) {
179: this .beforeDate = beforeDate;
180: //reset results
181: results = null;
182: }
183:
184: public java.util.Date getBeforeDate() {
185: return beforeDate;
186: }
187:
188: public void setAfterDate(java.util.Date afterDate) {
189: this .afterDate = afterDate;
190: //reset results
191: results = null;
192: }
193:
194: public java.util.Date getAfterDate() {
195: return afterDate;
196: }
197:
198: public User getFilteredUser() {
199: return user;
200: }
201:
202: public void filterOnUser(User user) {
203: this .user = user;
204: results = null;
205: }
206:
207: public ForumThread getFilteredThread() {
208: return thread;
209: }
210:
211: public void filterOnThread(ForumThread thread) {
212: this .thread = thread;
213: results = null;
214: }
215:
216: public int resultCount() {
217: if (results == null) {
218: executeQuery();
219: }
220: return results.length;
221: }
222:
223: public Iterator results() {
224: if (results == null) {
225: executeQuery();
226: }
227: return new DbQueryIterator(results, factory);
228: }
229:
230: public Iterator results(int startIndex, int numResults) {
231: if (results == null) {
232: executeQuery();
233: }
234: return new DbQueryIterator(results, factory, startIndex,
235: numResults);
236: }
237:
238: /**
239: * Execute the query and store the results in the results array.
240: */
241: private void executeQuery() {
242: try {
243: Searcher searcher = getSearcher();
244: if (searcher == null) {
245: //Searcher can be null if the index doesn't exist.
246: results = new int[0];
247: return;
248: }
249:
250: org.apache.lucene.search.Query bodyQuery = QueryParser
251: .parse(queryString, "body", analyzer);
252:
253: org.apache.lucene.search.Query subjectQuery = QueryParser
254: .parse(queryString, "subject", analyzer);
255:
256: BooleanQuery comboQuery = new BooleanQuery();
257: comboQuery.add(subjectQuery, false, false);
258: comboQuery.add(bodyQuery, false, false);
259:
260: MultiFilter multiFilter = new MultiFilter(2);
261: // This is added in case the Lucene is used to index other material.
262: multiFilter.add(new FieldFilter("Indexer", "FORUMS"));
263:
264: if (forum != null) { // if no forum specified, search all forums
265: //Forum filter
266: String forumID = Integer.toString(forum.getID());
267: multiFilter.add(new FieldFilter("forumID", forumID));
268: }
269:
270: //Date filter
271: if (beforeDate != null || afterDate != null) {
272: if (beforeDate != null && afterDate != null) {
273: multiFilter.add(new DateFilter("creationDate",
274: beforeDate, afterDate));
275: } else if (beforeDate == null) {
276: multiFilter.add(DateFilter.After("creationDate",
277: afterDate));
278: } else {
279: multiFilter.add(DateFilter.Before("creationDate",
280: beforeDate));
281: }
282: }
283:
284: //User filter
285: if (user != null) {
286: String userID = Integer.toString(user.getID());
287: multiFilter.add(new FieldFilter("userID", userID));
288: }
289:
290: //Thread filter
291: if (thread != null) {
292: String threadID = Integer.toString(thread.getID());
293: multiFilter.add(new FieldFilter("threadID", threadID));
294: }
295:
296: Hits hits = searcher.search(comboQuery, multiFilter);
297: //Don't return more search results than the maximum number allowed.
298: int numResults = hits.length() < MAX_RESULTS_SIZE ? hits
299: .length() : MAX_RESULTS_SIZE;
300: int[] messages = new int[numResults];
301: for (int i = 0; i < numResults; i++) {
302: messages[i] = Integer.parseInt(((Document) hits.doc(i))
303: .get("messageID"));
304: }
305: results = messages;
306: } catch (Exception e) {
307: e.printStackTrace();
308: results = new int[0];
309: }
310: }
311:
312: /**
313: * Returns a Lucene Searcher that can be used to execute queries. Lucene
314: * can handle index reading even while updates occur. However, in order
315: * for index changes to be reflected in search results, the reader must
316: * be re-opened whenever the modifiedDate changes.<p>
317: *
318: * The location of the index is the "search" subdirectory in [yazdHome]. If
319: * yazdHome is not set correctly, this method will fail.
320: *
321: * @return a Searcher that can be used to execute queries.
322: */
323: private static Searcher getSearcher() throws IOException {
324: if (indexPath == null) {
325: //Get path of where search index should be. It should be
326: //the search subdirectory of [yazdHome].
327: String yazdHome = PropertyManager.getProperty("yazdHome");
328: if (yazdHome == null) {
329: System.err
330: .println("ERROR: the yazdHome property is not set.");
331: throw new IOException(
332: "Unable to open index for searching "
333: + "because yazdHome was not set.");
334: }
335: indexPath = yazdHome + File.separator + "search";
336: }
337:
338: if (searcher == null) {
339: //Acquire a lock -- analyzer is a convenient object to do this on.
340: synchronized (analyzer) {
341: if (searcher == null) {
342: if (indexExists(indexPath)) {
343: searchDirectory = FSDirectory.getDirectory(
344: indexPath, false);
345: reader = IndexReader.open(searchDirectory);
346: indexLastModified = reader
347: .lastModified(searchDirectory);
348: searcher = new IndexSearcher(reader);
349: }
350: //Otherwise, the index doesn't exist, so return null.
351: else {
352: return null;
353: }
354: }
355: }
356: }
357: if (reader.lastModified(searchDirectory) > indexLastModified) {
358: synchronized (analyzer) {
359: if (reader.lastModified(searchDirectory) > indexLastModified) {
360: if (indexExists(indexPath)) {
361: indexLastModified = reader
362: .lastModified(searchDirectory);
363: //We need to close the indexReader because it has changed.
364: //Re-opening it will make changes visible.
365: reader.close();
366:
367: searchDirectory = FSDirectory.getDirectory(
368: indexPath, false);
369: reader = IndexReader.open(searchDirectory);
370: searcher = new IndexSearcher(reader);
371: }
372: //Otherwise, the index doesn't exist, so return null.
373: else {
374: return null;
375: }
376: }
377: }
378: }
379: return searcher;
380: }
381:
382: /**
383: * Returns true if the search index exists at the specified path.
384: *
385: * @param indexPath the path to check for the search index at.
386: */
387: private static boolean indexExists(String indexPath) {
388: //Lucene always creates a file called "segments" -- if it exists, we
389: //assume that the search index exists.
390: File segments = new File(indexPath + File.separator
391: + "segments");
392: return segments.exists();
393: }
394: }
|