package in.partake.service.impl; import in.partake.app.PartakeConfiguration; import in.partake.base.TimeUtil; import in.partake.model.dao.DAOException; import in.partake.model.dto.auxiliary.EventCategory; import in.partake.service.EventSearchServiceException; import java.io.File; import java.io.IOException; import java.util.Date; import org.apache.commons.lang.StringUtils; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.cjk.CJKAnalyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryWrapperFilter; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import play.Logger; /** * @author shinyak */ public class LuceneService { private static volatile LuceneService instance; private IndexWriter indexWriter; private IndexReader indexReader; private IndexSearcher indexSearcher; private Analyzer analyzer; public static LuceneService get() { return instance; } public static void initialize() throws EventSearchServiceException { Logger.info("LuceneService is being initialized."); if (instance != null) return; instance = new LuceneService(); } public static void destroy() throws EventSearchServiceException { Logger.info("LuceneService is being destructed."); if (instance == null) return; try { instance.cleanUp(); } catch (IOException e) { throw new EventSearchServiceException(e); } instance = null; } private LuceneService() { try { File indexDirFile = new File(PartakeConfiguration.luneceIndexDir()); Directory indexDir = FSDirectory.open(indexDirFile); // create index. Analyzer luceneAnalyzer = new StandardAnalyzer(Version.LUCENE_30); indexWriter = new IndexWriter(indexDir, luceneAnalyzer, new IndexWriter.MaxFieldLength(1024*1024)); indexReader = indexWriter.getReader(); indexSearcher = new IndexSearcher(indexReader); analyzer = new CJKAnalyzer(Version.LUCENE_30); } catch (IOException e) { Logger.error("LuceneService cannot be initialized", e); indexWriter = null; indexReader = null; indexSearcher = null; analyzer = null; } } public void addDocument(Document doc) throws EventSearchServiceException { try { indexWriter.addDocument(doc, analyzer); indexWriter.commit(); reset(); } catch (CorruptIndexException e) { throw new EventSearchServiceException(e); } catch (IOException e) { throw new EventSearchServiceException(e); } } public void updateDocument(Document doc) throws EventSearchServiceException { try { indexWriter.updateDocument(new Term("ID", doc.get("ID")), doc, analyzer); indexWriter.commit(); reset(); } catch (CorruptIndexException e) { throw new EventSearchServiceException(e); } catch (IOException e) { throw new EventSearchServiceException(e); } } public void removeDocument(String id) throws EventSearchServiceException { try { indexWriter.deleteDocuments(new Term("ID", id)); indexWriter.commit(); reset(); } catch (CorruptIndexException e) { throw new EventSearchServiceException(e); } catch (IOException e) { throw new EventSearchServiceException(e); } } public boolean hasDocument(String id) throws EventSearchServiceException { try { Query query = new TermQuery(new Term("ID", id)); TopDocs docs = indexSearcher.search(query, 1); return docs.totalHits > 0; } catch (IOException e) { throw new EventSearchServiceException(e); } } public Document getDocument(int doc) throws EventSearchServiceException { try { return indexSearcher.doc(doc); } catch (CorruptIndexException e) { throw new EventSearchServiceException(e); } catch (IOException e) { throw new EventSearchServiceException(e); } } public TopDocs getRecentDocuments(int n) throws EventSearchServiceException { try { long current = new Date().getTime(); Query query = new TermRangeQuery("DEADLINE-TIME", TimeUtil.getTimeString(current), TimeUtil.getTimeString(Long.MAX_VALUE), true, true); Sort sort = new Sort(new SortField("CREATED-AT", SortField.STRING, true)); return indexSearcher.search(query, null, n, sort); } catch (IOException e) { throw new EventSearchServiceException(e); } } public TopDocs getRecentCategoryDocuments(String category, int maxDocument) throws EventSearchServiceException, IllegalArgumentException { if (!EventCategory.isValidCategoryName(category)) { throw new IllegalArgumentException("Unknown category"); } Query query = new TermQuery(new Term("CATEGORY", category)); Sort sort = new Sort(new SortField("CREATED-AT", SortField.STRING, true)); try { return indexSearcher.search(query, null, maxDocument, sort); } catch (IOException e) { throw new EventSearchServiceException(e); } } public TopDocs search(String term, String category, String sortOrder, boolean beforeDeadlineOnly, int maxDocument) throws EventSearchServiceException, ParseException, IllegalArgumentException { try { Query query; if (StringUtils.isEmpty(term)) { // If the search term is not null, all events should be displayed. query = new MatchAllDocsQuery(); } else { QueryParser partialParser = new QueryParser(Version.LUCENE_30, "CONTENT", analyzer); query = partialParser.parse(term); } // TODO: なんか汚い...。 Filter filter; if (beforeDeadlineOnly) { long current = new Date().getTime(); if (EventCategory.getAllEventCategory().equals(category) || "".equals(category)) { Query filterQuery = new TermRangeQuery("DEADLINE-TIME", TimeUtil.getTimeString(current), TimeUtil.getTimeString(Long.MAX_VALUE), true, true); filter = new QueryWrapperFilter(filterQuery); } else { if (!EventCategory.isValidCategoryName(category)) { throw new IllegalArgumentException("Unknown category"); } BooleanQuery filterQuery = new BooleanQuery(); filterQuery.add(new BooleanClause(new TermQuery(new Term("CATEGORY", category)), Occur.MUST)); filterQuery.add(new BooleanClause(new TermRangeQuery("DEADLINE-TIME", TimeUtil.getTimeString(current), TimeUtil.getTimeString(Long.MAX_VALUE), true, true), Occur.MUST)); filter = new QueryWrapperFilter(filterQuery); } } else { if (EventCategory.getAllEventCategory().equals(category) || "".equals(category)) { filter = null; // filter = new QueryWrapperFilter(new MatchAllDocsQuery()); } else { if (!EventCategory.isValidCategoryName(category)) { throw new IllegalArgumentException("Unknown category"); } filter = new QueryWrapperFilter(new TermQuery(new Term("CATEGORY", category))); } } // TODO: このへんの定数なんとかするべき。いろんなところに散らばっていて使いにくい。 Sort sort; if ("score".equals(sortOrder)) { sort = new Sort(SortField.FIELD_SCORE, new SortField("BEGIN-TIME", SortField.STRING)); } else if ("createdAt".equals(sortOrder)) { sort = new Sort(new SortField("CREATED-AT", SortField.STRING, true)); } else if ("deadline".equals(sortOrder)) { sort = new Sort(new SortField("DEADLINE-TIME", SortField.STRING)); } else if ("deadline-r".equals(sortOrder)) { sort = new Sort(new SortField("DEADLINE-TIME", SortField.STRING, true)); } else if ("beginDate".equals(sortOrder)) { sort = new Sort(new SortField("BEGIN-TIME", SortField.STRING)); } else if ("beginDate-r".equals(sortOrder)) { sort = new Sort(new SortField("BEGIN-TIME", SortField.STRING, true)); } else { // 決まってない場合は、score 順にする。 sort = new Sort(SortField.FIELD_SCORE, new SortField("BEGIN-TIME", SortField.STRING)); } return indexSearcher.search(query, filter, maxDocument, sort); } catch (IOException e) { throw new EventSearchServiceException(e); } } /** * Lucene index を全て捨てる * @throws DAOException */ public void truncate() throws EventSearchServiceException { try { indexWriter.deleteAll(); indexWriter.commit(); reset(); } catch (CorruptIndexException e) { throw new EventSearchServiceException(e); } catch (IOException e) { throw new EventSearchServiceException(e); } } private void cleanUp() throws IOException { if (indexWriter != null) indexWriter.close(); if (indexReader != null) indexReader.close(); if (indexSearcher != null) indexSearcher.close(); } /** * When index is changed, reset() should be called. */ private synchronized void reset() { try { IndexReader oldReader = indexReader; indexReader = indexWriter.getReader(); indexSearcher = new IndexSearcher(indexReader); if (oldReader != indexReader) oldReader.close(); } catch (IOException e) { throw new RuntimeException(e); // TODO: Is this OK? } } }