/* * Copyright 2004-2009 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.compass.core.lucene.engine.transaction.readcommitted; import java.io.IOException; import java.util.Random; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.NoLockFactory; import org.apache.lucene.store.RAMDirectory; import org.compass.core.CompassException; import org.compass.core.config.CompassConfigurable; import org.compass.core.config.CompassSettings; import org.compass.core.engine.SearchEngineException; import org.compass.core.lucene.LuceneEnvironment; import org.compass.core.lucene.engine.LuceneSearchEngineFactory; import org.compass.core.lucene.engine.transaction.support.job.TransactionJob; /** * A transactional index holding a complete lucene index as the transactional index. * * <p>The transactional index can either be ram based one (default) or a file based one. If it is * a file based one, the temporary index is created (by defualt) under the JAVA temporary location * (with <code>compass/translog</code>) with a random generated transaction id. * * @author kimchy */ public class TransIndex implements CompassConfigurable { private static Random transId = new Random(); private static final String DEFAULT_LOCATION = System.getProperty("java.io.tmpdir") + "/compass/translog"; private final LuceneSearchEngineFactory searchEngineFactory; private final String subIndex; private final boolean concurrent; private Directory directory; private IndexWriter indexWriter; private IndexReader indexReader; private IndexSearcher indexSearcher; private volatile boolean flushRequired = false; private boolean optimize; public TransIndex(LuceneSearchEngineFactory searchEngineFactory, String subIndex, boolean concurrent) { this.searchEngineFactory = searchEngineFactory; this.subIndex = subIndex; this.concurrent = concurrent; } public void configure(CompassSettings settings) throws CompassException { try { String transLogConnection = settings.getSetting(LuceneEnvironment.Transaction.Processor.ReadCommitted.TransLog.CONNECTION, "ram://"); if ("ram://".equals(transLogConnection)) { directory = new RAMDirectory(); } else { if (transLogConnection.equals("file://")) { transLogConnection = DEFAULT_LOCATION; } else if (transLogConnection.startsWith("file://")) { transLogConnection = transLogConnection.substring("file://".length()); } transLogConnection += "/" + transId.nextLong(); directory = FSDirectory.getDirectory(transLogConnection); // TODO we can improve the file system one by starting with a ram one and then switching } // since this is single threaded access, there is no need to have locks directory.setLockFactory(NoLockFactory.getNoLockFactory()); // create an index writer with autoCommit=true since we want it to be visible to readers (still need to flush) indexWriter = searchEngineFactory.getLuceneIndexManager().getIndexWritersManager().openIndexWriter(settings, directory, true, new KeepOnlyLastCommitDeletionPolicy()); // TODO what about merge policy, which one is better? Default is async operations, so good to have merges in the backgournd optimize = settings.getSettingAsBoolean(LuceneEnvironment.Transaction.Processor.ReadCommitted.TransLog.OPTIMIZE_TRANS_LOG, true); } catch (IOException e) { throw new SearchEngineException("Failed to open transactional index for sub index [" + subIndex + "]", e); } } public void processJob(TransactionJob job) throws Exception { job.execute(indexWriter, searchEngineFactory); flushRequired = true; } public IndexReader getReader() throws IOException { refreshIfNeeded(); return this.indexReader; } public IndexSearcher getSearcher() throws IOException { refreshIfNeeded(); return this.indexSearcher; } public Directory getDirectory() { return this.directory; } public void commit() throws IOException { if (indexSearcher != null) { indexSearcher.close(); } if (indexReader != null) { indexReader.close(); } if (optimize) { indexWriter.optimize(); } indexWriter.close(); indexWriter = null; } public void close() throws IOException { directory.close(); } public void rollback() throws IOException { if (indexSearcher != null) { indexSearcher.close(); } if (indexReader != null) { indexReader.close(); } indexWriter.rollback(); indexWriter = null; } private void refreshIfNeeded() throws IOException { if (concurrent) { synchronized (this) { innerRefreshIfNeeded(); } } else { innerRefreshIfNeeded(); } } private void innerRefreshIfNeeded() throws IOException { if (flushRequired) { if (indexWriter != null) { indexWriter.commit(); } if (indexReader == null) { indexReader = IndexReader.open(directory, true); indexSearcher = searchEngineFactory.getLuceneIndexManager().openIndexSearcher(indexReader); } else { IndexReader tmpReader = indexReader.reopen(); if (tmpReader != indexReader) { indexReader.close(); indexSearcher.close(); indexReader = tmpReader; indexSearcher = searchEngineFactory.getLuceneIndexManager().openIndexSearcher(tmpReader); } } flushRequired = false; } } }