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