diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java
index 248d86c7c4217..5aa64a5c1375e 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java
@@ -51,6 +51,8 @@
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import java.io.IOException;
import java.util.Collections;
@@ -456,4 +458,26 @@ public void putSettingsAsync(UpdateSettingsRequest updateSettingsRequest, Action
UpdateSettingsResponse::fromXContent, listener, emptySet(), headers);
}
+ /**
+ * Puts an index template using the Index Templates API
+ *
+ * See Index Templates API
+ * on elastic.co
+ */
+ public PutIndexTemplateResponse putTemplate(PutIndexTemplateRequest putIndexTemplateRequest, Header... headers) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(putIndexTemplateRequest, RequestConverters::putTemplate,
+ PutIndexTemplateResponse::fromXContent, emptySet(), headers);
+ }
+
+ /**
+ * Asynchronously puts an index template using the Index Templates API
+ *
+ * See Index Templates API
+ * on elastic.co
+ */
+ public void putTemplateAsync(PutIndexTemplateRequest putIndexTemplateRequest,
+ ActionListener listener, Header... headers) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(putIndexTemplateRequest, RequestConverters::putTemplate,
+ PutIndexTemplateResponse::fromXContent, listener, emptySet(), headers);
+ }
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
index 705d8dfc9d252..720c934026b0b 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
@@ -47,6 +47,7 @@
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
@@ -77,7 +78,6 @@
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.rankeval.RankEvalRequest;
-import org.elasticsearch.rest.action.RestFieldCapabilitiesAction;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
@@ -86,10 +86,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.Locale;
-import java.util.Map;
import java.util.StringJoiner;
final class RequestConverters {
@@ -647,6 +644,21 @@ static Request indexPutSettings(UpdateSettingsRequest updateSettingsRequest) thr
return request;
}
+ static Request putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) throws IOException {
+ String endpoint = new EndpointBuilder().addPathPartAsIs("_template").addPathPart(putIndexTemplateRequest.name()).build();
+ Request request = new Request(HttpPut.METHOD_NAME, endpoint);
+ Params params = new Params(request);
+ params.withMasterTimeout(putIndexTemplateRequest.masterNodeTimeout());
+ if (putIndexTemplateRequest.create()) {
+ params.putParam("create", Boolean.TRUE.toString());
+ }
+ if (Strings.hasText(putIndexTemplateRequest.cause())) {
+ params.putParam("cause", putIndexTemplateRequest.cause());
+ }
+ request.setEntity(createEntity(putIndexTemplateRequest, REQUEST_BODY_CONTENT_TYPE));
+ return request;
+ }
+
private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
index eb09084200bd2..931447d85d44a 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
@@ -56,11 +56,14 @@
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
@@ -73,11 +76,19 @@
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Map;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
+import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractRawValues;
+import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue;
import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
@@ -812,4 +823,59 @@ public void testIndexPutSettingNonExistent() throws IOException {
+ "or check the breaking changes documentation for removed settings]"));
}
+ @SuppressWarnings("unchecked")
+ public void testPutTemplate() throws Exception {
+ PutIndexTemplateRequest putTemplateRequest = new PutIndexTemplateRequest()
+ .name("my-template")
+ .patterns(Arrays.asList("pattern-1", "name-*"))
+ .order(10)
+ .create(randomBoolean())
+ .settings(Settings.builder().put("number_of_shards", "3").put("number_of_replicas", "0"))
+ .mapping("doc", "host_name", "type=keyword", "description", "type=text")
+ .alias(new Alias("alias-1").indexRouting("abc")).alias(new Alias("{index}-write").searchRouting("xyz"));
+
+ PutIndexTemplateResponse putTemplateResponse = execute(putTemplateRequest,
+ highLevelClient().indices()::putTemplate, highLevelClient().indices()::putTemplateAsync);
+ assertThat(putTemplateResponse.isAcknowledged(), equalTo(true));
+
+ Map templates = getAsMap("/_template/my-template");
+ assertThat(templates.keySet(), hasSize(1));
+ assertThat(extractValue("my-template.order", templates), equalTo(10));
+ assertThat(extractRawValues("my-template.index_patterns", templates), contains("pattern-1", "name-*"));
+ assertThat(extractValue("my-template.settings.index.number_of_shards", templates), equalTo("3"));
+ assertThat(extractValue("my-template.settings.index.number_of_replicas", templates), equalTo("0"));
+ assertThat(extractValue("my-template.mappings.doc.properties.host_name.type", templates), equalTo("keyword"));
+ assertThat(extractValue("my-template.mappings.doc.properties.description.type", templates), equalTo("text"));
+ assertThat((Map) extractValue("my-template.aliases.alias-1", templates), hasEntry("index_routing", "abc"));
+ assertThat((Map) extractValue("my-template.aliases.{index}-write", templates), hasEntry("search_routing", "xyz"));
+ }
+
+ public void testPutTemplateBadRequests() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ // Failed to validate because index patterns are missing
+ PutIndexTemplateRequest withoutPattern = new PutIndexTemplateRequest("t1");
+ ValidationException withoutPatternError = expectThrows(ValidationException.class,
+ () -> execute(withoutPattern, client.indices()::putTemplate, client.indices()::putTemplateAsync));
+ assertThat(withoutPatternError.validationErrors(), contains("index patterns are missing"));
+
+ // Create-only specified but an template exists already
+ PutIndexTemplateRequest goodTemplate = new PutIndexTemplateRequest("t2").patterns(Arrays.asList("qa-*", "prod-*"));
+ assertTrue(execute(goodTemplate, client.indices()::putTemplate, client.indices()::putTemplateAsync).isAcknowledged());
+ goodTemplate.create(true);
+ ElasticsearchException alreadyExistsError = expectThrows(ElasticsearchException.class,
+ () -> execute(goodTemplate, client.indices()::putTemplate, client.indices()::putTemplateAsync));
+ assertThat(alreadyExistsError.getDetailedMessage(),
+ containsString("[type=illegal_argument_exception, reason=index_template [t2] already exists]"));
+ goodTemplate.create(false);
+ assertTrue(execute(goodTemplate, client.indices()::putTemplate, client.indices()::putTemplateAsync).isAcknowledged());
+
+ // Rejected due to unknown settings
+ PutIndexTemplateRequest unknownSettingTemplate = new PutIndexTemplateRequest("t3")
+ .patterns(Collections.singletonList("any"))
+ .settings(Settings.builder().put("this-setting-does-not-exist", 100));
+ ElasticsearchStatusException unknownSettingError = expectThrows(ElasticsearchStatusException.class,
+ () -> execute(unknownSettingTemplate, client.indices()::putTemplate, client.indices()::putTemplateAsync));
+ assertThat(unknownSettingError.getDetailedMessage(), containsString("unknown setting [index.this-setting-does-not-exist]"));
+ }
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
index 1953c820b8af8..70c209c30abf2 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
@@ -26,12 +26,11 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.entity.ContentType;
-import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
+import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
@@ -46,10 +45,11 @@
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
-import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
+import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkShardRequest;
import org.elasticsearch.action.delete.DeleteRequest;
@@ -70,6 +70,7 @@
import org.elasticsearch.action.support.master.MasterNodeRequest;
import org.elasticsearch.action.support.replication.ReplicationRequest;
import org.elasticsearch.action.update.UpdateRequest;
+import org.elasticsearch.client.RequestConverters.EndpointBuilder;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.Strings;
@@ -77,15 +78,13 @@
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.lucene.uid.Versions;
-import org.elasticsearch.common.settings.IndexScopedSettings;
+import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
-import org.elasticsearch.client.RequestConverters.EndpointBuilder;
-import org.elasticsearch.client.RequestConverters.Params;
import org.elasticsearch.index.RandomCreateIndexGenerator;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.query.TermQueryBuilder;
@@ -94,7 +93,6 @@
import org.elasticsearch.index.rankeval.RankEvalSpec;
import org.elasticsearch.index.rankeval.RatedRequest;
import org.elasticsearch.index.rankeval.RestRankEvalAction;
-import org.elasticsearch.rest.action.RestFieldCapabilitiesAction;
import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
@@ -111,8 +109,6 @@
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -121,7 +117,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -1432,6 +1427,48 @@ public void testIndexPutSettings() throws IOException {
assertEquals(expectedParams, request.getParameters());
}
+ public void testPutTemplateRequest() throws Exception {
+ Map names = new HashMap<>();
+ names.put("log", "log");
+ names.put("template#1", "template%231");
+ names.put("-#template", "-%23template");
+ names.put("foo^bar", "foo%5Ebar");
+
+ PutIndexTemplateRequest putTemplateRequest = new PutIndexTemplateRequest()
+ .name(randomFrom(names.keySet()))
+ .patterns(Arrays.asList(generateRandomStringArray(20, 100, false, false)));
+ if (randomBoolean()) {
+ putTemplateRequest.order(randomInt());
+ }
+ if (randomBoolean()) {
+ putTemplateRequest.version(randomInt());
+ }
+ if (randomBoolean()) {
+ putTemplateRequest.settings(Settings.builder().put("setting-" + randomInt(), randomTimeValue()));
+ }
+ if (randomBoolean()) {
+ putTemplateRequest.mapping("doc-" + randomInt(), "field-" + randomInt(), "type=" + randomFrom("text", "keyword"));
+ }
+ if (randomBoolean()) {
+ putTemplateRequest.alias(new Alias("alias-" + randomInt()));
+ }
+ Map expectedParams = new HashMap<>();
+ if (randomBoolean()) {
+ expectedParams.put("create", Boolean.TRUE.toString());
+ putTemplateRequest.create(true);
+ }
+ if (randomBoolean()) {
+ String cause = randomUnicodeOfCodepointLengthBetween(1, 50);
+ putTemplateRequest.cause(cause);
+ expectedParams.put("cause", cause);
+ }
+ setRandomMasterTimeout(putTemplateRequest, expectedParams);
+ Request request = RequestConverters.putTemplate(putTemplateRequest);
+ assertThat(request.getEndpoint(), equalTo("/_template/" + names.get(putTemplateRequest.name())));
+ assertThat(request.getParameters(), equalTo(expectedParams));
+ assertToXContentBody(putTemplateRequest, request.getEntity());
+ }
+
private static void assertToXContentBody(ToXContent expectedBody, HttpEntity actualEntity) throws IOException {
BytesReference expectedBytes = XContentHelper.toXContent(expectedBody, REQUEST_BODY_CONTENT_TYPE, false);
assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue());
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java
index 8f19ab6c8aac9..ed45f069e1d3f 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java
@@ -55,6 +55,10 @@
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
import org.elasticsearch.action.admin.indices.shrink.ResizeType;
+import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
+import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateResponse;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
import org.elasticsearch.action.support.IndicesOptions;
@@ -71,11 +75,14 @@
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import static org.hamcrest.Matchers.equalTo;
+
/**
* This class is used to generate the Java Indices API documentation.
* You need to wrap your code between two tags like:
@@ -1598,4 +1605,164 @@ public void onFailure(Exception e) {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
+ public void testPutTemplate() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ // tag::put-template-request
+ PutIndexTemplateRequest request = new PutIndexTemplateRequest("my-template"); // <1>
+ request.patterns(Arrays.asList("pattern-1", "log-*")); // <2>
+ // end::put-template-request
+
+ // tag::put-template-request-settings
+ request.settings(Settings.builder() // <1>
+ .put("index.number_of_shards", 3)
+ .put("index.number_of_replicas", 1)
+ );
+ // end::put-template-request-settings
+
+ {
+ // tag::create-put-template-request-mappings
+ request.mapping("tweet", // <1>
+ "{\n" +
+ " \"tweet\": {\n" +
+ " \"properties\": {\n" +
+ " \"message\": {\n" +
+ " \"type\": \"text\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}", // <2>
+ XContentType.JSON);
+ // end::create-put-template-mappings
+ assertTrue(client.indices().putTemplate(request).isAcknowledged());
+ }
+ {
+ //tag::put-template-mappings-map
+ Map jsonMap = new HashMap<>();
+ Map message = new HashMap<>();
+ message.put("type", "text");
+ Map properties = new HashMap<>();
+ properties.put("message", message);
+ Map tweet = new HashMap<>();
+ tweet.put("properties", properties);
+ jsonMap.put("tweet", tweet);
+ request.mapping("tweet", jsonMap); // <1>
+ //end::put-template-mappings-map
+ assertTrue(client.indices().putTemplate(request).isAcknowledged());
+ }
+ {
+ //tag::put-template-mappings-xcontent
+ XContentBuilder builder = XContentFactory.jsonBuilder();
+ builder.startObject();
+ {
+ builder.startObject("tweet");
+ {
+ builder.startObject("properties");
+ {
+ builder.startObject("message");
+ {
+ builder.field("type", "text");
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ request.mapping("tweet", builder); // <1>
+ //end::put-template-mappings-xcontent
+ assertTrue(client.indices().putTemplate(request).isAcknowledged());
+ }
+ {
+ //tag::put-template-mappings-shortcut
+ request.mapping("tweet", "message", "type=text"); // <1>
+ //end::put-template-mappings-shortcut
+ assertTrue(client.indices().putTemplate(request).isAcknowledged());
+ }
+
+ // tag::put-template-request-aliases
+ request.alias(new Alias("twitter_alias").filter(QueryBuilders.termQuery("user", "kimchy"))); // <1>
+ request.alias(new Alias("{index}_alias").searchRouting("xyz")); // <2>
+ // end::put-template-request-aliases
+
+ // tag::put-template-request-order
+ request.order(20); // <1>
+ // end::put-template-request-order
+
+ // tag::put-template-request-order
+ request.version(4); // <1>
+ // end::put-template-request-order
+
+ // tag::put-template-whole-source
+ request.source("{\n" +
+ " \"index_patterns\": [\n" +
+ " \"log-*\",\n" +
+ " \"pattern-1\"\n" +
+ " ],\n" +
+ " \"order\": 1,\n" +
+ " \"settings\": {\n" +
+ " \"number_of_shards\": 1\n" +
+ " },\n" +
+ " \"mappings\": {\n" +
+ " \"tweet\": {\n" +
+ " \"properties\": {\n" +
+ " \"message\": {\n" +
+ " \"type\": \"text\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " },\n" +
+ " \"aliases\": {\n" +
+ " \"alias-1\": {},\n" +
+ " \"{index}-alias\": {}\n" +
+ " }\n" +
+ "}", XContentType.JSON); // <1>
+ // end::put-template-whole-source
+
+ // tag::put-template-request-create
+ request.create(true); // <1>
+ // end::put-template-request-create
+
+ // tag::put-template-request-masterTimeout
+ request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+ request.masterNodeTimeout("1m"); // <2>
+ // end::put-template-request-masterTimeout
+
+ request.create(false); // make test happy
+
+ // tag::put-template-execute
+ PutIndexTemplateResponse putTemplateResponse = client.indices().putTemplate(request);
+ // end::put-template-execute
+
+ // tag::put-template-response
+ boolean acknowledged = putTemplateResponse.isAcknowledged(); // <1>
+ // end::put-template-response
+ assertTrue(acknowledged);
+
+ // tag::put-template-execute-listener
+ ActionListener listener =
+ new ActionListener() {
+ @Override
+ public void onResponse(PutIndexTemplateResponse putTemplateResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::put-template-execute-listener
+
+ // Replace the empty listener by a blocking listener in test
+ final CountDownLatch latch = new CountDownLatch(1);
+ listener = new LatchedActionListener<>(listener, latch);
+
+ // tag::put-template-execute-async
+ client.indices().putTemplateAsync(request, listener); // <1>
+ // end::put-template-execute-async
+
+ assertTrue(latch.await(30L, TimeUnit.SECONDS));
+ }
}
diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc
index 9e33c10a56b12..e9897fad0607d 100644
--- a/docs/CHANGELOG.asciidoc
+++ b/docs/CHANGELOG.asciidoc
@@ -156,6 +156,8 @@ synchronous and predictable. Also the trigger engine thread is only started on
data nodes. And the Execute Watch API can be triggered regardless is watcher is
started or stopped. ({pull}30118[#30118])
+Added put index template API to the high level rest client ({pull}30400[#30400])
+
[float]
=== Bug Fixes
diff --git a/docs/java-rest/high-level/indices/put_template.asciidoc b/docs/java-rest/high-level/indices/put_template.asciidoc
new file mode 100644
index 0000000000000..57409feece65c
--- /dev/null
+++ b/docs/java-rest/high-level/indices/put_template.asciidoc
@@ -0,0 +1,168 @@
+[[java-rest-high-put-template]]
+=== Put Template API
+
+[[java-rest-high-put-template-request]]
+==== Put Index Template Request
+
+A `PutIndexTemplateRequest` specifies the `name` of a template and `patterns`
+which controls whether the template should be applied to the new index.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request]
+--------------------------------------------------
+<1> The name of the template
+<2> The patterns of the template
+
+==== Settings
+The settings of the template will be applied to the new index whose name matches the
+template's patterns.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-settings]
+--------------------------------------------------
+<1> Settings for this template
+
+[[java-rest-high-put-template-request-mappings]]
+==== Mappings
+The mapping of the template will be applied to the new index whose name matches the
+template's patterns.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-mappings]
+--------------------------------------------------
+<1> The type to define
+<2> The mapping for this type, provided as a JSON string
+
+The mapping source can be provided in different ways in addition to the
+`String` example shown above:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-mappings-map]
+--------------------------------------------------
+<1> Mapping source provided as a `Map` which gets automatically converted
+to JSON format
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-mappings-xcontent]
+--------------------------------------------------
+<1> Mapping source provided as an `XContentBuilder` object, the Elasticsearch
+built-in helpers to generate JSON content
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-mappings-shortcut]
+--------------------------------------------------
+<1> Mapping source provided as `Object` key-pairs, which gets converted to
+JSON format
+
+==== Aliases
+The aliases of the template will define aliasing to the index whose name matches the
+template's patterns. A placeholder `{index}` can be used in an alias of a template.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-aliases]
+--------------------------------------------------
+<1> The alias to define
+<2> The alias to define with placeholder
+
+==== Order
+In case multiple templates match an index, the orders of matching templates determine
+the sequence that settings, mappings, and alias of each matching template is applied.
+Templates with lower orders are applied first, and higher orders override them.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-order]
+--------------------------------------------------
+<1> The order of the template
+
+==== Version
+A template can optionally specify a version number which can be used to simplify template
+management by external systems.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-version]
+--------------------------------------------------
+<1> The version number of the template
+
+==== Providing the whole source
+The whole source including all of its sections (mappings, settings and aliases)
+can also be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-whole-source]
+--------------------------------------------------
+<1> The source provided as a JSON string. It can also be provided as a `Map`
+or an `XContentBuilder`.
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-create]
+--------------------------------------------------
+<1> To force to only create a new template; do not overwrite the existing template
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-request-masterTimeout]
+--------------------------------------------------
+<1> Timeout to connect to the master node as a `TimeValue`
+<2> Timeout to connect to the master node as a `String`
+
+[[java-rest-high-put-template-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-execute]
+--------------------------------------------------
+
+[[java-rest-high-put-template-async]]
+==== Asynchronous Execution
+
+The asynchronous execution of a put template request requires both the `PutIndexTemplateRequest`
+instance and an `ActionListener` instance to be passed to the asynchronous method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-execute-async]
+--------------------------------------------------
+<1> The `PutIndexTemplateRequest` to execute and the `ActionListener` to use when
+the execution completes
+
+The asynchronous method does not block and returns immediately. Once it is
+completed the `ActionListener` is called back using the `onResponse` method
+if the execution successfully completed or using the `onFailure` method if
+it failed.
+
+A typical listener for `PutIndexTemplateResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-execute-listener]
+--------------------------------------------------
+<1> Called when the execution is successfully completed. The response is
+provided as an argument
+<2> Called in case of failure. The raised exception is provided as an argument
+
+[[java-rest-high-put-template-response]]
+==== Put Index Template Response
+
+The returned `PutIndexTemplateResponse` allows to retrieve information about the
+executed operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-template-response]
+--------------------------------------------------
+<1> Indicates whether all of the nodes have acknowledged the request
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index 4d845e538415f..6623e09242f32 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -95,6 +95,7 @@ include::indices/update_aliases.asciidoc[]
include::indices/exists_alias.asciidoc[]
include::indices/put_settings.asciidoc[]
include::indices/get_settings.asciidoc[]
+include::indices/put_template.asciidoc[]
== Cluster APIs
diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequest.java
index 8cd1fac6f6fd1..b018e24a565b8 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequest.java
@@ -39,6 +39,7 @@
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
@@ -58,14 +59,14 @@
import java.util.stream.Collectors;
import static org.elasticsearch.action.ValidateActions.addValidationError;
+import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
import static org.elasticsearch.common.settings.Settings.readSettingsFromStream;
import static org.elasticsearch.common.settings.Settings.writeSettingsToStream;
-import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
/**
* A request to create an index template.
*/
-public class PutIndexTemplateRequest extends MasterNodeRequest implements IndicesRequest {
+public class PutIndexTemplateRequest extends MasterNodeRequest implements IndicesRequest, ToXContent {
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(Loggers.getLogger(PutIndexTemplateRequest.class));
@@ -539,4 +540,34 @@ public void writeTo(StreamOutput out) throws IOException {
}
out.writeOptionalVInt(version);
}
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ if (customs.isEmpty() == false) {
+ throw new IllegalArgumentException("Custom data type is no longer supported in index template [" + customs + "]");
+ }
+ builder.field("index_patterns", indexPatterns);
+ builder.field("order", order);
+ if (version != null) {
+ builder.field("version", version);
+ }
+
+ builder.startObject("settings");
+ settings.toXContent(builder, params);
+ builder.endObject();
+
+ builder.startObject("mappings");
+ for (Map.Entry entry : mappings.entrySet()) {
+ Map mapping = XContentHelper.convertToMap(new BytesArray(entry.getValue()), false).v2();
+ builder.field(entry.getKey(), mapping);
+ }
+ builder.endObject();
+
+ builder.startObject("aliases");
+ for (Alias alias : aliases) {
+ alias.toXContent(builder, params);
+ }
+ builder.endObject();
+ return builder;
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateResponse.java
index bf6e05a6c7b43..6c8a5291b12d5 100644
--- a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateResponse.java
+++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateResponse.java
@@ -21,6 +21,8 @@
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
@@ -47,4 +49,14 @@ public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
writeAcknowledged(out);
}
+
+ private static final ConstructingObjectParser PARSER;
+ static {
+ PARSER = new ConstructingObjectParser<>("put_index_template", true, args -> new PutIndexTemplateResponse((boolean) args[0]));
+ declareAcknowledgedField(PARSER);
+ }
+
+ public static PutIndexTemplateResponse fromXContent(XContentParser parser) {
+ return PARSER.apply(parser, null);
+ }
}
diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestTests.java
index 72cbe2bd9ecab..294213452596f 100644
--- a/server/src/test/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestTests.java
+++ b/server/src/test/java/org/elasticsearch/action/admin/indices/template/put/PutIndexTemplateRequestTests.java
@@ -20,10 +20,15 @@
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.yaml.YamlXContent;
@@ -35,6 +40,7 @@
import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
@@ -131,4 +137,52 @@ public void testValidateErrorMessage() throws Exception {
assertThat(noError, is(nullValue()));
}
+ private PutIndexTemplateRequest randomPutIndexTemplateRequest() throws IOException {
+ PutIndexTemplateRequest request = new PutIndexTemplateRequest();
+ request.name("test");
+ if (randomBoolean()){
+ request.version(randomInt());
+ }
+ if (randomBoolean()){
+ request.order(randomInt());
+ }
+ request.patterns(Arrays.asList(generateRandomStringArray(20, 100, false, false)));
+ int numAlias = between(0, 5);
+ for (int i = 0; i < numAlias; i++) {
+ Alias alias = new Alias(randomRealisticUnicodeOfLengthBetween(1, 10));
+ if (randomBoolean()) {
+ alias.indexRouting(randomRealisticUnicodeOfLengthBetween(1, 10));
+ }
+ if (randomBoolean()) {
+ alias.searchRouting(randomRealisticUnicodeOfLengthBetween(1, 10));
+ }
+ request.alias(alias);
+ }
+ if (randomBoolean()) {
+ request.mapping("doc", XContentFactory.jsonBuilder().startObject()
+ .startObject("doc").startObject("properties")
+ .startObject("field-" + randomInt()).field("type", randomFrom("keyword", "text")).endObject()
+ .endObject().endObject().endObject());
+ }
+ if (randomBoolean()){
+ request.settings(Settings.builder().put("setting1", randomLong()).put("setting2", randomTimeValue()).build());
+ }
+ return request;
+ }
+
+ public void testFromToXContentPutTemplateRequest() throws Exception {
+ for (int i = 0; i < 10; i++) {
+ PutIndexTemplateRequest expected = randomPutIndexTemplateRequest();
+ XContentType xContentType = randomFrom(XContentType.values());
+ BytesReference shuffled = toShuffledXContent(expected, xContentType, ToXContent.EMPTY_PARAMS, randomBoolean());
+ PutIndexTemplateRequest parsed = new PutIndexTemplateRequest().source(shuffled, xContentType);
+ assertNotSame(expected, parsed);
+ assertThat(parsed.version(), equalTo(expected.version()));
+ assertThat(parsed.order(), equalTo(expected.order()));
+ assertThat(parsed.patterns(), equalTo(expected.patterns()));
+ assertThat(parsed.aliases(), equalTo(expected.aliases()));
+ assertThat(parsed.mappings(), equalTo(expected.mappings()));
+ assertThat(parsed.settings(), equalTo(expected.settings()));
+ }
+ }
}