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@5491b85f75
This commit is contained in:
uboness 2015-04-06 14:57:11 +02:00
parent a1cda57863
commit be5fefc6dd
2 changed files with 234 additions and 115 deletions

View File

@ -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<String> 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<String> 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<String> 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();

View File

@ -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<String, Object> 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));
}
}