Repository: searchbox-io/Jest
Pull request #408 (2016-11-02 10:16:29)

Add a Force-merge command
This PR updates the Optimize command and adds support for the [Force-merge](https://www.elastic.co/guide/en/elasticsearch/reference/2.4/indices-forcemerge.html) API, which replaces the Optimize API as of ES 2.1.

The whole thing is pretty much a copy-paste of the Optimize command and its tests. I did not remove the Optimize command because it's still needed for users of Elasticsearch 2.0.

Note that this additional command is only "nice" when using ES 2.x, but it is actually required when using Elasticsearch 5.0, in which the Optimize API has been completely removed.

Related issue (kind of): https://github.com/searchbox-io/Jest/issues/292


Patch:
diff --git a/jest-common/src/main/java/io/searchbox/indices/ForceMerge.java b/jest-common/src/main/java/io/searchbox/indices/ForceMerge.java
new file mode 100644
index 00000000..893d331b
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/indices/ForceMerge.java
@@ -0,0 +1,64 @@
+package io.searchbox.indices;
+
+import io.searchbox.action.AbstractMultiIndexActionBuilder;
+import io.searchbox.action.GenericResultAbstractAction;
+
+/**
+ * Force-merge API: https://www.elastic.co/guide/en/elasticsearch/reference/2.4/indices-forcemerge.html
+ *
+ * Caution: this API has been introduced in ES 2.1. With earlier versions, use {@link Optimize}.
+ *
+ * @author Dogukan Sonmez
+ * @author cihat keser
+ * @author Yoann Rodiere
+ */
+public class ForceMerge extends GenericResultAbstractAction {
+
+    protected ForceMerge(Builder builder) {
+        super(builder);
+        setURI(buildURI());
+    }
+
+    @Override
+    public String getRestMethodName() {
+        return "POST";
+    }
+
+    @Override
+    protected String buildURI() {
+        return super.buildURI() + "/_forcemerge";
+    }
+
+    public static class Builder extends AbstractMultiIndexActionBuilder<ForceMerge, Builder> {
+
+        /**
+         * The number of segments to merge to. To fully merge the index, set it to 1.
+         * Defaults to simply checking if a merge needs to execute, and if so, executes it.
+         */
+        public Builder maxNumSegments(Number maxNumSegments) {
+            return setParameter("max_num_segments", maxNumSegments);
+        }
+
+        /**
+         * Should the merge process only expunge segments with deletes in it. In Lucene,
+         * a document is not deleted from a segment, just marked as deleted. During a merge
+         * process of segments, a new segment is created that does not have those deletes.
+         * This flag allow to only merge segments that have deletes. Defaults to false.
+         */
+        public Builder onlyExpungeDeletes(boolean onlyExpungeDeletes) {
+            return setParameter("only_expunge_deletes", onlyExpungeDeletes);
+        }
+
+        /**
+         * Should a flush be performed after the forced merge. Defaults to true.
+         */
+        public Builder flush(boolean flush) {
+            return setParameter("flush", flush);
+        }
+
+        @Override
+        public ForceMerge build() {
+            return new ForceMerge(this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/jest-common/src/main/java/io/searchbox/indices/Optimize.java b/jest-common/src/main/java/io/searchbox/indices/Optimize.java
index 2c255001..f9aa28f4 100644
--- a/jest-common/src/main/java/io/searchbox/indices/Optimize.java
+++ b/jest-common/src/main/java/io/searchbox/indices/Optimize.java
@@ -4,6 +4,10 @@ import io.searchbox.action.AbstractMultiIndexActionBuilder;
 import io.searchbox.action.GenericResultAbstractAction;
 
 /**
+ * Optimize API: https://www.elastic.co/guide/en/elasticsearch/reference/2.4/indices-optimize.html
+ *
+ * Caution: this API has been deprecated in ES 2.1. With later versions, prefer {@link ForceMerge}.
+ *
  * @author Dogukan Sonmez
  * @author cihat keser
  */
@@ -44,13 +48,6 @@ public class Optimize extends GenericResultAbstractAction {
             return setParameter("only_expunge_deletes", onlyExpungeDeletes);
         }
 
-        /**
-         * Should a refresh be performed after the optimize. Defaults to true.
-         */
-        public Builder refresh(boolean refresh) {
-            return setParameter("refresh", refresh);
-        }
-
         /**
          * Should a flush be performed after the optimize. Defaults to true.
          */
@@ -58,14 +55,6 @@ public class Optimize extends GenericResultAbstractAction {
             return setParameter("flush", flush);
         }
 
-        /**
-         * Should the request wait for the merge to end. Defaults to true. Note, a merge can
-         * potentially be a very heavy operation, so it might make sense to run it set to false.
-         */
-        public Builder waitForMerge(boolean waitForMerge) {
-            return setParameter("wait_for_merge", waitForMerge);
-        }
-
         @Override
         public Optimize build() {
             return new Optimize(this);


Test patch:
diff --git a/jest-common/src/test/java/io/searchbox/indices/ForceMergeTest.java b/jest-common/src/test/java/io/searchbox/indices/ForceMergeTest.java
new file mode 100644
index 00000000..0ffa182f
--- /dev/null
+++ b/jest-common/src/test/java/io/searchbox/indices/ForceMergeTest.java
@@ -0,0 +1,34 @@
+package io.searchbox.indices;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class ForceMergeTest {
+
+    @Test
+    public void testBasicUriGeneration() {
+    	ForceMerge forceMerge = new ForceMerge.Builder().addIndex("twitter").build();
+
+        assertEquals("POST", forceMerge.getRestMethodName());
+        assertEquals("twitter/_forcemerge", forceMerge.getURI());
+    }
+
+    @Test
+    public void equalsReturnsTrueForSameIndex() {
+        ForceMerge forceMerge1 = new ForceMerge.Builder().addIndex("twitter").build();
+        ForceMerge forceMerge1Duplicate = new ForceMerge.Builder().addIndex("twitter").build();
+
+        assertEquals(forceMerge1, forceMerge1Duplicate);
+    }
+
+    @Test
+    public void equalsReturnsFalseForDifferentIndex() {
+        ForceMerge forceMerge1 = new ForceMerge.Builder().addIndex("twitter").build();
+        ForceMerge forceMerge2 = new ForceMerge.Builder().addIndex("myspace").build();
+
+        assertNotEquals(forceMerge1, forceMerge2);
+    }
+
+}
\ No newline at end of file
diff --git a/jest/src/test/java/io/searchbox/indices/ForceMergeIntegrationTest.java b/jest/src/test/java/io/searchbox/indices/ForceMergeIntegrationTest.java
new file mode 100644
index 00000000..8ae431ca
--- /dev/null
+++ b/jest/src/test/java/io/searchbox/indices/ForceMergeIntegrationTest.java
@@ -0,0 +1,36 @@
+package io.searchbox.indices;
+
+import io.searchbox.client.JestResult;
+import io.searchbox.common.AbstractIntegrationTest;
+import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
+import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
+import org.elasticsearch.test.ESIntegTestCase;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * @author cihat keser
+ */
+@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 1)
+public class ForceMergeIntegrationTest extends AbstractIntegrationTest {
+
+    // TODO find a way to confirm a previous merge request on server
+    @Ignore
+    @Test
+    public void testForceMergeDefault() throws InterruptedException, ExecutionException, TimeoutException, IOException {
+        ForceMerge forceMerge = new ForceMerge.Builder().maxNumSegments(1).build();
+        JestResult result = client.execute(forceMerge);
+        assertTrue(result.getErrorMessage(), result.isSucceeded());
+
+        IndicesStatsResponse statsResponse = client().admin().indices().stats(
+                new IndicesStatsRequest().clear().flush(true).refresh(true)).actionGet(10, TimeUnit.SECONDS);
+        assertNotNull(statsResponse);
+        assertNotNull(statsResponse.getTotal().getMerge());
+        assertEquals(1, statsResponse.getTotal().getMerge().getTotal());
+    }
+}