/*
 * 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.indices.template;

import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.indices.InvalidAliasNameException;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.ESIntegTestCase;
import org.junit.After;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThrows;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

public class SimpleIndexTemplateIT extends ESIntegTestCase {

    @After
    public void cleanupTemplates() {
        client().admin().indices().prepareDeleteTemplate("*").get();
    }

    public void testSimpleIndexTemplateTests() throws Exception {
        // clean all templates setup by the framework.
        client().admin().indices().prepareDeleteTemplate("*").get();

        // check get all templates on an empty index.
        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), empty());


        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setSettings(indexSettings())
                .setOrder(0)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field1").field("type", "text").field("store", true).endObject()
                        .startObject("field2").field("type", "keyword").field("store", true).endObject()
                        .endObject().endObject().endObject())
                .get();

        client().admin().indices().preparePutTemplate("template_2")
                .setPatterns(Collections.singletonList("test*"))
                .setSettings(indexSettings())
                .setOrder(1)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field2").field("type", "text").field("store", false).endObject()
                        .endObject().endObject().endObject())
                .get();

        // test create param
        assertThrows(client().admin().indices().preparePutTemplate("template_2")
                .setPatterns(Collections.singletonList("test*"))
                .setSettings(indexSettings())
                .setCreate(true)
                .setOrder(1)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field2").field("type", "text").field("store", false).endObject()
                        .endObject().endObject().endObject())
                , IllegalArgumentException.class
        );

        response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), hasSize(2));


        // index something into test_index, will match on both templates
        client().prepareIndex("test_index", "type1", "1")
                .setSource("field1", "value1", "field2", "value 2")
                .setRefreshPolicy(IMMEDIATE).get();

        ensureGreen();
        SearchResponse searchResponse = client().prepareSearch("test_index")
                .setQuery(termQuery("field1", "value1"))
                .addStoredField("field1").addStoredField("field2")
                .execute().actionGet();

        assertHitCount(searchResponse, 1);
        assertThat(searchResponse.getHits().getAt(0).field("field1").getValue().toString(), equalTo("value1"));
        // field2 is not stored.
        assertThat(searchResponse.getHits().getAt(0).field("field2"), nullValue());

        client().prepareIndex("text_index", "type1", "1")
                .setSource("field1", "value1", "field2", "value 2")
                .setRefreshPolicy(IMMEDIATE).get();

        ensureGreen();
        // now only match on one template (template_1)
        searchResponse = client().prepareSearch("text_index")
                .setQuery(termQuery("field1", "value1"))
                .addStoredField("field1").addStoredField("field2")
                .execute().actionGet();
        if (searchResponse.getFailedShards() > 0) {
            logger.warn("failed search {}", Arrays.toString(searchResponse.getShardFailures()));
        }
        assertHitCount(searchResponse, 1);
        assertThat(searchResponse.getHits().getAt(0).field("field1").getValue().toString(), equalTo("value1"));
        assertThat(searchResponse.getHits().getAt(0).field("field2").getValue().toString(), equalTo("value 2"));
    }

    public void testDeleteIndexTemplate() throws Exception {
        final int existingTemplates = admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size();
        logger.info("--> put template_1 and template_2");
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(0)
                .addMapping("type1", XContentFactory.jsonBuilder()
                    .startObject()
                        .startObject("type1")
                            .startObject("properties")
                                .startObject("field1")
                                    .field("type", "text")
                                    .field("store", true)
                                .endObject()
                                .startObject("field2")
                                    .field("type", "text")
                                    .field("store", true)
                                .endObject()
                            .endObject()
                        .endObject()
                    .endObject())
                .execute().actionGet();

        client().admin().indices().preparePutTemplate("template_2")
                .setPatterns(Collections.singletonList("test*"))
                .setOrder(1)
                .addMapping("type1", XContentFactory.jsonBuilder()
                    .startObject()
                        .startObject("type1")
                            .startObject("properties")
                                .startObject("field2")
                                    .field("type", "text")
                                    .field("store", false)
                                .endObject()
                            .endObject()
                        .endObject()
                    .endObject())
                .execute().actionGet();

        logger.info("--> explicitly delete template_1");
        admin().indices().prepareDeleteTemplate("template_1").execute().actionGet();

        ClusterState state = admin().cluster().prepareState().execute().actionGet().getState();

        assertThat(state.metaData().templates().size(), equalTo(1 + existingTemplates));
        assertThat(state.metaData().templates().containsKey("template_2"), equalTo(true));
        assertThat(state.metaData().templates().containsKey("template_1"), equalTo(false));


        logger.info("--> put template_1 back");
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(0)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field1").field("type", "text").field("store", true).endObject()
                        .startObject("field2").field("type", "keyword").field("store", true).endObject()
                        .endObject().endObject().endObject())
                .execute().actionGet();

        logger.info("--> delete template*");
        admin().indices().prepareDeleteTemplate("template*").execute().actionGet();
        assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(),
                   equalTo(existingTemplates));

        logger.info("--> delete * with no templates, make sure we don't get a failure");
        admin().indices().prepareDeleteTemplate("*").execute().actionGet();
        assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(),
                   equalTo(0));
    }

    public void testThatGetIndexTemplatesWorks() throws Exception {
        logger.info("--> put template_1");
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(0)
                .setVersion(123)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field1").field("type", "text").field("store", true).endObject()
                        .startObject("field2").field("type", "keyword").field("store", true).endObject()
                        .endObject().endObject().endObject())
                .execute().actionGet();

        logger.info("--> get template template_1");
        GetIndexTemplatesResponse getTemplate1Response = client().admin().indices().prepareGetTemplates("template_1").execute().actionGet();
        assertThat(getTemplate1Response.getIndexTemplates(), hasSize(1));
        assertThat(getTemplate1Response.getIndexTemplates().get(0), is(notNullValue()));
        assertThat(getTemplate1Response.getIndexTemplates().get(0).patterns(), is(Collections.singletonList("te*")));
        assertThat(getTemplate1Response.getIndexTemplates().get(0).getOrder(), is(0));
        assertThat(getTemplate1Response.getIndexTemplates().get(0).getVersion(), is(123));

        logger.info("--> get non-existing-template");
        GetIndexTemplatesResponse getTemplate2Response =
            client().admin().indices().prepareGetTemplates("non-existing-template").execute().actionGet();
        assertThat(getTemplate2Response.getIndexTemplates(), hasSize(0));
    }

    public void testThatGetIndexTemplatesWithSimpleRegexWorks() throws Exception {
        logger.info("--> put template_1");
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(0)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field1").field("type", "text").field("store", true).endObject()
                        .startObject("field2").field("type", "keyword").field("store", true).endObject()
                        .endObject().endObject().endObject())
                .execute().actionGet();

        logger.info("--> put template_2");
        client().admin().indices().preparePutTemplate("template_2")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(0)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field1").field("type", "text").field("store", true).endObject()
                        .startObject("field2").field("type", "keyword").field("store", true).endObject()
                        .endObject().endObject().endObject())
                .execute().actionGet();

        logger.info("--> put template3");
        client().admin().indices().preparePutTemplate("template3")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(0)
                .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field1").field("type", "text").field("store", true).endObject()
                        .startObject("field2").field("type", "keyword").field("store", true).endObject()
                        .endObject().endObject().endObject())
                .execute().actionGet();

        logger.info("--> get template template_*");
        GetIndexTemplatesResponse getTemplate1Response = client().admin().indices().prepareGetTemplates("template_*").execute().actionGet();
        assertThat(getTemplate1Response.getIndexTemplates(), hasSize(2));

        List<String> templateNames = new ArrayList<>();
        templateNames.add(getTemplate1Response.getIndexTemplates().get(0).name());
        templateNames.add(getTemplate1Response.getIndexTemplates().get(1).name());
        assertThat(templateNames, containsInAnyOrder("template_1", "template_2"));

        logger.info("--> get all templates");
        getTemplate1Response = client().admin().indices().prepareGetTemplates("template*").execute().actionGet();
        assertThat(getTemplate1Response.getIndexTemplates(), hasSize(3));

        templateNames = new ArrayList<>();
        templateNames.add(getTemplate1Response.getIndexTemplates().get(0).name());
        templateNames.add(getTemplate1Response.getIndexTemplates().get(1).name());
        templateNames.add(getTemplate1Response.getIndexTemplates().get(2).name());
        assertThat(templateNames, containsInAnyOrder("template_1", "template_2", "template3"));

        logger.info("--> get templates template_1 and template_2");
        getTemplate1Response = client().admin().indices().prepareGetTemplates("template_1", "template_2").execute().actionGet();
        assertThat(getTemplate1Response.getIndexTemplates(), hasSize(2));

        templateNames = new ArrayList<>();
        templateNames.add(getTemplate1Response.getIndexTemplates().get(0).name());
        templateNames.add(getTemplate1Response.getIndexTemplates().get(1).name());
        assertThat(templateNames, containsInAnyOrder("template_1", "template_2"));
    }

    public void testThatInvalidGetIndexTemplatesFails() throws Exception {
        logger.info("--> get template null");
        testExpectActionRequestValidationException((String[])null);

        logger.info("--> get template empty");
        testExpectActionRequestValidationException("");

        logger.info("--> get template 'a', '', 'c'");
        testExpectActionRequestValidationException("a", "", "c");

        logger.info("--> get template 'a', null, 'c'");
        testExpectActionRequestValidationException("a", null, "c");
    }

    private void testExpectActionRequestValidationException(String... names) {
        assertThrows(client().admin().indices().prepareGetTemplates(names),
                ActionRequestValidationException.class,
                "get template with " + Arrays.toString(names));
    }

    public void testBrokenMapping() throws Exception {
        // clean all templates setup by the framework.
        client().admin().indices().prepareDeleteTemplate("*").get();

        // check get all templates on an empty index.
        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), empty());

        MapperParsingException e = expectThrows( MapperParsingException.class,
            () -> client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addMapping("type1", "{\"foo\": \"abcde\"}", XContentType.JSON)
                .get());
        assertThat(e.getMessage(), containsString("Failed to parse mapping "));

        response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), hasSize(0));
    }

    public void testInvalidSettings() throws Exception {
        // clean all templates setup by the framework.
        client().admin().indices().prepareDeleteTemplate("*").get();

        // check get all templates on an empty index.
        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), empty());

        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
            () -> client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setSettings(Settings.builder().put("does_not_exist", "test"))
                .get());
        assertEquals("unknown setting [index.does_not_exist] please check that any required plugins are" +
            " installed, or check the breaking changes documentation for removed settings", e.getMessage());

        response = client().admin().indices().prepareGetTemplates().get();
        assertEquals(0, response.getIndexTemplates().size());

        createIndex("test");

        GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings("test").get();
        assertNull(getSettingsResponse.getIndexToSettings().get("test").getAsMap().get("index.does_not_exist"));
    }

    public void testIndexTemplateWithAliases() throws Exception {

        client().admin().indices().preparePutTemplate("template_with_aliases")
                .setPatterns(Collections.singletonList("te*"))
                .addMapping("type1", "{\"type1\" : {\"properties\" : {\"value\" : {\"type\" : \"text\"}}}}", XContentType.JSON)
                .addAlias(new Alias("simple_alias"))
                .addAlias(new Alias("templated_alias-{index}"))
                .addAlias(new Alias("filtered_alias").filter("{\"type\":{\"value\":\"type2\"}}"))
                .addAlias(new Alias("complex_filtered_alias")
                        .filter(QueryBuilders.termsQuery("_type",  "typeX", "typeY", "typeZ")))
                .get();

        assertAcked(prepareCreate("test_index")
                .setSettings("index.mapping.single_type", false)
                .addMapping("type1").addMapping("type2").addMapping("typeX").addMapping("typeY").addMapping("typeZ"));
        ensureGreen();

        client().prepareIndex("test_index", "type1", "1").setSource("field", "A value").get();
        client().prepareIndex("test_index", "type2", "2").setSource("field", "B value").get();
        client().prepareIndex("test_index", "typeX", "3").setSource("field", "C value").get();
        client().prepareIndex("test_index", "typeY", "4").setSource("field", "D value").get();
        client().prepareIndex("test_index", "typeZ", "5").setSource("field", "E value").get();

        GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get();
        assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
        assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(4));

        refresh();

        SearchResponse searchResponse = client().prepareSearch("test_index").get();
        assertHitCount(searchResponse, 5L);

        searchResponse = client().prepareSearch("simple_alias").get();
        assertHitCount(searchResponse, 5L);

        searchResponse = client().prepareSearch("templated_alias-test_index").get();
        assertHitCount(searchResponse, 5L);

        searchResponse = client().prepareSearch("filtered_alias").get();
        assertHitCount(searchResponse, 1L);
        assertThat(searchResponse.getHits().getAt(0).getType(), equalTo("type2"));

        // Search the complex filter alias
        searchResponse = client().prepareSearch("complex_filtered_alias").get();
        assertHitCount(searchResponse, 3L);

        Set<String> types = new HashSet<>();
        for (SearchHit searchHit : searchResponse.getHits().getHits()) {
            types.add(searchHit.getType());
        }
        assertThat(types.size(), equalTo(3));
        assertThat(types, containsInAnyOrder("typeX", "typeY", "typeZ"));
    }

    public void testIndexTemplateWithAliasesInSource() {
        client().admin().indices().preparePutTemplate("template_1")
                .setSource(new BytesArray("{\n" +
                        "    \"template\" : \"*\",\n" +
                        "    \"aliases\" : {\n" +
                        "        \"my_alias\" : {\n" +
                        "            \"filter\" : {\n" +
                        "                \"type\" : {\n" +
                        "                    \"value\" : \"type2\"\n" +
                        "                }\n" +
                        "            }\n" +
                        "        }\n" +
                        "    }\n" +
                        "}"), XContentType.JSON).get();


        assertAcked(prepareCreate("test_index")
                .setSettings("index.mapping.single_type", false)
                .addMapping("type1").addMapping("type2"));
        ensureGreen();

        GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get();
        assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
        assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(1));

        client().prepareIndex("test_index", "type1", "1").setSource("field", "value1").get();
        client().prepareIndex("test_index", "type2", "2").setSource("field", "value2").get();
        refresh();

        SearchResponse searchResponse = client().prepareSearch("test_index").get();
        assertHitCount(searchResponse, 2L);

        searchResponse = client().prepareSearch("my_alias").get();
        assertHitCount(searchResponse, 1L);
        assertThat(searchResponse.getHits().getAt(0).getType(), equalTo("type2"));
    }

    public void testIndexTemplateWithAliasesSource() {
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setAliases(
                        "    {\n" +
                        "        \"alias1\" : {},\n" +
                        "        \"alias2\" : {\n" +
                        "            \"filter\" : {\n" +
                        "                \"type\" : {\n" +
                        "                    \"value\" : \"type2\"\n" +
                        "                }\n" +
                        "            }\n" +
                        "         },\n" +
                        "        \"alias3\" : { \"routing\" : \"1\" }" +
                        "    }\n").get();

        assertAcked(prepareCreate("test_index")
                .setSettings("index.mapping.single_type", false)
                .addMapping("type1").addMapping("type2"));
        ensureGreen();

        GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get();
        assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
        assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(3));

        client().prepareIndex("test_index", "type1", "1").setSource("field", "value1").get();
        client().prepareIndex("test_index", "type2", "2").setSource("field", "value2").get();
        refresh();

        SearchResponse searchResponse = client().prepareSearch("test_index").get();
        assertHitCount(searchResponse, 2L);

        searchResponse = client().prepareSearch("alias1").get();
        assertHitCount(searchResponse, 2L);

        searchResponse = client().prepareSearch("alias2").get();
        assertHitCount(searchResponse, 1L);
        assertThat(searchResponse.getHits().getAt(0).getType(), equalTo("type2"));
    }

    public void testDuplicateAlias() throws Exception {
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addAlias(new Alias("my_alias").filter(termQuery("field", "value1")))
                .addAlias(new Alias("my_alias").filter(termQuery("field", "value2")))
                .get();

        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
        assertThat(response.getIndexTemplates().size(), equalTo(1));
        assertThat(response.getIndexTemplates().get(0).getAliases().size(), equalTo(1));
        assertThat(response.getIndexTemplates().get(0).getAliases().get("my_alias").filter().string(), containsString("\"value1\""));
    }

    public void testAliasInvalidFilterValidJson() throws Exception {
        //invalid filter but valid json: put index template works fine, fails during index creation
        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addAlias(new Alias("invalid_alias").filter("{ \"invalid\": {} }")).get();

        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
        assertThat(response.getIndexTemplates().size(), equalTo(1));
        assertThat(response.getIndexTemplates().get(0).getAliases().size(), equalTo(1));
        assertThat(response.getIndexTemplates().get(0).getAliases().get("invalid_alias").filter().string(), equalTo("{\"invalid\":{}}"));

        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
            () -> createIndex("test"));
        assertThat(e.getMessage(), equalTo("failed to parse filter for alias [invalid_alias]"));
        assertThat(e.getCause(), instanceOf(ParsingException.class));
        assertThat(e.getCause().getMessage(), equalTo("no [query] registered for [invalid]"));
    }

    public void testAliasInvalidFilterInvalidJson() throws Exception {
        //invalid json: put index template fails
        PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addAlias(new Alias("invalid_alias").filter("abcde"));

        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
            () -> putIndexTemplateRequestBuilder.get());
        assertThat(e.getMessage(), equalTo("failed to parse filter for alias [invalid_alias]"));

        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
        assertThat(response.getIndexTemplates().size(), equalTo(0));
    }

    public void testAliasNameExistingIndex() throws Exception {
        createIndex("index");

        client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addAlias(new Alias("index")).get();

        InvalidAliasNameException e = expectThrows(InvalidAliasNameException.class,
            () -> createIndex("test"));
        assertThat(e.getMessage(), equalTo("Invalid alias name [index], an index exists with the same name as the alias"));
    }

    public void testAliasEmptyName() throws Exception {
        PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addAlias(new Alias("  ").indexRouting("1,2,3"));

        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
            () -> putIndexTemplateRequestBuilder.get());
        assertThat(e.getMessage(), equalTo("alias name is required"));
    }

    public void testAliasWithMultipleIndexRoutings() throws Exception {
        PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .addAlias(new Alias("alias").indexRouting("1,2,3"));

        IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
            () -> putIndexTemplateRequestBuilder.get());
        assertThat(e.getMessage(), equalTo("alias [alias] has several index routing values associated with it"));
    }

    public void testMultipleAliasesPrecedence() throws Exception {
        client().admin().indices().preparePutTemplate("template1")
                .setPatterns(Collections.singletonList("*"))
                .setOrder(0)
                .addAlias(new Alias("alias1"))
                .addAlias(new Alias("{index}-alias"))
                .addAlias(new Alias("alias3").filter(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("test"))))
                .addAlias(new Alias("alias4")).get();

        client().admin().indices().preparePutTemplate("template2")
                .setPatterns(Collections.singletonList("te*"))
                .setOrder(1)
                .addAlias(new Alias("alias1").routing("test"))
                .addAlias(new Alias("alias3")).get();


        assertAcked(prepareCreate("test").addAlias(new Alias("test-alias").searchRouting("test-routing")));

        ensureGreen();

        GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().addIndices("test").get();
        assertThat(getAliasesResponse.getAliases().get("test").size(), equalTo(4));

        for (AliasMetaData aliasMetaData : getAliasesResponse.getAliases().get("test")) {
            assertThat(aliasMetaData.alias(), anyOf(equalTo("alias1"), equalTo("test-alias"), equalTo("alias3"), equalTo("alias4")));
            if ("alias1".equals(aliasMetaData.alias())) {
                assertThat(aliasMetaData.indexRouting(), equalTo("test"));
                assertThat(aliasMetaData.searchRouting(), equalTo("test"));
            } else if ("alias3".equals(aliasMetaData.alias())) {
                assertThat(aliasMetaData.filter(), nullValue());
            } else if ("test-alias".equals(aliasMetaData.alias())) {
                assertThat(aliasMetaData.indexRouting(), nullValue());
                assertThat(aliasMetaData.searchRouting(), equalTo("test-routing"));
            }
        }
    }

    public void testStrictAliasParsingInIndicesCreatedViaTemplates() throws Exception {
        // Indexing into a should succeed, because the field mapping for field 'field' is defined in the test mapping.
        client().admin().indices().preparePutTemplate("template1")
                .setPatterns(Collections.singletonList("a*"))
                .setOrder(0)
                .addMapping("test", "field", "type=text")
                .addAlias(new Alias("alias1").filter(termQuery("field", "value"))).get();
        // Indexing into b should succeed, because the field mapping for field 'field' is defined in the _default_ mapping and
        //  the test type exists.
        client().admin().indices().preparePutTemplate("template2")
                .setPatterns(Collections.singletonList("b*"))
                .setOrder(0)
                .addMapping("_default_", "field", "type=text")
                .addMapping("test")
                .addAlias(new Alias("alias2").filter(termQuery("field", "value"))).get();
        // Indexing into c should succeed, because the field mapping for field 'field' is defined in the _default_ mapping.
        client().admin().indices().preparePutTemplate("template3")
                .setPatterns(Collections.singletonList("c*"))
                .setOrder(0)
                .addMapping("_default_", "field", "type=text")
                .addAlias(new Alias("alias3").filter(termQuery("field", "value"))).get();
        // Indexing into d index should fail, since there is field with name 'field' in the mapping
        client().admin().indices().preparePutTemplate("template4")
                .setPatterns(Collections.singletonList("d*"))
                .setOrder(0)
                .addAlias(new Alias("alias4").filter(termQuery("field", "value"))).get();

        client().prepareIndex("a1", "test", "test").setSource("{}", XContentType.JSON).get();
        BulkResponse response = client().prepareBulk().add(new IndexRequest("a2", "test", "test").source("{}", XContentType.JSON)).get();
        assertThat(response.hasFailures(), is(false));
        assertThat(response.getItems()[0].isFailed(), equalTo(false));
        assertThat(response.getItems()[0].getIndex(), equalTo("a2"));
        assertThat(response.getItems()[0].getType(), equalTo("test"));
        assertThat(response.getItems()[0].getId(), equalTo("test"));
        assertThat(response.getItems()[0].getVersion(), equalTo(1L));

        client().prepareIndex("b1", "test", "test").setSource("{}", XContentType.JSON).get();
        response = client().prepareBulk().add(new IndexRequest("b2", "test", "test").source("{}", XContentType.JSON)).get();
        assertThat(response.hasFailures(), is(false));
        assertThat(response.getItems()[0].isFailed(), equalTo(false));
        assertThat(response.getItems()[0].getIndex(), equalTo("b2"));
        assertThat(response.getItems()[0].getType(), equalTo("test"));
        assertThat(response.getItems()[0].getId(), equalTo("test"));
        assertThat(response.getItems()[0].getVersion(), equalTo(1L));

        client().prepareIndex("c1", "test", "test").setSource("{}", XContentType.JSON).get();
        response = client().prepareBulk().add(new IndexRequest("c2", "test", "test").source("{}", XContentType.JSON)).get();
        assertThat(response.hasFailures(), is(false));
        assertThat(response.getItems()[0].isFailed(), equalTo(false));
        assertThat(response.getItems()[0].getIndex(), equalTo("c2"));
        assertThat(response.getItems()[0].getType(), equalTo("test"));
        assertThat(response.getItems()[0].getId(), equalTo("test"));
        assertThat(response.getItems()[0].getVersion(), equalTo(1L));

        // Before 2.0 alias filters were parsed at alias creation time, in order
        // for filters to work correctly ES required that fields mentioned in those
        // filters exist in the mapping.
        // From 2.0 and higher alias filters are parsed at request time and therefor
        // fields mentioned in filters don't need to exist in the mapping.
        // So the aliases defined in the index template for this index will not fail
        // even though the fields in the alias fields don't exist yet and indexing into
        // an index that doesn't exist yet will succeed
        client().prepareIndex("d1", "test", "test").setSource("{}", XContentType.JSON).get();

        response = client().prepareBulk().add(new IndexRequest("d2", "test", "test").source("{}", XContentType.JSON)).get();
        assertThat(response.hasFailures(), is(false));
        assertThat(response.getItems()[0].isFailed(), equalTo(false));
        assertThat(response.getItems()[0].getId(), equalTo("test"));
        assertThat(response.getItems()[0].getVersion(), equalTo(1L));
    }

    public void testCombineTemplates() throws Exception{
        // clean all templates setup by the framework.
        client().admin().indices().prepareDeleteTemplate("*").get();

        // check get all templates on an empty index.
        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), empty());

        //Now, a complete mapping with two separated templates is error
        // base template
        client().admin().indices().preparePutTemplate("template_1")
            .setPatterns(Collections.singletonList("*"))
            .setSettings(
                "    {\n" +
                    "        \"index\" : {\n" +
                    "            \"analysis\" : {\n" +
                    "                \"analyzer\" : {\n" +
                    "                    \"custom_1\" : {\n" +
                    "                        \"tokenizer\" : \"whitespace\"\n" +
                    "                    }\n" +
                    "                }\n" +
                    "            }\n" +
                    "         }\n" +
                    "    }\n", XContentType.JSON)
            .get();

        // put template using custom_1 analyzer
        MapperParsingException e = expectThrows(MapperParsingException.class,
            () -> client().admin().indices().preparePutTemplate("template_2")
                    .setPatterns(Collections.singletonList("test*"))
                    .setCreate(true)
                    .setOrder(1)
                    .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
                        .startObject("field2").field("type", "text").field("analyzer", "custom_1").endObject()
                        .endObject().endObject().endObject())
                .get());
        assertThat(e.getMessage(), containsString("analyzer [custom_1] not found for field [field2]"));

        response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), hasSize(1));

    }

    public void testOrderAndVersion() {
        int order = randomInt();
        Integer version = randomBoolean() ? randomInt() : null;

        assertAcked(client().admin().indices().preparePutTemplate("versioned_template")
                                              .setPatterns(Collections.singletonList("te*"))
                                              .setVersion(version)
                                              .setOrder(order)
                                              .addMapping("test", "field", "type=text")
                                              .get());

        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("versioned_template").get();
        assertThat(response.getIndexTemplates().size(), equalTo(1));
        assertThat(response.getIndexTemplates().get(0).getVersion(), equalTo(version));
        assertThat(response.getIndexTemplates().get(0).getOrder(), equalTo(order));
    }

    public void testMultipleTemplate() throws IOException {
        client().admin().indices().preparePutTemplate("template_1")
            .setPatterns(Arrays.asList("a*", "b*"))
            .setSettings(indexSettings())
            .addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
            .startObject("field1").field("type", "text").field("store", true).endObject()
            .startObject("field2").field("type", "keyword").field("store", false).endObject()
            .endObject().endObject().endObject())
            .get();

        client().prepareIndex("ax", "type1", "1")
            .setSource("field1", "value1", "field2", "value2")
            .setRefreshPolicy(IMMEDIATE).get();

        client().prepareIndex("bx", "type1", "1")
            .setSource("field1", "value1", "field2", "value2")
            .setRefreshPolicy(IMMEDIATE).get();

        ensureGreen();

        // ax -> matches template
        SearchResponse searchResponse = client().prepareSearch("ax")
            .setQuery(termQuery("field1", "value1"))
            .addStoredField("field1")
            .addStoredField("field2")
            .execute().actionGet();

        assertHitCount(searchResponse, 1);
        assertEquals("value1", searchResponse.getHits().getAt(0).field("field1").getValue().toString());
        assertNull(searchResponse.getHits().getAt(0).field("field2"));

        // bx -> matches template
        searchResponse = client().prepareSearch("bx")
            .setQuery(termQuery("field1", "value1"))
            .addStoredField("field1")
            .addStoredField("field2")
            .execute().actionGet();

        assertHitCount(searchResponse, 1);
        assertEquals("value1", searchResponse.getHits().getAt(0).field("field1").getValue().toString());
        assertNull(searchResponse.getHits().getAt(0).field("field2"));
    }

    public void testPartitionedTemplate() throws Exception {
        // clean all templates setup by the framework.
        client().admin().indices().prepareDeleteTemplate("*").get();

        // check get all templates on an empty index.
        GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates().get();
        assertThat(response.getIndexTemplates(), empty());

        // provide more partitions than shards
        IllegalArgumentException eBadSettings = expectThrows(IllegalArgumentException.class,
            () -> client().admin().indices().preparePutTemplate("template_1")
                .setPatterns(Collections.singletonList("te*"))
                .setSettings(Settings.builder()
                    .put("index.number_of_shards", "5")
                    .put("index.routing_partition_size", "6"))
                .get());
        assertThat(eBadSettings.getMessage(), containsString("partition size [6] should be a positive number "
                + "less than the number of shards [5]"));

        // provide an invalid mapping for a partitioned index
        IllegalArgumentException eBadMapping = expectThrows(IllegalArgumentException.class,
            () -> client().admin().indices().preparePutTemplate("template_2")
                .setPatterns(Collections.singletonList("te*"))
                .addMapping("type", "{\"type\":{\"_routing\":{\"required\":false}}}", XContentType.JSON)
                .setSettings(Settings.builder()
                    .put("index.number_of_shards", "6")
                    .put("index.routing_partition_size", "3"))
                .get());
        assertThat(eBadMapping.getMessage(), containsString("must have routing required for partitioned index"));

        // no templates yet
        response = client().admin().indices().prepareGetTemplates().get();
        assertEquals(0, response.getIndexTemplates().size());

        // a valid configuration that only provides the partition size
        assertAcked(client().admin().indices().preparePutTemplate("just_partitions")
            .setPatterns(Collections.singletonList("te*"))
            .setSettings(Settings.builder()
                .put("index.routing_partition_size", "6"))
            .get());

        // create an index with too few shards
        IllegalArgumentException eBadIndex = expectThrows(IllegalArgumentException.class,
                () -> prepareCreate("test_bad", Settings.builder()
            .put("index.number_of_shards", 5))
            .get());

        assertThat(eBadIndex.getMessage(), containsString("partition size [6] should be a positive number "
                + "less than the number of shards [5]"));

        // finally, create a valid index
        prepareCreate("test_good", Settings.builder()
            .put("index.number_of_shards", 7))
            .get();

        GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings("test_good").get();
        assertEquals("6", getSettingsResponse.getIndexToSettings().get("test_good").getAsMap().get("index.routing_partition_size"));
    }
}
