1
22
23 package com.liferay.portal.search.lucene;
24
25 import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
26 import com.liferay.portal.kernel.log.Log;
27 import com.liferay.portal.kernel.log.LogFactoryUtil;
28 import com.liferay.portal.kernel.search.Field;
29 import com.liferay.portal.kernel.util.PropsKeys;
30 import com.liferay.portal.kernel.util.StringPool;
31 import com.liferay.portal.kernel.util.StringUtil;
32 import com.liferay.portal.kernel.util.Validator;
33 import com.liferay.portal.util.PropsUtil;
34 import com.liferay.util.lucene.KeywordsUtil;
35
36 import java.io.IOException;
37
38 import java.util.HashSet;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.concurrent.ConcurrentHashMap;
42
43 import org.apache.lucene.analysis.Analyzer;
44 import org.apache.lucene.analysis.TokenStream;
45 import org.apache.lucene.analysis.WhitespaceAnalyzer;
46 import org.apache.lucene.document.Document;
47 import org.apache.lucene.index.Term;
48 import org.apache.lucene.queryParser.ParseException;
49 import org.apache.lucene.queryParser.QueryParser;
50 import org.apache.lucene.search.BooleanClause;
51 import org.apache.lucene.search.BooleanQuery;
52 import org.apache.lucene.search.IndexSearcher;
53 import org.apache.lucene.search.Query;
54 import org.apache.lucene.search.TermQuery;
55 import org.apache.lucene.search.WildcardQuery;
56 import org.apache.lucene.search.highlight.Highlighter;
57 import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
58 import org.apache.lucene.search.highlight.QueryScorer;
59 import org.apache.lucene.search.highlight.QueryTermExtractor;
60 import org.apache.lucene.search.highlight.SimpleFragmenter;
61 import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
62 import org.apache.lucene.search.highlight.WeightedTerm;
63
64
71 public class LuceneHelperImpl implements LuceneHelper {
72
73 public void addDocument(long companyId, Document document)
74 throws IOException {
75
76 IndexAccessor indexAccessor = _getIndexAccessor(companyId);
77
78 indexAccessor.addDocument(document);
79 }
80
81 public void addExactTerm(
82 BooleanQuery booleanQuery, String field, String value) {
83
84
86 Query query = new TermQuery(new Term(field, value));
87
88 booleanQuery.add(query, BooleanClause.Occur.SHOULD);
89 }
90
91 public void addRequiredTerm(
92 BooleanQuery booleanQuery, String field, String value, boolean like) {
93
94 if (like) {
95 value = StringUtil.replace(
96 value, StringPool.PERCENT, StringPool.STAR);
97
98 value = value.toLowerCase();
99
100 WildcardQuery wildcardQuery = new WildcardQuery(
101 new Term(field, value));
102
103 booleanQuery.add(wildcardQuery, BooleanClause.Occur.MUST);
104 }
105 else {
106
108 Term term = new Term(field, value);
109 TermQuery termQuery = new TermQuery(term);
110
111 booleanQuery.add(termQuery, BooleanClause.Occur.MUST);
112 }
113 }
114
115 public void addTerm(
116 BooleanQuery booleanQuery, String field, String value, boolean like)
117 throws ParseException {
118
119 if (Validator.isNull(value)) {
120 return;
121 }
122
123 if (like) {
124 value = value.toLowerCase();
125
126 StringBuilder sb = new StringBuilder();
127
128 sb.append(StringPool.STAR);
129 sb.append(value);
130 sb.append(StringPool.STAR);
131
132 WildcardQuery wildcardQuery = new WildcardQuery(
133 new Term(field, sb.toString()));
134
135 booleanQuery.add(wildcardQuery, BooleanClause.Occur.SHOULD);
136 }
137 else {
138 QueryParser queryParser = new QueryParser(field, getAnalyzer());
139
140 try {
141 Query query = queryParser.parse(value);
142
143 booleanQuery.add(query, BooleanClause.Occur.SHOULD);
144 }
145 catch (ParseException pe) {
146 if (_log.isDebugEnabled()) {
147 _log.debug(
148 "ParseException thrown, reverting to literal search",
149 pe);
150 }
151
152 value = KeywordsUtil.escape(value);
153
154 Query query = queryParser.parse(value);
155
156 booleanQuery.add(query, BooleanClause.Occur.SHOULD);
157 }
158 }
159 }
160
161 public void delete(long companyId) {
162 IndexAccessor indexAccessor = _getIndexAccessor(companyId);
163
164 indexAccessor.delete();
165 }
166
167 public void deleteDocuments(long companyId, Term term) throws IOException {
168 IndexAccessor indexAccessor = _getIndexAccessor(companyId);
169
170 indexAccessor.deleteDocuments(term);
171 }
172
173 public Analyzer getAnalyzer() {
174 try {
175 return (Analyzer)_analyzerClass.newInstance();
176 }
177 catch (Exception e) {
178 throw new RuntimeException(e);
179 }
180 }
181
182 public String[] getQueryTerms(Query query) {
183 String[] fieldNames = new String[] {
184 Field.CONTENT, Field.DESCRIPTION, Field.PROPERTIES, Field.TITLE,
185 Field.USER_NAME
186 };
187
188 WeightedTerm[] weightedTerms = null;
189
190 for (String fieldName : fieldNames) {
191 weightedTerms = QueryTermExtractor.getTerms(
192 query, false, fieldName);
193
194 if (weightedTerms.length > 0) {
195 break;
196 }
197 }
198
199 Set<String> queryTerms = new HashSet<String>();
200
201 for (WeightedTerm weightedTerm : weightedTerms) {
202 queryTerms.add(weightedTerm.getTerm());
203 }
204
205 return queryTerms.toArray(new String[queryTerms.size()]);
206 }
207
208 public IndexSearcher getSearcher(long companyId, boolean readOnly)
209 throws IOException {
210
211 IndexAccessor indexAccessor = _getIndexAccessor(companyId);
212
213 return new IndexSearcher(indexAccessor.getLuceneDir(), readOnly);
214 }
215
216 public String getSnippet(
217 Query query, String field, String s, int maxNumFragments,
218 int fragmentLength, String fragmentSuffix, String preTag,
219 String postTag)
220 throws IOException {
221
222 SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(
223 preTag, postTag);
224
225 QueryScorer queryScorer = new QueryScorer(query, field);
226
227 Highlighter highlighter = new Highlighter(
228 simpleHTMLFormatter, queryScorer);
229
230 highlighter.setTextFragmenter(new SimpleFragmenter(fragmentLength));
231
232 TokenStream tokenStream = getAnalyzer().tokenStream(
233 field, new UnsyncStringReader(s));
234
235 try {
236 String snippet = highlighter.getBestFragments(
237 tokenStream, s, maxNumFragments, fragmentSuffix);
238
239 if (Validator.isNotNull(snippet) &&
240 !StringUtil.endsWith(snippet, fragmentSuffix)) {
241
242 snippet = snippet + fragmentSuffix;
243 }
244
245 return snippet;
246 }
247 catch (InvalidTokenOffsetsException itoe) {
248 throw new IOException(itoe.getMessage());
249 }
250 }
251
252 public void updateDocument(long companyId, Term term, Document document)
253 throws IOException {
254
255 IndexAccessor indexAccessor = _getIndexAccessor(companyId);
256
257 indexAccessor.updateDocument(term, document);
258 }
259
260 public void shutdown() {
261 for (IndexAccessor indexAccessor : _indexAccessorMap.values()) {
262 indexAccessor.close();
263 }
264 }
265
266 private LuceneHelperImpl() {
267 String analyzerName = PropsUtil.get(PropsKeys.LUCENE_ANALYZER);
268
269 if (Validator.isNotNull(analyzerName)) {
270 try {
271 _analyzerClass = Class.forName(analyzerName);
272 }
273 catch (Exception e) {
274 _log.error(e);
275 }
276 }
277 }
278
279 private IndexAccessor _getIndexAccessor(long companyId) {
280 IndexAccessor indexAccessor = _indexAccessorMap.get(companyId);
281
282 if (indexAccessor == null) {
283 synchronized (this) {
284 indexAccessor = _indexAccessorMap.get(companyId);
285
286 if (indexAccessor == null) {
287 indexAccessor = new IndexAccessorImpl(companyId);
288
289 _indexAccessorMap.put(companyId, indexAccessor);
290 }
291 }
292 }
293
294 return indexAccessor;
295 }
296
297 private static Log _log = LogFactoryUtil.getLog(LuceneHelperImpl.class);
298
299 private Class<?> _analyzerClass = WhitespaceAnalyzer.class;
300 private Map<Long, IndexAccessor> _indexAccessorMap =
301 new ConcurrentHashMap<Long, IndexAccessor>();
302
303 }