• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.core.client.builder;
17 
18 import static software.amazon.awssdk.core.ClientType.ASYNC;
19 import static software.amazon.awssdk.core.ClientType.SYNC;
20 import static software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR;
21 import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.USER_AGENT_PREFIX;
22 import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.USER_AGENT_SUFFIX;
23 import static software.amazon.awssdk.core.client.config.SdkClientOption.ADDITIONAL_HTTP_HEADERS;
24 import static software.amazon.awssdk.core.client.config.SdkClientOption.ASYNC_HTTP_CLIENT;
25 import static software.amazon.awssdk.core.client.config.SdkClientOption.CLIENT_TYPE;
26 import static software.amazon.awssdk.core.client.config.SdkClientOption.CLIENT_USER_AGENT;
27 import static software.amazon.awssdk.core.client.config.SdkClientOption.COMPRESSION_CONFIGURATION;
28 import static software.amazon.awssdk.core.client.config.SdkClientOption.CONFIGURED_ASYNC_HTTP_CLIENT;
29 import static software.amazon.awssdk.core.client.config.SdkClientOption.CONFIGURED_ASYNC_HTTP_CLIENT_BUILDER;
30 import static software.amazon.awssdk.core.client.config.SdkClientOption.CONFIGURED_COMPRESSION_CONFIGURATION;
31 import static software.amazon.awssdk.core.client.config.SdkClientOption.CONFIGURED_SCHEDULED_EXECUTOR_SERVICE;
32 import static software.amazon.awssdk.core.client.config.SdkClientOption.CONFIGURED_SYNC_HTTP_CLIENT;
33 import static software.amazon.awssdk.core.client.config.SdkClientOption.CONFIGURED_SYNC_HTTP_CLIENT_BUILDER;
34 import static software.amazon.awssdk.core.client.config.SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED;
35 import static software.amazon.awssdk.core.client.config.SdkClientOption.DEFAULT_RETRY_MODE;
36 import static software.amazon.awssdk.core.client.config.SdkClientOption.EXECUTION_INTERCEPTORS;
37 import static software.amazon.awssdk.core.client.config.SdkClientOption.HTTP_CLIENT_CONFIG;
38 import static software.amazon.awssdk.core.client.config.SdkClientOption.IDENTITY_PROVIDERS;
39 import static software.amazon.awssdk.core.client.config.SdkClientOption.INTERNAL_USER_AGENT;
40 import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHERS;
41 import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE;
42 import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE_SUPPLIER;
43 import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_NAME;
44 import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_POLICY;
45 import static software.amazon.awssdk.core.client.config.SdkClientOption.SCHEDULED_EXECUTOR_SERVICE;
46 import static software.amazon.awssdk.core.client.config.SdkClientOption.SYNC_HTTP_CLIENT;
47 import static software.amazon.awssdk.utils.CollectionUtils.mergeLists;
48 import static software.amazon.awssdk.utils.Validate.paramNotNull;
49 
50 import java.net.URI;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.LinkedHashMap;
55 import java.util.List;
56 import java.util.Optional;
57 import java.util.concurrent.CompletableFuture;
58 import java.util.concurrent.Executor;
59 import java.util.concurrent.Executors;
60 import java.util.concurrent.LinkedBlockingQueue;
61 import java.util.concurrent.ScheduledExecutorService;
62 import java.util.concurrent.ThreadPoolExecutor;
63 import java.util.concurrent.TimeUnit;
64 import java.util.function.Function;
65 import java.util.function.Supplier;
66 import software.amazon.awssdk.annotations.SdkPreviewApi;
67 import software.amazon.awssdk.annotations.SdkProtectedApi;
68 import software.amazon.awssdk.annotations.SdkTestInternalApi;
69 import software.amazon.awssdk.core.CompressionConfiguration;
70 import software.amazon.awssdk.core.SdkPlugin;
71 import software.amazon.awssdk.core.SdkSystemSetting;
72 import software.amazon.awssdk.core.client.config.ClientAsyncConfiguration;
73 import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
74 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
75 import software.amazon.awssdk.core.client.config.SdkClientOption;
76 import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory;
77 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
78 import software.amazon.awssdk.core.internal.http.loader.DefaultSdkAsyncHttpClientBuilder;
79 import software.amazon.awssdk.core.internal.http.loader.DefaultSdkHttpClientBuilder;
80 import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage;
81 import software.amazon.awssdk.core.internal.http.pipeline.stages.CompressRequestStage;
82 import software.amazon.awssdk.core.internal.interceptor.HttpChecksumValidationInterceptor;
83 import software.amazon.awssdk.core.retry.RetryMode;
84 import software.amazon.awssdk.core.retry.RetryPolicy;
85 import software.amazon.awssdk.core.util.SdkUserAgent;
86 import software.amazon.awssdk.http.ExecutableHttpRequest;
87 import software.amazon.awssdk.http.HttpExecuteRequest;
88 import software.amazon.awssdk.http.SdkHttpClient;
89 import software.amazon.awssdk.http.async.AsyncExecuteRequest;
90 import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
91 import software.amazon.awssdk.identity.spi.IdentityProviders;
92 import software.amazon.awssdk.metrics.MetricPublisher;
93 import software.amazon.awssdk.profiles.ProfileFile;
94 import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
95 import software.amazon.awssdk.profiles.ProfileProperty;
96 import software.amazon.awssdk.utils.AttributeMap;
97 import software.amazon.awssdk.utils.AttributeMap.LazyValueSource;
98 import software.amazon.awssdk.utils.Either;
99 import software.amazon.awssdk.utils.Lazy;
100 import software.amazon.awssdk.utils.OptionalUtils;
101 import software.amazon.awssdk.utils.ThreadFactoryBuilder;
102 import software.amazon.awssdk.utils.Validate;
103 
104 /**
105  * An SDK-internal implementation of the methods in {@link SdkClientBuilder}, {@link SdkAsyncClientBuilder} and
106  * {@link SdkSyncClientBuilder}. This implements all methods required by those interfaces, allowing service-specific builders to
107  * just implement the configuration they wish to add.
108  *
109  * <p>By implementing both the sync and async interface's methods, service-specific builders can share code between their sync
110  * and
111  * async variants without needing one to extend the other. Note: This only defines the methods in the sync and async builder
112  * interfaces. It does not implement the interfaces themselves. This is because the sync and async client builder interfaces both
113  * require a type-constrained parameter for use in fluent chaining, and a generic type parameter conflict is introduced into the
114  * class hierarchy by this interface extending the builder interfaces themselves.</p>
115  *
116  * <p>Like all {@link SdkClientBuilder}s, this class is not thread safe.</p>
117  *
118  * @param <B> The type of builder, for chaining.
119  * @param <C> The type of client generated by this builder.
120  */
121 @SdkProtectedApi
122 public abstract class SdkDefaultClientBuilder<B extends SdkClientBuilder<B, C>, C> implements SdkClientBuilder<B, C> {
123 
124     private static final SdkHttpClient.Builder DEFAULT_HTTP_CLIENT_BUILDER = new DefaultSdkHttpClientBuilder();
125     private static final SdkAsyncHttpClient.Builder DEFAULT_ASYNC_HTTP_CLIENT_BUILDER = new DefaultSdkAsyncHttpClientBuilder();
126 
127     protected final SdkClientConfiguration.Builder clientConfiguration = SdkClientConfiguration.builder();
128 
129     protected final AttributeMap.Builder clientContextParams = AttributeMap.builder();
130 
131     private final SdkHttpClient.Builder defaultHttpClientBuilder;
132     private final SdkAsyncHttpClient.Builder defaultAsyncHttpClientBuilder;
133     private final List<SdkPlugin> plugins = new ArrayList<>();
134 
135     private ClientOverrideConfiguration overrideConfig;
136 
SdkDefaultClientBuilder()137     protected SdkDefaultClientBuilder() {
138         this(DEFAULT_HTTP_CLIENT_BUILDER, DEFAULT_ASYNC_HTTP_CLIENT_BUILDER);
139     }
140 
141     @SdkTestInternalApi
SdkDefaultClientBuilder(SdkHttpClient.Builder defaultHttpClientBuilder, SdkAsyncHttpClient.Builder defaultAsyncHttpClientBuilder)142     protected SdkDefaultClientBuilder(SdkHttpClient.Builder defaultHttpClientBuilder,
143                                       SdkAsyncHttpClient.Builder defaultAsyncHttpClientBuilder) {
144         this.defaultHttpClientBuilder = defaultHttpClientBuilder;
145         this.defaultAsyncHttpClientBuilder = defaultAsyncHttpClientBuilder;
146     }
147 
148     /**
149      * Build a client using the current state of this builder. This is marked final in order to allow this class to add standard
150      * "build" logic between all service clients. Service clients are expected to implement the {@link #buildClient} method, that
151      * accepts the immutable client configuration generated by this build method.
152      */
153     @Override
build()154     public final C build() {
155         return buildClient();
156     }
157 
158     /**
159      * Implemented by child classes to create a client using the provided immutable configuration objects. The async and sync
160      * configurations are not yet immutable. Child classes will need to make them immutable in order to validate them and pass
161      * them to the client's constructor.
162      *
163      * @return A client based on the provided configuration.
164      */
buildClient()165     protected abstract C buildClient();
166 
167     /**
168      * Return a client configuration object, populated with the following chain of priorities.
169      * <ol>
170      * <li>Client Configuration Overrides</li>
171      * <li>Customer Configuration</li>
172      * <li>Service-Specific Defaults</li>
173      * <li>Global Defaults</li>
174      * </ol>
175      */
syncClientConfiguration()176     protected final SdkClientConfiguration syncClientConfiguration() {
177         clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS, clientContextParams.build());
178         SdkClientConfiguration configuration = clientConfiguration.build();
179 
180         // Apply overrides
181         configuration = setOverrides(configuration);
182 
183         // Apply defaults
184         configuration = mergeChildDefaults(configuration);
185         configuration = mergeGlobalDefaults(configuration);
186 
187         // Create additional configuration from the default-applied configuration
188         configuration = finalizeChildConfiguration(configuration);
189         configuration = finalizeSyncConfiguration(configuration);
190         configuration = finalizeConfiguration(configuration);
191 
192         // Invoke the plugins
193         configuration = invokePlugins(configuration);
194 
195         return configuration;
196     }
197 
198     /**
199      * Return a client configuration object, populated with the following chain of priorities.
200      * <ol>
201      * <li>Client Configuration Overrides</li>
202      * <li>Customer Configuration</li>
203      * <li>Implementation/Service-Specific Configuration</li>
204      * <li>Global Default Configuration</li>
205      * </ol>
206      */
asyncClientConfiguration()207     protected final SdkClientConfiguration asyncClientConfiguration() {
208         clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS, clientContextParams.build());
209         SdkClientConfiguration configuration = clientConfiguration.build();
210 
211         // Apply overrides
212         configuration = setOverrides(configuration);
213 
214         // Apply defaults
215         configuration = mergeChildDefaults(configuration);
216         configuration = mergeGlobalDefaults(configuration);
217 
218         // Create additional configuration from the default-applied configuration
219         configuration = finalizeChildConfiguration(configuration);
220         configuration = finalizeAsyncConfiguration(configuration);
221         configuration = finalizeConfiguration(configuration);
222 
223         // Invoke the plugins
224         configuration = invokePlugins(configuration);
225 
226         return configuration;
227     }
228 
229     /**
230      * Apply the client override configuration to the provided configuration. This generally does not need to be overridden by
231      * child classes, but some previous client versions override it.
232      */
setOverrides(SdkClientConfiguration configuration)233     protected SdkClientConfiguration setOverrides(SdkClientConfiguration configuration) {
234         if (overrideConfig == null) {
235             return configuration;
236         }
237 
238         return configuration.toBuilder()
239                             .putAll(overrideConfig)
240                             .build();
241     }
242 
243     /**
244      * Optionally overridden by child implementations to apply implementation-specific default configuration.
245      * (eg. AWS's default credentials providers)
246      */
mergeChildDefaults(SdkClientConfiguration configuration)247     protected SdkClientConfiguration mergeChildDefaults(SdkClientConfiguration configuration) {
248         return configuration;
249     }
250 
251     /**
252      * Apply global default configuration
253      */
mergeGlobalDefaults(SdkClientConfiguration configuration)254     private SdkClientConfiguration mergeGlobalDefaults(SdkClientConfiguration configuration) {
255         Supplier<ProfileFile> defaultProfileFileSupplier = new Lazy<>(ProfileFile::defaultProfileFile)::getValue;
256 
257         configuration = configuration.merge(c -> c.option(EXECUTION_INTERCEPTORS, new ArrayList<>())
258                                                   .option(METRIC_PUBLISHERS, new ArrayList<>())
259                                                   .option(ADDITIONAL_HTTP_HEADERS, new LinkedHashMap<>())
260                                                   .option(PROFILE_FILE_SUPPLIER, defaultProfileFileSupplier)
261                                                   .lazyOption(PROFILE_FILE, conf -> conf.get(PROFILE_FILE_SUPPLIER).get())
262                                                   .option(PROFILE_NAME,
263                                                           ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow())
264                                                   .option(USER_AGENT_PREFIX, SdkUserAgent.create().userAgent())
265                                                   .option(USER_AGENT_SUFFIX, "")
266                                                   .option(CRC32_FROM_COMPRESSED_DATA_ENABLED, false)
267                                                   .option(CONFIGURED_COMPRESSION_CONFIGURATION,
268                                                           CompressionConfiguration.builder().build()));
269         return configuration;
270     }
271 
272     /**
273      * Optionally overridden by child implementations to derive implementation-specific configuration from the
274      * default-applied configuration. (eg. AWS's endpoint, derived from the region).
275      */
finalizeChildConfiguration(SdkClientConfiguration configuration)276     protected SdkClientConfiguration finalizeChildConfiguration(SdkClientConfiguration configuration) {
277         return configuration;
278     }
279 
280     /**
281      * Finalize sync-specific configuration from the default-applied configuration.
282      */
finalizeSyncConfiguration(SdkClientConfiguration config)283     private SdkClientConfiguration finalizeSyncConfiguration(SdkClientConfiguration config) {
284         return config.toBuilder()
285                      .lazyOption(SdkClientOption.SYNC_HTTP_CLIENT, c -> resolveSyncHttpClient(c, config))
286                      .option(SdkClientOption.CLIENT_TYPE, SYNC)
287                      .build();
288     }
289 
290     /**
291      * Finalize async-specific configuration from the default-applied configuration.
292      */
finalizeAsyncConfiguration(SdkClientConfiguration config)293     private SdkClientConfiguration finalizeAsyncConfiguration(SdkClientConfiguration config) {
294         return config.toBuilder()
295                      .lazyOptionIfAbsent(FUTURE_COMPLETION_EXECUTOR, this::resolveAsyncFutureCompletionExecutor)
296                      .lazyOption(ASYNC_HTTP_CLIENT, c -> resolveAsyncHttpClient(c, config))
297                      .option(SdkClientOption.CLIENT_TYPE, ASYNC)
298                      .build();
299     }
300 
301     /**
302      * Finalize global configuration from the default-applied configuration.
303      */
finalizeConfiguration(SdkClientConfiguration config)304     private SdkClientConfiguration finalizeConfiguration(SdkClientConfiguration config) {
305         return config.toBuilder()
306                      .lazyOption(SCHEDULED_EXECUTOR_SERVICE, this::resolveScheduledExecutorService)
307                      .lazyOptionIfAbsent(RETRY_POLICY, this::resolveRetryPolicy)
308                      .option(EXECUTION_INTERCEPTORS, resolveExecutionInterceptors(config))
309                      .lazyOption(CLIENT_USER_AGENT, this::resolveClientUserAgent)
310                      .lazyOption(COMPRESSION_CONFIGURATION, this::resolveCompressionConfiguration)
311                      .lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build())
312                      .build();
313     }
314 
resolveCompressionConfiguration(LazyValueSource config)315     private CompressionConfiguration resolveCompressionConfiguration(LazyValueSource config) {
316         CompressionConfiguration compressionConfig = config.get(CONFIGURED_COMPRESSION_CONFIGURATION);
317         return compressionConfig.toBuilder()
318                                 .requestCompressionEnabled(resolveCompressionEnabled(config, compressionConfig))
319                                 .minimumCompressionThresholdInBytes(resolveMinCompressionThreshold(config, compressionConfig))
320                                 .build();
321     }
322 
resolveCompressionEnabled(LazyValueSource config, CompressionConfiguration compressionConfig)323     private Boolean resolveCompressionEnabled(LazyValueSource config, CompressionConfiguration compressionConfig) {
324         Supplier<Optional<Boolean>> systemSettingConfiguration =
325             () -> SdkSystemSetting.AWS_DISABLE_REQUEST_COMPRESSION.getBooleanValue()
326                                                                   .map(v -> !v);
327 
328         Supplier<Optional<Boolean>> profileFileConfiguration =
329             () -> config.get(PROFILE_FILE_SUPPLIER).get()
330                         .profile(config.get(PROFILE_NAME))
331                         .flatMap(p -> p.booleanProperty(ProfileProperty.DISABLE_REQUEST_COMPRESSION))
332                         .map(v -> !v);
333 
334         return OptionalUtils.firstPresent(Optional.ofNullable(compressionConfig.requestCompressionEnabled()),
335                                           systemSettingConfiguration,
336                                           profileFileConfiguration)
337                             .orElse(true);
338     }
339 
resolveMinCompressionThreshold(LazyValueSource config, CompressionConfiguration compressionConfig)340     private Integer resolveMinCompressionThreshold(LazyValueSource config, CompressionConfiguration compressionConfig) {
341         Supplier<Optional<Integer>> systemSettingConfiguration =
342             SdkSystemSetting.AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES::getIntegerValue;
343 
344         Supplier<Optional<Integer>> profileFileConfiguration =
345             () -> config.get(PROFILE_FILE_SUPPLIER).get()
346                         .profile(config.get(PROFILE_NAME))
347                         .flatMap(p -> p.property(ProfileProperty.REQUEST_MIN_COMPRESSION_SIZE_BYTES))
348                         .map(Integer::parseInt);
349 
350         return OptionalUtils.firstPresent(Optional.ofNullable(compressionConfig.minimumCompressionThresholdInBytes()),
351                                           systemSettingConfiguration,
352                                           profileFileConfiguration)
353                             .orElse(CompressRequestStage.DEFAULT_MIN_COMPRESSION_SIZE);
354     }
355 
356     /**
357      * By default, returns the configuration as-is. Classes extending this method will take care of running the plugins and
358      * return the updated configuration if plugins are supported.
359      */
360     @SdkPreviewApi
invokePlugins(SdkClientConfiguration config)361     protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) {
362         return config;
363     }
364 
resolveClientUserAgent(LazyValueSource config)365     private String resolveClientUserAgent(LazyValueSource config) {
366         return ApplyUserAgentStage.resolveClientUserAgent(config.get(USER_AGENT_PREFIX),
367                                                           config.get(INTERNAL_USER_AGENT),
368                                                           config.get(CLIENT_TYPE),
369                                                           config.get(SYNC_HTTP_CLIENT),
370                                                           config.get(ASYNC_HTTP_CLIENT),
371                                                           config.get(RETRY_POLICY));
372     }
373 
resolveRetryPolicy(LazyValueSource config)374     private RetryPolicy resolveRetryPolicy(LazyValueSource config) {
375         RetryMode retryMode = RetryMode.resolver()
376                                        .profileFile(config.get(PROFILE_FILE_SUPPLIER))
377                                        .profileName(config.get(PROFILE_NAME))
378                                        .defaultRetryMode(config.get(DEFAULT_RETRY_MODE))
379                                        .resolve();
380         return RetryPolicy.forRetryMode(retryMode);
381     }
382 
383     /**
384      * Finalize which sync HTTP client will be used for the created client.
385      */
resolveSyncHttpClient(LazyValueSource config, SdkClientConfiguration deprecatedConfigDoNotUseThis)386     private SdkHttpClient resolveSyncHttpClient(LazyValueSource config,
387                                                 SdkClientConfiguration deprecatedConfigDoNotUseThis) {
388         SdkHttpClient httpClient = config.get(CONFIGURED_SYNC_HTTP_CLIENT);
389         SdkHttpClient.Builder<?> httpClientBuilder = config.get(CONFIGURED_SYNC_HTTP_CLIENT_BUILDER);
390         Validate.isTrue(httpClient == null ||
391                         httpClientBuilder == null,
392                         "The httpClient and the httpClientBuilder can't both be configured.");
393 
394         AttributeMap httpClientConfig = getHttpClientConfig(config, deprecatedConfigDoNotUseThis);
395 
396         return Either.fromNullable(httpClient, httpClientBuilder)
397                      .map(e -> e.map(Function.identity(), b -> b.buildWithDefaults(httpClientConfig)))
398                      .orElseGet(() -> defaultHttpClientBuilder.buildWithDefaults(httpClientConfig));
399     }
400 
401     /**
402      * Finalize which async HTTP client will be used for the created client.
403      */
resolveAsyncHttpClient(LazyValueSource config, SdkClientConfiguration deprecatedConfigDoNotUseThis)404     private SdkAsyncHttpClient resolveAsyncHttpClient(LazyValueSource config,
405                                                       SdkClientConfiguration deprecatedConfigDoNotUseThis) {
406         Validate.isTrue(config.get(CONFIGURED_ASYNC_HTTP_CLIENT) == null ||
407                         config.get(CONFIGURED_ASYNC_HTTP_CLIENT_BUILDER) == null,
408                         "The asyncHttpClient and the asyncHttpClientBuilder can't both be configured.");
409 
410         AttributeMap httpClientConfig = getHttpClientConfig(config, deprecatedConfigDoNotUseThis);
411 
412         return Either.fromNullable(config.get(CONFIGURED_ASYNC_HTTP_CLIENT), config.get(CONFIGURED_ASYNC_HTTP_CLIENT_BUILDER))
413                      .map(e -> e.map(Function.identity(), b -> b.buildWithDefaults(httpClientConfig)))
414                      .orElseGet(() -> defaultAsyncHttpClientBuilder.buildWithDefaults(httpClientConfig));
415     }
416 
getHttpClientConfig(LazyValueSource config, SdkClientConfiguration deprecatedConfigDoNotUseThis)417     private AttributeMap getHttpClientConfig(LazyValueSource config, SdkClientConfiguration deprecatedConfigDoNotUseThis) {
418         AttributeMap httpClientConfig = config.get(HTTP_CLIENT_CONFIG);
419         if (httpClientConfig == null) {
420             // We must be using an old client, use the deprecated way of loading HTTP_CLIENT_CONFIG, instead. This won't take
421             // into account any configuration changes (e.g. defaults mode) from plugins, but this is the best we can do without
422             // breaking protected APIs. TODO: if we ever break protected APIs, remove these "childHttpConfig" hooks.
423             httpClientConfig = childHttpConfig(deprecatedConfigDoNotUseThis);
424         }
425         return httpClientConfig;
426     }
427 
428     /**
429      * @deprecated Configure {@link SdkClientOption#HTTP_CLIENT_CONFIG} from {@link #finalizeChildConfiguration} instead.
430      */
431     @Deprecated
childHttpConfig(SdkClientConfiguration configuration)432     protected AttributeMap childHttpConfig(SdkClientConfiguration configuration) {
433         return childHttpConfig();
434     }
435 
436     /**
437      * @deprecated Configure {@link SdkClientOption#HTTP_CLIENT_CONFIG} from {@link #finalizeChildConfiguration} instead.
438      */
439     @Deprecated
childHttpConfig()440     protected AttributeMap childHttpConfig() {
441         return AttributeMap.empty();
442     }
443 
444     /**
445      * Finalize which async executor service will be used for the created client. The default async executor
446      * service has at least 8 core threads and can scale up to at least 64 threads when needed depending
447      * on the number of processors available.
448      */
resolveAsyncFutureCompletionExecutor(LazyValueSource config)449     private Executor resolveAsyncFutureCompletionExecutor(LazyValueSource config) {
450         int processors = Runtime.getRuntime().availableProcessors();
451         int corePoolSize = Math.max(8, processors);
452         int maxPoolSize = Math.max(64, processors * 2);
453         ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
454                                                              10, TimeUnit.SECONDS,
455                                                              new LinkedBlockingQueue<>(1_000),
456                                                              new ThreadFactoryBuilder()
457                                                                  .threadNamePrefix("sdk-async-response").build());
458         // Allow idle core threads to time out
459         executor.allowCoreThreadTimeOut(true);
460         return executor;
461     }
462 
463     /**
464      * Finalize the internal SDK scheduled executor service that is used for scheduling tasks such as async retry attempts and
465      * timeout task.
466      */
resolveScheduledExecutorService(LazyValueSource c)467     private ScheduledExecutorService resolveScheduledExecutorService(LazyValueSource c) {
468         ScheduledExecutorService executor = c.get(CONFIGURED_SCHEDULED_EXECUTOR_SERVICE);
469         if (executor != null) {
470             return executor;
471         }
472 
473         return Executors.newScheduledThreadPool(5, new ThreadFactoryBuilder().threadNamePrefix("sdk-ScheduledExecutor").build());
474     }
475 
476     /**
477      * Finalize which execution interceptors will be used for the created client.
478      */
resolveExecutionInterceptors(SdkClientConfiguration config)479     private List<ExecutionInterceptor> resolveExecutionInterceptors(SdkClientConfiguration config) {
480         List<ExecutionInterceptor> globalInterceptors = new ArrayList<>();
481         globalInterceptors.addAll(sdkInterceptors());
482         globalInterceptors.addAll(new ClasspathInterceptorChainFactory().getGlobalInterceptors());
483         return mergeLists(globalInterceptors, config.option(EXECUTION_INTERCEPTORS));
484     }
485 
486 
487     /**
488      * The set of interceptors that should be included with all services.
489      */
sdkInterceptors()490     private List<ExecutionInterceptor> sdkInterceptors() {
491         return Collections.unmodifiableList(Arrays.asList(
492             new HttpChecksumValidationInterceptor()
493         ));
494     }
495 
496     @Override
endpointOverride(URI endpointOverride)497     public final B endpointOverride(URI endpointOverride) {
498         if (endpointOverride == null) {
499             clientConfiguration.option(SdkClientOption.ENDPOINT, null);
500             clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN, false);
501         } else {
502             Validate.paramNotNull(endpointOverride.getScheme(), "The URI scheme of endpointOverride");
503             clientConfiguration.option(SdkClientOption.ENDPOINT, endpointOverride);
504             clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN, true);
505         }
506         return thisBuilder();
507     }
508 
setEndpointOverride(URI endpointOverride)509     public final void setEndpointOverride(URI endpointOverride) {
510         endpointOverride(endpointOverride);
511     }
512 
asyncConfiguration(ClientAsyncConfiguration asyncConfiguration)513     public final B asyncConfiguration(ClientAsyncConfiguration asyncConfiguration) {
514         clientConfiguration.option(FUTURE_COMPLETION_EXECUTOR, asyncConfiguration.advancedOption(FUTURE_COMPLETION_EXECUTOR));
515         return thisBuilder();
516     }
517 
setAsyncConfiguration(ClientAsyncConfiguration asyncConfiguration)518     public final void setAsyncConfiguration(ClientAsyncConfiguration asyncConfiguration) {
519         asyncConfiguration(asyncConfiguration);
520     }
521 
522     @Override
overrideConfiguration(ClientOverrideConfiguration overrideConfig)523     public final B overrideConfiguration(ClientOverrideConfiguration overrideConfig) {
524         this.overrideConfig = overrideConfig;
525         return thisBuilder();
526     }
527 
setOverrideConfiguration(ClientOverrideConfiguration overrideConfiguration)528     public final void setOverrideConfiguration(ClientOverrideConfiguration overrideConfiguration) {
529         overrideConfiguration(overrideConfiguration);
530     }
531 
532     @Override
overrideConfiguration()533     public final ClientOverrideConfiguration overrideConfiguration() {
534         if (overrideConfig == null) {
535             return ClientOverrideConfiguration.builder().build();
536         }
537         return overrideConfig;
538     }
539 
httpClient(SdkHttpClient httpClient)540     public final B httpClient(SdkHttpClient httpClient) {
541         if (httpClient != null) {
542             httpClient = new NonManagedSdkHttpClient(httpClient);
543         }
544         clientConfiguration.option(CONFIGURED_SYNC_HTTP_CLIENT, httpClient);
545         return thisBuilder();
546     }
547 
httpClientBuilder(SdkHttpClient.Builder httpClientBuilder)548     public final B httpClientBuilder(SdkHttpClient.Builder httpClientBuilder) {
549         clientConfiguration.option(CONFIGURED_SYNC_HTTP_CLIENT_BUILDER, httpClientBuilder);
550         return thisBuilder();
551     }
552 
httpClient(SdkAsyncHttpClient httpClient)553     public final B httpClient(SdkAsyncHttpClient httpClient) {
554         if (httpClient != null) {
555             httpClient = new NonManagedSdkAsyncHttpClient(httpClient);
556         }
557         clientConfiguration.option(CONFIGURED_ASYNC_HTTP_CLIENT, httpClient);
558         return thisBuilder();
559     }
560 
httpClientBuilder(SdkAsyncHttpClient.Builder httpClientBuilder)561     public final B httpClientBuilder(SdkAsyncHttpClient.Builder httpClientBuilder) {
562         clientConfiguration.option(CONFIGURED_ASYNC_HTTP_CLIENT_BUILDER, httpClientBuilder);
563         return thisBuilder();
564     }
565 
metricPublishers(List<MetricPublisher> metricPublishers)566     public final B metricPublishers(List<MetricPublisher> metricPublishers) {
567         clientConfiguration.option(METRIC_PUBLISHERS, metricPublishers);
568         return thisBuilder();
569     }
570 
571     @Override
addPlugin(SdkPlugin plugin)572     public final B addPlugin(SdkPlugin plugin) {
573         plugins.add(paramNotNull(plugin, "plugin"));
574         return thisBuilder();
575     }
576 
577     @Override
plugins()578     public final List<SdkPlugin> plugins() {
579         return Collections.unmodifiableList(plugins);
580     }
581 
582     /**
583      * Return "this" for method chaining.
584      */
585     @SuppressWarnings("unchecked")
thisBuilder()586     protected B thisBuilder() {
587         return (B) this;
588     }
589 
590     /**
591      * Wrapper around {@link SdkHttpClient} to prevent it from being closed. Used when the customer provides
592      * an already built client in which case they are responsible for the lifecycle of it.
593      */
594     @SdkTestInternalApi
595     public static final class NonManagedSdkHttpClient implements SdkHttpClient {
596 
597         private final SdkHttpClient delegate;
598 
NonManagedSdkHttpClient(SdkHttpClient delegate)599         private NonManagedSdkHttpClient(SdkHttpClient delegate) {
600             this.delegate = paramNotNull(delegate, "SdkHttpClient");
601         }
602 
603         @Override
prepareRequest(HttpExecuteRequest request)604         public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
605             return delegate.prepareRequest(request);
606         }
607 
608         @Override
close()609         public void close() {
610             // Do nothing, this client is managed by the customer.
611         }
612 
613         @Override
clientName()614         public String clientName() {
615             return delegate.clientName();
616         }
617     }
618 
619     /**
620      * Wrapper around {@link SdkAsyncHttpClient} to prevent it from being closed. Used when the customer provides
621      * an already built client in which case they are responsible for the lifecycle of it.
622      */
623     @SdkTestInternalApi
624     public static final class NonManagedSdkAsyncHttpClient implements SdkAsyncHttpClient {
625 
626         private final SdkAsyncHttpClient delegate;
627 
NonManagedSdkAsyncHttpClient(SdkAsyncHttpClient delegate)628         NonManagedSdkAsyncHttpClient(SdkAsyncHttpClient delegate) {
629             this.delegate = paramNotNull(delegate, "SdkAsyncHttpClient");
630         }
631 
632         @Override
execute(AsyncExecuteRequest request)633         public CompletableFuture<Void> execute(AsyncExecuteRequest request) {
634             return delegate.execute(request);
635         }
636 
637         @Override
clientName()638         public String clientName() {
639             return delegate.clientName();
640         }
641 
642         @Override
close()643         public void close() {
644             // Do nothing, this client is managed by the customer.
645         }
646     }
647 }
648