From 41e97a7cb162b6beb45d199e8f056390843add0e Mon Sep 17 00:00:00 2001 From: javanna Date: Tue, 12 Jul 2016 18:35:56 +0200 Subject: [PATCH] RestClient: take builder out to its own class The RestClient class is getting bigger and bigger, its builder can definitely be taken out to its own top level class: RestClientBuilder --- .../org/elasticsearch/client/RestClient.java | 158 +--------------- .../client/RestClientBuilder.java | 175 ++++++++++++++++++ .../client/RestClientBuilderTests.java | 6 +- .../elasticsearch/client/sniff/Sniffer.java | 3 +- .../elasticsearch/test/ESIntegTestCase.java | 7 +- .../test/rest/client/RestTestClient.java | 3 +- 6 files changed, 190 insertions(+), 162 deletions(-) create mode 100644 client/rest/src/main/java/org/elasticsearch/client/RestClientBuilder.java diff --git a/client/rest/src/main/java/org/elasticsearch/client/RestClient.java b/client/rest/src/main/java/org/elasticsearch/client/RestClient.java index b5bfd6ee07c..6db51a5ac52 100644 --- a/client/rest/src/main/java/org/elasticsearch/client/RestClient.java +++ b/client/rest/src/main/java/org/elasticsearch/client/RestClient.java @@ -27,7 +27,6 @@ import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; @@ -39,12 +38,8 @@ import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.utils.URIBuilder; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.nio.client.methods.HttpAsyncMethods; -import org.apache.http.nio.conn.SchemeIOSessionStrategy; import org.apache.http.nio.protocol.HttpAsyncRequestProducer; import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; @@ -70,7 +65,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Client that connects to an elasticsearch cluster through http. - * Must be created using {@link Builder}, which allows to set all the different options or just rely on defaults. + * Must be created using {@link RestClientBuilder}, which allows to set all the different options or just rely on defaults. * The hosts that are part of the cluster need to be provided at creation time, but can also be replaced later * by calling {@link #setHosts(HttpHost...)}. * The method {@link #performRequest(String, String, Map, HttpEntity, Header...)} allows to send a request to the cluster. When @@ -509,7 +504,6 @@ public final class RestClient implements Closeable { public void onSuccess(Response response) { this.response = response; latch.countDown(); - } @Override @@ -530,154 +524,10 @@ public final class RestClient implements Closeable { } /** - * Returns a new {@link Builder} to help with {@link RestClient} creation. + * Returns a new {@link RestClientBuilder} to help with {@link RestClient} creation. */ - public static Builder builder(HttpHost... hosts) { - return new Builder(hosts); - } - - /** - * Helps creating a new {@link RestClient}. Allows to set the most common http client configuration options when internally - * creating the underlying {@link org.apache.http.nio.client.HttpAsyncClient}. Also allows to provide an externally created - * {@link org.apache.http.nio.client.HttpAsyncClient} in case additional customization is needed. - */ - public static final class Builder { - public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 1000; - public static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 10000; - public static final int DEFAULT_MAX_RETRY_TIMEOUT_MILLIS = DEFAULT_SOCKET_TIMEOUT_MILLIS; - public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT_MILLIS = 500; - public static final int DEFAULT_MAX_CONN_PER_ROUTE = 10; - public static final int DEFAULT_MAX_CONN_TOTAL = 30; - - private static final Header[] EMPTY_HEADERS = new Header[0]; - - private final HttpHost[] hosts; - private int maxRetryTimeout = DEFAULT_MAX_RETRY_TIMEOUT_MILLIS; - private Header[] defaultHeaders = EMPTY_HEADERS; - private FailureListener failureListener; - private HttpClientConfigCallback httpClientConfigCallback; - private RequestConfigCallback requestConfigCallback; - - /** - * Creates a new builder instance and sets the hosts that the client will send requests to. - */ - private Builder(HttpHost... hosts) { - if (hosts == null || hosts.length == 0) { - throw new IllegalArgumentException("no hosts provided"); - } - this.hosts = hosts; - } - - /** - * Sets the default request headers, which will be sent along with each request - */ - public Builder setDefaultHeaders(Header[] defaultHeaders) { - Objects.requireNonNull(defaultHeaders, "defaultHeaders must not be null"); - for (Header defaultHeader : defaultHeaders) { - Objects.requireNonNull(defaultHeader, "default header must not be null"); - } - this.defaultHeaders = defaultHeaders; - return this; - } - - /** - * Sets the {@link FailureListener} to be notified for each request failure - */ - public Builder setFailureListener(FailureListener failureListener) { - Objects.requireNonNull(failureListener, "failureListener must not be null"); - this.failureListener = failureListener; - return this; - } - - /** - * Sets the maximum timeout (in milliseconds) to honour in case of multiple retries of the same request. - * {@link #DEFAULT_MAX_RETRY_TIMEOUT_MILLIS} if not specified. - * - * @throws IllegalArgumentException if maxRetryTimeoutMillis is not greater than 0 - */ - public Builder setMaxRetryTimeoutMillis(int maxRetryTimeoutMillis) { - if (maxRetryTimeoutMillis <= 0) { - throw new IllegalArgumentException("maxRetryTimeoutMillis must be greater than 0"); - } - this.maxRetryTimeout = maxRetryTimeoutMillis; - return this; - } - - /** - * Sets the {@link HttpClientConfigCallback} to be used to customize http client configuration - */ - public Builder setHttpClientConfigCallback(HttpClientConfigCallback httpClientConfigCallback) { - Objects.requireNonNull(httpClientConfigCallback, "httpClientConfigCallback must not be null"); - this.httpClientConfigCallback = httpClientConfigCallback; - return this; - } - - /** - * Sets the {@link RequestConfigCallback} to be used to customize http client configuration - */ - public Builder setRequestConfigCallback(RequestConfigCallback requestConfigCallback) { - Objects.requireNonNull(requestConfigCallback, "requestConfigCallback must not be null"); - this.requestConfigCallback = requestConfigCallback; - return this; - } - - /** - * Creates a new {@link RestClient} based on the provided configuration. - */ - public RestClient build() { - if (failureListener == null) { - failureListener = new FailureListener(); - } - CloseableHttpAsyncClient httpClient = createHttpClient(); - httpClient.start(); - return new RestClient(httpClient, maxRetryTimeout, defaultHeaders, hosts, failureListener); - } - - private CloseableHttpAsyncClient createHttpClient() { - //default timeouts are all infinite - RequestConfig.Builder requestConfigBuilder = RequestConfig.custom().setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MILLIS) - .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT_MILLIS) - .setConnectionRequestTimeout(DEFAULT_CONNECTION_REQUEST_TIMEOUT_MILLIS); - if (requestConfigCallback != null) { - requestConfigCallback.customizeRequestConfig(requestConfigBuilder); - } - - HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create().setDefaultRequestConfig(requestConfigBuilder.build()) - //default settings for connection pooling may be too constraining - .setMaxConnPerRoute(DEFAULT_MAX_CONN_PER_ROUTE).setMaxConnTotal(DEFAULT_MAX_CONN_TOTAL); - if (httpClientConfigCallback != null) { - httpClientConfigCallback.customizeHttpClient(httpClientBuilder); - } - return httpClientBuilder.build(); - } - } - - /** - * Callback used the default {@link RequestConfig} being set to the {@link CloseableHttpClient} - * @see HttpClientBuilder#setDefaultRequestConfig - */ - public interface RequestConfigCallback { - /** - * Allows to customize the {@link RequestConfig} that will be used with each request. - * It is common to customize the different timeout values through this method without losing any other useful default - * value that the {@link RestClient.Builder} internally sets. - */ - void customizeRequestConfig(RequestConfig.Builder requestConfigBuilder); - } - - /** - * Callback used to customize the {@link CloseableHttpClient} instance used by a {@link RestClient} instance. - * Allows to customize default {@link RequestConfig} being set to the client and any parameter that - * can be set through {@link HttpClientBuilder} - */ - public interface HttpClientConfigCallback { - /** - * Allows to customize the {@link CloseableHttpAsyncClient} being created and used by the {@link RestClient}. - * Commonly used to customize the default {@link org.apache.http.client.CredentialsProvider} for authentication - * or the {@link SchemeIOSessionStrategy} for communication through ssl without losing any other useful default - * value that the {@link RestClient.Builder} internally sets, like connection pooling. - */ - void customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder); + public static RestClientBuilder builder(HttpHost... hosts) { + return new RestClientBuilder(hosts); } /** diff --git a/client/rest/src/main/java/org/elasticsearch/client/RestClientBuilder.java b/client/rest/src/main/java/org/elasticsearch/client/RestClientBuilder.java new file mode 100644 index 00000000000..aa761c6e03f --- /dev/null +++ b/client/rest/src/main/java/org/elasticsearch/client/RestClientBuilder.java @@ -0,0 +1,175 @@ +/* + * 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; + +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.http.nio.conn.SchemeIOSessionStrategy; + +import java.util.Objects; + +/** + * Helps creating a new {@link RestClient}. Allows to set the most common http client configuration options when internally + * creating the underlying {@link org.apache.http.nio.client.HttpAsyncClient}. Also allows to provide an externally created + * {@link org.apache.http.nio.client.HttpAsyncClient} in case additional customization is needed. + */ +public final class RestClientBuilder { + public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 1000; + public static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 10000; + public static final int DEFAULT_MAX_RETRY_TIMEOUT_MILLIS = DEFAULT_SOCKET_TIMEOUT_MILLIS; + public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT_MILLIS = 500; + public static final int DEFAULT_MAX_CONN_PER_ROUTE = 10; + public static final int DEFAULT_MAX_CONN_TOTAL = 30; + + private static final Header[] EMPTY_HEADERS = new Header[0]; + + private final HttpHost[] hosts; + private int maxRetryTimeout = DEFAULT_MAX_RETRY_TIMEOUT_MILLIS; + private Header[] defaultHeaders = EMPTY_HEADERS; + private RestClient.FailureListener failureListener; + private HttpClientConfigCallback httpClientConfigCallback; + private RequestConfigCallback requestConfigCallback; + + /** + * Creates a new builder instance and sets the hosts that the client will send requests to. + */ + RestClientBuilder(HttpHost... hosts) { + if (hosts == null || hosts.length == 0) { + throw new IllegalArgumentException("no hosts provided"); + } + this.hosts = hosts; + } + + /** + * Sets the default request headers, which will be sent along with each request + */ + public RestClientBuilder setDefaultHeaders(Header[] defaultHeaders) { + Objects.requireNonNull(defaultHeaders, "defaultHeaders must not be null"); + for (Header defaultHeader : defaultHeaders) { + Objects.requireNonNull(defaultHeader, "default header must not be null"); + } + this.defaultHeaders = defaultHeaders; + return this; + } + + /** + * Sets the {@link RestClient.FailureListener} to be notified for each request failure + */ + public RestClientBuilder setFailureListener(RestClient.FailureListener failureListener) { + Objects.requireNonNull(failureListener, "failureListener must not be null"); + this.failureListener = failureListener; + return this; + } + + /** + * Sets the maximum timeout (in milliseconds) to honour in case of multiple retries of the same request. + * {@link #DEFAULT_MAX_RETRY_TIMEOUT_MILLIS} if not specified. + * + * @throws IllegalArgumentException if maxRetryTimeoutMillis is not greater than 0 + */ + public RestClientBuilder setMaxRetryTimeoutMillis(int maxRetryTimeoutMillis) { + if (maxRetryTimeoutMillis <= 0) { + throw new IllegalArgumentException("maxRetryTimeoutMillis must be greater than 0"); + } + this.maxRetryTimeout = maxRetryTimeoutMillis; + return this; + } + + /** + * Sets the {@link HttpClientConfigCallback} to be used to customize http client configuration + */ + public RestClientBuilder setHttpClientConfigCallback(HttpClientConfigCallback httpClientConfigCallback) { + Objects.requireNonNull(httpClientConfigCallback, "httpClientConfigCallback must not be null"); + this.httpClientConfigCallback = httpClientConfigCallback; + return this; + } + + /** + * Sets the {@link RequestConfigCallback} to be used to customize http client configuration + */ + public RestClientBuilder setRequestConfigCallback(RequestConfigCallback requestConfigCallback) { + Objects.requireNonNull(requestConfigCallback, "requestConfigCallback must not be null"); + this.requestConfigCallback = requestConfigCallback; + return this; + } + + /** + * Creates a new {@link RestClient} based on the provided configuration. + */ + public RestClient build() { + if (failureListener == null) { + failureListener = new RestClient.FailureListener(); + } + CloseableHttpAsyncClient httpClient = createHttpClient(); + httpClient.start(); + return new RestClient(httpClient, maxRetryTimeout, defaultHeaders, hosts, failureListener); + } + + private CloseableHttpAsyncClient createHttpClient() { + //default timeouts are all infinite + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom().setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MILLIS) + .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT_MILLIS) + .setConnectionRequestTimeout(DEFAULT_CONNECTION_REQUEST_TIMEOUT_MILLIS); + if (requestConfigCallback != null) { + requestConfigCallback.customizeRequestConfig(requestConfigBuilder); + } + + HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create().setDefaultRequestConfig(requestConfigBuilder.build()) + //default settings for connection pooling may be too constraining + .setMaxConnPerRoute(DEFAULT_MAX_CONN_PER_ROUTE).setMaxConnTotal(DEFAULT_MAX_CONN_TOTAL); + if (httpClientConfigCallback != null) { + httpClientConfigCallback.customizeHttpClient(httpClientBuilder); + } + return httpClientBuilder.build(); + } + + /** + * Callback used the default {@link RequestConfig} being set to the {@link CloseableHttpClient} + * @see HttpClientBuilder#setDefaultRequestConfig + */ + public interface RequestConfigCallback { + /** + * Allows to customize the {@link RequestConfig} that will be used with each request. + * It is common to customize the different timeout values through this method without losing any other useful default + * value that the {@link RestClientBuilder} internally sets. + */ + void customizeRequestConfig(RequestConfig.Builder requestConfigBuilder); + } + + /** + * Callback used to customize the {@link CloseableHttpClient} instance used by a {@link RestClient} instance. + * Allows to customize default {@link RequestConfig} being set to the client and any parameter that + * can be set through {@link HttpClientBuilder} + */ + public interface HttpClientConfigCallback { + /** + * Allows to customize the {@link CloseableHttpAsyncClient} being created and used by the {@link RestClient}. + * Commonly used to customize the default {@link org.apache.http.client.CredentialsProvider} for authentication + * or the {@link SchemeIOSessionStrategy} for communication through ssl without losing any other useful default + * value that the {@link RestClientBuilder} internally sets, like connection pooling. + */ + void customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder); + } +} diff --git a/client/rest/src/test/java/org/elasticsearch/client/RestClientBuilderTests.java b/client/rest/src/test/java/org/elasticsearch/client/RestClientBuilderTests.java index dc1c03b06a3..c36039c1bb1 100644 --- a/client/rest/src/test/java/org/elasticsearch/client/RestClientBuilderTests.java +++ b/client/rest/src/test/java/org/elasticsearch/client/RestClientBuilderTests.java @@ -104,16 +104,16 @@ public class RestClientBuilderTests extends RestClientTestCase { for (int i = 0; i < numNodes; i++) { hosts[i] = new HttpHost("localhost", 9200 + i); } - RestClient.Builder builder = RestClient.builder(hosts); + RestClientBuilder builder = RestClient.builder(hosts); if (getRandom().nextBoolean()) { - builder.setHttpClientConfigCallback(new RestClient.HttpClientConfigCallback() { + builder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public void customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { } }); } if (getRandom().nextBoolean()) { - builder.setRequestConfigCallback(new RestClient.RequestConfigCallback() { + builder.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { @Override public void customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) { } diff --git a/client/sniffer/src/main/java/org/elasticsearch/client/sniff/Sniffer.java b/client/sniffer/src/main/java/org/elasticsearch/client/sniff/Sniffer.java index 74a28cdd222..61fa32e7b62 100644 --- a/client/sniffer/src/main/java/org/elasticsearch/client/sniff/Sniffer.java +++ b/client/sniffer/src/main/java/org/elasticsearch/client/sniff/Sniffer.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import java.io.Closeable; import java.io.IOException; @@ -39,7 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * Must be created via {@link Builder}, which allows to set all of the different options or rely on defaults. * A background task fetches the nodes through the {@link HostsSniffer} and sets them to the {@link RestClient} instance. * It is possible to perform sniffing on failure by creating a {@link SniffOnFailureListener} and providing it as an argument to - * {@link org.elasticsearch.client.RestClient.Builder#setFailureListener(RestClient.FailureListener)}. The Sniffer implementation + * {@link RestClientBuilder#setFailureListener(RestClient.FailureListener)}. The Sniffer implementation * needs to be lazily set to the previously created SniffOnFailureListener through {@link SniffOnFailureListener#setSniffer(Sniffer)}. */ public final class Sniffer implements Closeable { diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java index 73edf000b96..9c51bf23bd2 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -29,6 +29,7 @@ import org.apache.lucene.util.TestUtil; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.discovery.DiscoveryModule; +import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.transport.MockTcpTransportPlugin; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ShardOperationFailedException; @@ -2094,11 +2095,11 @@ public abstract class ESIntegTestCase extends ESTestCase { return restClient; } - protected static RestClient createRestClient(RestClient.HttpClientConfigCallback httpClientConfigCallback) { + protected static RestClient createRestClient(RestClientBuilder.HttpClientConfigCallback httpClientConfigCallback) { return createRestClient(httpClientConfigCallback, "http"); } - protected static RestClient createRestClient(RestClient.HttpClientConfigCallback httpClientConfigCallback, String protocol) { + protected static RestClient createRestClient(RestClientBuilder.HttpClientConfigCallback httpClientConfigCallback, String protocol) { final NodesInfoResponse nodeInfos = client().admin().cluster().prepareNodesInfo().get(); final List nodes = nodeInfos.getNodes(); assertFalse(nodeInfos.hasFailures()); @@ -2111,7 +2112,7 @@ public abstract class ESIntegTestCase extends ESTestCase { hosts.add(new HttpHost(NetworkAddress.format(address.getAddress()), address.getPort(), protocol)); } } - RestClient.Builder builder = RestClient.builder(hosts.toArray(new HttpHost[hosts.size()])); + RestClientBuilder builder = RestClient.builder(hosts.toArray(new HttpHost[hosts.size()])); if (httpClientConfigCallback != null) { builder.setHttpClientConfigCallback(httpClientConfigCallback); } diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/client/RestTestClient.java b/test/framework/src/main/java/org/elasticsearch/test/rest/client/RestTestClient.java index 40fae122a5e..aec404f62e7 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/client/RestTestClient.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/client/RestTestClient.java @@ -31,6 +31,7 @@ import org.elasticsearch.Version; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.logging.ESLogger; @@ -267,7 +268,7 @@ public class RestTestClient implements Closeable { URL url = urls[i]; hosts[i] = new HttpHost(url.getHost(), url.getPort(), protocol); } - RestClient.Builder builder = RestClient.builder(hosts).setMaxRetryTimeoutMillis(30000) + RestClientBuilder builder = RestClient.builder(hosts).setMaxRetryTimeoutMillis(30000) .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setSocketTimeout(30000)); String keystorePath = settings.get(TRUSTSTORE_PATH);