Skip to content

Commit 2d5b039

Browse files
Modify Operation Name for Presigned URL, add Null Check for ResolvedEndpoint, enhance AsyncPresignedUrlManager API (#6275)
* modify operation name, null check for resolvedendpoint, add getObject APIs * added null check for resolvedendpointHeader, doc updates, test additions * fixed checkstyle issue
1 parent 76ac03e commit 2d5b039

File tree

9 files changed

+608
-42
lines changed

9 files changed

+608
-42
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ private MethodSpec modifyHttpRequestMethod() {
279279

280280
b.addStatement("$T resolvedEndpoint = executionAttributes.getAttribute($T.RESOLVED_ENDPOINT)",
281281
Endpoint.class, SdkInternalExecutionAttribute.class);
282-
b.beginControlFlow("if (resolvedEndpoint.headers().isEmpty())");
282+
b.beginControlFlow("if (resolvedEndpoint == null || $T.isNullOrEmpty(resolvedEndpoint.headers()))",
283+
CollectionUtils.class);
283284
b.addStatement("return context.httpRequest()");
284285
b.endControlFlow();
285286

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
109109
@Override
110110
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
111111
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
112-
if (resolvedEndpoint.headers().isEmpty()) {
112+
if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
113113
return context.httpRequest();
114114
}
115115
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
101101
@Override
102102
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
103103
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
104-
if (resolvedEndpoint.headers().isEmpty()) {
104+
if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
105105
return context.httpRequest();
106106
}
107107
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
9090
@Override
9191
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
9292
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
93-
if (resolvedEndpoint.headers().isEmpty()) {
93+
if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
9494
return context.httpRequest();
9595
}
9696
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
9292
@Override
9393
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
9494
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
95-
if (resolvedEndpoint.headers().isEmpty()) {
95+
if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
9696
return context.httpRequest();
9797
}
9898
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public <ReturnT> CompletableFuture<ReturnT> getObject(
9898
try {
9999
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "S3");
100100
//TODO: Discuss if we need to change OPERATION_NAME as part of Surface API Review
101-
apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetObject");
101+
apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PresignedUrlGetObject");
102102

103103
Pair<AsyncResponseTransformer<GetObjectResponse, ReturnT>, CompletableFuture<Void>> pair =
104104
AsyncResponseTransformerUtils.wrapWithEndOfStreamFuture(asyncResponseTransformer);
@@ -113,7 +113,7 @@ public <ReturnT> CompletableFuture<ReturnT> getObject(
113113

114114
CompletableFuture<ReturnT> executeFuture = clientHandler.execute(
115115
new ClientExecutionParams<PresignedUrlGetObjectRequestWrapper, GetObjectResponse>()
116-
.withOperationName("GetObject")
116+
.withOperationName("PresignedUrlGetObject")
117117
.withProtocolMetadata(protocolMetadata)
118118
.withResponseHandler(responseHandler)
119119
.withErrorResponseHandler(errorResponseHandler)

services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515

1616
package software.amazon.awssdk.services.s3.presignedurl;
1717

18+
import java.nio.file.Path;
1819
import java.util.concurrent.CompletableFuture;
20+
import java.util.function.Consumer;
1921
import software.amazon.awssdk.annotations.SdkPublicApi;
2022
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
2123
import software.amazon.awssdk.core.exception.SdkClientException;
2224
import software.amazon.awssdk.services.s3.S3AsyncClient;
2325
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
24-
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
25-
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
2626
import software.amazon.awssdk.services.s3.model.S3Exception;
2727
import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
2828

@@ -36,46 +36,89 @@ public interface AsyncPresignedUrlManager {
3636
* <p>
3737
* Downloads an S3 object asynchronously using a presigned URL.
3838
* </p>
39+
*
3940
* <p>
40-
* This operation uses a presigned URL that contains all necessary authentication information, eliminating the
41-
* need for AWS credentials at request time. The presigned URL must be valid and not expired.
41+
* This operation uses a presigned URL to download an object from Amazon S3. The presigned URL must be valid and not expired.
4242
* </p>
43-
* <dl>
44-
* <dt>Range Requests</dt>
45-
* <dd>
43+
*
4644
* <p>
47-
* Supports partial object downloads using HTTP Range headers. Specify the range parameter
48-
* in the request to download only a portion of the object (e.g., "bytes=0-1023").
45+
* To download a specific byte range of the object, use the range parameter in the request.
4946
* </p>
50-
* </dd>
51-
* </dl>
5247
*
53-
* @param request The presigned URL request containing the URL and optional range parameters
54-
* @param responseTransformer Transforms the response to the desired return type. See
55-
* {@link software.amazon.awssdk.core.async.AsyncResponseTransformer} for pre-built
56-
* implementations like downloading to a file or converting to bytes.
48+
* @param request The presigned URL request containing the URL and optional parameters
49+
* @param responseTransformer Transforms the response to the desired return type
5750
* @param <ReturnT> The type of the transformed response
58-
* @return A {@link CompletableFuture} containing the transformed result of the AsyncResponseTransformer
59-
* @throws software.amazon.awssdk.services.s3.model.NoSuchKeyException The specified object does not exist
60-
* @throws software.amazon.awssdk.services.s3.model.InvalidObjectStateException Object is archived and must be restored before
61-
* retrieval
62-
* @throws software.amazon.awssdk.core.exception.SdkClientException If any client side error occurs such as
63-
* network failures or invalid presigned URL
64-
* @throws S3Exception Base class for all S3 service exceptions.
65-
* Unknown exceptions will be thrown as an
66-
* instance of this type.
51+
* @return A {@link CompletableFuture} containing the transformed result
52+
* @throws SdkClientException If any client side error occurs
53+
* @throws S3Exception Base class for all S3 service exceptions
6754
*/
6855
default <ReturnT> CompletableFuture<ReturnT> getObject(PresignedUrlGetObjectRequest request,
69-
AsyncResponseTransformer<GetObjectResponse,
70-
ReturnT> responseTransformer) throws NoSuchKeyException,
71-
InvalidObjectStateException,
72-
SdkClientException,
73-
S3Exception {
56+
AsyncResponseTransformer<GetObjectResponse,
57+
ReturnT> responseTransformer) {
7458
throw new UnsupportedOperationException();
7559
}
7660

77-
// TODO: Add convenience methods :
78-
// - getObject(Consumer<Builder>, AsyncResponseTransformer) - consumer-based request building
79-
// - getObject(PresignedUrlGetObjectRequest, Path) - direct file download
80-
// - getObject(Consumer<Builder>, Path) - consumer + file download
81-
}
61+
/**
62+
* <p>
63+
* Downloads an S3 object asynchronously using a presigned URL with a consumer-based request builder.
64+
* </p>
65+
*
66+
* <p>
67+
* This is a convenience method that creates a {@link PresignedUrlGetObjectRequest} using the provided consumer.
68+
* </p>
69+
*
70+
* @param requestConsumer Consumer that will configure a {@link PresignedUrlGetObjectRequest.Builder}
71+
* @param responseTransformer Transforms the response to the desired return type
72+
* @param <ReturnT> The type of the transformed response
73+
* @return A {@link CompletableFuture} containing the transformed result
74+
* @throws SdkClientException If any client side error occurs
75+
* @throws S3Exception Base class for all S3 service exceptions
76+
*/
77+
default <ReturnT> CompletableFuture<ReturnT> getObject(Consumer<PresignedUrlGetObjectRequest.Builder> requestConsumer,
78+
AsyncResponseTransformer<GetObjectResponse,
79+
ReturnT> responseTransformer) {
80+
return getObject(PresignedUrlGetObjectRequest.builder().applyMutation(requestConsumer).build(), responseTransformer);
81+
}
82+
83+
/**
84+
* <p>
85+
* Downloads an S3 object asynchronously using a presigned URL and saves it to the specified file path.
86+
* </p>
87+
*
88+
* <p>
89+
* This is a convenience method that uses {@link AsyncResponseTransformer#toFile(Path)} to save the object
90+
* directly to a file.
91+
* </p>
92+
*
93+
* @param request The presigned URL request containing the URL and optional parameters
94+
* @param destinationPath The path where the downloaded object will be saved
95+
* @return A {@link CompletableFuture} containing the {@link GetObjectResponse}
96+
* @throws SdkClientException If any client side error occurs
97+
* @throws S3Exception Base class for all S3 service exceptions
98+
*/
99+
default CompletableFuture<GetObjectResponse> getObject(PresignedUrlGetObjectRequest request,
100+
Path destinationPath) {
101+
return getObject(request, AsyncResponseTransformer.toFile(destinationPath));
102+
}
103+
104+
/**
105+
* <p>
106+
* Downloads an S3 object asynchronously using a presigned URL with a consumer-based request builder
107+
* and saves it to the specified file path.
108+
* </p>
109+
*
110+
* <p>
111+
* This is a convenience method that combines consumer-based request building with file-based response handling.
112+
* </p>
113+
*
114+
* @param requestConsumer Consumer that will configure a {@link PresignedUrlGetObjectRequest.Builder}
115+
* @param destinationPath The path where the downloaded object will be saved
116+
* @return A {@link CompletableFuture} containing the {@link GetObjectResponse}
117+
* @throws SdkClientException If any client side error occurs
118+
* @throws S3Exception Base class for all S3 service exceptions
119+
*/
120+
default CompletableFuture<GetObjectResponse> getObject(Consumer<PresignedUrlGetObjectRequest.Builder> requestConsumer,
121+
Path destinationPath) {
122+
return getObject(requestConsumer, AsyncResponseTransformer.toFile(destinationPath));
123+
}
124+
}

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ void given_AsyncPresignedUrlManager_when_ExecutingDifferentScenarios_then_Should
256256
verify(mockPublisher).publish(metricsCaptor.capture());
257257
MetricCollection capturedMetrics = metricsCaptor.getValue();
258258
assertThat(capturedMetrics.metricValues(CoreMetric.SERVICE_ID)).contains("S3");
259-
assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("GetObject");
259+
assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("PresignedUrlGetObject");
260260
break;
261261
}
262262
}

0 commit comments

Comments
 (0)