Bug 493450 - Use RAFDirectory in Lucene indexer to prevent index from
being closed/corrupted in case of thread interruption

Change-Id: Id0d837002698a5cb7d8675ce5878bb9fecc1670d
Signed-off-by: Bartlomiej Laczkowski <bartlomiej.l@zend.com>
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/.classpath b/core/plugins/org.eclipse.dltk.core.index.lucene/.classpath
index 290a9f6..7376f74 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/.classpath
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/.classpath
@@ -2,6 +2,7 @@
 <classpath>
 	<classpathentry exported="true" kind="lib" path="lib/lucene-analyzers-common-5.2.1.jar"/>
 	<classpathentry exported="true" kind="lib" path="lib/lucene-core-5.2.1.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/lucene-misc-5.2.1.jar"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="src" path="src"/>
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/META-INF/MANIFEST.MF b/core/plugins/org.eclipse.dltk.core.index.lucene/META-INF/MANIFEST.MF
index 0170f7a..029ec8a 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/META-INF/MANIFEST.MF
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/META-INF/MANIFEST.MF
@@ -13,5 +13,6 @@
 Bundle-ActivationPolicy: lazy
 Bundle-ClassPath: .,
  lib/lucene-analyzers-common-5.2.1.jar,
- lib/lucene-core-5.2.1.jar
+ lib/lucene-core-5.2.1.jar,
+ lib/lucene-misc-5.2.1.jar
 Export-Package: org.eclipse.dltk.core.index.lucene
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/build.properties b/core/plugins/org.eclipse.dltk.core.index.lucene/build.properties
index a2351aa..eb43af9 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/build.properties
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/build.properties
@@ -16,5 +16,6 @@
                plugin.properties,\
                about.html,\
                lib/lucene-analyzers-common-5.2.1.jar,\
-               lib/lucene-core-5.2.1.jar
+               lib/lucene-core-5.2.1.jar,\
+               lib/lucene-misc-5.2.1.jar
 source.. = src/
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/lib/lucene-misc-5.2.1.jar b/core/plugins/org.eclipse.dltk.core.index.lucene/lib/lucene-misc-5.2.1.jar
new file mode 100644
index 0000000..ce6eeba
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/lib/lucene-misc-5.2.1.jar
Binary files differ
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java
index f5da1fd..84e9a5d 100644
--- a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexContainer.java
@@ -28,7 +28,6 @@
 import org.apache.lucene.search.SearcherFactory;
 import org.apache.lucene.search.SearcherManager;
 import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.FSDirectory;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
@@ -120,7 +119,7 @@
 	public synchronized IndexWriter getTimestampsWriter() {
 		if (fTimestampsWriter == null) {
 			try {
-				Directory indexDir = FSDirectory.open(
+				Directory indexDir = new IndexDirectory(
 						Paths.get(fIndexRoot, fContainerId, TIMESTAMPS_DIR));
 				IndexWriterConfig config = new IndexWriterConfig(
 						new SimpleAnalyzer());
@@ -152,7 +151,7 @@
 		IndexWriter writer = fIndexWriters.get(dataType).get(elementType);
 		if (writer == null) {
 			try {
-				Directory indexDir = FSDirectory.open(Paths.get(fIndexRoot,
+				Directory indexDir = new IndexDirectory(Paths.get(fIndexRoot,
 						fContainerId, dataType.getDirectory(),
 						String.valueOf(elementType)));
 				IndexWriterConfig config = new IndexWriterConfig(
diff --git a/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexDirectory.java b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexDirectory.java
new file mode 100644
index 0000000..20f3b00
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.core.index.lucene/src/org/eclipse/dltk/internal/core/index/lucene/IndexDirectory.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Zend Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     Zend Technologies - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.dltk.internal.core.index.lucene;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.concurrent.Future;
+
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.IOContext;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.LockFactory;
+import org.apache.lucene.store.OutputStreamIndexOutput;
+import org.apache.lucene.store.RAFDirectory;
+
+/**
+ * <p>
+ * Default directory implementation that store index files in the file system.
+ * </p>
+ * <p>
+ * This extended implementation of {@link RAFDirectory} prevents from abnormal
+ * index closing in case of interrupting indexing/searching threads via
+ * {@link Thread#interrupt()} or {@link Future#cancel(boolean)}. As RAFDirectory
+ * itself is using {@link FSDirectory.FSIndexOutput} that is vulnerable to
+ * thread interruption, this implementation provides additional
+ * {@link RAFIndexOutput} as a safe substitution.
+ * </p>
+ * 
+ * @author Bartlomiej Laczkowski
+ */
+public class IndexDirectory extends RAFDirectory {
+
+	final class RAFIndexOutput extends OutputStreamIndexOutput {
+
+		private static final int CHUNK_SIZE = 8192;
+		private static final String DESCRIPTION = "RAFIndexOutput(path=\"{0}\")"; //$NON-NLS-1$
+
+		public RAFIndexOutput(String name) throws IOException {
+			super(MessageFormat.format(DESCRIPTION, directory.resolve(name)),
+					new RAFOutputStream(
+							new FileOutputStream(new File(
+									directory.resolve(name).toString())),
+							CHUNK_SIZE),
+					CHUNK_SIZE);
+		}
+
+	}
+
+	final static class RAFOutputStream extends FilterOutputStream {
+
+		final private int fChunkSize;
+
+		public RAFOutputStream(OutputStream out, int chunkSize) {
+			super(out);
+			fChunkSize = chunkSize;
+		}
+
+		@Override
+		public void write(byte[] b, int offset, int length) throws IOException {
+			while (length > 0) {
+				final int chunk = Math.min(length, fChunkSize);
+				out.write(b, offset, chunk);
+				length -= chunk;
+				offset += chunk;
+			}
+		}
+
+	}
+
+	public IndexDirectory(Path path) throws IOException {
+		super(path);
+	}
+
+	public IndexDirectory(Path path, LockFactory lockFactory)
+			throws IOException {
+		super(path, lockFactory);
+	}
+
+	@Override
+	public IndexOutput createOutput(String name, IOContext context)
+			throws IOException {
+		ensureOpen();
+		ensureCanWrite(name);
+		return new RAFIndexOutput(name);
+	}
+
+}