Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. The ASF licenses this file to You * 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. For additional information regarding * copyright in this work, please see the NOTICE file in the top level * directory of this distribution. */ package org.apache.roller.weblogger.business.search; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.beanutils.ConstructorUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.miscellaneous.LimitTokenCountAnalyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.Version; import org.apache.roller.weblogger.WebloggerException; import org.apache.roller.weblogger.business.InitializationException; import org.apache.roller.weblogger.business.Weblogger; import org.apache.roller.weblogger.business.search.operations.AddEntryOperation; import org.apache.roller.weblogger.business.search.operations.IndexOperation; import org.apache.roller.weblogger.business.search.operations.ReIndexEntryOperation; import org.apache.roller.weblogger.business.search.operations.RebuildWebsiteIndexOperation; import org.apache.roller.weblogger.business.search.operations.RemoveEntryOperation; import org.apache.roller.weblogger.business.search.operations.RemoveWebsiteIndexOperation; import org.apache.roller.weblogger.business.search.operations.WriteToIndexOperation; import org.apache.roller.weblogger.pojos.WeblogEntry; import org.apache.roller.weblogger.pojos.Weblog; import org.apache.roller.weblogger.config.WebloggerConfig; /** * Lucene implementation of IndexManager. This is the central entry point into * the Lucene searching API. * * @author Mindaugas Idzelis (min@idzelis.com) * @author mraible (formatting and making indexDir configurable) */ @com.google.inject.Singleton public class IndexManagerImpl implements IndexManager { // ~ Static fields/initializers // ============================================= private IndexReader reader; private final Weblogger roller; static Log mLogger = LogFactory.getFactory().getInstance(IndexManagerImpl.class); // ~ Instance fields // ======================================================== private boolean searchEnabled = true; File indexConsistencyMarker; private boolean useRAMIndex = false; private RAMDirectory fRAMindex; private String indexDir = null; private boolean inconsistentAtStartup = false; private ReadWriteLock rwl = new ReentrantReadWriteLock(); // ~ Constructors // =========================================================== /** * Creates a new lucene index manager. This should only be created once. * Creating the index manager more than once will definitely result in * errors. The preferred way of getting an index is through the * RollerContext. * * @param roller - the weblogger instance */ @com.google.inject.Inject protected IndexManagerImpl(Weblogger roller) { this.roller = roller; // check config to see if the internal search is enabled String enabled = WebloggerConfig.getProperty("search.enabled"); if ("false".equalsIgnoreCase(enabled)) { this.searchEnabled = false; } // we also need to know what our index directory is // Note: system property expansion is now handled by WebloggerConfig String searchIndexDir = WebloggerConfig.getProperty("search.index.dir"); this.indexDir = searchIndexDir.replace('/', File.separatorChar); // a little debugging mLogger.info("search enabled: " + this.searchEnabled); mLogger.info("index dir: " + this.indexDir); String test = indexDir + File.separator + ".index-inconsistent"; indexConsistencyMarker = new File(test); } /** * @inheritDoc */ public void initialize() throws InitializationException { // only initialize the index if search is enabled if (this.searchEnabled) { // 1. If inconsistency marker exists. // Delete index // 2. if we're using RAM index // load ram index wrapper around index // if (indexConsistencyMarker.exists()) { getFSDirectory(true); inconsistentAtStartup = true; mLogger.debug("Index inconsistent: marker exists"); } else { try { File makeIndexDir = new File(indexDir); if (!makeIndexDir.exists()) { makeIndexDir.mkdirs(); inconsistentAtStartup = true; mLogger.debug("Index inconsistent: new"); } indexConsistencyMarker.createNewFile(); } catch (IOException e) { mLogger.error(e); } } if (indexExists()) { if (useRAMIndex) { Directory filesystem = getFSDirectory(false); try { fRAMindex = new RAMDirectory(filesystem, IOContext.DEFAULT); } catch (IOException e) { mLogger.error("Error creating in-memory index", e); } } } else { mLogger.debug("Creating index"); inconsistentAtStartup = true; if (useRAMIndex) { fRAMindex = new RAMDirectory(); createIndex(fRAMindex); } else { createIndex(getFSDirectory(true)); } } if (isInconsistentAtStartup()) { mLogger.info("Index was inconsistent. Rebuilding index in the background..."); try { rebuildWebsiteIndex(); } catch (WebloggerException e) { mLogger.error("ERROR: scheduling re-index operation"); } } else { mLogger.info("Index initialized and ready for use."); } } } // ~ Methods // ================================================================ public void rebuildWebsiteIndex() throws WebloggerException { scheduleIndexOperation(new RebuildWebsiteIndexOperation(roller, this, null)); } public void rebuildWebsiteIndex(Weblog website) throws WebloggerException { scheduleIndexOperation(new RebuildWebsiteIndexOperation(roller, this, website)); } public void removeWebsiteIndex(Weblog website) throws WebloggerException { scheduleIndexOperation(new RemoveWebsiteIndexOperation(roller, this, website)); } public void addEntryIndexOperation(WeblogEntry entry) throws WebloggerException { AddEntryOperation addEntry = new AddEntryOperation(roller, this, entry); scheduleIndexOperation(addEntry); } public void addEntryReIndexOperation(WeblogEntry entry) throws WebloggerException { ReIndexEntryOperation reindex = new ReIndexEntryOperation(roller, this, entry); scheduleIndexOperation(reindex); } public void removeEntryIndexOperation(WeblogEntry entry) throws WebloggerException { RemoveEntryOperation removeOp = new RemoveEntryOperation(roller, this, entry); executeIndexOperationNow(removeOp); } public ReadWriteLock getReadWriteLock() { return rwl; } public boolean isInconsistentAtStartup() { return inconsistentAtStartup; } /** * This is the analyzer that will be used to tokenize comment text. * * @return Analyzer to be used in manipulating the database. */ public static final Analyzer getAnalyzer() { return instantiateAnalyzer(FieldConstants.LUCENE_VERSION); } private static Analyzer instantiateAnalyzer(final Version luceneVersion) { final String className = WebloggerConfig.getProperty("lucene.analyzer.class"); try { final Class<?> clazz = Class.forName(className); return (Analyzer) ConstructorUtils.invokeConstructor(clazz, luceneVersion); } catch (final ClassNotFoundException e) { mLogger.error("failed to lookup analyzer class: " + className, e); return instantiateDefaultAnalyzer(luceneVersion); } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { mLogger.error("failed to instantiate analyzer: " + className, e); return instantiateDefaultAnalyzer(luceneVersion); } } private static Analyzer instantiateDefaultAnalyzer(final Version luceneVersion) { return new StandardAnalyzer(luceneVersion); } private void scheduleIndexOperation(final IndexOperation op) { try { // only if search is enabled if (this.searchEnabled) { mLogger.debug("Starting scheduled index operation: " + op.getClass().getName()); roller.getThreadManager().executeInBackground(op); } } catch (InterruptedException e) { mLogger.error("Error executing operation", e); } } /** * @param op */ public void executeIndexOperationNow(final IndexOperation op) { try { // only if search is enabled if (this.searchEnabled) { mLogger.debug("Executing index operation now: " + op.getClass().getName()); roller.getThreadManager().executeInForeground(op); } } catch (InterruptedException e) { mLogger.error("Error executing operation", e); } } public synchronized void resetSharedReader() { reader = null; } public synchronized IndexReader getSharedIndexReader() { if (reader == null) { try { reader = DirectoryReader.open(getIndexDirectory()); } catch (IOException e) { } } return reader; } /** * Get the directory that is used by the lucene index. This method will * return null if there is no index at the directory location. If we are * using a RAM index, the directory will be a ram directory. * * @return Directory The directory containing the index, or null if error. */ public Directory getIndexDirectory() { if (useRAMIndex) { return fRAMindex; } else { return getFSDirectory(false); } } private boolean indexExists() { try { return DirectoryReader.indexExists(getIndexDirectory()); } catch (IOException e) { mLogger.error("Problem accessing index directory", e); } return false; } private Directory getFSDirectory(boolean delete) { Directory directory = null; try { directory = FSDirectory.open(new File(indexDir)); if (delete && directory != null) { // clear old files String[] files = directory.listAll(); for (int i = 0; i < files.length; i++) { File file = new File(indexDir, files[i]); if (!file.delete()) { throw new IOException("couldn't delete " + files[i]); } } } } catch (IOException e) { mLogger.error("Problem accessing index directory", e); } return directory; } private void createIndex(Directory dir) { IndexWriter writer = null; try { IndexWriterConfig config = new IndexWriterConfig(FieldConstants.LUCENE_VERSION, new LimitTokenCountAnalyzer(IndexManagerImpl.getAnalyzer(), IndexWriterConfig.DEFAULT_TERM_INDEX_INTERVAL)); writer = new IndexWriter(dir, config); } catch (IOException e) { mLogger.error("Error creating index", e); } finally { try { if (writer != null) { writer.close(); } } catch (IOException e) { } } } private IndexOperation getSaveIndexOperation() { return new WriteToIndexOperation(this) { public void doRun() { Directory dir = getIndexDirectory(); Directory fsdir = getFSDirectory(true); IndexWriter writer = null; try { IndexWriterConfig config = new IndexWriterConfig(FieldConstants.LUCENE_VERSION, new LimitTokenCountAnalyzer(IndexManagerImpl.getAnalyzer(), IndexWriterConfig.DEFAULT_TERM_INDEX_INTERVAL)); writer = new IndexWriter(fsdir, config); writer.addIndexes(new Directory[] { dir }); writer.commit(); indexConsistencyMarker.delete(); } catch (IOException e) { mLogger.error("Problem saving index to disk", e); // Delete the directory, since there was a problem saving the RAM contents getFSDirectory(true); } finally { try { if (writer != null) { writer.close(); } } catch (IOException e1) { mLogger.warn("Unable to close IndexWriter."); } } } }; } public void release() { // no-op } public void shutdown() { if (useRAMIndex) { scheduleIndexOperation(getSaveIndexOperation()); } else { indexConsistencyMarker.delete(); } try { if (reader != null) { reader.close(); } } catch (IOException e) { // won't happen, since it was } } }