From 22c0c7a666c1b66e31e4866efc459f7c8831333a Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Wed, 28 Nov 2018 14:24:38 +0100 Subject: [PATCH] address review --- .../client/watcher/GetWatchResponse.java | 20 +- .../client/watcher/WatchStatus.java | 16 +- .../documentation/WatcherDocumentationIT.java | 19 +- .../client/watcher/GetWatchResponseTests.java | 285 ------------------ .../high-level/watcher/get-watch.asciidoc | 37 +-- .../test/AbstractSerializingTestCase.java | 2 + .../AbstractStreamableXContentTestCase.java | 13 +- .../test/AbstractXContentTestCase.java | 80 ++++- .../support/xcontent/XContentSource.java | 14 + .../actions/get/GetWatchResponse.java | 48 ++- .../xpack/core/watcher/watch/WatchStatus.java | 14 +- ...bstractHlrcStreamableXContentTestCase.java | 2 +- .../AbstractHlrcXContentTestCase.java | 2 + .../xpack/watcher/GetWatchResponseTests.java | 175 +++++++++++ 14 files changed, 367 insertions(+), 360 deletions(-) delete mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/GetWatchResponseTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/xpack/watcher/GetWatchResponseTests.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchResponse.java index 5b3a7b87a09d7..86f63ad7ddf37 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchResponse.java @@ -35,21 +35,19 @@ public class GetWatchResponse { private final WatchStatus status; private final BytesReference source; - private final XContentType xContentType; /** * Ctor for missing watch */ public GetWatchResponse(String id) { - this(id, Versions.NOT_FOUND, null, null, null); + this(id, Versions.NOT_FOUND, null, null); } - public GetWatchResponse(String id, long version, WatchStatus status, BytesReference source, XContentType xContentType) { + public GetWatchResponse(String id, long version, WatchStatus status, BytesReference source) { this.id = id; this.version = version; this.status = status; this.source = source; - this.xContentType = xContentType; } public String getId() { @@ -73,7 +71,7 @@ public BytesReference getSource() { } public XContentType getContentType() { - return xContentType; + return XContentType.JSON; } @Override @@ -84,13 +82,12 @@ public boolean equals(Object o) { return version == that.version && Objects.equals(id, that.id) && Objects.equals(status, that.status) && - Objects.equals(source, that.source) && - xContentType == that.xContentType; + Objects.equals(source, that.source); } @Override public int hashCode() { - return Objects.hash(id, status, source, xContentType, version); + return Objects.hash(id, status, source, version); } private static final ParseField ID_FIELD = new ParseField("_id"); @@ -104,9 +101,8 @@ public int hashCode() { a -> { boolean isFound = (boolean) a[1]; if (isFound) { - XContentBuilder builder = (XContentBuilder) a[4]; - BytesReference source = BytesReference.bytes(builder); - return new GetWatchResponse((String) a[0], (long) a[2], (WatchStatus) a[3], source, builder.contentType()); + BytesReference source = (BytesReference) a[4]; + return new GetWatchResponse((String) a[0], (long) a[2], (WatchStatus) a[3], source); } else { return new GetWatchResponse((String) a[0]); } @@ -122,7 +118,7 @@ public int hashCode() { (parser, context) -> { try (XContentBuilder builder = XContentBuilder.builder(parser.contentType().xContent())) { builder.copyCurrentStructure(parser); - return builder; + return BytesReference.bytes(builder); } }, WATCH_FIELD); } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatchStatus.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatchStatus.java index 035fdeedc0de7..bc37dd8de89b1 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatchStatus.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatchStatus.java @@ -20,6 +20,7 @@ package org.elasticsearch.client.watcher; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.XContentParser; import org.joda.time.DateTime; @@ -44,19 +45,22 @@ public class WatchStatus { private final DateTime lastMetCondition; private final long version; private final Map actions; + @Nullable private Map headers; public WatchStatus(long version, State state, ExecutionState executionState, DateTime lastChecked, DateTime lastMetCondition, - Map actions) { + Map actions, + Map headers) { this.version = version; this.lastChecked = lastChecked; this.lastMetCondition = lastMetCondition; this.actions = actions; this.state = state; this.executionState = executionState; + this.headers = headers; } public State state() { @@ -79,7 +83,7 @@ public ActionStatus actionStatus(String actionId) { return actions.get(actionId); } - Map getActions() { + public Map getActions() { return actions; } @@ -116,6 +120,7 @@ public static WatchStatus parse(XContentParser parser) throws IOException { DateTime lastChecked = null; DateTime lastMetCondition = null; Map actions = null; + Map headers = null; long version = -1; ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation); @@ -176,13 +181,17 @@ public static WatchStatus parse(XContentParser parser) throws IOException { throw new ElasticsearchParseException("could not parse watch status. expecting field [{}] to be an object, " + "found [{}] instead", currentFieldName, token); } + } else if (Field.HEADERS.match(currentFieldName, parser.getDeprecationHandler())) { + if (token == XContentParser.Token.START_OBJECT) { + headers = parser.mapStrings(); + } } else { parser.skipChildren(); } } actions = actions == null ? emptyMap() : unmodifiableMap(actions); - return new WatchStatus(version, state, executionState, lastChecked, lastMetCondition, actions); + return new WatchStatus(version, state, executionState, lastChecked, lastMetCondition, actions, headers); } public static class State { @@ -233,5 +242,6 @@ public interface Field { ParseField ACTIONS = new ParseField("actions"); ParseField VERSION = new ParseField("version"); ParseField EXECUTION_STATE = new ParseField("execution_state"); + ParseField HEADERS = new ParseField("headers"); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java index 4fd4c86575f20..03dfc2ea7e088 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java @@ -200,23 +200,26 @@ public void onFailure(Exception e) { } { - //tag::x-pack-get-watch-execute + //tag::get-watch-request GetWatchRequest request = new GetWatchRequest("my_watch_id"); + //end::get-watch-request + + //tag::ack-watch-execute GetWatchResponse response = client.watcher().getWatch(request, RequestOptions.DEFAULT); - //end::x-pack-get-watch-execute + //end::get-watch-request - //tag::x-pack-get-watch-response + //tag::get-watch-response String watchId = response.getId(); // <1> boolean found = response.isFound(); // <2> long version = response.getVersion(); // <3> WatchStatus status = response.getStatus(); // <4> BytesReference source = response.getSource(); // <5> - //end::x-pack-get-watch-response + //end::get-watch-response } { GetWatchRequest request = new GetWatchRequest("my_other_watch_id"); - // tag::x-pack-get-watch-execute-listener + // tag::get-watch-execute-listener ActionListener listener = new ActionListener() { @Override public void onResponse(GetWatchResponse response) { @@ -228,15 +231,15 @@ public void onFailure(Exception e) { // <2> } }; - // end::x-pack-get-watch-execute-listener + // end::get-watch-execute-listener // Replace the empty listener by a blocking listener in test final CountDownLatch latch = new CountDownLatch(1); listener = new LatchedActionListener<>(listener, latch); - // tag::x-pack-get-watch-execute-async + // tag::get-watch-execute-async client.watcher().getWatchAsync(request, RequestOptions.DEFAULT, listener); // <1> - // end::x-pack-get-watch-execute-async + // end::get-watch-execute-async assertTrue(latch.await(30L, TimeUnit.SECONDS)); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/GetWatchResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/GetWatchResponseTests.java deleted file mode 100644 index 4ba52f4d84b14..0000000000000 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/GetWatchResponseTests.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * 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.client.watcher; - -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.xcontent.DeprecationHandler; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; -import org.elasticsearch.common.xcontent.NamedXContentRegistry; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.ToXContentObject; -import org.elasticsearch.common.xcontent.XContent; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.test.AbstractXContentTestCase; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; - -import static org.elasticsearch.index.mapper.DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER; - -public class GetWatchResponseTests extends AbstractXContentTestCase { - /** - * A wrapper for {@link GetWatchResponse} that is able to serialize the response with {@link ToXContent}. - */ - static class ResponseWrapper implements ToXContentObject { - final NamedXContentRegistry xContentRegistry; - final GetWatchResponse response; - - ResponseWrapper(NamedXContentRegistry xContentRegistry, GetWatchResponse response) { - this.xContentRegistry = xContentRegistry; - this.response = response; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return GetWatchResponseTests.toXContent(xContentRegistry, response, builder); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ResponseWrapper that = (ResponseWrapper) o; - return Objects.equals(response, that.response); - } - - @Override - public int hashCode() { - return Objects.hash(response); - } - } - - @Override - protected ResponseWrapper createTestInstance() { - String id = randomAlphaOfLength(10); - if (rarely()) { - return new ResponseWrapper(xContentRegistry(), new GetWatchResponse(id)); - } - long version = randomLongBetween(-1, Long.MAX_VALUE); - WatchStatus status = randomWatchStatus(); - BytesReference source = simpleWatch(); - ResponseWrapper wrapper = - new ResponseWrapper(xContentRegistry(), new GetWatchResponse(id, version, status, source, XContentType.JSON)); - return wrapper; - } - - @Override - protected ResponseWrapper doParseInstance(XContentParser parser) throws IOException { - return new ResponseWrapper(xContentRegistry(), GetWatchResponse.fromXContent(parser)); - } - - @Override - protected boolean supportsUnknownFields() { - return false; - } - - static String[] SHUFFLE_FIELDS_EXCEPTION = new String[] { "watch" }; - @Override - protected String[] getShuffleFieldsExceptions() { - return SHUFFLE_FIELDS_EXCEPTION; - } - - @Override - protected void assertEqualInstances(ResponseWrapper expectedInstance, ResponseWrapper newInstance) { - if (newInstance.response.isFound() && - expectedInstance.response.getContentType() != newInstance.response.getContentType()) { - /** - * The {@link GetWatchResponse#getContentType()} depends on the content type that - * was used to serialize the main object so we use the same content type than the - * expectedInstance to translate the watch of the newInstance. - */ - XContent from = XContentFactory.xContent(newInstance.response.getContentType()); - XContent to = XContentFactory.xContent(expectedInstance.response.getContentType()); - final BytesReference source; - // It is safe to use EMPTY here because this never uses namedObject - try (InputStream stream = newInstance.response.getSource().streamInput(); - XContentParser parser = XContentFactory.xContent(from.type()).createParser(NamedXContentRegistry.EMPTY, - DeprecationHandler.THROW_UNSUPPORTED_OPERATION, stream)) { - parser.nextToken(); - XContentBuilder builder = XContentFactory.contentBuilder(to.type()); - builder.copyCurrentStructure(parser); - source = BytesReference.bytes(builder); - } catch (IOException e) { - throw new AssertionError(e); - } - newInstance = new ResponseWrapper(xContentRegistry(), - new GetWatchResponse(newInstance.response.getId(), newInstance.response.getVersion(), - newInstance.response.getStatus(), source, expectedInstance.response.getContentType())); - } - super.assertEqualInstances(expectedInstance, newInstance); - } - - static XContentBuilder toXContentFragment(ActionStatus.Execution execution, XContentBuilder builder) throws IOException { - builder.field("timestamp").value(DEFAULT_DATE_TIME_FORMATTER.printer().print(execution.timestamp())); - builder.field("successful", execution.successful()); - if (execution.successful() == false) { - builder.field("reason", execution.reason()); - } - return builder; - } - - static XContentBuilder toXContentFragment(ActionStatus.Throttle throttle, XContentBuilder builder) throws IOException { - builder.field("timestamp").value(DEFAULT_DATE_TIME_FORMATTER.printer().print(throttle.timestamp())); - builder.field("reason", throttle.reason()); - return builder; - } - - static XContentBuilder toXContentFragment(ActionStatus.AckStatus status, XContentBuilder builder) throws IOException { - builder.field("timestamp").value(DEFAULT_DATE_TIME_FORMATTER.printer().print(status.timestamp())); - builder.field("state", status.state().name().toLowerCase(Locale.ROOT)); - return builder; - } - - static XContentBuilder toXContentFragment(ActionStatus status, XContentBuilder builder) throws IOException { - builder.startObject("ack"); - toXContentFragment(status.ackStatus(), builder); - builder.endObject(); - if (status.lastExecution() != null) { - builder.startObject("last_execution"); - toXContentFragment(status.lastExecution(), builder); - builder.endObject(); - } - if (status.lastSuccessfulExecution() != null) { - builder.startObject("last_successful_execution"); - toXContentFragment(status.lastSuccessfulExecution(), builder); - builder.endObject(); - } - if (status.lastThrottle() != null) { - builder.startObject("last_throttle"); - toXContentFragment(status.lastThrottle(), builder); - builder.endObject(); - } - return builder; - } - - static XContentBuilder toXContent(WatchStatus status, XContentBuilder builder) throws IOException { - builder.startObject("status"); - if (status.state() != null) { - builder.startObject("state"); - builder.field("active", status.state().isActive()); - builder.field("timestamp", DEFAULT_DATE_TIME_FORMATTER.printer().print(status.state().getTimestamp())); - builder.endObject(); - } - - if (status.lastChecked() != null) { - builder.timeField("last_checked", status.lastChecked()); - } - if (status.lastMetCondition() != null) { - builder.timeField("last_met_condition", status.lastMetCondition()); - } - builder.startObject("actions"); - for (Map.Entry entry :status.getActions().entrySet()) { - builder.startObject(entry.getKey()); - toXContentFragment(entry.getValue(), builder); - builder.endObject(); - } - builder.endObject(); - builder.field("execution_state", status.getExecutionState().id()); - builder.field("version", status.version()); - return builder.endObject(); - } - - static XContentParser parser(NamedXContentRegistry xContentRegistry, - XContentType contentType, InputStream stream) throws IOException { - return contentType.xContent().createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, stream); - } - - static XContentBuilder toXContent(NamedXContentRegistry xContentRegistry, - GetWatchResponse response, XContentBuilder builder) throws IOException { - builder.startObject(); - builder.field("_id", response.getId()); - builder.field("found", response.isFound()); - if (response.isFound() == false) { - return builder.endObject(); - } - builder.field("_version", response.getVersion()); - toXContent(response.getStatus(), builder); - builder.field("watch"); - try (InputStream stream = response.getSource().streamInput(); - XContentParser parser = parser(xContentRegistry, response.getContentType(), stream)) { - parser.nextToken(); - builder.generator().copyCurrentStructure(parser); - } - return builder.endObject(); - } - - static ActionStatus.Execution randomExecution() { - if (randomBoolean()) { - return null; - } - boolean successful = randomBoolean(); - String reason = null; - if (successful == false) { - reason = randomAlphaOfLengthBetween(10, 20); - - } - return new ActionStatus.Execution(DateTime.now(DateTimeZone.UTC), successful, reason); - } - - static WatchStatus randomWatchStatus() { - long version = randomLongBetween(-1, Long.MAX_VALUE); - WatchStatus.State state = rarely() ? null : new WatchStatus.State(randomBoolean(), DateTime.now(DateTimeZone.UTC)); - ExecutionState executionState = randomFrom(ExecutionState.values()); - DateTime lastChecked = rarely() ? null : DateTime.now(DateTimeZone.UTC); - DateTime lastMetCondition = rarely() ? null : DateTime.now(DateTimeZone.UTC); - int size = randomIntBetween(0, 5); - Map actionMap = new HashMap<>(); - for (int i = 0; i < size; i++) { - ActionStatus.AckStatus ack = new ActionStatus.AckStatus(DateTime.now(DateTimeZone.UTC), - randomFrom(ActionStatus.AckStatus.State.values())); - ActionStatus actionStatus = new ActionStatus(ack, randomExecution(), randomExecution(), null); - actionMap.put(randomAlphaOfLength(10), actionStatus); - } - return new WatchStatus(version, state, executionState, lastChecked, lastMetCondition, actionMap); - } - - static BytesReference simpleWatch() { - XContentBuilder builder = null; - try { - builder = XContentBuilder.builder(XContentType.JSON.xContent()); - builder.startObject() - .startObject("trigger") - .startObject("schedule") - .field("interval", "10h") - .endObject() - .endObject() - .startObject("input") - .startObject("none") - .endObject() - .endObject() - .startObject("actions") - .startObject("logme") - .field("text", "{{ctx.payload}}") - .endObject() - .endObject().endObject(); - return BytesReference.bytes(builder); - } catch (IOException e) { - throw new AssertionError(e); - } - } -} diff --git a/docs/java-rest/high-level/watcher/get-watch.asciidoc b/docs/java-rest/high-level/watcher/get-watch.asciidoc index 6fa10e59a07e9..c4773d70ad731 100644 --- a/docs/java-rest/high-level/watcher/get-watch.asciidoc +++ b/docs/java-rest/high-level/watcher/get-watch.asciidoc @@ -14,18 +14,18 @@ A watch can be retrieved as follows: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-get-watch-execute] +include-tagged::{doc-tests-file}[{api}-request] -------------------------------------------------- -[[java-rest-high-x-pack-watcher-get-watch-response]] +[id="{upid}-{api}-response"] ==== Response -The returned `GetWatchResponse` contains `id`, -, `version`, `status` and `source` information. +The returned +{response}+ contains `id`, `version`, `status` and `source` +information. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-get-watch-response] +include-tagged::{doc-tests-file}[{api}-response] -------------------------------------------------- <1> `_id`, id of the watch <2> `found` is a boolean indicating whether the watch was found @@ -33,29 +33,4 @@ include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-get-watch-respons <3> `status` contains status of the watch <4> `source` the source of the watch -[[java-rest-high-x-pack-watcher-put-watch-async]] -==== Asynchronous Execution - -This request can be executed asynchronously: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-get-watch-execute-async] --------------------------------------------------- -<1> The `GetWatchRequest` 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 `GetWatchResponse` looks like: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/WatcherDocumentationIT.java[x-pack-get-watch-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 +include::../execution.asciidoc[] \ No newline at end of file diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java index 5aeb30bfdbd5d..f6b0294b6efc2 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractSerializingTestCase.java @@ -24,8 +24,10 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; +import java.util.Arrays; import java.util.function.Predicate; public abstract class AbstractSerializingTestCase extends AbstractWireSerializingTestCase { diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java index 5bc9c66d4c9a7..09536bfaa9e1e 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java @@ -23,8 +23,11 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; +import java.util.Arrays; +import java.util.List; import java.util.function.Predicate; public abstract class AbstractStreamableXContentTestCase extends AbstractStreamableTestCase { @@ -36,7 +39,7 @@ public abstract class AbstractStreamableXContentTestCase supportedContentTypes() { + return Arrays.asList(XContentType.values()); + } + /** * Params that have to be provided when calling calling {@link ToXContent#toXContent(XContentBuilder, ToXContent.Params)} */ diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java index 767cd6d260ceb..804f9d851c1aa 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractXContentTestCase.java @@ -33,6 +33,8 @@ import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; +import java.util.Arrays; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -43,12 +45,30 @@ public abstract class AbstractXContentTestCase extends ESTestCase { protected static final int NUMBER_OF_TEST_RUNS = 20; + public static XContentTester xContentTester( + CheckedBiFunction createParser, + Supplier instanceSupplier, + CheckedBiConsumer toXContent, + CheckedFunction fromXContent) { + return new XContentTester<>( + createParser, + instanceSupplier, + (testInstance, xContentType) -> { + try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) { + toXContent.accept(testInstance, builder); + return BytesReference.bytes(builder); + } + }, + fromXContent, Arrays.asList(XContentType.values())); + } + public static XContentTester xContentTester( CheckedBiFunction createParser, Supplier instanceSupplier, CheckedBiConsumer toXContent, - CheckedFunction fromXContent) { - return new XContentTester( + CheckedFunction fromXContent, + List supportedContentTypes) { + return new XContentTester<>( createParser, instanceSupplier, (testInstance, xContentType) -> { @@ -57,7 +77,7 @@ public static XContentTester xContentTester( return BytesReference.bytes(builder); } }, - fromXContent); + fromXContent, supportedContentTypes); } public static XContentTester xContentTester( @@ -67,17 +87,31 @@ public static XContentTester xContentTester( return xContentTester(createParser, instanceSupplier, ToXContent.EMPTY_PARAMS, fromXContent); } + public static XContentTester xContentTester( + CheckedBiFunction createParser, + Supplier instanceSupplier, + ToXContent.Params toXContentParams, + CheckedFunction fromXContent) { + return new XContentTester<>( + createParser, + instanceSupplier, + (testInstance, xContentType) -> + XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false), + fromXContent, Arrays.asList(XContentType.values())); + } + public static XContentTester xContentTester( CheckedBiFunction createParser, Supplier instanceSupplier, ToXContent.Params toXContentParams, - CheckedFunction fromXContent) { - return new XContentTester( + CheckedFunction fromXContent, + List supportedContentTypes) { + return new XContentTester<>( createParser, instanceSupplier, (testInstance, xContentType) -> XContentHelper.toXContent(testInstance, xContentType, toXContentParams, false), - fromXContent); + fromXContent, supportedContentTypes); } /** @@ -88,6 +122,7 @@ public static class XContentTester { private final Supplier instanceSupplier; private final CheckedBiFunction toXContent; private final CheckedFunction fromXContent; + private final List supportedContentTypes; private int numberOfTestRuns = NUMBER_OF_TEST_RUNS; private boolean supportsUnknownFields = false; @@ -104,17 +139,19 @@ private XContentTester( CheckedBiFunction createParser, Supplier instanceSupplier, CheckedBiFunction toXContent, - CheckedFunction fromXContent) { + CheckedFunction fromXContent, + List supportedContentTypes) { this.createParser = createParser; this.instanceSupplier = instanceSupplier; this.toXContent = toXContent; this.fromXContent = fromXContent; + this.supportedContentTypes = supportedContentTypes; } public void test() throws IOException { for (int runs = 0; runs < numberOfTestRuns; runs++) { T testInstance = instanceSupplier.get(); - XContentType xContentType = randomFrom(XContentType.values()); + XContentType xContentType = randomFrom(supportedContentTypes); BytesReference originalXContent = toXContent.apply(testInstance, xContentType); BytesReference shuffledContent = insertRandomFieldsAndShuffle(originalXContent, xContentType, supportsUnknownFields, shuffleFieldsExceptions, randomFieldsExcludeFilter, createParser); @@ -161,6 +198,28 @@ public XContentTester assertToXContentEquivalence(boolean assertToXContentEqu } } + public static void testFromXContent( + int numberOfTestRuns, + Supplier instanceSupplier, + boolean supportsUnknownFields, + String[] shuffleFieldsExceptions, + Predicate randomFieldsExcludeFilter, + CheckedBiFunction createParserFunction, + CheckedFunction fromXContent, + BiConsumer assertEqualsConsumer, + boolean assertToXContentEquivalence, + ToXContent.Params toXContentParams) throws IOException { + xContentTester(createParserFunction, instanceSupplier, toXContentParams, fromXContent, Arrays.asList(XContentType.values())) + .numberOfTestRuns(numberOfTestRuns) + .supportsUnknownFields(supportsUnknownFields) + .shuffleFieldsExceptions(shuffleFieldsExceptions) + .randomFieldsExcludeFilter(randomFieldsExcludeFilter) + .assertEqualsConsumer(assertEqualsConsumer) + .assertToXContentEquivalence(assertToXContentEquivalence) + .test(); + } + + public static void testFromXContent( int numberOfTestRuns, Supplier instanceSupplier, @@ -171,8 +230,9 @@ public static void testFromXContent( CheckedFunction fromXContent, BiConsumer assertEqualsConsumer, boolean assertToXContentEquivalence, - ToXContent.Params toXContentParams) throws IOException { - xContentTester(createParserFunction, instanceSupplier, toXContentParams, fromXContent) + ToXContent.Params toXContentParams, + List supportedContentTypes) throws IOException { + xContentTester(createParserFunction, instanceSupplier, toXContentParams, fromXContent, supportedContentTypes) .numberOfTestRuns(numberOfTestRuns) .supportsUnknownFields(supportsUnknownFields) .shuffleFieldsExceptions(shuffleFieldsExceptions) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/XContentSource.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/XContentSource.java index e0724795c297c..27d8c130fcdaa 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/XContentSource.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/XContentSource.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Encapsulates the xcontent source @@ -133,4 +134,17 @@ private Object data() { return data; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + XContentSource that = (XContentSource) o; + return Objects.equals(bytes, that.bytes) && + contentType == that.contentType; + } + + @Override + public int hashCode() { + return Objects.hash(bytes, contentType); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/transport/actions/get/GetWatchResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/transport/actions/get/GetWatchResponse.java index ca7025e72ad22..d92ae1dcc4626 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/transport/actions/get/GetWatchResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/transport/actions/get/GetWatchResponse.java @@ -6,19 +6,23 @@ package org.elasticsearch.xpack.core.watcher.transport.actions.get; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.lucene.uid.Versions; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.core.watcher.watch.WatchStatus; import java.io.IOException; +import java.util.Objects; -public class GetWatchResponse extends ActionResponse { +public class GetWatchResponse extends ActionResponse implements ToXContent { private String id; private WatchStatus status; - private boolean found = false; + private boolean found; private XContentSource source; private long version; @@ -30,9 +34,10 @@ public GetWatchResponse() { */ public GetWatchResponse(String id) { this.id = id; + this.status = null; this.found = false; this.source = null; - version = Versions.NOT_FOUND; + this.version = Versions.NOT_FOUND; } /** @@ -75,6 +80,10 @@ public void readFrom(StreamInput in) throws IOException { status = WatchStatus.read(in); source = XContentSource.readFrom(in); version = in.readZLong(); + } else { + status = null; + source = null; + version = Versions.NOT_FOUND; } } @@ -89,4 +98,37 @@ public void writeTo(StreamOutput out) throws IOException { out.writeZLong(version); } } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field("found", found); + builder.field("_id", id); + if (found) { + builder.field("_version", version); + builder.field("status", status, params); + builder.field("watch", source, params); + } + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetWatchResponse that = (GetWatchResponse) o; + return version == that.version && + Objects.equals(id, that.id) && + Objects.equals(status, that.status) && + Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(id, status, version); + } + + @Override + public String toString() { + return Strings.toString(this); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/WatchStatus.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/WatchStatus.java index 69d114bd2b045..59715d7607cea 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/WatchStatus.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/watch/WatchStatus.java @@ -54,11 +54,11 @@ private WatchStatus() { } public WatchStatus(DateTime now, Map actions) { - this(-1, new State(true, now), null, null, null, actions, Collections.emptyMap()); + this(-1, new State(true, now), null, null, null, actions, null); } - private WatchStatus(long version, State state, ExecutionState executionState, DateTime lastChecked, DateTime lastMetCondition, - Map actions, Map headers) { + public WatchStatus(long version, State state, ExecutionState executionState, DateTime lastChecked, DateTime lastMetCondition, + Map actions, Map headers) { this.version = version; this.lastChecked = lastChecked; this.lastMetCondition = lastMetCondition; @@ -209,7 +209,7 @@ public void writeTo(StreamOutput out) throws IOException { if (executionState != null) { out.writeString(executionState.id()); } - boolean statusHasHeaders = headers != null && headers.isEmpty() == false; + boolean statusHasHeaders = headers != null; out.writeBoolean(statusHasHeaders); if (statusHasHeaders) { out.writeMap(headers, StreamOutput::writeString, StreamOutput::writeString); @@ -234,6 +234,8 @@ public void readFrom(StreamInput in) throws IOException { } if (in.readBoolean()) { headers = in.readMap(StreamInput::readString, StreamInput::readString); + } else { + headers = null; } } @@ -265,7 +267,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (executionState != null) { builder.field(Field.EXECUTION_STATE.getPreferredName(), executionState.id()); } - if (headers != null && headers.isEmpty() == false && WatcherParams.hideHeaders(params) == false) { + if (headers != null && WatcherParams.hideHeaders(params) == false) { builder.field(Field.HEADERS.getPreferredName(), headers); } builder.field(Field.VERSION.getPreferredName(), version); @@ -279,7 +281,7 @@ public static WatchStatus parse(String watchId, WatcherXContentParser parser) th DateTime lastMetCondition = null; Map actions = null; long version = -1; - Map headers = Collections.emptyMap(); + Map headers = null; String currentFieldName = null; XContentParser.Token token; diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcStreamableXContentTestCase.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcStreamableXContentTestCase.java index d471a1a03f8ef..24c96a02c9a72 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcStreamableXContentTestCase.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcStreamableXContentTestCase.java @@ -23,7 +23,7 @@ public abstract class AbstractHlrcStreamableXContentTestCase convertHlrcToInternal(doHlrcParseInstance(p))) + p -> convertHlrcToInternal(doHlrcParseInstance(p)), supportedContentTypes()) .numberOfTestRuns(NUMBER_OF_TEST_RUNS) .supportsUnknownFields(supportsUnknownFields()) .shuffleFieldsExceptions(getShuffleFieldsExceptions()) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcXContentTestCase.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcXContentTestCase.java index d6d8f9afe3659..2fe33923edd26 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcXContentTestCase.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/AbstractHlrcXContentTestCase.java @@ -7,9 +7,11 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.AbstractXContentTestCase; import java.io.IOException; +import java.util.Arrays; public abstract class AbstractHlrcXContentTestCase extends AbstractXContentTestCase { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/xpack/watcher/GetWatchResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/xpack/watcher/GetWatchResponseTests.java new file mode 100644 index 0000000000000..e32731a1e8924 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/xpack/watcher/GetWatchResponseTests.java @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.protocol.xpack.watcher; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.protocol.AbstractHlrcStreamableXContentTestCase; +import org.elasticsearch.xpack.core.watcher.actions.ActionStatus; +import org.elasticsearch.xpack.core.watcher.execution.ExecutionState; +import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource; +import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchResponse; +import org.elasticsearch.xpack.core.watcher.watch.WatchStatus; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GetWatchResponseTests extends + AbstractHlrcStreamableXContentTestCase { + + private static final String[] SHUFFLE_FIELDS_EXCEPTION = new String[] { "watch" }; + + @Override + protected String[] getShuffleFieldsExceptions() { + return SHUFFLE_FIELDS_EXCEPTION; + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } + + @Override + protected List supportedContentTypes() { + // The get watch API supports JSON output only. + return Arrays.asList(XContentType.JSON); + } + + @Override + protected GetWatchResponse createBlankInstance() { + return new GetWatchResponse(); + } + + @Override + protected GetWatchResponse createTestInstance() { + String id = randomAlphaOfLength(10); + if (rarely()) { + return new GetWatchResponse(id); + } + long version = randomLongBetween(0, 10); + WatchStatus status = randomWatchStatus(); + BytesReference source = emptyWatch(); + return new GetWatchResponse(id, version, status, new XContentSource(source, XContentType.JSON)); + } + + private static BytesReference emptyWatch() { + try { + XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent()); + builder.startObject().endObject(); + return BytesReference.bytes(builder); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private static WatchStatus randomWatchStatus() { + long version = randomLongBetween(-1, Long.MAX_VALUE); + WatchStatus.State state = new WatchStatus.State(randomBoolean(), DateTime.now(DateTimeZone.UTC)); + ExecutionState executionState = randomFrom(ExecutionState.values()); + DateTime lastChecked = rarely() ? null : DateTime.now(DateTimeZone.UTC); + DateTime lastMetCondition = rarely() ? null : DateTime.now(DateTimeZone.UTC); + int size = randomIntBetween(0, 5); + Map actionMap = new HashMap<>(); + for (int i = 0; i < size; i++) { + ActionStatus.AckStatus ack = new ActionStatus.AckStatus( + DateTime.now(DateTimeZone.UTC), + randomFrom(ActionStatus.AckStatus.State.values()) + ); + ActionStatus actionStatus = new ActionStatus( + ack, + randomBoolean() ? null : randomExecution(), + randomBoolean() ? null : randomExecution(), + randomBoolean() ? null : randomThrottle() + ); + actionMap.put(randomAlphaOfLength(10), actionStatus); + } + return new WatchStatus(version, state, executionState, lastChecked, lastMetCondition, actionMap, null); + } + + private static ActionStatus.Throttle randomThrottle() { + return new ActionStatus.Throttle(DateTime.now(DateTimeZone.UTC), randomAlphaOfLengthBetween(10, 20)); + } + + private static ActionStatus.Execution randomExecution() { + if (randomBoolean()) { + return null; + } else if (randomBoolean()) { + return ActionStatus.Execution.failure(DateTime.now(DateTimeZone.UTC), randomAlphaOfLengthBetween(10, 20)); + } else { + return ActionStatus.Execution.successful(DateTime.now(DateTimeZone.UTC)); + } + } + + @Override + public org.elasticsearch.client.watcher.GetWatchResponse doHlrcParseInstance(XContentParser parser) throws IOException { + return org.elasticsearch.client.watcher.GetWatchResponse.fromXContent(parser); + } + + @Override + public GetWatchResponse convertHlrcToInternal(org.elasticsearch.client.watcher.GetWatchResponse instance) { + if (instance.isFound()) { + return new GetWatchResponse(instance.getId(), instance.getVersion(), convertHlrcToInternal(instance.getStatus()), + new XContentSource(instance.getSource(), instance.getContentType())); + } else { + return new GetWatchResponse(instance.getId()); + } + } + + private static WatchStatus convertHlrcToInternal(org.elasticsearch.client.watcher.WatchStatus status) { + final Map actions = new HashMap<>(); + for (Map.Entry entry : status.getActions().entrySet()) { + actions.put(entry.getKey(), convertHlrcToInternal(entry.getValue())); + } + return new WatchStatus(status.version(), + convertHlrcToInternal(status.state()), + status.getExecutionState() == null ? null : convertHlrcToInternal(status.getExecutionState()), + status.lastChecked(), status.lastMetCondition(), actions, null + ); + } + + private static ActionStatus convertHlrcToInternal(org.elasticsearch.client.watcher.ActionStatus actionStatus) { + return new ActionStatus(convertHlrcToInternal(actionStatus.ackStatus()), + actionStatus.lastExecution() == null ? null : convertHlrcToInternal(actionStatus.lastExecution()), + actionStatus.lastSuccessfulExecution() == null ? null : convertHlrcToInternal(actionStatus.lastSuccessfulExecution()), + actionStatus.lastThrottle() == null ? null : convertHlrcToInternal(actionStatus.lastThrottle()) + ); + } + + private static ActionStatus.AckStatus convertHlrcToInternal(org.elasticsearch.client.watcher.ActionStatus.AckStatus ackStatus) { + return new ActionStatus.AckStatus(ackStatus.timestamp(), convertHlrcToInternal(ackStatus.state())); + } + + private static ActionStatus.AckStatus.State convertHlrcToInternal(org.elasticsearch.client.watcher.ActionStatus.AckStatus.State state) { + return ActionStatus.AckStatus.State.valueOf(state.name()); + } + + private static WatchStatus.State convertHlrcToInternal(org.elasticsearch.client.watcher.WatchStatus.State state) { + return new WatchStatus.State(state.isActive(), state.getTimestamp()); + } + + private static ExecutionState convertHlrcToInternal(org.elasticsearch.client.watcher.ExecutionState executionState) { + return ExecutionState.valueOf(executionState.name()); + } + + private static ActionStatus.Execution convertHlrcToInternal(org.elasticsearch.client.watcher.ActionStatus.Execution execution) { + if (execution.successful()) { + return ActionStatus.Execution.successful(execution.timestamp()); + } else { + return ActionStatus.Execution.failure(execution.timestamp(), execution.reason()); + } + } + + private static ActionStatus.Throttle convertHlrcToInternal(org.elasticsearch.client.watcher.ActionStatus.Throttle throttle) { + return new ActionStatus.Throttle(throttle.timestamp(), throttle.reason()); + } +}