/* * Copyright 2002-2007 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.Term; import org.apache.lucene.search.TermQuery; import org.apache.lucene.store.Directory; import org.springmodules.lucene.index.DocumentHandlerException; import org.springmodules.lucene.index.LuceneIndexAccessException; import org.springmodules.lucene.index.LuceneIndexingException; 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.index.factory.LuceneIndexReader; import org.springmodules.lucene.index.factory.LuceneIndexWriter; import org.springmodules.lucene.search.factory.LuceneHits; import org.springmodules.lucene.search.factory.LuceneSearcher; 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 data 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) { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); try { reader.deleteDocument(internalDocumentId); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during deleting a document.",ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public void deleteDocuments(Term term) { System.out.println("> indexFactory = "+indexFactory); LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); System.out.println("> reader = "+reader); try { reader.deleteDocuments(term); } catch(IOException ex) { throw new LuceneIndexAccessException("Error during deleting a document.",ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public void undeleteDocuments() { LuceneIndexReader 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) { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.isDeleted(internalDocumentId); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public boolean hasDeletions() { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.hasDeletions(); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } //------------------------------------------------------------------------- // Methods dealing with index informations //------------------------------------------------------------------------- public int getMaxDoc() { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return reader.maxDoc(); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory,reader); } } public int getNumDocs() { LuceneIndexReader 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 (Exception ex) { throw new LuceneIndexAccessException("Construction of the desired Document failed", ex); } } protected List createDocuments(DocumentsCreator documentsCreator) { try { return documentsCreator.createDocuments(); } catch (Exception 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) { LuceneIndexWriter 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(); if( inputStream!=null ) { addDocument(documentCreator.createDocumentFromInputStream(inputStream), analyzer); } } catch(DocumentHandlerException ex) { throw ex; } catch(Exception ex) { throw new LuceneIndexingException("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) { LuceneIndexWriter 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(LuceneIndexWriter 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(LuceneHits 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(Term identifierTerm, DocumentModifier documentModifier) { updateDocument(identifierTerm, documentModifier, null); } public void updateDocument(Term identifierTerm, DocumentModifier documentModifier, Analyzer analyzer) { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); LuceneSearcher searcher = null; Document updatedDocument = null; try { searcher = reader.createSearcher(); LuceneHits hits = searcher.search(new TermQuery(identifierTerm)); checkHitsForUpdate(hits); updatedDocument = documentModifier.updateDocument(hits.doc(0)); } catch(Exception ex) { throw new LuceneIndexAccessException("Error during updating a document.", ex); } finally { SearcherFactoryUtils.closeSearcher(searcher); IndexReaderFactoryUtils.releaseIndexReader(indexFactory, reader); } deleteDocuments(identifierTerm); addDocument(updatedDocument, analyzer); } public void updateDocuments(Term identifierTerm, DocumentsModifier documentsModifier) { updateDocuments(identifierTerm, documentsModifier, null); } public void updateDocuments(Term identifierTerm, DocumentsModifier documentsModifier, Analyzer analyzer) { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); LuceneSearcher searcher = reader.createSearcher(); List updatedDocuments = null; try { LuceneHits hits = searcher.search(new TermQuery(identifierTerm)); updatedDocuments = documentsModifier.updateDocuments(hits); } catch(Exception ex) { throw new LuceneIndexAccessException("Error during updating a document.", ex); } finally { SearcherFactoryUtils.closeSearcher(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) { LuceneIndexWriter 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 optimization //------------------------------------------------------------------------- public void optimize() { LuceneIndexWriter 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) { LuceneIndexReader reader = IndexReaderFactoryUtils.getIndexReader(indexFactory); try { return callback.doWithReader(reader); } catch(Exception ex) { throw new LuceneIndexAccessException("Error during using the IndexReader.", ex); } finally { IndexReaderFactoryUtils.releaseIndexReader(indexFactory, reader); } } public Object write(WriterCallback callback) { LuceneIndexWriter writer = IndexWriterFactoryUtils.getIndexWriter(indexFactory); try { return callback.doWithWriter(writer); } catch(Exception ex) { throw new LuceneIndexAccessException("Error during using the IndexWriter.", ex); } finally { IndexWriterFactoryUtils.releaseIndexWriter(indexFactory, writer); } } }