Skip to content

Commit 588bca7

Browse files
committed
Translate S3Exception for headObject and headBucket
Add modifyException interceptor
1 parent 59a625c commit 588bca7

File tree

16 files changed

+499
-46
lines changed

16 files changed

+499
-46
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "Amazon S3",
3+
"type": "bugfix",
4+
"description": "Update S3 headObject/headBucket operations to throw NoSuchKey/NoSuchException when S3 is returning 404. See [#123](https://github.com/aws/aws-sdk-java-v2/issues/123), [#544](https://github.com/aws/aws-sdk-java-v2/issues/544)"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"type": "feature",
4+
"description": "Add `modifyException` API to `ExecutionInterceptor`."
5+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/exception/AwsErrorDetails.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import software.amazon.awssdk.annotations.SdkPublicApi;
2020
import software.amazon.awssdk.core.SdkBytes;
2121
import software.amazon.awssdk.http.SdkHttpResponse;
22+
import software.amazon.awssdk.utils.ToString;
2223

2324
@SdkPublicApi
2425
public class AwsErrorDetails implements Serializable {
@@ -101,6 +102,15 @@ public static Class<? extends Builder> serializableBuilderClass() {
101102
return BuilderImpl.class;
102103
}
103104

105+
@Override
106+
public String toString() {
107+
return ToString.builder("AwsErrorDetails")
108+
.add("errorMessage", errorMessage)
109+
.add("errorCode", errorCode)
110+
.add("serviceName", serviceName)
111+
.build();
112+
}
113+
104114
public interface Builder {
105115
/**
106116
* Specifies the error message returned by the service.

core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,23 @@ default void afterExecution(Context.AfterExecution context, ExecutionAttributes
330330

331331
}
332332

333+
/**
334+
* Modify the exception before it is thrown.
335+
*
336+
* <p>This will only be invoked if the entire execution fails. If a retriable error happens (according to the
337+
* {@link RetryPolicy}) and a subsequent retry succeeds, this method will not be invoked.
338+
*
339+
* @param context The context associated with the execution that failed. An SDK request will always be available, but
340+
* depending on the time at which the failure happened, the HTTP request, HTTP response and SDK response may
341+
* not be available. This also includes the exception that triggered the failure.
342+
* @param executionAttributes A mutable set of attributes scoped to one specific request/response cycle that can be used to
343+
* give data to future lifecycle methods.
344+
* @return the modified Exception
345+
*/
346+
default Throwable modifyException(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
347+
return context.exception();
348+
}
349+
333350
/**
334351
* Invoked when any error happens during an execution that prevents the request from succeeding. This could be due to an
335352
* error returned by a service call, a request timeout or even another interceptor raising an exception. The provided

core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptorChain.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import software.amazon.awssdk.core.SdkRequest;
2525
import software.amazon.awssdk.core.SdkResponse;
2626
import software.amazon.awssdk.core.async.AsyncRequestBody;
27+
import software.amazon.awssdk.core.internal.interceptor.DefaultFailedExecutionContext;
2728
import software.amazon.awssdk.core.sync.RequestBody;
2829
import software.amazon.awssdk.http.SdkHttpFullRequest;
2930
import software.amazon.awssdk.http.SdkHttpRequest;
@@ -167,6 +168,18 @@ public void afterExecution(Context.AfterExecution context, ExecutionAttributes e
167168
reverseForEach(i -> i.afterExecution(context, executionAttributes));
168169
}
169170

171+
public DefaultFailedExecutionContext modifyException(DefaultFailedExecutionContext context,
172+
ExecutionAttributes executionAttributes) {
173+
DefaultFailedExecutionContext result = context;
174+
for (int i = interceptors.size() - 1; i >= 0; i--) {
175+
Throwable interceptorResult = interceptors.get(i).modifyException(result, executionAttributes);
176+
validateInterceptorResult(result.exception(), interceptorResult, interceptors.get(i), "modifyException");
177+
result = result.copy(b -> b.exception(interceptorResult));
178+
}
179+
180+
return result;
181+
}
182+
170183
public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
171184
interceptors.forEach(i -> i.onExecutionFailure(context, executionAttributes));
172185
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncExecutionFailureExceptionReportingStage.java

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,20 @@
1515

1616
package software.amazon.awssdk.core.internal.http.pipeline.stages;
1717

18+
import static software.amazon.awssdk.core.internal.http.pipeline.stages.utils.ExceptionReportingUtils.reportFailureToInterceptors;
19+
1820
import java.util.concurrent.CompletableFuture;
1921
import java.util.concurrent.CompletionException;
20-
2122
import software.amazon.awssdk.annotations.SdkInternalApi;
22-
import software.amazon.awssdk.core.interceptor.Context;
2323
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
2424
import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline;
25-
import software.amazon.awssdk.core.internal.interceptor.DefaultFailedExecutionContext;
2625
import software.amazon.awssdk.core.internal.util.ThrowableUtils;
2726
import software.amazon.awssdk.http.SdkHttpFullRequest;
2827
import software.amazon.awssdk.utils.CompletableFutureUtils;
29-
import software.amazon.awssdk.utils.Logger;
3028

3129
@SdkInternalApi
32-
public class AsyncExecutionFailureExceptionReportingStage<OutputT>
30+
public final class AsyncExecutionFailureExceptionReportingStage<OutputT>
3331
implements RequestPipeline<SdkHttpFullRequest, CompletableFuture<OutputT>> {
34-
private static final Logger log = Logger.loggerFor(AsyncExecutionFailureExceptionReportingStage.class);
3532

3633
private final RequestPipeline<SdkHttpFullRequest, CompletableFuture<OutputT>> wrapped;
3734

@@ -48,7 +45,7 @@ public CompletableFuture<OutputT> execute(SdkHttpFullRequest input, RequestExecu
4845
if (toReport instanceof CompletionException) {
4946
toReport = toReport.getCause();
5047
}
51-
reportFailureToInterceptors(context, toReport);
48+
toReport = reportFailureToInterceptors(context, toReport);
5249

5350
throw CompletableFutureUtils.errorAsCompletionException(ThrowableUtils.asSdkException(toReport));
5451
} else {
@@ -57,20 +54,5 @@ public CompletableFuture<OutputT> execute(SdkHttpFullRequest input, RequestExecu
5754
});
5855
}
5956

60-
/**
61-
* Report the failure to the execution interceptors. Swallow any exceptions thrown from the interceptor since we don't
62-
* want to replace the execution failure.
63-
*
64-
* @param context The execution context.
65-
* @param failure The execution failure.
66-
*/
67-
private static void reportFailureToInterceptors(RequestExecutionContext context, Throwable failure) {
68-
try {
69-
Context.FailedExecution failedContext =
70-
new DefaultFailedExecutionContext(context.executionContext().interceptorContext(), failure);
71-
context.interceptorChain().onExecutionFailure(failedContext, context.executionAttributes());
72-
} catch (Throwable t) {
73-
log.warn(() -> "Interceptor chain threw an error from onExecutionFailure().", t);
74-
}
75-
}
57+
7658
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ExecutionFailureExceptionReportingStage.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515

1616
package software.amazon.awssdk.core.internal.http.pipeline.stages;
1717

18+
import static software.amazon.awssdk.core.internal.http.pipeline.stages.utils.ExceptionReportingUtils.reportFailureToInterceptors;
19+
import static software.amazon.awssdk.core.internal.util.ThrowableUtils.failure;
20+
1821
import software.amazon.awssdk.annotations.SdkInternalApi;
19-
import software.amazon.awssdk.core.interceptor.Context;
2022
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
2123
import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline;
22-
import software.amazon.awssdk.core.internal.interceptor.DefaultFailedExecutionContext;
2324
import software.amazon.awssdk.http.SdkHttpFullRequest;
2425

2526
@SdkInternalApi
26-
public class ExecutionFailureExceptionReportingStage<OutputT> implements RequestPipeline<SdkHttpFullRequest, OutputT> {
27-
27+
public final class ExecutionFailureExceptionReportingStage<OutputT> implements RequestPipeline<SdkHttpFullRequest, OutputT> {
2828
private final RequestPipeline<SdkHttpFullRequest, OutputT> wrapped;
2929

3030
public ExecutionFailureExceptionReportingStage(RequestPipeline<SdkHttpFullRequest, OutputT> wrapped) {
@@ -36,10 +36,8 @@ public OutputT execute(SdkHttpFullRequest input, RequestExecutionContext context
3636
try {
3737
return wrapped.execute(input, context);
3838
} catch (Exception e) {
39-
Context.FailedExecution failedContext =
40-
new DefaultFailedExecutionContext(context.executionContext().interceptorContext(), e);
41-
context.interceptorChain().onExecutionFailure(failedContext, context.executionAttributes());
42-
throw e;
39+
Throwable throwable = reportFailureToInterceptors(context, e);
40+
throw failure(throwable);
4341
}
4442
}
4543
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2010-2018 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.internal.http.pipeline.stages.utils;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
20+
import software.amazon.awssdk.core.internal.interceptor.DefaultFailedExecutionContext;
21+
import software.amazon.awssdk.utils.Logger;
22+
23+
@SdkInternalApi
24+
public final class ExceptionReportingUtils {
25+
private static final Logger log = Logger.loggerFor(ExceptionReportingUtils.class);
26+
27+
private ExceptionReportingUtils() {
28+
}
29+
30+
/**
31+
* Report the failure to the execution interceptors. Swallow any exceptions thrown from the interceptor since
32+
* we don't want to replace the execution failure.
33+
*
34+
* @param context The execution context.
35+
* @param failure The execution failure.
36+
*/
37+
public static Throwable reportFailureToInterceptors(RequestExecutionContext context, Throwable failure) {
38+
DefaultFailedExecutionContext modifiedContext = runModifyException(context, failure);
39+
40+
try {
41+
context.interceptorChain().onExecutionFailure(modifiedContext, context.executionAttributes());
42+
} catch (Exception exception) {
43+
log.warn(() -> "Interceptor chain threw an error from onExecutionFailure().", exception);
44+
}
45+
46+
return modifiedContext.exception();
47+
}
48+
49+
private static DefaultFailedExecutionContext runModifyException(RequestExecutionContext context, Throwable e) {
50+
DefaultFailedExecutionContext failedContext =
51+
DefaultFailedExecutionContext.builder()
52+
.interceptorContext(context.executionContext().interceptorContext())
53+
.exception(e).build();
54+
return context.interceptorChain().modifyException(failedContext, context.executionAttributes());
55+
}
56+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/interceptor/DefaultFailedExecutionContext.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,22 @@
2525
import software.amazon.awssdk.http.SdkHttpRequest;
2626
import software.amazon.awssdk.http.SdkHttpResponse;
2727
import software.amazon.awssdk.utils.Validate;
28+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
29+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
2830

2931
/**
3032
* An SDK-internal implementation of {@link Context.FailedExecution}.
3133
*/
3234
@SdkInternalApi
33-
public class DefaultFailedExecutionContext implements Context.FailedExecution {
35+
public class DefaultFailedExecutionContext implements Context.FailedExecution,
36+
ToCopyableBuilder<DefaultFailedExecutionContext.Builder,
37+
DefaultFailedExecutionContext> {
3438
private final InterceptorContext interceptorContext;
3539
private final Throwable exception;
3640

37-
public DefaultFailedExecutionContext(InterceptorContext interceptorContext, Throwable exception) {
38-
this.interceptorContext = Validate.paramNotNull(interceptorContext, "interceptorContext");
39-
this.exception = unwrap(Validate.paramNotNull(exception, "exception"));
41+
private DefaultFailedExecutionContext(Builder builder) {
42+
this.exception = unwrap(Validate.paramNotNull(builder.exception, "exception"));
43+
this.interceptorContext = Validate.paramNotNull(builder.interceptorContext, "interceptorContext");
4044
}
4145

4246
private Throwable unwrap(Throwable exception) {
@@ -70,4 +74,41 @@ public Optional<SdkResponse> response() {
7074
public Throwable exception() {
7175
return exception;
7276
}
77+
78+
@Override
79+
public Builder toBuilder() {
80+
return new Builder(this);
81+
}
82+
83+
public static Builder builder() {
84+
return new Builder();
85+
}
86+
87+
public static final class Builder implements CopyableBuilder<Builder, DefaultFailedExecutionContext> {
88+
private InterceptorContext interceptorContext;
89+
private Throwable exception;
90+
91+
private Builder() {
92+
}
93+
94+
public Builder(DefaultFailedExecutionContext defaultFailedExecutionContext) {
95+
this.exception = defaultFailedExecutionContext.exception;
96+
this.interceptorContext = defaultFailedExecutionContext.interceptorContext;
97+
}
98+
99+
public Builder exception(Throwable exception) {
100+
this.exception = exception;
101+
return this;
102+
}
103+
104+
public Builder interceptorContext(InterceptorContext interceptorContext) {
105+
this.interceptorContext = interceptorContext;
106+
return this;
107+
}
108+
109+
@Override
110+
public DefaultFailedExecutionContext build() {
111+
return new DefaultFailedExecutionContext(this);
112+
}
113+
}
73114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2010-2018 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.internal.http.pipeline.stages;
17+
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.util.Arrays;
22+
import java.util.List;
23+
import org.junit.Test;
24+
import software.amazon.awssdk.core.exception.ApiCallTimeoutException;
25+
import software.amazon.awssdk.core.exception.SdkClientException;
26+
import software.amazon.awssdk.core.http.ExecutionContext;
27+
import software.amazon.awssdk.core.interceptor.Context;
28+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
29+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
30+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain;
31+
import software.amazon.awssdk.core.interceptor.InterceptorContext;
32+
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
33+
import software.amazon.awssdk.core.internal.http.pipeline.stages.utils.ExceptionReportingUtils;
34+
import software.amazon.awssdk.core.signer.NoOpSigner;
35+
import utils.ValidSdkObjects;
36+
37+
public class ExceptionReportingUtilsTest {
38+
39+
@Test
40+
public void onExecutionFailureThrowException_shouldSwallow() {
41+
RequestExecutionContext context = context(new ThrowErrorOnExecutionFailureInterceptor());
42+
43+
assertThat(ExceptionReportingUtils.reportFailureToInterceptors(context, SdkClientException.create("test")))
44+
.isExactlyInstanceOf(SdkClientException.class);
45+
}
46+
47+
@Test
48+
public void modifyException_shouldReturnModifiedException() {
49+
ApiCallTimeoutException modifiedException = ApiCallTimeoutException.create(1000);
50+
RequestExecutionContext context = context(new ModifyExceptionInterceptor(modifiedException));
51+
assertThat(ExceptionReportingUtils.reportFailureToInterceptors(context, SdkClientException.create("test")))
52+
.isEqualTo(modifiedException);
53+
}
54+
55+
public RequestExecutionContext context(ExecutionInterceptor... executionInterceptors) {
56+
List<ExecutionInterceptor> interceptors = Arrays.asList(executionInterceptors);
57+
ExecutionInterceptorChain executionInterceptorChain = new ExecutionInterceptorChain(interceptors);
58+
return RequestExecutionContext.builder()
59+
.executionContext(ExecutionContext.builder()
60+
.signer(new NoOpSigner())
61+
.executionAttributes(new ExecutionAttributes())
62+
.interceptorContext(InterceptorContext.builder()
63+
.request(ValidSdkObjects.sdkRequest())
64+
.build())
65+
.interceptorChain(executionInterceptorChain)
66+
.build())
67+
.originalRequest(ValidSdkObjects.sdkRequest())
68+
.build();
69+
}
70+
71+
private static class ThrowErrorOnExecutionFailureInterceptor implements ExecutionInterceptor {
72+
73+
@Override
74+
public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
75+
throw new RuntimeException("OOPS");
76+
}
77+
}
78+
79+
private static class ModifyExceptionInterceptor implements ExecutionInterceptor {
80+
81+
private final Exception exceptionToThrow;
82+
83+
private ModifyExceptionInterceptor(Exception exceptionToThrow) {
84+
this.exceptionToThrow = exceptionToThrow;
85+
}
86+
87+
@Override
88+
public Throwable modifyException(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
89+
return exceptionToThrow;
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)