diff --git a/pom.xml b/pom.xml
index b83085a4607fd..78ba573cb5aac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -163,7 +163,14 @@
-
+
+
+ com.github.spullara.mustache.java
+ compiler
+ 0.8.13
+ true
+
+
diff --git a/src/main/java/org/elasticsearch/index/query/TemplateQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/TemplateQueryBuilder.java
new file mode 100644
index 0000000000000..659366ba1aabe
--- /dev/null
+++ b/src/main/java/org/elasticsearch/index/query/TemplateQueryBuilder.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+/**
+ * Facilitates creating template query requests.
+ * */
+public class TemplateQueryBuilder extends BaseQueryBuilder {
+
+ /** Parameters to fill the template with. */
+ private Map vars;
+ /** Template to fill.*/
+ private String template;
+
+ /**
+ * @param template the template to use for that query.
+ * @param vars the parameters to fill the template with.
+ * */
+ public TemplateQueryBuilder(String template, Map vars) {
+ this.template = template;
+ this.vars = vars;
+ }
+
+ @Override
+ protected void doXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject(TemplateQueryParser.NAME);
+ builder.field(TemplateQueryParser.STRING, template);
+ builder.field(TemplateQueryParser.VARS, vars);
+ builder.endObject();
+ }
+}
diff --git a/src/main/java/org/elasticsearch/index/query/TemplateQueryParser.java b/src/main/java/org/elasticsearch/index/query/TemplateQueryParser.java
new file mode 100644
index 0000000000000..a713d4f536ea3
--- /dev/null
+++ b/src/main/java/org/elasticsearch/index/query/TemplateQueryParser.java
@@ -0,0 +1,111 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.search.Query;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.script.ExecutableScript;
+import org.elasticsearch.script.ScriptService;
+
+/**
+ * In the simplest case, parse template string and variables from the request, compile the template and
+ * execute the template against the given variables.
+ *
+ * TODO support named templates
+ * */
+public class TemplateQueryParser implements QueryParser {
+
+ /** Name to reference this type of query. */
+ public static final String NAME = "template";
+ /** Name of query parameter containing the template string. */
+ public static final String STRING = "template_string";
+ /** Name of query parameter containing the template parameters. */
+ public static final String VARS = "template_vars";
+ /** This is what we are registered with for query executions. */
+ private final ScriptService scriptService;
+
+ /**
+ * @param scriptService will automatically be wired by Guice
+ * */
+ @Inject
+ public TemplateQueryParser(ScriptService scriptService) {
+ this.scriptService = scriptService;
+ }
+
+ /**
+ * @return a list of names this query is registered under.
+ * */
+ @Override
+ public String[] names() {
+ return new String[] {NAME};
+ }
+
+ @Override
+ @Nullable
+ public Query parse(QueryParseContext parseContext) throws IOException {
+ XContentParser parser = parseContext.parser();
+
+ String template = "";
+ Map vars = new HashMap();
+
+ String currentFieldName = null;
+ XContentParser.Token token;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (STRING.equals(currentFieldName)) {
+ template = (String) parser.objectText();
+ } else if (VARS.equals(currentFieldName)) {
+ XContentParser.Token innerToken;
+ String key = "";
+ while ((innerToken = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ // parsing template parameter map
+ if (innerToken == XContentParser.Token.FIELD_NAME) {
+ key = parser.currentName();
+ } else {
+ vars.put(key, parser.objectText());
+ key = "";
+ }
+ }
+ }
+ }
+
+ ExecutableScript executable = this.scriptService.executable("mustache", template, vars);
+ String querySource = (String) executable.run();
+
+ XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource);
+ try {
+ final QueryParseContext context = new QueryParseContext(parseContext.index(), parseContext.indexQueryParser);
+ context.reset(qSourceParser);
+ Query result = context.parseInnerQuery();
+ parser.nextToken();
+ return result;
+ } finally {
+ qSourceParser.close();
+ }
+ }
+}
diff --git a/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java b/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java
index da7029f6ea3c0..44a9474669af3 100644
--- a/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java
+++ b/src/main/java/org/elasticsearch/indices/query/IndicesQueriesModule.java
@@ -103,6 +103,7 @@ protected void configure() {
qpBinders.addBinding().to(SpanMultiTermQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(FunctionScoreQueryParser.class).asEagerSingleton();
qpBinders.addBinding().to(SimpleQueryStringParser.class).asEagerSingleton();
+ qpBinders.addBinding().to(TemplateQueryParser.class).asEagerSingleton();
if (ShapesAvailability.JTS_AVAILABLE) {
qpBinders.addBinding().to(GeoShapeQueryParser.class).asEagerSingleton();
diff --git a/src/main/java/org/elasticsearch/script/ScriptModule.java b/src/main/java/org/elasticsearch/script/ScriptModule.java
index bee40a4fb881e..22a2230937673 100644
--- a/src/main/java/org/elasticsearch/script/ScriptModule.java
+++ b/src/main/java/org/elasticsearch/script/ScriptModule.java
@@ -26,6 +26,7 @@
import org.elasticsearch.common.inject.multibindings.MapBinder;
import org.elasticsearch.common.inject.multibindings.Multibinder;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.script.mustache.MustacheScriptEngineService;
import org.elasticsearch.script.mvel.MvelScriptEngineService;
import java.util.List;
@@ -80,6 +81,13 @@ protected void configure() {
} catch (Throwable t) {
// no MVEL
}
+
+ try {
+ multibinder.addBinding().to(MustacheScriptEngineService.class);
+ } catch (Throwable t) {
+ // no Mustache
+ }
+
for (Class extends ScriptEngineService> scriptEngine : scriptEngines) {
multibinder.addBinding().to(scriptEngine);
}
diff --git a/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java b/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java
new file mode 100644
index 0000000000000..953ffbcca78ae
--- /dev/null
+++ b/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngineService.java
@@ -0,0 +1,157 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.script.mustache;
+
+import com.fasterxml.jackson.core.io.SegmentedStringWriter;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.component.AbstractComponent;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.FastStringReader;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.script.ExecutableScript;
+import org.elasticsearch.script.ScriptEngineService;
+import org.elasticsearch.script.SearchScript;
+import org.elasticsearch.search.lookup.SearchLookup;
+
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * Main entry point handling template registration, compilation and
+ * execution.
+ *
+ * Template handling is based on Mustache. Template handling is a two step
+ * process: First compile the string representing the template, the resulting
+ * {@link Mustache} object can then be re-used for subsequent executions.
+ */
+public class MustacheScriptEngineService extends AbstractComponent implements ScriptEngineService {
+
+ /** Factory to generate Mustache objects from. */
+ private static final MustacheFactory MFACTORY = new DefaultMustacheFactory();
+
+ /**
+ * @param settings automatically wired by Guice.
+ * */
+ @Inject
+ public MustacheScriptEngineService(Settings settings) {
+ super(settings);
+ }
+
+ /**
+ * Compile a template string to (in this case) a Mustache object than can
+ * later be re-used for execution to fill in missing parameter values.
+ *
+ * @param template
+ * a string representing the template to compile.
+ * @return a compiled template object for later execution.
+ * */
+ public Object compile(String template) {
+ return MFACTORY.compile(new FastStringReader(template), "query-template");
+ }
+
+ /**
+ * Execute a compiled template object (as retrieved from the compile method)
+ * and fill potential place holders with the variables given.
+ *
+ * @param template
+ * compiled template object.
+ * @param vars
+ * map of variables to use during substitution.
+ *
+ * @return the processed string with all given variables substitued.
+ * */
+ public Object execute(Object template, Map vars) {
+ SegmentedStringWriter result = new SegmentedStringWriter(new BufferRecycler());
+ ((Mustache) template).execute(result, vars);
+ return result.getAndClear();
+ }
+
+ @Override
+ public String[] types() {
+ return new String[] {"mustache"};
+ }
+
+ @Override
+ public String[] extensions() {
+ return new String[] {"mustache"};
+ }
+
+ @Override
+ public ExecutableScript executable(Object mustache,
+ @Nullable Map vars) {
+ return new MustacheExecutableScript((Mustache) mustache, vars);
+ }
+
+ @Override
+ public SearchScript search(Object compiledScript, SearchLookup lookup,
+ @Nullable Map vars) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object unwrap(Object value) {
+ return value;
+ }
+
+ @Override
+ public void close() {
+ // Nothing to do here
+ }
+
+ /**
+ * Used at query execution time by script service in order to execute a query template.
+ * */
+ private class MustacheExecutableScript implements ExecutableScript {
+ /** Compiled template object. */
+ private Mustache mustache;
+ /** Parameters to fill above object with. */
+ private Map vars;
+
+ /**
+ * @param mustache the compiled template object
+ * @param vars the parameters to fill above object with
+ **/
+ public MustacheExecutableScript(Mustache mustache,
+ Map vars) {
+ this.mustache = mustache;
+ this.vars = vars;
+ }
+
+ @Override
+ public void setNextVar(String name, Object value) {
+ this.vars.put(name, value);
+ }
+
+ @Override
+ public Object run() {
+ StringWriter result = new StringWriter();
+ ((Mustache) mustache).execute(result, vars);
+ return result.toString();
+ }
+
+ @Override
+ public Object unwrap(Object value) {
+ return value;
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/index/query/TemplateQueryBuilderTest.java b/src/test/java/org/elasticsearch/index/query/TemplateQueryBuilderTest.java
new file mode 100644
index 0000000000000..b34883b6c5094
--- /dev/null
+++ b/src/test/java/org/elasticsearch/index/query/TemplateQueryBuilderTest.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test building and serialising a template search request.
+ * */
+public class TemplateQueryBuilderTest {
+
+ @Test
+ public void testJSONGeneration() throws IOException {
+ Map vars = new HashMap();
+ vars.put("template", "filled");
+ TemplateQueryBuilder builder = new TemplateQueryBuilder("I am a $template string", vars);
+ XContentBuilder content = XContentFactory.jsonBuilder();
+ content.startObject();
+ builder.doXContent(content, null);
+ content.endObject();
+ content.close();
+ assertEquals(content.string(), "{\"template\":{\"template_string\":\"I am a $template string\",\"template_vars\":{\"template\":\"filled\"}}}");
+ }
+
+}
diff --git a/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java b/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java
new file mode 100644
index 0000000000000..e39888bdcae36
--- /dev/null
+++ b/src/test/java/org/elasticsearch/index/query/TemplateQueryParserTest.java
@@ -0,0 +1,101 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query;
+
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.Query;
+import org.elasticsearch.cache.recycler.CacheRecyclerModule;
+import org.elasticsearch.cluster.ClusterService;
+import org.elasticsearch.common.inject.AbstractModule;
+import org.elasticsearch.common.inject.Injector;
+import org.elasticsearch.common.inject.ModulesBuilder;
+import org.elasticsearch.common.inject.util.Providers;
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.settings.SettingsModule;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.index.Index;
+import org.elasticsearch.index.IndexNameModule;
+import org.elasticsearch.index.analysis.AnalysisModule;
+import org.elasticsearch.index.cache.IndexCacheModule;
+import org.elasticsearch.index.codec.CodecModule;
+import org.elasticsearch.index.engine.IndexEngineModule;
+import org.elasticsearch.index.query.functionscore.FunctionScoreModule;
+import org.elasticsearch.index.settings.IndexSettingsModule;
+import org.elasticsearch.index.similarity.SimilarityModule;
+import org.elasticsearch.indices.fielddata.breaker.CircuitBreakerService;
+import org.elasticsearch.indices.fielddata.breaker.DummyCircuitBreakerService;
+import org.elasticsearch.indices.query.IndicesQueriesModule;
+import org.elasticsearch.script.ScriptModule;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.threadpool.ThreadPoolModule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Test parsing and executing a template request.
+ * */
+public class TemplateQueryParserTest extends ElasticsearchTestCase {
+
+ @Test
+ public void testParser() throws IOException {
+ String templateString = "{\"template\": {"
+ + "\"template_string\":\"{\\\"match_{{template}}\\\": {}}\","
+ + "\"template_vars\":{\"template\":\"all\"}}" + "}";
+
+ Settings settings = ImmutableSettings.Builder.EMPTY_SETTINGS;
+
+ Index index = new Index("test");
+ Injector injector = new ModulesBuilder().add(
+ new SettingsModule(settings),
+ new CacheRecyclerModule(settings),
+ new CodecModule(settings),
+ new ThreadPoolModule(settings),
+ new IndicesQueriesModule(),
+ new ScriptModule(settings),
+ new IndexSettingsModule(index, settings),
+ new IndexCacheModule(settings),
+ new AnalysisModule(settings),
+ new IndexEngineModule(settings),
+ new SimilarityModule(settings),
+ new IndexNameModule(index),
+ new IndexQueryParserModule(settings),
+ new FunctionScoreModule(),
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(ClusterService.class).toProvider(Providers.of((ClusterService) null));
+ bind(CircuitBreakerService.class).to(DummyCircuitBreakerService.class);
+ }
+ }
+ ).createInjector();
+
+ IndexQueryParserService queryParserService = injector.getInstance(IndexQueryParserService.class);
+
+ XContentParser templateSourceParser = XContentFactory.xContent(templateString).createParser(templateString);
+ QueryParseContext context = new QueryParseContext(index, queryParserService);
+ context.reset(templateSourceParser);
+
+ TemplateQueryParser parser = injector.getInstance(TemplateQueryParser.class);
+ Query query = parser.parse(context);
+ assertTrue("Parsing template query failed.", query instanceof ConstantScoreQuery);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/index/query/TemplateQueryPluginTest.java b/src/test/java/org/elasticsearch/index/query/TemplateQueryPluginTest.java
new file mode 100644
index 0000000000000..0417796513954
--- /dev/null
+++ b/src/test/java/org/elasticsearch/index/query/TemplateQueryPluginTest.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query;
+
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ElasticsearchIntegrationTest;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Full integration test of the template query plugin.
+ * */
+@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE)
+public class TemplateQueryPluginTest extends ElasticsearchIntegrationTest {
+
+ @Test
+ public void testTemplateInBody() {
+ createIndex("test");
+ ensureGreen();
+
+ client().prepareIndex("test", "testtype").setId("1")
+ .setSource("text", "value1").get();
+ client().prepareIndex("test", "testtype").setId("2")
+ .setSource("text", "value2").get();
+ refresh();
+
+ Map vars = new HashMap();
+ vars.put("template", "all");
+
+ TemplateQueryBuilder builder = new TemplateQueryBuilder(
+ "{\"match_{{template}}\": {}}\"", vars);
+ SearchResponse sr = client().prepareSearch().setQuery(builder)
+ .execute().actionGet();
+ assertEquals("Template query didn't return correct number of hits.", 2,
+ sr.getHits().totalHits());
+ }
+
+ @Test
+ public void testTemplateInFile() {
+ createIndex("test");
+ ensureGreen();
+
+ client().prepareIndex("test", "testtype").setId("1")
+ .setSource("text", "value1").get();
+ client().prepareIndex("test", "testtype").setId("2")
+ .setSource("text", "value2").get();
+ refresh();
+
+ Map vars = new HashMap();
+ vars.put("template", "all");
+ TemplateQueryBuilder builder = new TemplateQueryBuilder(
+ "storedTemplate", vars);
+ SearchResponse sr = client().prepareSearch().setQuery(builder)
+ .execute().actionGet();
+ assertEquals("Template query didn't return correct number of hits.", 2,
+ sr.getHits().totalHits());
+
+ }
+
+ @Override
+ public Settings nodeSettings(int nodeOrdinal) {
+ String scriptPath = this.getClass()
+ .getResource("config").getPath();
+
+ Settings settings = ImmutableSettings
+ .settingsBuilder()
+ .put("path.conf", scriptPath).build();
+
+ return settings;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/script/mustache/MustacheScriptEngineTest.java b/src/test/java/org/elasticsearch/script/mustache/MustacheScriptEngineTest.java
new file mode 100644
index 0000000000000..2d86257afe5e0
--- /dev/null
+++ b/src/test/java/org/elasticsearch/script/mustache/MustacheScriptEngineTest.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.script.mustache;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Mustache based templating test
+ * */
+public class MustacheScriptEngineTest {
+ private MustacheScriptEngineService qe;
+
+ private static String TEMPLATE = "GET _search {\"query\": " + "{\"boosting\": {" + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ + "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
+
+ @Before
+ public void setup() {
+ qe = new MustacheScriptEngineService(ImmutableSettings.Builder.EMPTY_SETTINGS);
+ }
+
+ @Test
+ public void testSimpleParameterReplace() {
+ Map vars = new HashMap();
+ vars.put("boost_val", "0.3");
+ Object o = qe.execute(qe.compile(TEMPLATE), vars);
+ assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ + "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.3 } }}", ((String) o));
+ }
+
+}
diff --git a/src/test/java/org/elasticsearch/script/mustache/MustacheTest.java b/src/test/java/org/elasticsearch/script/mustache/MustacheTest.java
new file mode 100644
index 0000000000000..d887fa1d14c41
--- /dev/null
+++ b/src/test/java/org/elasticsearch/script/mustache/MustacheTest.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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.elasticsearch.script.mustache;
+
+import static org.junit.Assert.*;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.HashMap;
+
+import org.junit.Test;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+/**
+ * Figure out how Mustache works for the simplest use case. Leaving in here for now for reference.
+ * */
+public class MustacheTest {
+
+ @Test
+ public void test() {
+ HashMap scopes = new HashMap();
+ scopes.put("boost_val", "0.2");
+
+ String template = "GET _search {\"query\": " + "{\"boosting\": {"
+ + "\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ + "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}"
+ + "}}, \"negative_boost\": {{boost_val}} } }}";
+ MustacheFactory f = new DefaultMustacheFactory();
+ Mustache mustache = f.compile(new StringReader(template), "example");
+ StringWriter writer = new StringWriter();
+ mustache.execute(writer, scopes);
+ writer.flush();
+ assertEquals(
+ "Mustache templating broken",
+ "GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
+ + "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.2 } }}",
+ writer.toString());
+ }
+}
diff --git a/src/test/resources/org/elasticsearch/index/query/config/scripts/storedTemplate.mustache b/src/test/resources/org/elasticsearch/index/query/config/scripts/storedTemplate.mustache
new file mode 100644
index 0000000000000..a779da7c467c5
--- /dev/null
+++ b/src/test/resources/org/elasticsearch/index/query/config/scripts/storedTemplate.mustache
@@ -0,0 +1,3 @@
+{
+ "match_{{template}}": {}
+}