From be5fefc6ddc4680a275fa5798f17c7b109e198ca Mon Sep 17 00:00:00 2001 From: uboness Date: Mon, 6 Apr 2015 14:57:11 +0200 Subject: [PATCH] Enhanced search request de/serialization - now enables defining `types` (for document types) - enables defining `types` and `indices` as comma-delimited strings (not just string arrays) - aligned the parsing in `WatcherUtils` with the way we're parsing xcontent across the board (e.g. using `ParseField`) - Added additional unit test to test deserialization Original commit: elastic/x-pack-elasticsearch@5491b85f75ec56adf5b55054e2733ca401518736 --- .../watcher/support/WatcherUtils.java | 235 ++++++++++-------- .../watcher/support/WatcherUtilsTests.java | 114 ++++++++- 2 files changed, 234 insertions(+), 115 deletions(-) diff --git a/src/main/java/org/elasticsearch/watcher/support/WatcherUtils.java b/src/main/java/org/elasticsearch/watcher/support/WatcherUtils.java index 8471ff1e816..03f5a78b191 100644 --- a/src/main/java/org/elasticsearch/watcher/support/WatcherUtils.java +++ b/src/main/java/org/elasticsearch/watcher/support/WatcherUtils.java @@ -5,12 +5,10 @@ */ package org.elasticsearch.watcher.support; -import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.watcher.WatcherException; -import org.elasticsearch.watcher.WatcherSettingsException; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.joda.time.DateTime; import org.elasticsearch.common.unit.TimeValue; @@ -19,18 +17,33 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.script.ScriptService; +import org.elasticsearch.watcher.WatcherException; +import org.elasticsearch.watcher.WatcherSettingsException; import java.io.IOException; import java.lang.reflect.Array; import java.util.*; -import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate; /** */ public final class WatcherUtils { + static final ParseField INDICES_FIELD = new ParseField("indices"); + static final ParseField TYPES_FIELD = new ParseField("types"); + static final ParseField BODY_FIELD = new ParseField("body"); + static final ParseField SEARCH_TYPE_FIELD = new ParseField("search_type"); + static final ParseField INDICES_OPTIONS_FIELD = new ParseField("indices_options"); + static final ParseField EXPAND_WILDCARDS_FIELD = new ParseField("expand_wildcards"); + static final ParseField IGNORE_UNAVAILABLE_FIELD = new ParseField("ignore_unavailable"); + static final ParseField ALLOW_NO_INDICES_FIELD = new ParseField("allow_no_indices"); + static final ParseField TEMPLATE_FIELD = new ParseField("template"); + static final ParseField TEMPLATE_NAME_FIELD = new ParseField("name"); + static final ParseField TEMPLATE_TYPE_FIELD = new ParseField("type"); + static final ParseField TEMPLATE_PARAMS_FIELD = new ParseField("params"); + public final static IndicesOptions DEFAULT_INDICES_OPTIONS = IndicesOptions.lenientExpandOpen(); private WatcherUtils() { @@ -57,115 +70,118 @@ public final class WatcherUtils { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_ARRAY) { - switch (currentFieldName) { - case "indices": - List indices = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - if (token == XContentParser.Token.VALUE_STRING) { - indices.add(parser.textOrNull()); - } else { - throw new ElasticsearchIllegalArgumentException("Unexpected token [" + token + "]"); - } + if (INDICES_FIELD.match(currentFieldName)) { + List indices = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + if (token == XContentParser.Token.VALUE_STRING) { + indices.add(parser.textOrNull()); + } else { + throw new WatcherSettingsException("could not read search request. expected string values in [" + currentFieldName + "] field, but instead found [" + token + "]"); } - searchRequest.indices(indices.toArray(new String[indices.size()])); - break; - default: - throw new ElasticsearchIllegalArgumentException("Unexpected field [" + currentFieldName + "]"); + } + searchRequest.indices(indices.toArray(new String[indices.size()])); + } else if (TYPES_FIELD.match(currentFieldName)) { + List types = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + if (token == XContentParser.Token.VALUE_STRING) { + types.add(parser.textOrNull()); + } else { + throw new WatcherSettingsException("could not read search request. expected string values in [" + currentFieldName + "] field, but instead found [" + token + "]"); + } + } + searchRequest.types(types.toArray(new String[types.size()])); + } else { + throw new WatcherSettingsException("could not read search request. unexpected array field [" + currentFieldName + "]"); } } else if (token == XContentParser.Token.START_OBJECT) { XContentBuilder builder; - switch (currentFieldName) { - case "body": - builder = XContentBuilder.builder(parser.contentType().xContent()); - builder.copyCurrentStructure(parser); - searchRequest.source(builder); - break; - case "indices_options": - boolean expandOpen = DEFAULT_INDICES_OPTIONS.expandWildcardsOpen(); - boolean expandClosed = DEFAULT_INDICES_OPTIONS.expandWildcardsClosed(); - boolean allowNoIndices = DEFAULT_INDICES_OPTIONS.allowNoIndices(); - boolean ignoreUnavailable = DEFAULT_INDICES_OPTIONS.ignoreUnavailable(); - - String indicesFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - indicesFieldName = parser.currentName(); - } else if (token.isValue()) { - switch (indicesFieldName) { - case "expand_wildcards": - switch (parser.text()) { - case "all": - expandOpen = true; - expandClosed = true; - break; - case "open": - expandOpen = true; - expandClosed = false; - break; - case "closed": - expandOpen = false; - expandClosed = true; - break; - case "none": - expandOpen = false; - expandClosed = false; - break; - default: - throw new ElasticsearchIllegalArgumentException("Unexpected value [" + parser.text() + "]"); - } + if (BODY_FIELD.match(currentFieldName)) { + builder = XContentBuilder.builder(parser.contentType().xContent()); + builder.copyCurrentStructure(parser); + searchRequest.source(builder); + } else if (INDICES_OPTIONS_FIELD.match(currentFieldName)) { + boolean expandOpen = DEFAULT_INDICES_OPTIONS.expandWildcardsOpen(); + boolean expandClosed = DEFAULT_INDICES_OPTIONS.expandWildcardsClosed(); + boolean allowNoIndices = DEFAULT_INDICES_OPTIONS.allowNoIndices(); + boolean ignoreUnavailable = DEFAULT_INDICES_OPTIONS.ignoreUnavailable(); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if (EXPAND_WILDCARDS_FIELD.match(currentFieldName)) { + switch (parser.text()) { + case "all": + expandOpen = true; + expandClosed = true; break; - case "ignore_unavailable": - ignoreUnavailable = parser.booleanValue(); + case "open": + expandOpen = true; + expandClosed = false; break; - case "allow_no_indices": - allowNoIndices = parser.booleanValue(); + case "closed": + expandOpen = false; + expandClosed = true; + break; + case "none": + expandOpen = false; + expandClosed = false; break; default: - throw new ElasticsearchIllegalArgumentException("Unexpected field [" + indicesFieldName + "]"); + throw new WatcherSettingsException("could not read search request. unknown value [" + parser.text() + "] for [" + currentFieldName + "] field "); + } + } else if (IGNORE_UNAVAILABLE_FIELD.match(currentFieldName)) { + ignoreUnavailable = parser.booleanValue(); + } else if (ALLOW_NO_INDICES_FIELD.match(currentFieldName)) { + allowNoIndices = parser.booleanValue(); + } else { + throw new WatcherSettingsException("could not read search request. unexpected index option [" + currentFieldName + "]"); + } + } else { + throw new WatcherSettingsException("could not read search request. unexpected object field [" + currentFieldName + "]"); + } + } + indicesOptions = IndicesOptions.fromOptions(ignoreUnavailable, allowNoIndices, expandOpen, expandClosed, DEFAULT_INDICES_OPTIONS); + } else if (TEMPLATE_FIELD.match(currentFieldName)) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_STRING) { + if (TEMPLATE_NAME_FIELD.match(currentFieldName)) { + searchRequest.templateName(parser.textOrNull()); + } else if (TEMPLATE_TYPE_FIELD.match(currentFieldName)) { + try { + searchRequest.templateType(ScriptService.ScriptType.valueOf(parser.text().toUpperCase(Locale.ROOT))); + } catch (IllegalArgumentException iae) { + throw new WatcherSettingsException("could not parse search request. unknown template type [" + parser.text() + "]"); } } else { - throw new ElasticsearchIllegalArgumentException("Unexpected token [" + token + "]"); + throw new WatcherSettingsException("could not read search request. unexpected template field [" + currentFieldName + "]"); } - } - indicesOptions = IndicesOptions.fromOptions(ignoreUnavailable, allowNoIndices, expandOpen, expandClosed, DEFAULT_INDICES_OPTIONS); - break; - case "template": - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.VALUE_STRING) { - switch (currentFieldName) { - case "name": - searchRequest.templateName(parser.textOrNull()); - break; - case "type": - try { - searchRequest.templateType(ScriptService.ScriptType.valueOf(parser.text().toUpperCase(Locale.ROOT))); - } catch (IllegalArgumentException iae) { - throw new WatcherSettingsException("could not parse search request. unknown template type [" + parser.text() + "]"); - } - } - } else if (token == XContentParser.Token.START_OBJECT) { - if ("params".equals(currentFieldName)) { - searchRequest.templateParams(flattenModel(parser.map())); - } + } else if (token == XContentParser.Token.START_OBJECT) { + if ("params".equals(currentFieldName)) { + searchRequest.templateParams(flattenModel(parser.map())); } + } else { + throw new WatcherSettingsException("could not read search request. unexpected template token [" + token + "]"); } - break; - - default: - throw new ElasticsearchIllegalArgumentException("Unexpected field [" + currentFieldName + "]"); + } + } else { + throw new WatcherSettingsException("could not read search request. unexpected object field [" + currentFieldName + "]"); } - } else if (token.isValue()) { - switch (currentFieldName) { - case "search_type": - searchType = SearchType.fromString(parser.text().toLowerCase(Locale.ROOT)); - break; - default: - throw new ElasticsearchIllegalArgumentException("Unexpected field [" + currentFieldName + "]"); + } else if (token == XContentParser.Token.VALUE_STRING) { + if (INDICES_FIELD.match(currentFieldName)) { + String indicesStr = parser.text(); + searchRequest.indices(Strings.delimitedListToStringArray(indicesStr, ",", " \t")); + } else if (TYPES_FIELD.match(currentFieldName)) { + String typesStr = parser.text(); + searchRequest.types(Strings.delimitedListToStringArray(typesStr, ",", " \t")); + } else if (SEARCH_TYPE_FIELD.match(currentFieldName)) { + searchType = SearchType.fromString(parser.text().toLowerCase(Locale.ROOT)); + } else { + throw new WatcherSettingsException("could not read search request. unexpected string field [" + currentFieldName + "]"); } } else { - throw new ElasticsearchIllegalArgumentException("Unexpected field [" + currentFieldName + "]"); + throw new WatcherSettingsException("could not read search request. unexpected token [" + token + "]"); } } @@ -188,29 +204,32 @@ public final class WatcherUtils { builder.startObject(); if (searchRequest.searchType() != null) { - builder.field("search_type", searchRequest.searchType().toString().toLowerCase(Locale.ENGLISH)); + builder.field(SEARCH_TYPE_FIELD.getPreferredName(), searchRequest.searchType().toString().toLowerCase(Locale.ENGLISH)); } if (searchRequest.indices() != null) { - builder.array("indices", searchRequest.indices()); + builder.array(INDICES_FIELD.getPreferredName(), searchRequest.indices()); + } + if (searchRequest.types() != null) { + builder.array(TYPES_FIELD.getPreferredName(), searchRequest.types()); } if (Strings.hasLength(searchRequest.source())) { - XContentHelper.writeRawField("body", searchRequest.source(), builder, params); + XContentHelper.writeRawField(BODY_FIELD.getPreferredName(), searchRequest.source(), builder, params); } if (searchRequest.templateName() != null) { - builder.startObject("template") - .field("name", searchRequest.templateName()); + builder.startObject(TEMPLATE_FIELD.getPreferredName()) + .field(TEMPLATE_NAME_FIELD.getPreferredName(), searchRequest.templateName()); if (searchRequest.templateType() != null) { - builder.field("type", searchRequest.templateType().name().toLowerCase(Locale.ROOT)); + builder.field(TEMPLATE_TYPE_FIELD.getPreferredName(), searchRequest.templateType().name().toLowerCase(Locale.ROOT)); } if (searchRequest.templateParams() != null && !searchRequest.templateParams().isEmpty()) { - builder.field("params", searchRequest.templateParams()); + builder.field(TEMPLATE_PARAMS_FIELD.getPreferredName(), searchRequest.templateParams()); } builder.endObject(); } if (searchRequest.indicesOptions() != DEFAULT_INDICES_OPTIONS) { IndicesOptions options = searchRequest.indicesOptions(); - builder.startObject("indices_options"); + builder.startObject(INDICES_OPTIONS_FIELD.getPreferredName()); String value; if (options.expandWildcardsClosed() && options.expandWildcardsOpen()) { value = "all"; @@ -221,9 +240,9 @@ public final class WatcherUtils { } else { value = "none"; } - builder.field("expand_wildcards", value); - builder.field("ignore_unavailable", options.ignoreUnavailable()); - builder.field("allow_no_indices", options.allowNoIndices()); + builder.field(EXPAND_WILDCARDS_FIELD.getPreferredName(), value); + builder.field(IGNORE_UNAVAILABLE_FIELD.getPreferredName(), options.ignoreUnavailable()); + builder.field(ALLOW_NO_INDICES_FIELD.getPreferredName(), options.allowNoIndices()); builder.endObject(); } return builder.endObject(); diff --git a/src/test/java/org/elasticsearch/watcher/support/WatcherUtilsTests.java b/src/test/java/org/elasticsearch/watcher/support/WatcherUtilsTests.java index 449fa650ed5..420ea6ca232 100644 --- a/src/test/java/org/elasticsearch/watcher/support/WatcherUtilsTests.java +++ b/src/test/java/org/elasticsearch/watcher/support/WatcherUtilsTests.java @@ -5,10 +5,12 @@ */ package org.elasticsearch.watcher.support; +import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.watcher.input.search.SearchInput; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.collect.ImmutableMap; import org.elasticsearch.common.joda.time.DateTime; @@ -18,16 +20,16 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.test.ElasticsearchTestCase; +import org.elasticsearch.watcher.input.search.SearchInput; import org.junit.Test; import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; -import static org.elasticsearch.watcher.support.WatcherUtils.flattenModel; -import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.watcher.support.WatcherDateUtils.formatDate; +import static org.elasticsearch.watcher.support.WatcherUtils.DEFAULT_INDICES_OPTIONS; +import static org.elasticsearch.watcher.support.WatcherUtils.flattenModel; import static org.hamcrest.Matchers.*; /** @@ -83,10 +85,16 @@ public class WatcherUtilsTests extends ElasticsearchTestCase { assertThat(result, equalTo(expected)); } - @Test + @Test @Repeat(iterations = 20) public void testSerializeSearchRequest() throws Exception { String[] randomIndices = generateRandomStringArray(5, 5); SearchRequest expectedRequest = new SearchRequest(randomIndices); + + if (randomBoolean()) { + String[] randomTypes = generateRandomStringArray(2, 5); + expectedRequest.types(randomTypes); + } + expectedRequest.indicesOptions(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), WatcherUtils.DEFAULT_INDICES_OPTIONS)); expectedRequest.searchType(randomFrom(SearchType.values())); @@ -116,6 +124,7 @@ public class WatcherUtilsTests extends ElasticsearchTestCase { SearchRequest result = WatcherUtils.readSearchRequest(parser, SearchInput.DEFAULT_SEARCH_TYPE); assertThat(result.indices(), arrayContainingInAnyOrder(expectedRequest.indices())); + assertThat(result.types(), arrayContainingInAnyOrder(expectedRequest.types())); assertThat(result.indicesOptions(), equalTo(expectedRequest.indicesOptions())); assertThat(result.searchType(), equalTo(expectedRequest.searchType())); assertThat(result.source().toUtf8(), equalTo(expectedSource)); @@ -124,4 +133,95 @@ public class WatcherUtilsTests extends ElasticsearchTestCase { assertThat(result.templateParams(), equalTo(expectedRequest.templateParams())); } + @Test @Repeat(iterations = 100) + public void testDeserializeSearchRequest() throws Exception { + + XContentBuilder builder = jsonBuilder().startObject(); + + String[] indices = Strings.EMPTY_ARRAY; + if (randomBoolean()) { + indices = generateRandomStringArray(5, 5); + if (randomBoolean()) { + builder.array("indices", indices); + } else { + builder.field("indices", Strings.arrayToCommaDelimitedString(indices)); + } + } + + String[] types = Strings.EMPTY_ARRAY; + if (randomBoolean()) { + types = generateRandomStringArray(2, 5); + if (randomBoolean()) { + builder.array("types", types); + } else { + builder.field("types", Strings.arrayToCommaDelimitedString(types)); + } + } + + IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS; + if (randomBoolean()) { + indicesOptions = IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean(), WatcherUtils.DEFAULT_INDICES_OPTIONS); + builder.startObject("indices_options") + .field("allow_no_indices", indicesOptions.allowNoIndices()) + .field("expand_wildcards", indicesOptions.expandWildcardsClosed() && indicesOptions.expandWildcardsOpen() ? "all" : + indicesOptions.expandWildcardsClosed() ? "closed" : + indicesOptions.expandWildcardsOpen() ? "open" : + "none") + .field("ignore_unavailable", indicesOptions.ignoreUnavailable()) + .endObject(); + } + + SearchType searchType = SearchType.DEFAULT; + if (randomBoolean()) { + searchType = randomFrom(SearchType.values()); + builder.field("search_type", randomBoolean() ? searchType.name() : searchType.name().toLowerCase(Locale.ROOT)); + } + + BytesReference source = null; + if (randomBoolean()) { + SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource().query(QueryBuilders.matchAllQuery()).size(11); + XContentBuilder searchSourceJsonBuilder = jsonBuilder(); + searchSourceBuilder.toXContent(searchSourceJsonBuilder, ToXContent.EMPTY_PARAMS); + source = searchSourceBuilder.buildAsBytes(XContentType.JSON); + builder.rawField("body", source); + } + + String templateName = null; + ScriptService.ScriptType templateType = null; + Map templateParams = Collections.emptyMap(); + if (randomBoolean()) { + builder.startObject("template"); + if (randomBoolean()) { + templateName = randomAsciiOfLengthBetween(1, 5); + builder.field("name", templateName); + } + if (randomBoolean()) { + templateType = randomFrom(ScriptService.ScriptType.values()); + builder.field("type", randomBoolean() ? templateType.name() : templateType.name().toLowerCase(Locale.ROOT)); + } + if (randomBoolean()) { + templateParams = new HashMap<>(); + int maxParams = randomIntBetween(1, 10); + for (int i = 0; i < maxParams; i++) { + templateParams.put(randomAsciiOfLengthBetween(1, 5), randomAsciiOfLengthBetween(1, 5)); + } + builder.field("params", templateParams); + } + builder.endObject(); + } + + XContentParser parser = XContentHelper.createParser(builder.bytes()); + assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); + SearchRequest result = WatcherUtils.readSearchRequest(parser, SearchInput.DEFAULT_SEARCH_TYPE); + + assertThat(result.indices(), arrayContainingInAnyOrder(indices)); + assertThat(result.types(), arrayContainingInAnyOrder(types)); + assertThat(result.indicesOptions(), equalTo(indicesOptions)); + assertThat(result.searchType(), equalTo(searchType)); + assertThat(result.source(), equalTo(source)); + assertThat(result.templateName(), equalTo(templateName)); + assertThat(result.templateType(), equalTo(templateType)); + assertThat(result.templateParams(), equalTo(templateParams)); + } + }