diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java index c06493aea7381..ed0043c801c7b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java @@ -26,6 +26,8 @@ import org.elasticsearch.client.watcher.ActivateWatchResponse; import org.elasticsearch.client.watcher.AckWatchRequest; import org.elasticsearch.client.watcher.AckWatchResponse; +import org.elasticsearch.client.watcher.GetWatchRequest; +import org.elasticsearch.client.watcher.GetWatchResponse; import org.elasticsearch.client.watcher.StartWatchServiceRequest; import org.elasticsearch.client.watcher.StopWatchServiceRequest; import org.elasticsearch.client.watcher.DeleteWatchRequest; @@ -129,6 +131,34 @@ public void putWatchAsync(PutWatchRequest request, RequestOptions options, PutWatchResponse::fromXContent, listener, emptySet()); } + /** + * Gets a watch from the cluster + * See + * the docs for more. + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public GetWatchResponse getWatch(GetWatchRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, WatcherRequestConverters::getWatch, options, + GetWatchResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously gets a watch into the cluster + * See + * the docs for more. + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void getWatchAsync(GetWatchRequest request, RequestOptions options, + ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, WatcherRequestConverters::getWatch, options, + GetWatchResponse::fromXContent, listener, emptySet()); + } + /** * Deactivate an existing watch * See diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java index a017779495b9f..1da7ef4c617ff 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java @@ -28,12 +28,13 @@ import org.elasticsearch.client.watcher.DeactivateWatchRequest; import org.elasticsearch.client.watcher.ActivateWatchRequest; import org.elasticsearch.client.watcher.AckWatchRequest; +import org.elasticsearch.client.watcher.DeleteWatchRequest; +import org.elasticsearch.client.watcher.GetWatchRequest; +import org.elasticsearch.client.watcher.PutWatchRequest; import org.elasticsearch.client.watcher.StartWatchServiceRequest; import org.elasticsearch.client.watcher.StopWatchServiceRequest; import org.elasticsearch.client.watcher.WatcherStatsRequest; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.client.watcher.DeleteWatchRequest; -import org.elasticsearch.client.watcher.PutWatchRequest; final class WatcherRequestConverters { @@ -76,6 +77,16 @@ static Request putWatch(PutWatchRequest putWatchRequest) { return request; } + + static Request getWatch(GetWatchRequest getWatchRequest) { + String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_xpack", "watcher", "watch") + .addPathPart(getWatchRequest.getId()) + .build(); + + return new Request(HttpGet.METHOD_NAME, endpoint); + } + static Request deactivateWatch(DeactivateWatchRequest deactivateWatchRequest) { String endpoint = new RequestConverters.EndpointBuilder() .addPathPartAsIs("_xpack") diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchRequest.java new file mode 100644 index 0000000000000..fae2d31a256be --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchRequest.java @@ -0,0 +1,54 @@ +/* + * 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.client.Validatable; +import org.elasticsearch.client.ValidationException; + +/** + * The request to get the watch by name (id) + */ +public final class GetWatchRequest implements Validatable { + + private final String id; + + public GetWatchRequest(String watchId) { + validateId(watchId); + this.id = watchId; + } + + private void validateId(String id) { + ValidationException exception = new ValidationException(); + if (id == null) { + exception.addValidationError("watch id is missing"); + } else if (PutWatchRequest.isValidId(id) == false) { + exception.addValidationError("watch id contains whitespace"); + } + if (exception.validationErrors().isEmpty() == false) { + throw exception; + } + } + + /** + * @return The name of the watch to retrieve + */ + public String getId() { + return id; + } +} 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 new file mode 100644 index 0000000000000..9f5934b33eb30 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/GetWatchResponse.java @@ -0,0 +1,148 @@ +/* + * 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.ParseField; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.lucene.uid.Versions; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +public class GetWatchResponse { + private final String id; + private final long version; + 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); + } + + public GetWatchResponse(String id, long version, WatchStatus status, BytesReference source, XContentType xContentType) { + this.id = id; + this.version = version; + this.status = status; + this.source = source; + this.xContentType = xContentType; + } + + public String getId() { + return id; + } + + public long getVersion() { + return version; + } + + public boolean isFound() { + return version != Versions.NOT_FOUND; + } + + public WatchStatus getStatus() { + return status; + } + + /** + * Returns the {@link XContentType} of the source + */ + public XContentType getContentType() { + return xContentType; + } + + /** + * Returns the serialized watch + */ + public BytesReference getSource() { + return source; + } + + /** + * Returns the source as a map + */ + public Map getSourceAsMap() { + return source == null ? null : XContentHelper.convertToMap(source, false, getContentType()).v2(); + } + + @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(xContentType, that.xContentType) && + Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(id, status, source, version); + } + + private static final ParseField ID_FIELD = new ParseField("_id"); + private static final ParseField FOUND_FIELD = new ParseField("found"); + private static final ParseField VERSION_FIELD = new ParseField("_version"); + private static final ParseField STATUS_FIELD = new ParseField("status"); + private static final ParseField WATCH_FIELD = new ParseField("watch"); + + private static ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("get_watch_response", true, + 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()); + } else { + return new GetWatchResponse((String) a[0]); + } + }); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), ID_FIELD); + PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), FOUND_FIELD); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), VERSION_FIELD); + PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), + (parser, context) -> WatchStatus.parse(parser), STATUS_FIELD); + PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), + (parser, context) -> { + try (XContentBuilder builder = XContentBuilder.builder(parser.contentType().xContent())) { + builder.copyCurrentStructure(parser); + return builder; + } + }, WATCH_FIELD); + } + + public static GetWatchResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } +} 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 04b747c03635a..ab543abbf594a 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,6 +83,10 @@ public ActionStatus actionStatus(String actionId) { return actions.get(actionId); } + public Map getActions() { + return actions; + } + public long version() { return version; } @@ -87,6 +95,10 @@ public ExecutionState getExecutionState() { return executionState; } + public Map getHeaders() { + return headers; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -98,7 +110,8 @@ public boolean equals(Object o) { Objects.equals(lastMetCondition, that.lastMetCondition) && Objects.equals(version, that.version) && Objects.equals(executionState, that.executionState) && - Objects.equals(actions, that.actions); + Objects.equals(actions, that.actions) && + Objects.equals(headers, that.headers); } @Override @@ -112,6 +125,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); @@ -172,13 +186,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 { @@ -214,6 +232,8 @@ public static State parse(XContentParser parser) throws IOException { active = parser.booleanValue(); } else if (Field.TIMESTAMP.match(currentFieldName, parser.getDeprecationHandler())) { timestamp = parseDate(currentFieldName, parser); + } else { + parser.skipChildren(); } } return new State(active, timestamp); @@ -229,5 +249,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/WatcherRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherRequestConvertersTests.java index 2712dbc0438db..ff7050fd67ce7 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherRequestConvertersTests.java @@ -28,6 +28,7 @@ import org.elasticsearch.client.watcher.DeactivateWatchRequest; import org.elasticsearch.client.watcher.DeleteWatchRequest; import org.elasticsearch.client.watcher.PutWatchRequest; +import org.elasticsearch.client.watcher.GetWatchRequest; import org.elasticsearch.client.watcher.StartWatchServiceRequest; import org.elasticsearch.client.watcher.StopWatchServiceRequest; import org.elasticsearch.client.watcher.WatcherStatsRequest; @@ -91,6 +92,16 @@ public void testPutWatch() throws Exception { assertThat(bos.toString("UTF-8"), is(body)); } + public void testGetWatch() throws Exception { + String watchId = randomAlphaOfLength(10); + GetWatchRequest getWatchRequest = new GetWatchRequest(watchId); + + Request request = WatcherRequestConverters.getWatch(getWatchRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/watcher/watch/" + watchId, request.getEndpoint()); + assertThat(request.getEntity(), nullValue()); + } + public void testDeactivateWatch() { String watchId = randomAlphaOfLength(10); DeactivateWatchRequest deactivateWatchRequest = new DeactivateWatchRequest(watchId); 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 74562a1d17fd0..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 @@ -34,6 +34,8 @@ import org.elasticsearch.client.watcher.ActionStatus.AckStatus; import org.elasticsearch.client.watcher.DeactivateWatchRequest; import org.elasticsearch.client.watcher.DeactivateWatchResponse; +import org.elasticsearch.client.watcher.GetWatchRequest; +import org.elasticsearch.client.watcher.GetWatchResponse; import org.elasticsearch.client.watcher.StartWatchServiceRequest; import org.elasticsearch.client.watcher.StopWatchServiceRequest; import org.elasticsearch.client.watcher.WatchStatus; @@ -197,6 +199,51 @@ public void onFailure(Exception e) { assertTrue(latch.await(30L, TimeUnit.SECONDS)); } + { + //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::get-watch-request + + //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::get-watch-response + } + + { + GetWatchRequest request = new GetWatchRequest("my_other_watch_id"); + // tag::get-watch-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(GetWatchResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // 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::get-watch-execute-async + client.watcher().getWatchAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::get-watch-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + { //tag::x-pack-delete-watch-execute DeleteWatchRequest request = new DeleteWatchRequest("my_watch_id"); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/WatchRequestValidationTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/WatchRequestValidationTests.java index 1fea3bccb62a7..7f3bc2e0c8931 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/WatchRequestValidationTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/WatchRequestValidationTests.java @@ -91,4 +91,10 @@ public void testPutWatchContentNull() { () -> new PutWatchRequest("foo", BytesArray.EMPTY, null)); assertThat(exception.getMessage(), is("request body is missing")); } + + public void testGetWatchInvalidWatchId() { + ValidationException e = expectThrows(ValidationException.class, + () -> new GetWatchRequest("id with whitespaces")); + assertThat(e.validationErrors(), hasItem("watch id contains whitespace")); + } } diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 096a9e593827c..c7ecccc01c9bc 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -420,6 +420,7 @@ The Java High Level REST Client supports the following Watcher APIs: * <<{upid}-start-watch-service>> * <<{upid}-stop-watch-service>> * <> +* <> * <> * <> * <<{upid}-ack-watch>> @@ -429,6 +430,7 @@ The Java High Level REST Client supports the following Watcher APIs: include::watcher/start-watch-service.asciidoc[] include::watcher/stop-watch-service.asciidoc[] include::watcher/put-watch.asciidoc[] +include::watcher/get-watch.asciidoc[] include::watcher/delete-watch.asciidoc[] include::watcher/ack-watch.asciidoc[] include::watcher/deactivate-watch.asciidoc[] diff --git a/docs/java-rest/high-level/watcher/get-watch.asciidoc b/docs/java-rest/high-level/watcher/get-watch.asciidoc new file mode 100644 index 0000000000000..c4773d70ad731 --- /dev/null +++ b/docs/java-rest/high-level/watcher/get-watch.asciidoc @@ -0,0 +1,36 @@ +-- +:api: get-watch +:request: GetWatchRequest +:response: GetWatchResponse +-- + +[id="{upid}-{api}"] +=== Get Watch API + +[id="{upid}-{api}-request"] +==== Execution + +A watch can be retrieved as follows: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- + +[id="{upid}-{api}-response"] +==== Response + +The returned +{response}+ contains `id`, `version`, `status` and `source` +information. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- +<1> `_id`, id of the watch +<2> `found` is a boolean indicating whether the watch was found +<2> `_version` returns the version of the watch +<3> `status` contains status of the watch +<4> `source` the source of the watch + +include::../execution.asciidoc[] \ No newline at end of file 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..b54acc441e74a 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 @@ -51,6 +52,13 @@ public XContentSource(XContentBuilder builder) { this(BytesReference.bytes(builder), builder.contentType()); } + /** + * @return The content type of the source + */ + public XContentType getContentType() { + return contentType; + } + /** * @return The bytes reference of the source */ @@ -133,4 +141,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 7b94b446ffefa..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,21 +6,23 @@ package org.elasticsearch.xpack.core.watcher.transport.actions.get; import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.common.bytes.BytesReference; +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.XContentType; +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; @@ -32,19 +34,20 @@ 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; } /** * ctor for found watch */ - public GetWatchResponse(String id, long version, WatchStatus status, BytesReference source, XContentType contentType) { + public GetWatchResponse(String id, long version, WatchStatus status, XContentSource source) { this.id = id; this.status = status; this.found = true; - this.source = new XContentSource(source, contentType); + this.source = source; this.version = version; } @@ -77,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; } } @@ -91,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..c65e019780a90 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 @@ -57,8 +57,8 @@ public WatchStatus(DateTime now, Map actions) { this(-1, new State(true, now), null, null, null, actions, Collections.emptyMap()); } - 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; @@ -340,6 +340,8 @@ public static WatchStatus parse(String watchId, WatcherXContentParser parser) th if (token == XContentParser.Token.START_OBJECT) { headers = parser.mapStrings(); } + } else { + parser.skipChildren(); } } @@ -395,6 +397,8 @@ public static State parse(XContentParser parser) throws IOException { active = parser.booleanValue(); } else if (Field.TIMESTAMP.match(currentFieldName, parser.getDeprecationHandler())) { timestamp = parseDate(currentFieldName, parser, UTC); + } else { + parser.skipChildren(); } } return new State(active, timestamp); 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..bcfed1c7b0be4 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/protocol/xpack/watcher/GetWatchResponseTests.java @@ -0,0 +1,229 @@ +/* + * 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.DeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.ToXContent; +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.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.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +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 ToXContent.Params getToXContentParams() { + return new ToXContent.MapParams(Collections.singletonMap("hide_headers", "false")); + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + return f -> f.contains("watch") || f.contains("actions") || f.contains("headers"); + } + + @Override + protected void assertEqualInstances(GetWatchResponse expectedInstance, GetWatchResponse newInstance) { + if (expectedInstance.isFound() && + expectedInstance.getSource().getContentType() != newInstance.getSource().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.getSource().getContentType()); + XContent to = XContentFactory.xContent(expectedInstance.getSource().getContentType()); + final BytesReference newSource; + // It is safe to use EMPTY here because this never uses namedObject + try (InputStream stream = newInstance.getSource().getBytes().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); + newSource = BytesReference.bytes(builder); + } catch (IOException e) { + throw new AssertionError(e); + } + newInstance = new GetWatchResponse(newInstance.getId(), newInstance.getVersion(), + newInstance.getStatus(), new XContentSource(newSource, expectedInstance.getSource().getContentType())); + } + super.assertEqualInstances(expectedInstance, newInstance); + } + + @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 = simpleWatch(); + return new GetWatchResponse(id, version, status, new XContentSource(source, XContentType.JSON)); + } + + private static BytesReference simpleWatch() { + try { + XContentBuilder 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); + } + } + + 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); + } + Map headers = randomBoolean() ? new HashMap<>() : null; + if (headers != null) { + int headerSize = randomIntBetween(1, 5); + for (int i = 0; i < headerSize; i++) { + headers.put(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(1, 10)); + } + } + return new WatchStatus(version, state, executionState, lastChecked, lastMetCondition, actionMap, headers); + } + + 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, status.getHeaders() + ); + } + + 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()); + } +} diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java index 2e6a49ac8bdc1..4262703f7db1a 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/transport/actions/get/TransportGetWatchAction.java @@ -19,6 +19,7 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.watcher.support.xcontent.WatcherParams; +import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchAction; import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchRequest; import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchResponse; @@ -71,7 +72,7 @@ protected void doExecute(GetWatchRequest request, ActionListener