• 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.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