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.services.s3; 17 18 19 import java.net.MalformedURLException; 20 import java.net.URI; 21 import java.net.URL; 22 import java.util.ArrayList; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Optional; 27 import java.util.function.Consumer; 28 import java.util.function.Supplier; 29 import java.util.regex.Matcher; 30 import java.util.regex.Pattern; 31 import software.amazon.awssdk.annotations.Immutable; 32 import software.amazon.awssdk.annotations.SdkInternalApi; 33 import software.amazon.awssdk.annotations.SdkPublicApi; 34 import software.amazon.awssdk.awscore.AwsExecutionAttribute; 35 import software.amazon.awssdk.awscore.client.config.AwsClientOption; 36 import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; 37 import software.amazon.awssdk.awscore.endpoint.DefaultServiceEndpointBuilder; 38 import software.amazon.awssdk.awscore.endpoint.DualstackEnabledProvider; 39 import software.amazon.awssdk.awscore.endpoint.FipsEnabledProvider; 40 import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeConfiguration; 41 import software.amazon.awssdk.core.ClientType; 42 import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; 43 import software.amazon.awssdk.core.client.config.SdkClientConfiguration; 44 import software.amazon.awssdk.core.client.config.SdkClientOption; 45 import software.amazon.awssdk.core.exception.SdkException; 46 import software.amazon.awssdk.core.interceptor.ExecutionAttributes; 47 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; 48 import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain; 49 import software.amazon.awssdk.core.interceptor.InterceptorContext; 50 import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; 51 import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; 52 import software.amazon.awssdk.http.SdkHttpFullRequest; 53 import software.amazon.awssdk.http.SdkHttpMethod; 54 import software.amazon.awssdk.http.SdkHttpRequest; 55 import software.amazon.awssdk.profiles.ProfileFile; 56 import software.amazon.awssdk.profiles.ProfileFileSupplier; 57 import software.amazon.awssdk.protocols.core.OperationInfo; 58 import software.amazon.awssdk.protocols.core.PathMarshaller; 59 import software.amazon.awssdk.protocols.core.ProtocolUtils; 60 import software.amazon.awssdk.regions.Region; 61 import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; 62 import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams; 63 import software.amazon.awssdk.services.s3.endpoints.S3EndpointProvider; 64 import software.amazon.awssdk.services.s3.endpoints.internal.S3RequestSetEndpointInterceptor; 65 import software.amazon.awssdk.services.s3.endpoints.internal.S3ResolveEndpointInterceptor; 66 import software.amazon.awssdk.services.s3.internal.endpoints.UseGlobalEndpointResolver; 67 import software.amazon.awssdk.services.s3.model.GetObjectRequest; 68 import software.amazon.awssdk.services.s3.model.GetUrlRequest; 69 import software.amazon.awssdk.utils.AttributeMap; 70 import software.amazon.awssdk.utils.StringUtils; 71 import software.amazon.awssdk.utils.Validate; 72 import software.amazon.awssdk.utils.http.SdkHttpUtils; 73 74 /** 75 * Utilities for working with Amazon S3 objects. An instance of this class can be created by: 76 * <p> 77 * 1) Directly using the {@link #builder()} method. You have to manually specify the configuration params like region, 78 * s3Configuration on the builder. 79 * 80 * <pre> 81 * S3Utilities utilities = S3Utilities.builder().region(Region.US_WEST_2).build() 82 * GetUrlRequest request = GetUrlRequest.builder().bucket("foo-bucket").key("key-without-spaces").build(); 83 * URL url = utilities.getUrl(request); 84 * </pre> 85 * 86 * <p> 87 * 2) Using the low-level client {@link S3Client#utilities()} method. This is recommended as SDK will use the same 88 * configuration from the {@link S3Client} object to create the {@link S3Utilities} object. 89 * 90 * <pre> 91 * S3Client s3client = S3Client.create(); 92 * S3Utilities utilities = s3client.utilities(); 93 * GetUrlRequest request = GetUrlRequest.builder().bucket("foo-bucket").key("key-without-spaces").build(); 94 * URL url = utilities.getUrl(request); 95 * </pre> 96 * 97 * Note: This class does not make network calls. 98 */ 99 @Immutable 100 @SdkPublicApi 101 public final class S3Utilities { 102 private static final String SERVICE_NAME = "s3"; 103 private static final Pattern ENDPOINT_PATTERN = Pattern.compile("^(.+\\.)?s3[.-]([a-z0-9-]+)\\."); 104 private final Region region; 105 private final URI endpoint; 106 private final S3Configuration s3Configuration; 107 private final Supplier<ProfileFile> profileFile; 108 private final String profileName; 109 private final boolean fipsEnabled; 110 private final ExecutionInterceptorChain interceptorChain; 111 private final UseGlobalEndpointResolver useGlobalEndpointResolver; 112 113 /** 114 * SDK currently validates that region is present while constructing {@link S3Utilities} object. 115 * This can be relaxed in the future when more methods are added that don't use region. 116 */ S3Utilities(Builder builder)117 private S3Utilities(Builder builder) { 118 this.region = Validate.paramNotNull(builder.region, "Region"); 119 this.endpoint = builder.endpoint; 120 this.profileFile = Optional.ofNullable(builder.profileFile) 121 .orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile())); 122 this.profileName = builder.profileName; 123 124 if (builder.s3Configuration == null) { 125 this.s3Configuration = S3Configuration.builder().dualstackEnabled(builder.dualstackEnabled).build(); 126 } else { 127 this.s3Configuration = builder.s3Configuration.toBuilder() 128 .applyMutation(b -> resolveDualstackSetting(b, builder)) 129 .build(); 130 } 131 132 this.fipsEnabled = builder.fipsEnabled != null ? builder.fipsEnabled 133 : FipsEnabledProvider.builder() 134 .profileFile(profileFile) 135 .profileName(profileName) 136 .build() 137 .isFipsEnabled() 138 .orElse(false); 139 140 this.interceptorChain = createEndpointInterceptorChain(); 141 142 this.useGlobalEndpointResolver = createUseGlobalEndpointResolver(); 143 } 144 resolveDualstackSetting(S3Configuration.Builder s3ConfigBuilder, Builder s3UtiltiesBuilder)145 private void resolveDualstackSetting(S3Configuration.Builder s3ConfigBuilder, Builder s3UtiltiesBuilder) { 146 Validate.validState(s3ConfigBuilder.dualstackEnabled() == null || s3UtiltiesBuilder.dualstackEnabled == null, 147 "Only one of S3Configuration.Builder's dualstackEnabled or S3Utilities.Builder's dualstackEnabled " 148 + "should be set."); 149 150 if (s3ConfigBuilder.dualstackEnabled() != null) { 151 return; 152 } 153 154 if (s3UtiltiesBuilder.dualstackEnabled != null) { 155 s3ConfigBuilder.dualstackEnabled(s3UtiltiesBuilder.dualstackEnabled); 156 return; 157 } 158 159 s3ConfigBuilder.dualstackEnabled(DualstackEnabledProvider.builder() 160 .profileFile(profileFile) 161 .profileName(profileName) 162 .build() 163 .isDualstackEnabled() 164 .orElse(false)); 165 } 166 167 /** 168 * Creates a builder for {@link S3Utilities}. 169 */ builder()170 public static Builder builder() { 171 return new Builder(); 172 } 173 174 // Used by low-level client 175 @SdkInternalApi create(SdkClientConfiguration clientConfiguration)176 static S3Utilities create(SdkClientConfiguration clientConfiguration) { 177 S3Utilities.Builder builder = builder() 178 .region(clientConfiguration.option(AwsClientOption.AWS_REGION)) 179 .s3Configuration((S3Configuration) clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION)) 180 .profileFile(clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) 181 .profileName(clientConfiguration.option(SdkClientOption.PROFILE_NAME)); 182 183 if (Boolean.TRUE.equals(clientConfiguration.option(SdkClientOption.ENDPOINT_OVERRIDDEN))) { 184 builder.endpoint(clientConfiguration.option(SdkClientOption.ENDPOINT)); 185 } 186 187 return builder.build(); 188 } 189 190 /** 191 * Returns the URL for an object stored in Amazon S3. 192 * 193 * If the object identified by the given bucket and key has public read permissions, 194 * then this URL can be directly accessed to retrieve the object's data. 195 * 196 * <p> 197 * If same configuration options are set on both #GetUrlRequest and #S3Utilities objects (for example: region), 198 * the configuration set on the #GetUrlRequest takes precedence. 199 * </p> 200 * 201 * <p> 202 * This is a convenience which creates an instance of the {@link GetUrlRequest.Builder} avoiding the need to 203 * create one manually via {@link GetUrlRequest#builder()} 204 * </p> 205 * 206 * @param getUrlRequest A {@link Consumer} that will call methods on {@link GetUrlRequest.Builder} to create a request. 207 * @return A URL for an object stored in Amazon S3. 208 * @throws SdkException Generated Url is malformed 209 */ getUrl(Consumer<GetUrlRequest.Builder> getUrlRequest)210 public URL getUrl(Consumer<GetUrlRequest.Builder> getUrlRequest) { 211 return getUrl(GetUrlRequest.builder().applyMutation(getUrlRequest).build()); 212 } 213 214 /** 215 * Returns the URL for an object stored in Amazon S3. 216 * 217 * If the object identified by the given bucket and key has public read permissions, 218 * then this URL can be directly accessed to retrieve the object's data. 219 * 220 * <p> 221 * If same configuration options are set on both #GetUrlRequest and #S3Utilities objects (for example: region), 222 * the configuration set on the #GetUrlRequest takes precedence. 223 * </p> 224 * 225 * @param getUrlRequest request to construct url 226 * @return A URL for an object stored in Amazon S3. 227 * @throws SdkException Generated Url is malformed 228 */ getUrl(GetUrlRequest getUrlRequest)229 public URL getUrl(GetUrlRequest getUrlRequest) { 230 Region resolvedRegion = resolveRegionForGetUrl(getUrlRequest); 231 URI endpointOverride = getEndpointOverride(getUrlRequest); 232 URI resolvedEndpoint = resolveEndpoint(endpointOverride, resolvedRegion); 233 234 SdkHttpFullRequest marshalledRequest = createMarshalledRequest(getUrlRequest, resolvedEndpoint); 235 236 GetObjectRequest getObjectRequest = GetObjectRequest.builder() 237 .bucket(getUrlRequest.bucket()) 238 .key(getUrlRequest.key()) 239 .versionId(getUrlRequest.versionId()) 240 .build(); 241 242 InterceptorContext interceptorContext = InterceptorContext.builder() 243 .httpRequest(marshalledRequest) 244 .request(getObjectRequest) 245 .build(); 246 247 ExecutionAttributes executionAttributes = createExecutionAttributes(resolvedEndpoint, 248 resolvedRegion, 249 endpointOverride != null); 250 251 SdkHttpRequest modifiedRequest = runInterceptors(interceptorContext, executionAttributes).httpRequest(); 252 try { 253 return modifiedRequest.getUri().toURL(); 254 } catch (MalformedURLException exception) { 255 throw SdkException.create("Generated URI is malformed: " + modifiedRequest.getUri(), 256 exception); 257 } 258 } 259 260 /** 261 * Returns a parsed {@link S3Uri} with which a user can easily retrieve the bucket, key, region, style, and query 262 * parameters of the URI. Only path-style and virtual-hosted-style URI parsing is supported, including CLI-style 263 * URIs, e.g., "s3://bucket/key". AccessPoints and Outposts URI parsing is not supported. If you work with object keys 264 * and/or query parameters with special characters, they must be URL-encoded, e.g., replace " " with "%20". If you work with 265 * virtual-hosted-style URIs with bucket names that contain a dot, i.e., ".", the dot must not be URL-encoded. Encoded 266 * buckets, keys, and query parameters will be returned decoded. 267 * 268 * <p> 269 * For more information on path-style and virtual-hosted-style URIs, see <a href= 270 * "https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html" 271 * >Methods for accessing a bucket</a>. 272 * 273 * @param uri The URI to be parsed 274 * @return Parsed {@link S3Uri} 275 * 276 * <p><b>Example Usage</b> 277 * <p> 278 * {@snippet : 279 * S3Client s3Client = S3Client.create(); 280 * S3Utilities s3Utilities = s3Client.utilities(); 281 * String uriString = "https://myBucket.s3.us-west-1.amazonaws.com/doc.txt?versionId=abc123"; 282 * URI uri = URI.create(uriString); 283 * S3Uri s3Uri = s3Utilities.parseUri(uri); 284 * 285 * String bucket = s3Uri.bucket().orElse(null); // "myBucket" 286 * String key = s3Uri.key().orElse(null); // "doc.txt" 287 * Region region = s3Uri.region().orElse(null); // Region.US_WEST_1 288 * boolean isPathStyle = s3Uri.isPathStyle(); // false 289 * String versionId = s3Uri.firstMatchingRawQueryParameter("versionId").orElse(null); // "abc123" 290 *} 291 */ parseUri(URI uri)292 public S3Uri parseUri(URI uri) { 293 validateUri(uri); 294 295 if ("s3".equalsIgnoreCase(uri.getScheme())) { 296 return parseAwsCliStyleUri(uri); 297 } 298 299 return parseStandardUri(uri); 300 } 301 parseStandardUri(URI uri)302 private S3Uri parseStandardUri(URI uri) { 303 304 if (uri.getHost() == null) { 305 throw new IllegalArgumentException("Invalid S3 URI: no hostname: " + uri); 306 } 307 308 Matcher matcher = ENDPOINT_PATTERN.matcher(uri.getHost()); 309 if (!matcher.find()) { 310 throw new IllegalArgumentException("Invalid S3 URI: hostname does not appear to be a valid S3 endpoint: " + uri); 311 } 312 313 S3Uri.Builder builder = S3Uri.builder().uri(uri); 314 addRegionIfNeeded(builder, matcher.group(2)); 315 addQueryParamsIfNeeded(builder, uri); 316 317 String prefix = matcher.group(1); 318 if (StringUtils.isEmpty(prefix)) { 319 return parsePathStyleUri(builder, uri); 320 } 321 return parseVirtualHostedStyleUri(builder, uri, matcher); 322 } 323 addRegionIfNeeded(S3Uri.Builder builder, String region)324 private S3Uri.Builder addRegionIfNeeded(S3Uri.Builder builder, String region) { 325 if (!"amazonaws".equals(region)) { 326 return builder.region(Region.of(region)); 327 } 328 return builder; 329 } 330 addQueryParamsIfNeeded(S3Uri.Builder builder, URI uri)331 private S3Uri.Builder addQueryParamsIfNeeded(S3Uri.Builder builder, URI uri) { 332 if (uri.getQuery() != null) { 333 return builder.queryParams(SdkHttpUtils.uriParams(uri)); 334 } 335 return builder; 336 } 337 parsePathStyleUri(S3Uri.Builder builder, URI uri)338 private S3Uri parsePathStyleUri(S3Uri.Builder builder, URI uri) { 339 String bucket = null; 340 String key = null; 341 String path = uri.getPath(); 342 343 if (!StringUtils.isEmpty(path) && !"/".equals(path)) { 344 int index = path.indexOf('/', 1); 345 346 if (index == -1) { 347 // No trailing slash, e.g., "https://s3.amazonaws.com/bucket" 348 bucket = path.substring(1); 349 } else { 350 bucket = path.substring(1, index); 351 if (index != path.length() - 1) { 352 key = path.substring(index + 1); 353 } 354 } 355 } 356 return builder.key(key) 357 .bucket(bucket) 358 .isPathStyle(true) 359 .build(); 360 } 361 parseVirtualHostedStyleUri(S3Uri.Builder builder, URI uri, Matcher matcher)362 private S3Uri parseVirtualHostedStyleUri(S3Uri.Builder builder, URI uri, Matcher matcher) { 363 String bucket; 364 String key = null; 365 String path = uri.getPath(); 366 String prefix = matcher.group(1); 367 368 bucket = prefix.substring(0, prefix.length() - 1); 369 if (!StringUtils.isEmpty(path) && !"/".equals(path)) { 370 key = path.substring(1); 371 } 372 373 return builder.key(key) 374 .bucket(bucket) 375 .build(); 376 } 377 parseAwsCliStyleUri(URI uri)378 private S3Uri parseAwsCliStyleUri(URI uri) { 379 String key = null; 380 String bucket = uri.getAuthority(); 381 Region region = null; 382 boolean isPathStyle = false; 383 Map<String, List<String>> queryParams = new HashMap<>(); 384 String path = uri.getPath(); 385 386 if (bucket == null) { 387 throw new IllegalArgumentException("Invalid S3 URI: bucket not included: " + uri); 388 } 389 390 if (path.length() > 1) { 391 key = path.substring(1); 392 } 393 394 return S3Uri.builder() 395 .uri(uri) 396 .bucket(bucket) 397 .key(key) 398 .region(region) 399 .isPathStyle(isPathStyle) 400 .queryParams(queryParams) 401 .build(); 402 } 403 validateUri(URI uri)404 private void validateUri(URI uri) { 405 Validate.paramNotNull(uri, "uri"); 406 407 if (uri.toString().contains(".s3-accesspoint")) { 408 throw new IllegalArgumentException("AccessPoints URI parsing is not supported: " + uri); 409 } 410 411 if (uri.toString().contains(".s3-outposts")) { 412 throw new IllegalArgumentException("Outposts URI parsing is not supported: " + uri); 413 } 414 } 415 resolveRegionForGetUrl(GetUrlRequest getUrlRequest)416 private Region resolveRegionForGetUrl(GetUrlRequest getUrlRequest) { 417 if (getUrlRequest.region() == null && this.region == null) { 418 throw new IllegalArgumentException("Region should be provided either in GetUrlRequest object or S3Utilities object"); 419 } 420 421 return getUrlRequest.region() != null ? getUrlRequest.region() : this.region; 422 } 423 424 /** 425 * If endpoint is not present, construct a default endpoint using the region information. 426 */ resolveEndpoint(URI overrideEndpoint, Region region)427 private URI resolveEndpoint(URI overrideEndpoint, Region region) { 428 return overrideEndpoint != null 429 ? overrideEndpoint 430 : new DefaultServiceEndpointBuilder("s3", "https").withRegion(region) 431 .withProfileFile(profileFile) 432 .withProfileName(profileName) 433 .withDualstackEnabled(s3Configuration.dualstackEnabled()) 434 .withFipsEnabled(fipsEnabled) 435 .getServiceEndpoint(); 436 } 437 getEndpointOverride(GetUrlRequest request)438 private URI getEndpointOverride(GetUrlRequest request) { 439 URI requestOverrideEndpoint = request.endpoint(); 440 return requestOverrideEndpoint != null ? requestOverrideEndpoint : endpoint; 441 } 442 443 /** 444 * Create a {@link SdkHttpFullRequest} object with the bucket and key values marshalled into the path params. 445 */ createMarshalledRequest(GetUrlRequest getUrlRequest, URI endpoint)446 private SdkHttpFullRequest createMarshalledRequest(GetUrlRequest getUrlRequest, URI endpoint) { 447 OperationInfo operationInfo = OperationInfo.builder() 448 .requestUri("/{Key+}") 449 .httpMethod(SdkHttpMethod.HEAD) 450 .build(); 451 452 SdkHttpFullRequest.Builder builder = ProtocolUtils.createSdkHttpRequest(operationInfo, endpoint); 453 454 // encode bucket 455 builder.encodedPath(PathMarshaller.NON_GREEDY.marshall(builder.encodedPath(), 456 "Bucket", 457 getUrlRequest.bucket())); 458 459 // encode key 460 builder.encodedPath(PathMarshaller.GREEDY.marshall(builder.encodedPath(), "Key", getUrlRequest.key())); 461 462 if (getUrlRequest.versionId() != null) { 463 builder.appendRawQueryParameter("versionId", getUrlRequest.versionId()); 464 } 465 466 return builder.build(); 467 } 468 469 /** 470 * Create the execution attributes to provide to the endpoint interceptors. 471 * @return 472 */ createExecutionAttributes(URI clientEndpoint, Region region, boolean isEndpointOverridden)473 private ExecutionAttributes createExecutionAttributes(URI clientEndpoint, Region region, boolean isEndpointOverridden) { 474 ExecutionAttributes executionAttributes = new ExecutionAttributes() 475 .putAttribute(AwsExecutionAttribute.AWS_REGION, region) 476 .putAttribute(SdkExecutionAttribute.CLIENT_TYPE, ClientType.SYNC) 477 .putAttribute(SdkExecutionAttribute.SERVICE_NAME, SERVICE_NAME) 478 .putAttribute(SdkExecutionAttribute.OPERATION_NAME, "GetObject") 479 .putAttribute(SdkExecutionAttribute.SERVICE_CONFIG, s3Configuration) 480 .putAttribute(AwsExecutionAttribute.FIPS_ENDPOINT_ENABLED, fipsEnabled) 481 .putAttribute(AwsExecutionAttribute.DUALSTACK_ENDPOINT_ENABLED, s3Configuration.dualstackEnabled()) 482 .putAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER, S3EndpointProvider.defaultProvider()) 483 .putAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS, createClientContextParams()) 484 .putAttribute(SdkExecutionAttribute.CLIENT_ENDPOINT, clientEndpoint) 485 .putAttribute(AwsExecutionAttribute.USE_GLOBAL_ENDPOINT, useGlobalEndpointResolver.resolve(region)); 486 487 if (isEndpointOverridden) { 488 executionAttributes.putAttribute(SdkExecutionAttribute.ENDPOINT_OVERRIDDEN, true); 489 } 490 491 return executionAttributes; 492 } 493 createClientContextParams()494 private AttributeMap createClientContextParams() { 495 AttributeMap.Builder params = AttributeMap.builder(); 496 497 params.put(S3ClientContextParams.USE_ARN_REGION, s3Configuration.useArnRegionEnabled()); 498 params.put(S3ClientContextParams.DISABLE_MULTI_REGION_ACCESS_POINTS, 499 !s3Configuration.multiRegionEnabled()); 500 params.put(S3ClientContextParams.FORCE_PATH_STYLE, s3Configuration.pathStyleAccessEnabled()); 501 params.put(S3ClientContextParams.ACCELERATE, s3Configuration.accelerateModeEnabled()); 502 return params.build(); 503 } 504 runInterceptors(InterceptorContext context, ExecutionAttributes executionAttributes)505 private InterceptorContext runInterceptors(InterceptorContext context, ExecutionAttributes executionAttributes) { 506 context = interceptorChain.modifyRequest(context, executionAttributes); 507 return interceptorChain.modifyHttpRequestAndHttpContent(context, executionAttributes); 508 } 509 createEndpointInterceptorChain()510 private ExecutionInterceptorChain createEndpointInterceptorChain() { 511 List<ExecutionInterceptor> interceptors = new ArrayList<>(); 512 interceptors.add(new S3ResolveEndpointInterceptor()); 513 interceptors.add(new S3RequestSetEndpointInterceptor()); 514 return new ExecutionInterceptorChain(interceptors); 515 } 516 createUseGlobalEndpointResolver()517 private UseGlobalEndpointResolver createUseGlobalEndpointResolver() { 518 String standardOption = 519 DefaultsModeConfiguration.defaultConfig(DefaultsMode.LEGACY) 520 .get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT); 521 522 SdkClientConfiguration config = 523 SdkClientConfiguration.builder() 524 .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, standardOption) 525 .option(SdkClientOption.PROFILE_FILE_SUPPLIER, profileFile) 526 .option(SdkClientOption.PROFILE_NAME, profileName) 527 .build(); 528 529 return new UseGlobalEndpointResolver(config); 530 } 531 532 /** 533 * Builder class to construct {@link S3Utilities} object 534 */ 535 public static final class Builder { 536 private Region region; 537 private URI endpoint; 538 539 private S3Configuration s3Configuration; 540 private Supplier<ProfileFile> profileFile; 541 private String profileName; 542 private Boolean dualstackEnabled; 543 private Boolean fipsEnabled; 544 Builder()545 private Builder() { 546 } 547 548 /** 549 * The default region to use when working with the methods in {@link S3Utilities} class. 550 * 551 * There can be methods in {@link S3Utilities} that don't need the region info. 552 * In that case, this option will be ignored when using those methods. 553 * 554 * @return This object for method chaining 555 */ region(Region region)556 public Builder region(Region region) { 557 this.region = region; 558 return this; 559 } 560 561 /** 562 * The default endpoint to use when working with the methods in {@link S3Utilities} class. 563 * 564 * There can be methods in {@link S3Utilities} that don't need the endpoint info. 565 * In that case, this option will be ignored when using those methods. 566 * 567 * @return This object for method chaining 568 */ endpoint(URI endpoint)569 public Builder endpoint(URI endpoint) { 570 this.endpoint = endpoint; 571 return this; 572 } 573 574 /** 575 * Configure whether the SDK should use the AWS dualstack endpoint. 576 * 577 * <p>If this is not specified, the SDK will attempt to determine whether the dualstack endpoint should be used 578 * automatically using the following logic: 579 * <ol> 580 * <li>Check the 'aws.useDualstackEndpoint' system property for 'true' or 'false'.</li> 581 * <li>Check the 'AWS_USE_DUALSTACK_ENDPOINT' environment variable for 'true' or 'false'.</li> 582 * <li>Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_dualstack_endpoint' 583 * property set to 'true' or 'false'.</li> 584 * </ol> 585 * 586 * <p>If the setting is not found in any of the locations above, 'false' will be used. 587 */ dualstackEnabled(Boolean dualstackEnabled)588 public Builder dualstackEnabled(Boolean dualstackEnabled) { 589 this.dualstackEnabled = dualstackEnabled; 590 return this; 591 } 592 593 /** 594 * Configure whether the SDK should use the AWS fips endpoint. 595 * 596 * <p>If this is not specified, the SDK will attempt to determine whether the fips endpoint should be used 597 * automatically using the following logic: 598 * <ol> 599 * <li>Check the 'aws.useFipsEndpoint' system property for 'true' or 'false'.</li> 600 * <li>Check the 'AWS_USE_FIPS_ENDPOINT' environment variable for 'true' or 'false'.</li> 601 * <li>Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_fips_endpoint' 602 * property set to 'true' or 'false'.</li> 603 * </ol> 604 * 605 * <p>If the setting is not found in any of the locations above, 'false' will be used. 606 */ fipsEnabled(Boolean fipsEnabled)607 public Builder fipsEnabled(Boolean fipsEnabled) { 608 this.fipsEnabled = fipsEnabled; 609 return this; 610 } 611 612 /** 613 * Sets the S3 configuration to enable options like path style access, dual stack, accelerate mode etc. 614 * 615 * There can be methods in {@link S3Utilities} that don't need the region info. 616 * In that case, this option will be ignored when using those methods. 617 * 618 * @return This object for method chaining 619 */ s3Configuration(S3Configuration s3Configuration)620 public Builder s3Configuration(S3Configuration s3Configuration) { 621 this.s3Configuration = s3Configuration; 622 return this; 623 } 624 625 /** 626 * The profile file from the {@link ClientOverrideConfiguration#defaultProfileFile()}. This is private and only used 627 * when the utilities is created via {@link S3Client#utilities()}. This is not currently public because it may be less 628 * confusing to support the full {@link ClientOverrideConfiguration} object in the future. 629 */ profileFile(Supplier<ProfileFile> profileFileSupplier)630 private Builder profileFile(Supplier<ProfileFile> profileFileSupplier) { 631 this.profileFile = profileFileSupplier; 632 return this; 633 } 634 635 /** 636 * The profile name from the {@link ClientOverrideConfiguration#defaultProfileFile()}. This is private and only used 637 * when the utilities is created via {@link S3Client#utilities()}. This is not currently public because it may be less 638 * confusing to support the full {@link ClientOverrideConfiguration} object in the future. 639 */ profileName(String profileName)640 private Builder profileName(String profileName) { 641 this.profileName = profileName; 642 return this; 643 } 644 645 /** 646 * Construct a {@link S3Utilities} object. 647 */ build()648 public S3Utilities build() { 649 return new S3Utilities(this); 650 } 651 } 652 } 653