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