001: /*
002: * Copyright (c) JForum Team
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms,
006: * with or without modification, are permitted provided
007: * that the following conditions are met:
008: *
009: * 1) Redistributions of source code must retain the above
010: * copyright notice, this list of conditions and the
011: * following disclaimer.
012: * 2) Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and
014: * the following disclaimer in the documentation and/or
015: * other materials provided with the distribution.
016: * 3) Neither the name of "Rafael Steil" nor
017: * the names of its contributors may be used to endorse
018: * or promote products derived from this software without
019: * specific prior written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
022: * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
023: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
024: * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
025: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR
026: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
027: * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
028: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
029: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES
030: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
031: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
032: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
033: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
034: * IN CONTRACT, STRICT LIABILITY, OR TORT
035: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
036: * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
037: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
038: *
039: * Created on 18/07/2007 22:05:37
040: *
041: * The JForum Project
042: * http://www.jforum.net
043: */
044: package net.jforum.search;
045:
046: import java.io.IOException;
047: import java.io.StringReader;
048: import java.util.ArrayList;
049: import java.util.List;
050:
051: import net.jforum.exceptions.SearchException;
052:
053: import org.apache.log4j.Logger;
054: import org.apache.lucene.analysis.Token;
055: import org.apache.lucene.analysis.TokenStream;
056: import org.apache.lucene.analysis.standard.StandardAnalyzer;
057: import org.apache.lucene.document.Document;
058: import org.apache.lucene.index.Term;
059: import org.apache.lucene.queryParser.QueryParser;
060: import org.apache.lucene.search.Filter;
061: import org.apache.lucene.search.Hits;
062: import org.apache.lucene.search.IndexSearcher;
063: import org.apache.lucene.search.Query;
064: import org.apache.lucene.search.Sort;
065: import org.apache.lucene.search.TermQuery;
066:
067: /**
068: * @author Rafael Steil
069: * @version $Id: LuceneSearch.java,v 1.38 2007/10/05 03:22:37 rafaelsteil Exp $
070: */
071: public class LuceneSearch implements NewDocumentAdded {
072: private static final Logger logger = Logger
073: .getLogger(LuceneSearch.class);
074:
075: private IndexSearcher search;
076: private LuceneSettings settings;
077: private LuceneResultCollector contentCollector;
078:
079: public LuceneSearch(LuceneSettings settings,
080: LuceneResultCollector contentCollector) {
081: this .settings = settings;
082: this .contentCollector = contentCollector;
083:
084: this .openSearch();
085: }
086:
087: /**
088: * @see net.jforum.search.NewDocumentAdded#newDocumentAdded()
089: */
090: public void newDocumentAdded() {
091: try {
092: this .search.close();
093: this .openSearch();
094: } catch (Exception e) {
095: throw new SearchException(e);
096: }
097: }
098:
099: /**
100: * @see net.jforum.dao.SearchDAO#search(net.jforum.search.SearchArgs)
101: */
102: public SearchResult search(SearchArgs args) {
103: return this .performSearch(args, this .contentCollector, null);
104: }
105:
106: public Document findDocumentByPostId(int postId) {
107: Document doc = null;
108:
109: try {
110: Hits hit = this .search.search(new TermQuery(new Term(
111: SearchFields.Keyword.POST_ID, String
112: .valueOf(postId))));
113:
114: if (hit.length() > 0) {
115: doc = hit.doc(0);
116:
117: }
118: } catch (IOException e) {
119: throw new SearchException(e);
120: }
121:
122: return doc;
123: }
124:
125: private SearchResult performSearch(SearchArgs args,
126: LuceneResultCollector resultCollector, Filter filter) {
127: SearchResult result;
128:
129: try {
130: StringBuffer criteria = new StringBuffer(256);
131:
132: this .filterByForum(args, criteria);
133: this .filterByKeywords(args, criteria);
134: this .filterByDateRange(args, criteria);
135:
136: Query query = new QueryParser("", new StandardAnalyzer())
137: .parse(criteria.toString());
138:
139: if (logger.isDebugEnabled()) {
140: logger.debug("Generated query: " + query);
141: }
142:
143: Hits hits = filter == null ? this .search.search(query, this
144: .getSorter(args)) : this .search.search(query,
145: filter, this .getSorter(args));
146:
147: if (hits != null && hits.length() > 0) {
148: result = new SearchResult(resultCollector.collect(args,
149: hits, query), hits.length());
150: } else {
151: result = new SearchResult(new ArrayList(), 0);
152: }
153: } catch (Exception e) {
154: throw new SearchException(e);
155: }
156:
157: return result;
158: }
159:
160: private Sort getSorter(SearchArgs args) {
161: Sort sort = Sort.RELEVANCE;
162:
163: if ("time".equals(args.getOrderBy())) {
164: sort = new Sort(SearchFields.Keyword.POST_ID, "DESC"
165: .equals(args.getOrderDir()));
166: }
167:
168: return sort;
169: }
170:
171: private void filterByDateRange(SearchArgs args,
172: StringBuffer criteria) {
173: if (args.getFromDate() != null) {
174: criteria.append('(').append(SearchFields.Keyword.DATE)
175: .append(": [").append(
176: this .settings.formatDateTime(args
177: .getFromDate())).append(" TO ")
178: .append(
179: this .settings.formatDateTime(args
180: .getToDate())).append(']').append(
181: ')');
182: }
183: }
184:
185: private void filterByKeywords(SearchArgs args, StringBuffer criteria) {
186: String[] keywords = this .analyzeKeywords(args.rawKeywords());
187:
188: for (int i = 0; i < keywords.length; i++) {
189: if (args.shouldMatchAllKeywords()) {
190: criteria.append(" +");
191: }
192:
193: criteria.append('(').append(SearchFields.Indexed.CONTENTS)
194: .append(':')
195: .append(QueryParser.escape(keywords[i])).append(
196: ") ");
197: }
198: }
199:
200: private void filterByForum(SearchArgs args, StringBuffer criteria) {
201: if (args.getForumId() > 0) {
202: criteria.append("+(").append(SearchFields.Keyword.FORUM_ID)
203: .append(':').append(args.getForumId()).append(") ");
204: }
205: }
206:
207: private String[] analyzeKeywords(String contents) {
208: try {
209: TokenStream stream = this .settings.analyzer().tokenStream(
210: "contents", new StringReader(contents));
211: List tokens = new ArrayList();
212:
213: while (true) {
214: Token token = stream.next();
215:
216: if (token == null) {
217: break;
218: }
219:
220: tokens.add(token.termText());
221: }
222:
223: return (String[]) tokens.toArray(new String[0]);
224: } catch (IOException e) {
225: throw new SearchException(e);
226: }
227: }
228:
229: private void openSearch() {
230: try {
231: this .search = new IndexSearcher(this .settings.directory());
232: } catch (IOException e) {
233: throw new SearchException(e.toString(), e);
234: }
235: }
236: }
|