001: /**
002: * Copyright (c) 2000-2008 Liferay, Inc. All rights reserved.
003: *
004: * Permission is hereby granted, free of charge, to any person obtaining a copy
005: * of this software and associated documentation files (the "Software"), to deal
006: * in the Software without restriction, including without limitation the rights
007: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008: * copies of the Software, and to permit persons to whom the Software is
009: * furnished to do so, subject to the following conditions:
010: *
011: * The above copyright notice and this permission notice shall be included in
012: * all copies or substantial portions of the Software.
013: *
014: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
020: * SOFTWARE.
021: */package com.liferay.portal.lucene;
022:
023: import com.liferay.portal.SystemException;
024: import com.liferay.portal.kernel.search.Hits;
025: import com.liferay.portal.kernel.util.GetterUtil;
026: import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
027: import com.liferay.portal.kernel.util.StringMaker;
028: import com.liferay.portal.kernel.util.StringPool;
029: import com.liferay.portal.kernel.util.Validator;
030: import com.liferay.portal.spring.hibernate.HibernateUtil;
031: import com.liferay.portal.util.PropsUtil;
032: import com.liferay.portal.util.PropsValues;
033: import com.liferay.util.CollectionFactory;
034: import com.liferay.util.FileUtil;
035: import com.liferay.util.dao.DataAccess;
036: import com.liferay.util.lucene.HitsImpl;
037: import com.liferay.util.lucene.KeywordsUtil;
038:
039: import java.io.IOException;
040:
041: import java.sql.Connection;
042: import java.sql.DatabaseMetaData;
043: import java.sql.ResultSet;
044: import java.sql.Statement;
045:
046: import java.util.Date;
047: import java.util.Map;
048: import java.util.regex.Pattern;
049:
050: import javax.sql.DataSource;
051:
052: import org.apache.commons.logging.Log;
053: import org.apache.commons.logging.LogFactory;
054: import org.apache.lucene.analysis.Analyzer;
055: import org.apache.lucene.analysis.WhitespaceAnalyzer;
056: import org.apache.lucene.document.Document;
057: import org.apache.lucene.index.IndexReader;
058: import org.apache.lucene.index.IndexWriter;
059: import org.apache.lucene.index.Term;
060: import org.apache.lucene.queryParser.ParseException;
061: import org.apache.lucene.queryParser.QueryParser;
062: import org.apache.lucene.search.BooleanClause;
063: import org.apache.lucene.search.BooleanQuery;
064: import org.apache.lucene.search.IndexSearcher;
065: import org.apache.lucene.search.Query;
066: import org.apache.lucene.search.Searcher;
067: import org.apache.lucene.search.TermQuery;
068: import org.apache.lucene.store.Directory;
069: import org.apache.lucene.store.FSDirectory;
070: import org.apache.lucene.store.RAMDirectory;
071: import org.apache.lucene.store.jdbc.JdbcDirectory;
072: import org.apache.lucene.store.jdbc.JdbcStoreException;
073: import org.apache.lucene.store.jdbc.dialect.Dialect;
074: import org.apache.lucene.store.jdbc.lock.JdbcLock;
075: import org.apache.lucene.store.jdbc.support.JdbcTemplate;
076:
077: /**
078: * <a href="LuceneUtil.java.html"><b><i>View Source</i></b></a>
079: *
080: * @author Brian Wing Shun Chan
081: * @author Harry Mark
082: *
083: */
084: public class LuceneUtil {
085:
086: public static boolean INDEX_READ_ONLY = GetterUtil
087: .getBoolean(PropsUtil.get(PropsUtil.INDEX_READ_ONLY));
088:
089: public static final Pattern TERM_END_PATTERN = Pattern
090: .compile("(\\w{4,}?)\\b");
091:
092: public static void acquireLock(long companyId) {
093: try {
094: _instance._sharedWriter.acquireLock(companyId, true);
095: } catch (InterruptedException ie) {
096: _log.error(ie);
097: }
098: }
099:
100: public static void addDate(Document doc, String field, Date value) {
101: doc.add(LuceneFields.getDate(field, value));
102: }
103:
104: public static void addExactTerm(BooleanQuery booleanQuery,
105: String field, long value) throws ParseException {
106:
107: addExactTerm(booleanQuery, field, String.valueOf(value));
108: }
109:
110: public static void addExactTerm(BooleanQuery booleanQuery,
111: String field, String text) throws ParseException {
112:
113: //text = KeywordsUtil.escape(text);
114:
115: Query query = new TermQuery(new Term(field, text));
116:
117: booleanQuery.add(query, BooleanClause.Occur.SHOULD);
118: }
119:
120: public static void addKeyword(Document doc, String field,
121: double value) {
122: addKeyword(doc, field, String.valueOf(value));
123: }
124:
125: public static void addKeyword(Document doc, String field, long value) {
126: addKeyword(doc, field, String.valueOf(value));
127: }
128:
129: public static void addKeyword(Document doc, String field,
130: String value) {
131: if (Validator.isNotNull(value)) {
132: doc.add(LuceneFields.getKeyword(field, value));
133: }
134: }
135:
136: public static void addKeyword(Document doc, String field,
137: String[] values) {
138: if (values == null) {
139: return;
140: }
141:
142: for (int i = 0; i < values.length; i++) {
143: addKeyword(doc, field, values[i]);
144: }
145: }
146:
147: public static void addRequiredTerm(BooleanQuery booleanQuery,
148: String field, long value) {
149:
150: addRequiredTerm(booleanQuery, field, String.valueOf(value));
151: }
152:
153: public static void addRequiredTerm(BooleanQuery booleanQuery,
154: String field, String text) {
155:
156: //text = KeywordsUtil.escape(text);
157:
158: Term term = new Term(field, text);
159: TermQuery termQuery = new TermQuery(term);
160:
161: booleanQuery.add(termQuery, BooleanClause.Occur.MUST);
162: }
163:
164: public static void addModifiedDate(Document doc) {
165: doc.add(LuceneFields.getDate(LuceneFields.MODIFIED));
166: }
167:
168: public static void addTerm(BooleanQuery booleanQuery, String field,
169: long value) throws ParseException {
170:
171: addTerm(booleanQuery, field, String.valueOf(value));
172: }
173:
174: public static void addTerm(BooleanQuery booleanQuery, String field,
175: String text) throws ParseException {
176:
177: if (Validator.isNotNull(text)) {
178: //Matcher matcher = TERM_END_PATTERN.matcher(text);
179:
180: // Add wildcard to the end of every word 4 chars or longer
181:
182: //text = matcher.replaceAll("$1*");
183:
184: // Add fuzzy query to the end of every word 4 chars or longer
185:
186: //text = text + " " + matcher.replaceAll("$1~");
187:
188: // Remove invalid hints
189:
190: //text = StringUtil.replace(text, "**", "*");
191: //text = StringUtil.replace(text, "*~", "*");
192: //text = StringUtil.replace(text, "~~", "~");
193: //text = StringUtil.replace(text, "~*", "~");
194:
195: QueryParser queryParser = new QueryParser(field, LuceneUtil
196: .getAnalyzer());
197:
198: try {
199: Query query = queryParser.parse(text);
200:
201: booleanQuery.add(query, BooleanClause.Occur.SHOULD);
202: } catch (ParseException pe) {
203: if (_log.isDebugEnabled()) {
204: _log
205: .debug(
206: "ParseException thrown, reverting to literal search",
207: pe);
208: }
209:
210: text = KeywordsUtil.escape(text);
211:
212: Query query = queryParser.parse(text);
213:
214: booleanQuery.add(query, BooleanClause.Occur.SHOULD);
215: }
216: }
217: }
218:
219: public static void addText(Document doc, String field, long value) {
220: addText(doc, field, String.valueOf(value));
221: }
222:
223: public static void addText(Document doc, String field, String value) {
224: if (Validator.isNotNull(value)) {
225: doc.add(LuceneFields.getText(field, value));
226: }
227: }
228:
229: public static void checkLuceneDir(long companyId) {
230: if (INDEX_READ_ONLY) {
231: return;
232: }
233:
234: Directory luceneDir = LuceneUtil.getLuceneDir(companyId);
235:
236: IndexWriter writer = null;
237:
238: // Lucene does not properly release its lock on the index when
239: // IndexWriter throws an exception
240:
241: try {
242: if (luceneDir.fileExists("segments.gen")) {
243: writer = new IndexWriter(luceneDir, LuceneUtil
244: .getAnalyzer(), false);
245: } else {
246: writer = new IndexWriter(luceneDir, LuceneUtil
247: .getAnalyzer(), true);
248: }
249: } catch (IOException ioe) {
250: _log.error(ioe);
251: } finally {
252: if (writer != null) {
253: try {
254: writer.close();
255: } catch (IOException ioe) {
256: _log.error(ioe);
257: }
258: }
259: }
260: }
261:
262: public static void closeSearcher(Searcher searcher) {
263: try {
264: if (searcher != null) {
265: searcher.close();
266: }
267: } catch (Exception e) {
268: }
269: }
270:
271: public static Hits closeSearcher(Searcher searcher,
272: String keywords, Exception e) throws SystemException {
273:
274: closeSearcher(searcher);
275:
276: if (e instanceof BooleanQuery.TooManyClauses
277: || e instanceof ParseException) {
278:
279: _log.error("Parsing keywords " + keywords, e);
280:
281: return new HitsImpl();
282: } else {
283: throw new SystemException(e);
284: }
285: }
286:
287: public static void delete(long companyId) {
288: _instance._delete(companyId);
289: }
290:
291: public static void deleteDocuments(long companyId, Term term)
292: throws IOException {
293:
294: try {
295: _instance._sharedWriter.deleteDocuments(companyId, term);
296: } catch (InterruptedException ie) {
297: _log.error(ie);
298: }
299: }
300:
301: public static Analyzer getAnalyzer() {
302: return _instance._getAnalyzer();
303: }
304:
305: public static Directory getLuceneDir(long companyId) {
306: return _instance._getLuceneDir(companyId);
307: }
308:
309: public static IndexReader getReader(long companyId)
310: throws IOException {
311: return IndexReader.open(getLuceneDir(companyId));
312: }
313:
314: public static IndexSearcher getSearcher(long companyId)
315: throws IOException {
316:
317: return new IndexSearcher(getLuceneDir(companyId));
318: }
319:
320: public static IndexWriter getWriter(long companyId)
321: throws IOException {
322: return getWriter(companyId, false);
323: }
324:
325: public static IndexWriter getWriter(long companyId, boolean create)
326: throws IOException {
327:
328: return _instance._sharedWriter.getWriter(companyId, create);
329: }
330:
331: public static void releaseLock(long companyId) {
332: _instance._sharedWriter.releaseLock(companyId);
333: }
334:
335: public static void write(long companyId) throws IOException {
336: _instance._sharedWriter.write(companyId);
337: }
338:
339: public static void write(IndexWriter writer) throws IOException {
340: _instance._sharedWriter.write(writer);
341: }
342:
343: private LuceneUtil() {
344: String analyzerName = PropsUtil.get(PropsUtil.LUCENE_ANALYZER);
345:
346: if (Validator.isNotNull(analyzerName)) {
347: try {
348: _analyzerClass = Class.forName(analyzerName);
349: } catch (Exception e) {
350: _log.error(e);
351: }
352: }
353:
354: // Dialect
355:
356: if (PropsValues.LUCENE_STORE_TYPE
357: .equals(_LUCENE_STORE_TYPE_JDBC)) {
358: Connection con = null;
359:
360: try {
361: con = HibernateUtil.getConnection();
362:
363: String url = con.getMetaData().getURL();
364:
365: int x = url.indexOf(":");
366: int y = url.indexOf(":", x + 1);
367:
368: String urlPrefix = url.substring(x + 1, y);
369:
370: String dialectClass = PropsUtil
371: .get(PropsUtil.LUCENE_STORE_JDBC_DIALECT
372: + urlPrefix);
373:
374: if (dialectClass != null) {
375: if (_log.isDebugEnabled()) {
376: _log.debug("JDBC class implementation "
377: + dialectClass);
378: }
379: } else {
380: if (_log.isDebugEnabled()) {
381: _log.debug("JDBC class implementation is null");
382: }
383: }
384:
385: if (dialectClass != null) {
386: _dialect = (Dialect) Class.forName(dialectClass)
387: .newInstance();
388: }
389: } catch (Exception e) {
390: _log.error(e);
391: } finally {
392: DataAccess.cleanUp(con);
393: }
394:
395: if (_dialect == null) {
396: _log.error("No JDBC dialect found");
397: }
398: }
399: }
400:
401: public void _delete(long companyId) {
402: if (LuceneUtil.INDEX_READ_ONLY) {
403: return;
404: }
405:
406: if (_log.isDebugEnabled()) {
407: _log.debug("Lucene store type "
408: + PropsValues.LUCENE_STORE_TYPE);
409: }
410:
411: if (PropsValues.LUCENE_STORE_TYPE
412: .equals(_LUCENE_STORE_TYPE_FILE)) {
413: _deleteFile(companyId);
414: } else if (PropsValues.LUCENE_STORE_TYPE
415: .equals(_LUCENE_STORE_TYPE_JDBC)) {
416:
417: _deleteJdbc(companyId);
418: } else if (PropsValues.LUCENE_STORE_TYPE
419: .equals(_LUCENE_STORE_TYPE_RAM)) {
420: _deleteRam(companyId);
421: } else {
422: throw new RuntimeException("Invalid store type "
423: + PropsValues.LUCENE_STORE_TYPE);
424: }
425: }
426:
427: private void _deleteFile(long companyId) {
428: String path = _getPath(companyId);
429:
430: try {
431: Directory directory = FSDirectory.getDirectory(path, false);
432:
433: directory.close();
434: } catch (Exception e) {
435: if (_log.isWarnEnabled()) {
436: _log.warn("Could not close directory " + path);
437: }
438: }
439:
440: FileUtil.deltree(path);
441: }
442:
443: private void _deleteJdbc(long companyId) {
444: String tableName = _getTableName(companyId);
445:
446: try {
447: Directory directory = (Directory) _jdbcDirectories
448: .remove(tableName);
449:
450: if (directory != null) {
451: directory.close();
452: }
453: } catch (Exception e) {
454: if (_log.isWarnEnabled()) {
455: _log.warn("Could not close directory " + tableName);
456: }
457: }
458:
459: Connection con = null;
460: Statement s = null;
461:
462: try {
463: con = HibernateUtil.getConnection();
464:
465: s = con.createStatement();
466:
467: s.executeUpdate("DELETE FROM " + tableName);
468: } catch (Exception e) {
469: if (_log.isWarnEnabled()) {
470: _log.warn("Could not truncate " + tableName);
471: }
472: } finally {
473: DataAccess.cleanUp(con, s);
474: }
475: }
476:
477: private void _deleteRam(long companyId) {
478: }
479:
480: private Analyzer _getAnalyzer() {
481: try {
482: return (Analyzer) _analyzerClass.newInstance();
483: } catch (Exception e) {
484: throw new RuntimeException(e);
485: }
486: }
487:
488: private Directory _getLuceneDir(long companyId) {
489: if (_log.isDebugEnabled()) {
490: _log.debug("Lucene store type "
491: + PropsValues.LUCENE_STORE_TYPE);
492: }
493:
494: if (PropsValues.LUCENE_STORE_TYPE
495: .equals(_LUCENE_STORE_TYPE_FILE)) {
496: return _getLuceneDirFile(companyId);
497: } else if (PropsValues.LUCENE_STORE_TYPE
498: .equals(_LUCENE_STORE_TYPE_JDBC)) {
499:
500: return _getLuceneDirJdbc(companyId);
501: } else if (PropsValues.LUCENE_STORE_TYPE
502: .equals(_LUCENE_STORE_TYPE_RAM)) {
503: return _getLuceneDirRam(companyId);
504: } else {
505: throw new RuntimeException("Invalid store type "
506: + PropsValues.LUCENE_STORE_TYPE);
507: }
508: }
509:
510: private Directory _getLuceneDirFile(long companyId) {
511: Directory directory = null;
512:
513: String path = _getPath(companyId);
514:
515: try {
516: directory = FSDirectory.getDirectory(path, false);
517: } catch (IOException ioe1) {
518: try {
519: if (directory != null) {
520: directory.close();
521: }
522:
523: directory = FSDirectory.getDirectory(path, true);
524: } catch (IOException ioe2) {
525: throw new RuntimeException(ioe2);
526: }
527: }
528:
529: return directory;
530: }
531:
532: private Directory _getLuceneDirJdbc(long companyId) {
533: JdbcDirectory directory = null;
534:
535: ClassLoader contextClassLoader = Thread.currentThread()
536: .getContextClassLoader();
537:
538: try {
539: Thread.currentThread().setContextClassLoader(
540: PortalClassLoaderUtil.getClassLoader());
541:
542: String tableName = _getTableName(companyId);
543:
544: directory = (JdbcDirectory) _jdbcDirectories.get(tableName);
545:
546: if (directory != null) {
547: return directory;
548: }
549:
550: try {
551: DataSource ds = HibernateUtil.getDataSource();
552:
553: directory = new JdbcDirectory(ds, _dialect, tableName);
554:
555: _jdbcDirectories.put(tableName, directory);
556:
557: if (!directory.tableExists()) {
558: directory.create();
559: }
560: } catch (IOException ioe) {
561: throw new RuntimeException(ioe);
562: } catch (UnsupportedOperationException uoe) {
563: if (_log.isWarnEnabled()) {
564: _log
565: .warn("Database doesn't support the ability to check "
566: + "whether a table exists");
567: }
568:
569: _manuallyCreateJdbcDirectory(directory, tableName);
570: }
571: } finally {
572: Thread.currentThread().setContextClassLoader(
573: contextClassLoader);
574: }
575:
576: return directory;
577: }
578:
579: private Directory _getLuceneDirRam(long companyId) {
580: String path = _getPath(companyId);
581:
582: Directory directory = (Directory) _ramDirectories.get(path);
583:
584: if (directory == null) {
585: directory = new RAMDirectory();
586:
587: _ramDirectories.put(path, directory);
588: }
589:
590: return directory;
591: }
592:
593: private String _getPath(long companyId) {
594: StringMaker sm = new StringMaker();
595:
596: sm.append(PropsValues.LUCENE_DIR);
597: sm.append(companyId);
598: sm.append(StringPool.SLASH);
599:
600: return sm.toString();
601: }
602:
603: private String _getTableName(long companyId) {
604: return _LUCENE_TABLE_PREFIX + companyId;
605: }
606:
607: private void _manuallyCreateJdbcDirectory(JdbcDirectory directory,
608: String tableName) {
609:
610: // LEP-2181
611:
612: Connection con = null;
613: ResultSet rs = null;
614:
615: try {
616: con = HibernateUtil.getConnection();
617:
618: // Check if table exists
619:
620: DatabaseMetaData metaData = con.getMetaData();
621:
622: rs = metaData.getTables(null, null, tableName, null);
623:
624: if (!rs.next()) {
625: JdbcTemplate jdbcTemplate = directory.getJdbcTemplate();
626:
627: jdbcTemplate.executeUpdate(directory.getTable()
628: .sqlCreate());
629:
630: Class lockClass = directory.getSettings()
631: .getLockClass();
632:
633: JdbcLock jdbcLock = null;
634:
635: try {
636: jdbcLock = (JdbcLock) lockClass.newInstance();
637: } catch (Exception e) {
638: throw new JdbcStoreException(
639: "Failed to create lock class " + lockClass);
640: }
641:
642: jdbcLock.initializeDatabase(directory);
643: }
644: } catch (Exception e) {
645: if (_log.isWarnEnabled()) {
646: _log.warn("Could not create " + tableName);
647: }
648: } finally {
649: DataAccess.cleanUp(con, null, rs);
650: }
651: }
652:
653: private static final String _LUCENE_STORE_TYPE_FILE = "file";
654:
655: private static final String _LUCENE_STORE_TYPE_JDBC = "jdbc";
656:
657: private static final String _LUCENE_STORE_TYPE_RAM = "ram";
658:
659: private static final String _LUCENE_TABLE_PREFIX = "LUCENE_";
660:
661: private static Log _log = LogFactory.getLog(LuceneUtil.class);
662:
663: private static LuceneUtil _instance = new LuceneUtil();
664:
665: private IndexWriterFactory _sharedWriter = new IndexWriterFactory();
666: private Class _analyzerClass = WhitespaceAnalyzer.class;
667: private Dialect _dialect;
668: private Map _jdbcDirectories = CollectionFactory.getSyncHashMap();
669: private Map _ramDirectories = CollectionFactory.getSyncHashMap();
670:
671: }
|