/*
* 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.apache.lucene.store.wrapper;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.Lock;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.store.RAMOutputStream;
import org.compass.core.util.concurrent.NamedThreadFactory;
/**
* Wraps a Lucene {@link org.apache.lucene.store.Directory} with
* an in memory directory which mirrors it asynchronously.
* <p/>
* The original directory is read into memory when this wrapper
* is constructed. All read realted operations are performed
* against the in memory directory. All write related operations
* are performed against the in memeory directory and are scheduled
* to be performed against the original directory (using {@link ExecutorService}).
* Locking is performed using the in memory directory.
* <p/>
* NOTE: This wrapper will only work in cases when either the
* index is read only (i.e. only search operations are performed
* against it), or when there is a single instance which updates
* the directory.
*
* @author kimchy
*/
public class AsyncMemoryMirrorDirectoryWrapper extends Directory {
private static final Log log = LogFactory.getLog(AsyncMemoryMirrorDirectoryWrapper.class);
private Directory dir;
private RAMDirectory ramDir;
private ExecutorService executorService;
private long awaitTermination;
public AsyncMemoryMirrorDirectoryWrapper(Directory dir) throws IOException {
this(dir, 2);
}
public AsyncMemoryMirrorDirectoryWrapper(Directory dir, long awaitTermination) throws IOException {
this(dir, awaitTermination, Executors.newSingleThreadExecutor(new NamedThreadFactory("AsyncMirror[" + dir + "]", false)));
}
public AsyncMemoryMirrorDirectoryWrapper(Directory dir, long awaitTermination, ExecutorService executorService) throws IOException {
this.dir = dir;
this.ramDir = new RAMDirectory(dir);
this.executorService = executorService;
this.awaitTermination = awaitTermination;
}
public void deleteFile(final String name) throws IOException {
ramDir.deleteFile(name);
executorService.submit(new Runnable() {
public void run() {
try {
dir.deleteFile(name);
} catch (IOException e) {
logAsyncErrorMessage("delete [" + name + "]");
}
}
});
}
public boolean fileExists(String name) throws IOException {
return ramDir.fileExists(name);
}
public long fileLength(String name) throws IOException {
return ramDir.fileLength(name);
}
public long fileModified(String name) throws IOException {
return ramDir.fileModified(name);
}
public String[] list() throws IOException {
return ramDir.list();
}
public void renameFile(final String from, final String to) throws IOException {
ramDir.renameFile(from, to);
executorService.submit(new Runnable() {
public void run() {
try {
dir.renameFile(from, to);
} catch (IOException e) {
logAsyncErrorMessage("rename from[" + from + "] to[" + to + "]");
}
}
});
}
public void touchFile(final String name) throws IOException {
ramDir.touchFile(name);
executorService.submit(new Runnable() {
public void run() {
try {
dir.touchFile(name);
} catch (IOException e) {
logAsyncErrorMessage("touch [" + name + "]");
}
}
});
}
public Lock makeLock(String name) {
return ramDir.makeLock(name);
}
public void close() throws IOException {
ramDir.close();
if (log.isDebugEnabled()) {
log.debug("Directory [" + dir + "] shutsdown, waiting for [" + awaitTermination +
"] minutes for tasks to finish executing");
}
executorService.shutdown();
if (!executorService.isTerminated()) {
try {
if (!executorService.awaitTermination(60 * awaitTermination, TimeUnit.SECONDS)) {
logAsyncErrorMessage("wait for async tasks to shutdown");
}
} catch (InterruptedException e) {
logAsyncErrorMessage("wait for async tasks to shutdown");
}
}
dir.close();
}
public IndexInput openInput(String name) throws IOException {
return ramDir.openInput(name);
}
public IndexOutput createOutput(String name) throws IOException {
return new AsyncMemoryMirrorIndexOutput(name, (RAMOutputStream) ramDir.createOutput(name));
}
private void logAsyncErrorMessage(String message) {
log.error("Async wrapper for [" + dir + "] failed to " + message);
}
public class AsyncMemoryMirrorIndexOutput extends IndexOutput {
private String name;
private RAMOutputStream ramIndexOutput;
public AsyncMemoryMirrorIndexOutput(String name, RAMOutputStream ramIndexOutput) {
this.name = name;
this.ramIndexOutput = ramIndexOutput;
}
public void writeByte(byte b) throws IOException {
ramIndexOutput.writeByte(b);
}
public void writeBytes(byte[] b, int offset, int length) throws IOException {
ramIndexOutput.writeBytes(b, offset, length);
}
public void seek(long size) throws IOException {
ramIndexOutput.seek(size);
}
public long length() throws IOException {
return ramIndexOutput.length();
}
public long getFilePointer() {
return ramIndexOutput.getFilePointer();
}
public void flush() throws IOException {
ramIndexOutput.flush();
}
public void close() throws IOException {
ramIndexOutput.close();
executorService.submit(new Runnable() {
public void run() {
try {
IndexOutput indexOutput = dir.createOutput(name);
ramIndexOutput.writeTo(indexOutput);
indexOutput.close();
} catch (IOException e) {
logAsyncErrorMessage("write [" + name + "]");
}
}
});
}
}
}