diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java index 94bf62438352d..7687c96835208 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/LicenseClient.java @@ -31,6 +31,8 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; import org.elasticsearch.protocol.xpack.license.GetLicenseResponse; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; +import org.elasticsearch.protocol.xpack.license.PostStartTrialResponse; import org.elasticsearch.protocol.xpack.license.PutLicenseRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; @@ -40,6 +42,7 @@ import java.nio.charset.StandardCharsets; import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; /** * A wrapper for the {@link RestHighLevelClient} that provides methods for @@ -98,6 +101,28 @@ public void getLicenseAsync(GetLicenseRequest request, RequestOptions options, A response -> new GetLicenseResponse(convertResponseToJson(response)), listener, emptySet()); } + /** + * Enables and starts a trial license for the cluster. + * @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 PostStartTrialResponse postStartTrial(PostStartTrialRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, RequestConverters::postStartTrial, options, + PostStartTrialResponse::fromXContent, singleton(403) + ); + } + + /** + * Asynchronously enabled and starts a trial license for the cluster. + * @param opts 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 postStartTrialAsync(PostStartTrialRequest request, RequestOptions opts, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, RequestConverters::postStartTrial, opts, + PostStartTrialResponse::fromXContent, listener, singleton(403) + ); + } /** * Converts an entire response into a json sting diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index a81060e66247b..84c5c54b91ef4 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -110,6 +110,7 @@ import org.elasticsearch.protocol.xpack.XPackUsageRequest; import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseRequest; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.protocol.xpack.ml.PutJobRequest; import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; @@ -1189,6 +1190,24 @@ static Request getLicense(GetLicenseRequest getLicenseRequest) { return request; } + static Request postStartTrial(PostStartTrialRequest postStartTrialRequest) { + Request request = new Request(HttpPost.METHOD_NAME, new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("license") + .addPathPartAsIs("start_trial") + .build()); + + Params parameters = new Params(request); + parameters.withMasterTimeout(postStartTrialRequest.masterNodeTimeout()); + if (postStartTrialRequest.isAcknowledged()) { + parameters.putParam("acknowledge", "true"); + } + if (postStartTrialRequest.getType() != null) { + parameters.putParam("type", postStartTrialRequest.getType()); + } + return request; + } + static Request putMachineLearningJob(PutJobRequest putJobRequest) throws IOException { String endpoint = new EndpointBuilder() .addPathPartAsIs("_xpack") diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index e4aa690acb617..14613ee494a4a 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -126,6 +126,7 @@ import org.elasticsearch.index.rankeval.RatedRequest; import org.elasticsearch.index.rankeval.RestRankEvalAction; import org.elasticsearch.protocol.xpack.XPackInfoRequest; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.protocol.xpack.watcher.DeleteWatchRequest; import org.elasticsearch.protocol.xpack.watcher.PutWatchRequest; import org.elasticsearch.repositories.fs.FsRepository; @@ -2592,6 +2593,27 @@ public void testXPackDeleteWatch() { assertThat(request.getEntity(), nullValue()); } + public void testPostStartTrial() { + PostStartTrialRequest postStartTrialRequest = new PostStartTrialRequest(); + + final boolean acknowledged = randomBoolean(); + postStartTrialRequest.acknowledge(acknowledged); + + final String type = randomBoolean() + ? null + : randomAlphaOfLength(10); + postStartTrialRequest.setType(type); + + Request request = RequestConverters.postStartTrial(postStartTrialRequest); + if (acknowledged) { + assertEquals(Boolean.toString(acknowledged), request.getParameters().get("acknowledge")); + } + + if (type != null) { + assertEquals(type, request.getParameters().get("type")); + } + } + /** * Randomize the {@link FetchSourceContext} request parameters. */ diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java index 7173d1eb336df..7b19e666d84f0 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/LicensingDocumentationIT.java @@ -28,6 +28,8 @@ import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; import org.elasticsearch.protocol.xpack.license.GetLicenseResponse; import org.elasticsearch.protocol.xpack.license.LicensesStatus; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; +import org.elasticsearch.protocol.xpack.license.PostStartTrialResponse; import org.elasticsearch.protocol.xpack.license.PutLicenseRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; @@ -36,6 +38,7 @@ import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; @@ -167,4 +170,63 @@ public void onFailure(Exception e) { assertThat(currentLicense, endsWith("}")); } } + + public void testPostStartTrial() throws Exception { + RestHighLevelClient client = highLevelClient(); + { + // tag::post-start-trial-execute + PostStartTrialRequest request = new PostStartTrialRequest(); + request.acknowledge(false); + + PostStartTrialResponse response = client.license().postStartTrial(request, RequestOptions.DEFAULT); + // end::post-start-trial-execute + + // tag::post-start-trial-response + boolean acknowledged = response.isAcknowledged(); + boolean trialWasStarted = response.isTrialWasStarted(); + String errorMessage = response.getErrorMessage(); + String type = response.getType(); + String acknowledgeMessage = response.getAcknowledgeMessage(); // todo rename this to acknowledgeheader for consistency + Map acknowledgeMessages = response.getAcknowledgeMessages(); + // end::post-start-trial-response + + assertFalse(acknowledged); + assertFalse(trialWasStarted); + assertEquals("Operation failed: Needs acknowledgement.", errorMessage); + assertNull(type); + assertThat(acknowledgeMessage, containsString("To begin your free trial, call /start_trial again and specify " + + "the \"acknowledge=true\" parameter.")); + assertThat(acknowledgeMessages.entrySet(), not(empty())); + } + + { + PostStartTrialRequest request = new PostStartTrialRequest(); + + // tag::post-start-trial-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(PostStartTrialResponse postStartTrialResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::post-start-trial-execute-listener + + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::post-start-trial-execute-async + client.license().postStartTrialAsync(request, RequestOptions.DEFAULT, listener); + // end::post-start-trial-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + + // todo add some other cases with randomization + // todo add a case that succeeds in starting the trial + } + } } diff --git a/docs/java-rest/high-level/licensing/post-start-trial.asciidoc b/docs/java-rest/high-level/licensing/post-start-trial.asciidoc new file mode 100644 index 0000000000000..25c59b9a17ddf --- /dev/null +++ b/docs/java-rest/high-level/licensing/post-start-trial.asciidoc @@ -0,0 +1,59 @@ +[[java-rest-high-post-start-trial]] +=== Post Start Trial + +[[java-rest-high-post-start-license-execution]] +==== Execution + +This API creates and enables a trial license using the `postStartTrial()` +method. + +["source","java",subs="attributes,callouts,macros"] +--------------------------------------------------- +include-tagged::{doc_tests}/LicensingDocumentationIT.java[post-start-trial-execute] +--------------------------------------------------- + +[[java-rest-high-post-start-license-response]] +==== Response + +The returned `PostStartTrialResponse` returns a field indicating whether the +trial was started. If it was started, the response returns a the type of +license started. If it was not started, it returns an error message describing +why. + +Acknowledgement messages may also be returned if this API was called without +the `acknowledge` flag set to `true`. In this case you need to display the +messages to the end user and if they agree, resubmit the license with the +`acknowledge` flag set to `true`. Please note that the request will still +return a 200 return code even if requires an acknowledgement. So, it is +necessary to check the `acknowledged` flag. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[post-start-trial-response] +-------------------------------------------------- + +[[java-rest-high-post-start-trial-async]] + +==== Asynchronous execution + +This request can be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[post-start-trial-execute-async] +-------------------------------------------------- + +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 `PostStartTrialResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/LicensingDocumentationIT.java[post-start-trial-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 \ No newline at end of file diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 1acba88222641..8e8d52c7ec90a 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -194,9 +194,11 @@ The Java High Level REST Client supports the following Licensing APIs: * <> * <> +* <> include::licensing/put-license.asciidoc[] include::licensing/get-license.asciidoc[] +include::licensing/post-start-trial.asciidoc[] == Watcher APIs diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java index d299406aad06c..2887a2159615e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java @@ -29,6 +29,7 @@ import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.protocol.xpack.XPackInfoResponse; import org.elasticsearch.protocol.xpack.license.LicensesStatus; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackPlugin; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensingClient.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensingClient.java index 9adfba64119b9..1a6698638fa28 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensingClient.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensingClient.java @@ -8,6 +8,7 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.protocol.xpack.license.GetLicenseRequest; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; public class LicensingClient { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequestBuilder.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequestBuilder.java index 8e12c879f997d..71bd831928bce 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequestBuilder.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequestBuilder.java @@ -7,6 +7,7 @@ import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; class PostStartTrialRequestBuilder extends ActionRequestBuilder { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java index af738b9aadf7f..0630e4e5ebede 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java @@ -7,6 +7,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; @@ -32,6 +33,7 @@ protected RestChannelConsumer doPrepareRequest(RestRequest request, XPackClient PostStartTrialRequest startTrialRequest = new PostStartTrialRequest(); startTrialRequest.setType(request.param("type", "trial")); startTrialRequest.acknowledge(request.paramAsBoolean("acknowledge", false)); + startTrialRequest.masterNodeTimeout(request.paramAsTime("master_timeout", startTrialRequest.masterNodeTimeout())); return channel -> client.licensing().postStartTrial(startTrialRequest, new RestBuilderListener(channel) { @Override diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java index 98fb6115710e3..34f8a59550689 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java @@ -13,6 +13,7 @@ import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.Nullable; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.xpack.core.XPackPlugin; import java.time.Clock; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportPostStartTrialAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportPostStartTrialAction.java index e70662cdc3131..5952e5298d59f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportPostStartTrialAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportPostStartTrialAction.java @@ -15,6 +15,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.protocol.xpack.license.PostStartTrialRequest; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequest.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/license/PostStartTrialRequest.java similarity index 74% rename from x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequest.java rename to x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/license/PostStartTrialRequest.java index cf94312b6a72b..739c5b7ffece7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/PostStartTrialRequest.java +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/license/PostStartTrialRequest.java @@ -1,9 +1,23 @@ /* - * 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. + * 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.license; + +package org.elasticsearch.protocol.xpack.license; import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; diff --git a/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/license/PostStartTrialResponse.java b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/license/PostStartTrialResponse.java new file mode 100644 index 0000000000000..8af91bd4b0375 --- /dev/null +++ b/x-pack/protocol/src/main/java/org/elasticsearch/protocol/xpack/license/PostStartTrialResponse.java @@ -0,0 +1,236 @@ +/* + * 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.protocol.xpack.license; + +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParseException; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.protocol.xpack.common.ProtocolUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class PostStartTrialResponse extends AcknowledgedResponse { + + private static final ParseField TRIAL_WAS_STARTED_FIELD = new ParseField("trial_was_started"); + private static final ParseField TYPE_FIELD = new ParseField("type"); + private static final ParseField ERROR_MESSAGE_FIELD = new ParseField("error_message"); + private static final ParseField ACKNOWLEDGE_DETAILS_FIELD = new ParseField("acknowledge"); + private static final ParseField ACKNOWLEDGE_HEADER_FIELD = new ParseField("message"); + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "post_start_trial_response", + true, + (arguments, aVoid) -> { + boolean acknowledged = (boolean) arguments[0]; + boolean trialWasStarted = (boolean) arguments[1]; + String type = (String) arguments[2]; + String errorMessage = (String) arguments[3]; + @SuppressWarnings("unchecked") + Tuple> acknowledgeDetails = (Tuple>) arguments[4]; + + if (trialWasStarted) { + return new PostStartTrialResponse(acknowledged, type); + } else { + return new PostStartTrialResponse(acknowledged, errorMessage, acknowledgeDetails.v1(), acknowledgeDetails.v2()); + } + } + ); + + static { + declareAcknowledgedField(PARSER); + PARSER.declareBoolean(constructorArg(), TRIAL_WAS_STARTED_FIELD); + PARSER.declareString(optionalConstructorArg(), TYPE_FIELD); + PARSER.declareString(optionalConstructorArg(), ERROR_MESSAGE_FIELD); + PARSER.declareObject(optionalConstructorArg(), (parser, v) -> { + Map acknowledgeMessages = new HashMap<>(); + String message = null; + XContentParser.Token token; + String currentFieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else { + if (currentFieldName == null) { + throw new XContentParseException(parser.getTokenLocation(), "expected message header or acknowledgement"); + } + if ("message".equals(currentFieldName)) { + if (token != XContentParser.Token.VALUE_STRING) { + throw new XContentParseException(parser.getTokenLocation(), "unexpected message header type"); + } + message = parser.text(); + } else { + if (token != XContentParser.Token.START_ARRAY) { + throw new XContentParseException(parser.getTokenLocation(), "unexpected acknowledgement type"); + } + List acknowledgeMessagesList = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + if (token != XContentParser.Token.VALUE_STRING) { + throw new XContentParseException(parser.getTokenLocation(), "unexpected acknowledgement text"); + } + acknowledgeMessagesList.add(parser.text()); + } + acknowledgeMessages.put(currentFieldName, acknowledgeMessagesList.toArray(new String[0])); + } + } + } + return new Tuple<>(message, acknowledgeMessages); + }, ACKNOWLEDGE_DETAILS_FIELD); + } + + private boolean trialWasStarted; + private String type; + private String errorMessage; + private String acknowledgeMessage; + private Map acknowledgeMessages; + + public PostStartTrialResponse() {} + + public PostStartTrialResponse(boolean acknowledged, String type) { + this(acknowledged, true, type, null, null, Collections.emptyMap()); + + Objects.requireNonNull(type); + } + + public PostStartTrialResponse(boolean acknowledged, + String errorMessage, + String acknowledgeMessage, + Map acknowledgeMessages) { + this(acknowledged, false, null, errorMessage, acknowledgeMessage, acknowledgeMessages); + + Objects.requireNonNull(errorMessage); + Objects.requireNonNull(acknowledgeMessage); + Objects.requireNonNull(acknowledgeMessages); + } + + private PostStartTrialResponse(boolean acknowledged, + boolean trialWasStarted, + String type, + String errorMessage, + String acknowledgeMessage, + Map acknowledgeMessages) { + + super(acknowledged); + this.trialWasStarted = trialWasStarted; + this.type = type; + this.errorMessage = errorMessage; + this.acknowledgeMessage = acknowledgeMessage; + this.acknowledgeMessages = acknowledgeMessages; + } + + public boolean isTrialWasStarted() { + return trialWasStarted; + } + + public String getType() { + return type; + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getAcknowledgeMessage() { + return acknowledgeMessage; + } + + public Map getAcknowledgeMessages() { + return acknowledgeMessages; + } + + @Override + protected void addCustomFields(XContentBuilder builder, Params params) throws IOException { + builder.field(TRIAL_WAS_STARTED_FIELD.getPreferredName(), trialWasStarted); + if (trialWasStarted) { + builder.field(TYPE_FIELD.getPreferredName(), type); + } else { + builder.field(ERROR_MESSAGE_FIELD.getPreferredName(), errorMessage); + } + + if (acknowledgeMessages.isEmpty() == false) { + builder.startObject(ACKNOWLEDGE_DETAILS_FIELD.getPreferredName()); + builder.field(ACKNOWLEDGE_HEADER_FIELD.getPreferredName(), acknowledgeMessage); + for (Map.Entry entry : acknowledgeMessages.entrySet()) { + builder.startArray(entry.getKey()); + for (String message : entry.getValue()) { + builder.value(message); + } + builder.endArray(); + } + builder.endObject(); + } + } + + public static PostStartTrialResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null) { + return false; + } + + if (getClass() != other.getClass()) { + return false; + } + + if (super.equals(other) == false) { + return false; + } + + PostStartTrialResponse otherResponse = (PostStartTrialResponse) other; + + return Objects.equals(acknowledged, otherResponse.acknowledged) + && Objects.equals(trialWasStarted, otherResponse.trialWasStarted) + && Objects.equals(type, otherResponse.type) + && Objects.equals(acknowledgeMessage, otherResponse.acknowledgeMessage) + && ProtocolUtils.equals(acknowledgeMessages, otherResponse.acknowledgeMessages); + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + acknowledged, + trialWasStarted, + type, + acknowledgeMessage, + ProtocolUtils.hashCode(acknowledgeMessages) + ); + } + +} diff --git a/x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/license/PostStartTrialResponseTests.java b/x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/license/PostStartTrialResponseTests.java new file mode 100644 index 0000000000000..ff8793b2a1151 --- /dev/null +++ b/x-pack/protocol/src/test/java/org/elasticsearch/protocol/xpack/license/PostStartTrialResponseTests.java @@ -0,0 +1,81 @@ +/* + * 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.protocol.xpack.license; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +public class PostStartTrialResponseTests extends AbstractXContentTestCase { + + @Override + protected PostStartTrialResponse createTestInstance() { + final boolean acknowledged = randomBoolean(); + + final boolean successfulStartedTrial = randomBoolean(); + + if (successfulStartedTrial) { + final String licenseType = randomAlphaOfLengthBetween(3, 20); + return new PostStartTrialResponse(acknowledged, licenseType); + } else { + final String errorMessage = randomAlphaOfLengthBetween(3, 20); + final String acknowledgeHeader = randomAlphaOfLengthBetween(3, 20); + final Map acknowledgeMessages = randomAckMessages(); + return new PostStartTrialResponse(acknowledged, errorMessage, acknowledgeHeader, acknowledgeMessages); + } + } + + private static Map randomAckMessages() { + int nFeatures = randomIntBetween(1, 5); + + Map ackMessages = new HashMap<>(); + + for (int i = 0; i < nFeatures; i++) { + String feature = randomAlphaOfLengthBetween(9, 15); + int nMessages = randomIntBetween(1, 5); + String[] messages = new String[nMessages]; + for (int j = 0; j < nMessages; j++) { + messages[j] = randomAlphaOfLengthBetween(10, 30); + } + ackMessages.put(feature, messages); + } + + return ackMessages; + } + + @Override + protected PostStartTrialResponse doParseInstance(XContentParser parser) throws IOException { + return PostStartTrialResponse.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + return p -> p.equals("acknowledge"); + } +}