/* * Copyright 2002-2005 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springmodules.lucene.index.core; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.List; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.springmodules.lucene.index.LuceneIndexAccessException; import org.springmodules.lucene.index.core.DocumentCreator; import org.springmodules.lucene.index.core.DocumentIdentifier; import org.springmodules.lucene.index.core.DocumentModifier; import org.springmodules.lucene.index.core.DocumentsCreator; import org.springmodules.lucene.index.core.DocumentsIdentifier; import org.springmodules.lucene.index.core.DocumentsModifier; import org.springmodules.lucene.index.core.InputStreamDocumentCreator; import org.springmodules.lucene.index.core.LuceneIndexTemplate; import org.springmodules.lucene.index.core.ReaderCallback; import org.springmodules.lucene.index.core.WriterCallback; import org.springmodules.lucene.index.factory.IndexFactory; import org.springmodules.lucene.index.factory.IndexReaderFactoryUtils; import org.springmodules.lucene.index.factory.IndexWriterFactoryUtils; import org.springmodules.lucene.search.factory.SearcherFactoryUtils; import org.springmodules.lucene.util.IOUtils; /** * <b>This is the central class in the lucene indexing core package.</b> * It simplifies the use of lucene to index documents or datas using * index reader and writer. It helps to avoid common errors and to * manage these resource in a flexible manner. * It executes core Lucene workflow, leaving application code to focus on * the way to create Lucene documents and make some operations on the * index. * * <p>This class is based on the IndexFactory abstraction which is a * factory to create IndexReader and IndexWriter for the configured * Directory. So the template doesn't need to always hold resources and * this avoids some locking problems on the index. You can too apply * different strategies for managing index resources. * * <p>Can be used within a service implementation via direct instantiation * with a IndexFactory reference, or get prepared in an application context * and given to services as bean reference. Note: The IndexFactory should * always be configured as a bean in the application context, in the first case * given to the service directly, in the second case to the prepared template. * * <p>You must be aware that the use of some methods (like undeleteDocuments, * isDeleted, hasDeletions, flush) have sense only if you share the Lucene * underlying resources across several template method calls. As a matter of * fact, when IndexReader and IndexWriter are closed every changes deferred * until the closing of these resources. Moreover some Lucene operations are * incompatible if you share resources across several calls. * * @author Brian McCallister * @author Thierry Templier * @see DocumentCreator * @see DocumentsCreator * @see org.springmodules.lucene.index.factory */ public class DefaultLuceneIndexTemplate implements LuceneIndexTemplate { private IndexFactory indexFactory; private Analyzer analyzer; /** * Construct a new LuceneIndexTemplate for bean usage. * Note: The IndexFactory has to be set before using the instance. * This constructor can be used to prepare a LuceneIndexTemplate via a BeanFactory, * typically setting the IndexFactory via setIndexFactory. * @see #setIndexFactory */ public DefaultLuceneIndexTemplate() { } /** * Construct a new LuceneIndexTemplate, given an IndexFactory to obtain both * IndexReader and IndexWriter, and an Analyzer to be used unless an other * one is specified as method parameter. * @param indexFactory IndexFactory to obtain both IndexReader and IndexWriter * @param analyzer Lucene analyzer to extract tokens out of the text to index */ public DefaultLuceneIndexTemplate(IndexFactory indexFactory,Analyzer analyzer) { setIndexFactory(indexFactory); setAnalyzer(analyzer); afterPropertiesSet(); } /** * Check if the indexFactory is set. The analyzer could be not set. */ public void afterPropertiesSet() { if (getIndexFactory() == null) { throw new IllegalArgumentException("indexFactory is required"); } } /** * Set the IndexFactory to obtain both IndexReader and IndexWriter. */ public void setIndexFactory(IndexFactory factory) { indexFactory = factory; } /** * Return the IndexFactory used by this template. */ public IndexFactory getIndexFactory() { return indexFactory; } /** * Set the default Lucene Analyzer used to extract tokens out of the * text to index. */ public void setAnalyzer(Analyzer analyzer) { this.analyzer = analyzer; } /** * Return the Lucene Analyzer used by this template. */ public Analyzer getAnalyzer() { return analyzer; } //------------------------------------------------------------------------- // Methods dealing with document deletions //------------------------------------------------------------------------- public void deleteDocument(int internalDocumentId) { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { reader.delete(internalDocumentId); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during deleting a document.",ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public void deleteDocuments(Term term) { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { reader.delete(term); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during deleting a document.",ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public void undeleteDocuments() { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { reader.undeleteAll(); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during undeleting all documents.",ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public boolean isDeleted(int internalDocumentId) { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.isDeleted(internalDocumentId); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public boolean hasDeletions() { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.hasDeletions(); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } //------------------------------------------------------------------------- // Methods dealing with index informations //------------------------------------------------------------------------- public int getMaxDoc() { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.maxDoc(); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public int getNumDocs() { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.numDocs(); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } //------------------------------------------------------------------------- // Methods dealing with document creations //------------------------------------------------------------------------- protected Document createDocument(DocumentCreator documentCreator) { try { return documentCreator.createDocument(); } catch (IOException ex) { throw new LuceneIndexAccessException("Construction of the desired Document failed", ex); } } protected List createDocuments(DocumentsCreator documentsCreator) { try { return documentsCreator.createDocuments(); } catch (IOException ex) { throw new LuceneIndexAccessException("Construction of the desired Document failed", ex); } } public void addDocument(Document document) { addDocument(document,null); } public void addDocument(Document document,Analyzer analyzer) { IndexWriter writer=IndexWriterFactoryUtils.getIndexWriter(indexFactory); try { doAddDocument(writer,document,null); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during adding a document.",ex); } finally { IndexWriterFactoryUtils.releaseIndexWriter(indexFactory,writer); } } public void addDocument(DocumentCreator creator) { addDocument(createDocument(creator),null); } public void addDocument(DocumentCreator documentCreator,Analyzer analyzer) { addDocument(createDocument(documentCreator),analyzer); } public void addDocument(InputStreamDocumentCreator creator) { addDocument(creator,null); } public void addDocument(InputStreamDocumentCreator documentCreator,Analyzer analyzer) { InputStream inputStream=null; try { inputStream=documentCreator.createInputStream(); addDocument(documentCreator.createDocumentFromInputStream(inputStream),analyzer); } catch(IOException ex) { //throw new LuceneInputStreamException("Error during adding a document.",ex); throw new RuntimeException("Error during adding a document.",ex); } finally { IOUtils.closeInputStream(inputStream); } } public void addDocuments(List documents) { addDocuments(documents,null); } public void addDocuments(List documents,Analyzer analyzer) { IndexWriter writer=IndexWriterFactoryUtils.getIndexWriter(indexFactory); try { for(Iterator i=documents.iterator();i.hasNext();) { Document document=(Document)i.next(); doAddDocument(writer,document,analyzer); } } catch(IOException ex) { throw new LuceneIndexAccessException("Error during adding a document.",ex); } finally { IndexWriterFactoryUtils.releaseIndexWriter(indexFactory,writer); } } public void addDocuments(DocumentsCreator creator) { addDocuments(creator,null); } public void addDocuments(DocumentsCreator creator,Analyzer analyzer) { addDocuments(createDocuments(creator),analyzer); } private void doAddDocument(IndexWriter writer,Document document, Analyzer analyzer) throws IOException { if( document!=null ) { if( analyzer==null ) { writer.addDocument(document); } else if( getAnalyzer()==null ) { writer.addDocument(document); } else if( analyzer!=null ) { writer.addDocument(document,analyzer); } else if( getAnalyzer()!=null ) { writer.addDocument(document,getAnalyzer()); } else { writer.addDocument(document); } } else { throw new LuceneIndexAccessException("The document created is null."); } } //------------------------------------------------------------------------- // Methods dealing with document updates //------------------------------------------------------------------------- private void checkHitsForUpdate(Hits hits) { if( hits.length()==0 ) { throw new LuceneIndexAccessException("The identifier returns no document."); } if( hits.length()>1 ) { throw new LuceneIndexAccessException("The identifier returns more than one document."); } } public void updateDocument(DocumentModifier documentModifier,DocumentIdentifier identifier) { updateDocument(documentModifier,identifier,null); } public void updateDocument(DocumentModifier documentModifier,DocumentIdentifier identifier,Analyzer analyzer) { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); IndexSearcher searcher=new IndexSearcher(reader); Term identifierTerm=identifier.getIdentifier(); Document updatedDocument=null; try { Hits hits=searcher.search(new TermQuery(identifierTerm)); checkHitsForUpdate(hits); updatedDocument=documentModifier.updateDocument(hits.doc(0)); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during updating a document.",ex); } finally { SearcherFactoryUtils.releaseSearcher(searcher); IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } deleteDocuments(identifierTerm); addDocument(updatedDocument,analyzer); } public void updateDocuments(DocumentsModifier documentsModifier,DocumentsIdentifier identifier) { updateDocuments(documentsModifier,identifier,null); } public void updateDocuments(DocumentsModifier documentsModifier,DocumentsIdentifier identifier,Analyzer analyzer) { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); IndexSearcher searcher=new IndexSearcher(reader); Term identifierTerm=identifier.getIdentifier(); List updatedDocuments=null; try { Hits hits=searcher.search(new TermQuery(identifierTerm)); updatedDocuments=documentsModifier.updateDocuments(hits); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during updating a document.",ex); } finally { SearcherFactoryUtils.releaseSearcher(searcher); IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } deleteDocuments(identifierTerm); addDocuments(updatedDocuments,analyzer); } //------------------------------------------------------------------------- // Methods dealing with index insertions //------------------------------------------------------------------------- public void addIndex(Directory directory) { addIndexes(new Directory[] { directory }); } public void addIndexes(Directory[] directories) { IndexWriter writer=IndexWriterFactoryUtils.getIndexWriter(indexFactory); try { writer.addIndexes(directories); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during adding indexes.",ex); } finally { IndexWriterFactoryUtils.releaseIndexWriter(indexFactory,writer); } } //------------------------------------------------------------------------- // Methods dealing with index optmization //------------------------------------------------------------------------- public void optimize() { IndexWriter writer=IndexWriterFactoryUtils.getIndexWriter(indexFactory); try { writer.optimize(); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during optimize the index.",ex); } finally { IndexWriterFactoryUtils.releaseIndexWriter(indexFactory,writer); } } //------------------------------------------------------------------------- // Methods dealing with index reader and writer directly //------------------------------------------------------------------------- public Object read(ReaderCallback callback) { IndexReader reader=IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return callback.doWithReader(reader); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during using the IndexReader.",ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public Object write(WriterCallback callback) { IndexWriter writer=IndexWriterFactoryUtils.getIndexWriter(indexFactory); try { return callback.doWithWriter(writer); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during using the IndexWriter.",ex); } finally { IndexWriterFactoryUtils.releaseIndexWriter(indexFactory,writer); } } }