1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   *
13   */
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  /**
59   * <a href="LuceneHelperImpl.java.html"><b><i>View Source</i></b></a>
60   *
61   * @author Brian Wing Shun Chan
62   * @author Harry Mark
63   * @author Bruno Farache
64   */
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          //text = KeywordsUtil.escape(value);
79  
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             //text = KeywordsUtil.escape(value);
101 
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 }