Repository: searchbox-io/Jest
Pull request #213 (2015-06-22 23:17:36)

Added action for adding and deleting indexed scripts
I would like to add two more actions.
One adds indexed scripts, the other deletes them.

As a basis, I've used the reference at:
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html

Feedback is welcome.

Regards
Martin


Patch:
diff --git a/README.md b/README.md
index 5ad24114..d2370004 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,7 @@ We also would like to thank the following people for their significant contribut
 * [Igor Kupczyński](https://github.com/puszczyk)
 * [Kristoffer Renholm](https://github.com/renholm)
 * [Mark Woon](https://github.com/markwoon)
+* [Martin W. Kirst](https://github.com/nitram509)
 * [Matthew Bogner](https://github.com/matthewbogner)
 * [Min Cha](https://github.com/MinCha)
 * [Neil Gentleman](https://github.com/nigelzor)
diff --git a/jest-common/src/main/java/io/searchbox/indices/script/CreateIndexedScript.java b/jest-common/src/main/java/io/searchbox/indices/script/CreateIndexedScript.java
new file mode 100644
index 00000000..01480e85
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/indices/script/CreateIndexedScript.java
@@ -0,0 +1,103 @@
+package io.searchbox.indices.script;
+
+import com.google.common.io.CharStreams;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.searchbox.action.AbstractAction;
+import io.searchbox.action.GenericResultAbstractAction;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+import static io.searchbox.indices.script.ScriptLanguage.GROOVY;
+import static java.net.URLEncoder.encode;
+
+public class CreateIndexedScript extends GenericResultAbstractAction {
+
+    private final String scriptName;
+    private final ScriptLanguage scriptLanguage;
+
+    protected CreateIndexedScript(Builder builder) {
+        super(builder);
+        this.payload = builder.payload;
+        this.scriptName = builder.scriptName;
+        this.scriptLanguage = builder.scriptLanguage;
+        setURI(buildURI());
+    }
+
+    protected String buildURI() {
+        String finalUri = super.buildURI() + "/_scripts/" + scriptLanguage.pathParameterName + "/";
+        try {
+            finalUri += encode(scriptName, CHARSET);
+        } catch (UnsupportedEncodingException e) {
+            // unless CHARSET is overridden with a wrong value in a subclass,
+            // this exception won't be thrown.
+            log.error("Error occurred while adding parameters to uri.", e);
+        }
+        return finalUri;
+    }
+
+    @Override
+    public String getRestMethodName() {
+        return "POST";
+    }
+
+    public String getScriptName() {
+        return scriptName;
+    }
+
+    public ScriptLanguage getScriptLanguage() {
+        return scriptLanguage;
+    }
+
+    public static class Builder extends AbstractAction.Builder<CreateIndexedScript, Builder> {
+
+        private String scriptName;
+        private JsonElement payload;
+        private ScriptLanguage scriptLanguage = GROOVY;
+
+        public Builder(String scriptName) {
+            this.scriptName = scriptName;
+        }
+
+        @Override
+        public CreateIndexedScript build() {
+            return new CreateIndexedScript(this);
+        }
+
+        public Builder setSource(String source) {
+            createPayload(source);
+            return this;
+        }
+
+        public Builder loadSource(File srcFile) throws IOException {
+            return loadSource(srcFile, Charset.forName(AbstractAction.CHARSET));
+        }
+
+        public Builder loadSource(File srcFile, Charset encoding) throws IOException {
+            return loadSource(new FileInputStream(srcFile), encoding);
+        }
+
+        public Builder loadSource(InputStream srcStream) throws IOException {
+            return loadSource(srcStream, Charset.forName(AbstractAction.CHARSET));
+        }
+
+        public Builder loadSource(InputStream srcStream, Charset encoding) throws IOException {
+            String src = CharStreams.toString(new InputStreamReader(srcStream, encoding));
+            createPayload(src);
+            return this;
+        }
+
+        public Builder setLanguage(ScriptLanguage language) {
+            this.scriptLanguage = language;
+            return this;
+        }
+
+        private void createPayload(String source) {
+            JsonObject jsonObject = new JsonObject();
+            jsonObject.addProperty("script", source);
+            payload = jsonObject;
+        }
+
+    }
+}
diff --git a/jest-common/src/main/java/io/searchbox/indices/script/DeleteIndexedScript.java b/jest-common/src/main/java/io/searchbox/indices/script/DeleteIndexedScript.java
new file mode 100644
index 00000000..88487fac
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/indices/script/DeleteIndexedScript.java
@@ -0,0 +1,68 @@
+package io.searchbox.indices.script;
+
+import io.searchbox.action.AbstractAction;
+import io.searchbox.action.GenericResultAbstractAction;
+
+import java.io.UnsupportedEncodingException;
+
+import static io.searchbox.indices.script.ScriptLanguage.GROOVY;
+import static java.net.URLEncoder.encode;
+
+public class DeleteIndexedScript extends GenericResultAbstractAction {
+
+    private final String scriptName;
+    private final ScriptLanguage scriptLanguage;
+
+    protected DeleteIndexedScript(Builder builder) {
+        super(builder);
+        this.scriptName = builder.scriptName;
+        this.scriptLanguage = builder.scriptLanguage;
+        setURI(buildURI());
+    }
+
+    protected String buildURI() {
+        String finalUri = super.buildURI() + "/_scripts/" + scriptLanguage.pathParameterName + "/";
+        try {
+            finalUri += encode(scriptName, CHARSET);
+        } catch (UnsupportedEncodingException e) {
+            // unless CHARSET is overridden with a wrong value in a subclass,
+            // this exception won't be thrown.
+            log.error("Error occurred while adding parameters to uri.", e);
+        }
+        return finalUri;
+    }
+
+    @Override
+    public String getRestMethodName() {
+        return "DELETE";
+    }
+
+    public String getScriptName() {
+        return scriptName;
+    }
+
+    public ScriptLanguage getScriptLanguage() {
+        return scriptLanguage;
+    }
+
+    public static class Builder extends AbstractAction.Builder<DeleteIndexedScript, Builder> {
+
+        private String scriptName;
+        private ScriptLanguage scriptLanguage = GROOVY;
+
+        public Builder(String scriptName) {
+            this.scriptName = scriptName;
+        }
+
+        public Builder setLanguage(ScriptLanguage scriptLanguage) {
+            this.scriptLanguage = scriptLanguage;
+            return this;
+        }
+
+        @Override
+        public DeleteIndexedScript build() {
+            return new DeleteIndexedScript(this);
+        }
+
+    }
+}
diff --git a/jest-common/src/main/java/io/searchbox/indices/script/ScriptLanguage.java b/jest-common/src/main/java/io/searchbox/indices/script/ScriptLanguage.java
new file mode 100644
index 00000000..5ea85f89
--- /dev/null
+++ b/jest-common/src/main/java/io/searchbox/indices/script/ScriptLanguage.java
@@ -0,0 +1,22 @@
+package io.searchbox.indices.script;
+
+/**
+ * As described in Elasticsearch Reference
+ * <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html">
+ * https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html
+ * </a>
+ */
+public enum ScriptLanguage {
+    GROOVY("groovy"),
+    EXPRESSION("expression"),
+    MUSTACHE("mustache"),
+    MVEL("mvel"),
+    JAVASCRIPT("javascript"),
+    PYTHON("python");
+
+    public final String pathParameterName;
+
+    ScriptLanguage(String pathParameterName) {
+        this.pathParameterName = pathParameterName;
+    }
+}


Test patch:
diff --git a/jest-common/src/test/java/io/searchbox/indices/script/CreateIndexedScriptTest.java b/jest-common/src/test/java/io/searchbox/indices/script/CreateIndexedScriptTest.java
new file mode 100644
index 00000000..2cb180cb
--- /dev/null
+++ b/jest-common/src/test/java/io/searchbox/indices/script/CreateIndexedScriptTest.java
@@ -0,0 +1,87 @@
+package io.searchbox.indices.script;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import static io.searchbox.indices.script.ScriptLanguage.GROOVY;
+import static io.searchbox.indices.script.ScriptLanguage.JAVASCRIPT;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.*;
+
+public class CreateIndexedScriptTest {
+
+    private static final String A_NAME = "a_name";
+    private CreateIndexedScript script;
+    private CreateIndexedScript.Builder builder;
+    private String groovysnippet;
+
+    @Before
+    public void setUp() throws Exception {
+        builder = new CreateIndexedScript.Builder(A_NAME).setLanguage(JAVASCRIPT);
+        script = builder.build();
+        groovysnippet = "def test_a=123\n" +
+                "def test_b=\"$test_a\"\n";
+    }
+
+    @Test
+    public void defaultScriptingLanguageIsGroovy() throws Exception {
+        CreateIndexedScript script = new CreateIndexedScript.Builder(A_NAME).build();
+
+        assertEquals(GROOVY, script.getScriptLanguage());
+        assertThat(script.buildURI(), containsString(GROOVY.pathParameterName));
+    }
+
+    @Test
+    public void scriptingLanguageIsSetIntoPath() throws Exception {
+
+        assertThat(script.buildURI(), containsString("/_scripts/" + JAVASCRIPT.pathParameterName + "/"));
+    }
+
+    @Test
+    public void nameOfTheScriptIsSetIntoPath() throws Exception {
+
+        assertThat(script.buildURI(), containsString("/_scripts/" + JAVASCRIPT.pathParameterName + "/" + A_NAME));
+    }
+
+    @Test
+    public void scriptSourceIsValidJsonString() throws Exception {
+        builder.setSource(groovysnippet);
+
+        script = builder.build();
+
+        JsonObject jsonPayload = parseAsGson(script.getData(new Gson()));
+        assertNotNull(jsonPayload.get("script"));
+        assertEquals(groovysnippet, jsonPayload.get("script").getAsString());
+    }
+
+    @Test
+    public void fileSourceIsValidJsonString() throws Exception {
+        builder.loadSource(createTempGroovySnippetFile());
+
+        script = builder.build();
+
+        JsonObject jsonPayload = parseAsGson(script.getData(new Gson()));
+        assertNotNull(jsonPayload.get("script"));
+        assertEquals(groovysnippet, jsonPayload.get("script").getAsString());
+    }
+
+    private File createTempGroovySnippetFile() throws IOException {
+        File file = File.createTempFile("test", ".groovy");
+        file.deleteOnExit();
+        FileWriter writer = new FileWriter(file);
+        writer.write(groovysnippet);
+        writer.close();
+        return file;
+    }
+
+    private JsonObject parseAsGson(String data) {
+        return new JsonParser().parse(data).getAsJsonObject();
+    }
+}
\ No newline at end of file
diff --git a/jest-common/src/test/java/io/searchbox/indices/script/DeleteIndexedScriptTest.java b/jest-common/src/test/java/io/searchbox/indices/script/DeleteIndexedScriptTest.java
new file mode 100644
index 00000000..52a2702c
--- /dev/null
+++ b/jest-common/src/test/java/io/searchbox/indices/script/DeleteIndexedScriptTest.java
@@ -0,0 +1,43 @@
+package io.searchbox.indices.script;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static io.searchbox.indices.script.ScriptLanguage.GROOVY;
+import static io.searchbox.indices.script.ScriptLanguage.JAVASCRIPT;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class DeleteIndexedScriptTest {
+
+    private static final String A_NAME = "a_name";
+    private DeleteIndexedScript script;
+
+    @Before
+    public void setUp() throws Exception {
+        DeleteIndexedScript.Builder builder = new DeleteIndexedScript.Builder(A_NAME).setLanguage(JAVASCRIPT);
+        script = builder.build();
+    }
+
+    @Test
+    public void defaultScriptingLanguageIsGroovy() throws Exception {
+        DeleteIndexedScript script = new DeleteIndexedScript.Builder(A_NAME).build();
+
+        assertEquals(GROOVY, script.getScriptLanguage());
+        assertThat(script.buildURI(), containsString(GROOVY.pathParameterName));
+    }
+
+    @Test
+    public void scriptingLanguageIsSetIntoPath() throws Exception {
+
+        assertThat(script.buildURI(), containsString("/_scripts/" + JAVASCRIPT.pathParameterName + "/"));
+    }
+
+    @Test
+    public void nameOfTheScriptIsSetIntoPath() throws Exception {
+
+        assertThat(script.buildURI(), containsString("/_scripts/" + JAVASCRIPT.pathParameterName + "/" + A_NAME));
+    }
+
+}
\ No newline at end of file
diff --git a/jest/src/test/java/io/searchbox/indices/script/CreateIndexedScriptIntegrationTest.java b/jest/src/test/java/io/searchbox/indices/script/CreateIndexedScriptIntegrationTest.java
new file mode 100644
index 00000000..1d7edcc4
--- /dev/null
+++ b/jest/src/test/java/io/searchbox/indices/script/CreateIndexedScriptIntegrationTest.java
@@ -0,0 +1,27 @@
+package io.searchbox.indices.script;
+
+import io.searchbox.client.JestResult;
+import io.searchbox.common.AbstractIntegrationTest;
+import org.elasticsearch.test.ElasticsearchIntegrationTest;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static io.searchbox.indices.script.ScriptLanguage.GROOVY;
+
+@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST, numDataNodes = 1)
+public class CreateIndexedScriptIntegrationTest extends AbstractIntegrationTest {
+
+    @Test
+    public void create_an_indexed_script_for_Groovy() throws IOException {
+        CreateIndexedScript createIndexedScript = new CreateIndexedScript.Builder("script-test")
+                .setLanguage(GROOVY)
+                .setSource("def aVariable = 1\n" +
+                        "return aVariable")
+                .build();
+        JestResult result = client.execute(createIndexedScript);
+        assertTrue(result.isSucceeded());
+    }
+
+}
+
diff --git a/jest/src/test/java/io/searchbox/indices/script/DeleteIndexedScriptIntegrationTest.java b/jest/src/test/java/io/searchbox/indices/script/DeleteIndexedScriptIntegrationTest.java
new file mode 100644
index 00000000..9c15bf9c
--- /dev/null
+++ b/jest/src/test/java/io/searchbox/indices/script/DeleteIndexedScriptIntegrationTest.java
@@ -0,0 +1,43 @@
+package io.searchbox.indices.script;
+
+import io.searchbox.client.JestResult;
+import io.searchbox.common.AbstractIntegrationTest;
+import org.elasticsearch.test.ElasticsearchIntegrationTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static io.searchbox.indices.script.ScriptLanguage.GROOVY;
+
+@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST, numDataNodes = 1)
+public class DeleteIndexedScriptIntegrationTest extends AbstractIntegrationTest {
+
+    private static final String A_SCRIPT_NAME = "script-test";
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        createIndexedScript();
+    }
+
+    @Test
+    public void delete_an_indexed_script_for_Groovy() throws IOException {
+        DeleteIndexedScript deleteIndexedScript = new DeleteIndexedScript.Builder(A_SCRIPT_NAME)
+                .setLanguage(ScriptLanguage.GROOVY)
+                .build();
+        JestResult result = client.execute(deleteIndexedScript);
+        assertTrue(result.isSucceeded());
+    }
+
+    private void createIndexedScript() throws IOException {
+        client.execute(new CreateIndexedScript.Builder(A_SCRIPT_NAME)
+                .setLanguage(GROOVY)
+                .setSource("def aVariable = 1\n" +
+                        "return aVariable")
+                .build());
+    }
+
+}
+