/*
* Copyright (c) JForum Team
* All rights reserved.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* 1) Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* 2) Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* 3) Neither the name of "Rafael Steil" nor
* the names of its contributors may be used to endorse
* or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
* HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*
* Created on 18/07/2007 17:18:41
*
* The JForum Project
* http://www.jforum.net
*/
package net.jforum.search;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.jforum.entities.Post;
import net.jforum.exceptions.SearchException;
import net.jforum.util.preferences.ConfigKeys;
import net.jforum.util.preferences.SystemGlobals;
import org.apache.log4j.Logger;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
/**
* @author Rafael Steil
* @version $Id: LuceneIndexer.java,v 1.11 2007/09/01 05:46:53 rafaelsteil Exp $
*/
public class LuceneIndexer
{
private static final Logger logger = Logger.getLogger(LuceneIndexer.class);
private static final Object MUTEX = new Object();
private LuceneSettings settings;
private Directory ramDirectory;
private IndexWriter ramWriter;
private int ramNumDocs;
private List newDocumentAddedList = new ArrayList();
public LuceneIndexer(LuceneSettings settings)
{
this.settings = settings;
this.createRAMWriter();
}
public void watchNewDocuDocumentAdded(NewDocumentAdded newDoc)
{
this.newDocumentAddedList.add(newDoc);
}
public void batchCreate(Post post)
{
synchronized (MUTEX) {
try {
Document document = this.createDocument(post);
this.ramWriter.addDocument(document);
this.flushRAMDirectoryIfNecessary();
}
catch (IOException e) {
throw new SearchException(e);
}
}
}
private void createRAMWriter()
{
try {
if (this.ramWriter != null) {
this.ramWriter.close();
}
this.ramDirectory = new RAMDirectory();
this.ramWriter = new IndexWriter(this.ramDirectory, this.settings.analyzer(), true);
this.ramNumDocs = SystemGlobals.getIntValue(ConfigKeys.LUCENE_INDEXER_RAM_NUMDOCS);
}
catch (IOException e) {
throw new SearchException(e);
}
}
private void flushRAMDirectoryIfNecessary()
{
if (this.ramWriter.docCount() >= this.ramNumDocs) {
this.flushRAMDirectory();
}
}
public void flushRAMDirectory()
{
synchronized (MUTEX) {
IndexWriter writer = null;
try {
writer = new IndexWriter(this.settings.directory(), this.settings.analyzer());
writer.addIndexes(new Directory[] { this.ramDirectory });
writer.optimize();
this.createRAMWriter();
}
catch (IOException e) {
throw new SearchException(e);
}
finally {
if (writer != null) {
try {
writer.flush();
writer.close();
this.notifyNewDocumentAdded();
}
catch (Exception e) {}
}
}
}
}
public void create(Post post)
{
synchronized (MUTEX) {
IndexWriter writer = null;
try {
writer = new IndexWriter(this.settings.directory(), this.settings.analyzer());
Document document = this.createDocument(post);
writer.addDocument(document);
this.optimize(writer);
if (logger.isDebugEnabled()) {
logger.debug("Indexed " + document);
}
}
catch (Exception e) {
logger.error(e.toString(), e);
}
finally {
if (writer != null) {
try {
writer.flush();
writer.close();
this.notifyNewDocumentAdded();
}
catch (Exception e) {}
}
}
}
}
public void update(Post post)
{
if (this.performDelete(post)) {
this.create(post);
}
}
private void optimize(IndexWriter writer) throws Exception
{
if (writer.docCount() % 100 == 0) {
if (logger.isInfoEnabled()) {
logger.info("Optimizing indexes. Current number of documents is " + writer.docCount());
}
writer.optimize();
if (logger.isDebugEnabled()) {
logger.debug("Indexes optimized");
}
}
}
private Document createDocument(Post p)
{
Document d = new Document();
d.add(new Field(SearchFields.Keyword.POST_ID, String.valueOf(p.getId()), Store.YES, Index.UN_TOKENIZED));
d.add(new Field(SearchFields.Keyword.FORUM_ID, String.valueOf(p.getForumId()), Store.YES, Index.UN_TOKENIZED));
d.add(new Field(SearchFields.Keyword.TOPIC_ID, String.valueOf(p.getTopicId()), Store.YES, Index.UN_TOKENIZED));
d.add(new Field(SearchFields.Keyword.USER_ID, String.valueOf(p.getUserId()), Store.YES, Index.UN_TOKENIZED));
d.add(new Field(SearchFields.Keyword.DATE, this.settings.formatDateTime(p.getTime()), Store.YES, Index.UN_TOKENIZED));
// We add the subject and message text together because, when searching, we only care about the
// matches, not where it was performed. The real subject and contents will be fetched from the database
d.add(new Field(SearchFields.Indexed.CONTENTS, p.getSubject() + " " + p.getText(), Store.NO, Index.TOKENIZED));
return d;
}
private void notifyNewDocumentAdded()
{
for (Iterator iter = this.newDocumentAddedList.iterator(); iter.hasNext(); ) {
((NewDocumentAdded)iter.next()).newDocumentAdded();
}
}
public void delete(Post p)
{
this.performDelete(p);
}
private boolean performDelete(Post p)
{
synchronized (MUTEX) {
IndexReader reader = null;
boolean status = false;
try {
reader = IndexReader.open(this.settings.directory());
reader.deleteDocuments(new Term(SearchFields.Keyword.POST_ID, String.valueOf(p.getId())));
status = true;
}
catch (IOException e) {
logger.error(e.toString(), e);
}
finally {
if (reader != null) {
try {
reader.close();
}
catch (Exception e) {}
}
}
return status;
}
}
}