From 5ff3309710be364b350c32b869e352aefaaea534 Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Thu, 14 Sep 2017 14:53:16 -0700 Subject: [PATCH 1/2] Implementing string and byte[] async response handlers. Porting over temp fixes so PutObject will work --- .../awssdk/async/AsyncResponseHandler.java | 33 +++++++ .../async/ByteArrayAsyncResponseHandler.java | 91 ++++++++++++++++++ .../async/FileAsyncResponseHandler.java | 5 + .../async/StringAsyncResponseHandler.java | 63 ++++++++++++ .../netty/internal/ChannelAttributeKeys.java | 4 + .../nio/netty/internal/ResponseHandler.java | 95 +++++++++++++++---- .../s3/GetObjectAsyncIntegrationTest.java | 14 +++ 7 files changed, 284 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/software/amazon/awssdk/async/ByteArrayAsyncResponseHandler.java create mode 100644 core/src/main/java/software/amazon/awssdk/async/StringAsyncResponseHandler.java diff --git a/core/src/main/java/software/amazon/awssdk/async/AsyncResponseHandler.java b/core/src/main/java/software/amazon/awssdk/async/AsyncResponseHandler.java index d8bae4888441..bcd050600fc4 100644 --- a/core/src/main/java/software/amazon/awssdk/async/AsyncResponseHandler.java +++ b/core/src/main/java/software/amazon/awssdk/async/AsyncResponseHandler.java @@ -16,6 +16,8 @@ package software.amazon.awssdk.async; import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; @@ -91,4 +93,35 @@ static AsyncResponseHandler toFile(Path path) return new FileAsyncResponseHandler<>(path); } + /** + * Creates an {@link AsyncResponseHandler} that writes all content to a byte array. + * + * @param Pojo response type. + * @return AsyncResponseHandler instance. + */ + static AsyncResponseHandler toByteArray() { + return new ByteArrayAsyncResponseHandler<>(); + } + + /** + * Creates an {@link AsyncResponseHandler} that writes all content to a string using the specified encoding. + * + * @param charset {@link Charset} to use when constructing the string. + * @param Pojo response type. + * @return AsyncResponseHandler instance. + */ + static AsyncResponseHandler toString(Charset charset) { + return new StringAsyncResponseHandler<>(toByteArray(), charset); + } + + /** + * Creates an {@link AsyncResponseHandler} that writes all content to UTF8 encoded string. + * + * @param Pojo response type. + * @return AsyncResponseHandler instance. + */ + static AsyncResponseHandler toUtf8String() { + return toString(StandardCharsets.UTF_8); + } + } diff --git a/core/src/main/java/software/amazon/awssdk/async/ByteArrayAsyncResponseHandler.java b/core/src/main/java/software/amazon/awssdk/async/ByteArrayAsyncResponseHandler.java new file mode 100644 index 000000000000..ac4053610c0d --- /dev/null +++ b/core/src/main/java/software/amazon/awssdk/async/ByteArrayAsyncResponseHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.async; + +import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import software.amazon.awssdk.annotation.SdkInternalApi; +import software.amazon.awssdk.utils.BinaryUtils; + +/** + * Implementation of {@link AsyncResponseHandler} that dumps content into a byte array. + * + * @param Pojo response type. + */ +@SdkInternalApi +class ByteArrayAsyncResponseHandler implements AsyncResponseHandler { + + private ByteArrayOutputStream baos; + + @Override + public void responseReceived(ResponseT response) { + } + + @Override + public void onStream(Publisher publisher) { + baos = new ByteArrayOutputStream(); + publisher.subscribe(new BaosSubscriber()); + } + + @Override + public void exceptionOccurred(Throwable throwable) { + baos = null; + } + + @Override + public byte[] complete() { + try { + return baos.toByteArray(); + } finally { + baos = null; + } + } + + /** + * Requests chunks sequentially and dumps them into a {@link ByteArrayOutputStream}. + */ + private class BaosSubscriber implements Subscriber { + + private Subscription subscription; + + @Override + public void onSubscribe(Subscription s) { + this.subscription = s; + subscription.request(1); + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + invokeSafely(() -> baos.write(BinaryUtils.copyBytesFrom(byteBuffer))); + subscription.request(1); + } + + @Override + public void onError(Throwable throwable) { + // Handled by response handler + } + + @Override + public void onComplete() { + // Handled by response handler + } + } +} diff --git a/core/src/main/java/software/amazon/awssdk/async/FileAsyncResponseHandler.java b/core/src/main/java/software/amazon/awssdk/async/FileAsyncResponseHandler.java index 0d63e77c304b..885a8bf90e4b 100644 --- a/core/src/main/java/software/amazon/awssdk/async/FileAsyncResponseHandler.java +++ b/core/src/main/java/software/amazon/awssdk/async/FileAsyncResponseHandler.java @@ -123,5 +123,10 @@ public void onError(Throwable t) { public void onComplete() { // Completion handled by response handler } + + @Override + public String toString() { + return getClass() + ":" + path.toString(); + } } } diff --git a/core/src/main/java/software/amazon/awssdk/async/StringAsyncResponseHandler.java b/core/src/main/java/software/amazon/awssdk/async/StringAsyncResponseHandler.java new file mode 100644 index 000000000000..c467fbdff8aa --- /dev/null +++ b/core/src/main/java/software/amazon/awssdk/async/StringAsyncResponseHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.async; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotation.SdkInternalApi; + +/** + * Implementation of {@link AsyncResponseHandler} that dumps content into a string using the specified {@link Charset}. + * + * @param Pojo response type. + */ +@SdkInternalApi +class StringAsyncResponseHandler implements AsyncResponseHandler { + + private final AsyncResponseHandler byteArrayResponseHandler; + private final Charset charset; + + /** + * @param byteArrayResponseHandler {@link AsyncResponseHandler} implementation that dumps data into a byte array. + * @param charset Charset to use for String. + */ + StringAsyncResponseHandler(AsyncResponseHandler byteArrayResponseHandler, + Charset charset) { + this.byteArrayResponseHandler = byteArrayResponseHandler; + this.charset = charset; + } + + @Override + public void responseReceived(ResponseT response) { + byteArrayResponseHandler.responseReceived(response); + } + + @Override + public void onStream(Publisher publisher) { + byteArrayResponseHandler.onStream(publisher); + } + + @Override + public void exceptionOccurred(Throwable throwable) { + byteArrayResponseHandler.exceptionOccurred(throwable); + } + + @Override + public String complete() { + return new String(byteArrayResponseHandler.complete(), charset); + } +} diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelAttributeKeys.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelAttributeKeys.java index ace763ed486a..5fc6e8903f35 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelAttributeKeys.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelAttributeKeys.java @@ -16,6 +16,8 @@ package software.amazon.awssdk.http.nio.netty.internal; import io.netty.util.AttributeKey; +import java.nio.ByteBuffer; +import org.reactivestreams.Subscriber; /** * Keys for attributes attached via {@link io.netty.channel.Channel#attr(AttributeKey)}. @@ -27,6 +29,8 @@ class ChannelAttributeKeys { */ static final AttributeKey REQUEST_CONTEXT_KEY = AttributeKey.newInstance("requestContext"); + static final AttributeKey> SUBSCRIBER_KEY = AttributeKey.newInstance("subscriber"); + private ChannelAttributeKeys() { } } diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java index 6b8963330aff..5330177291ee 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java @@ -19,15 +19,17 @@ import static java.util.stream.Collectors.mapping; import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKeys.REQUEST_CONTEXT_KEY; -import com.typesafe.netty.http.HttpStreamsClientHandler; import com.typesafe.netty.http.StreamedHttpResponse; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.AttributeKey; import java.nio.ByteBuffer; import java.util.List; @@ -58,8 +60,8 @@ protected void channelRead0(ChannelHandlerContext channelContext, HttpObject msg RequestContext requestContext = channelContext.channel().attr(REQUEST_CONTEXT_KEY).get(); - if (msg instanceof StreamedHttpResponse) { - StreamedHttpResponse response = (StreamedHttpResponse) msg; + if (msg instanceof HttpResponse) { + HttpResponse response = (HttpResponse) msg; SdkHttpResponse sdkResponse = SdkHttpFullResponse.builder() .headers(fromNettyHeaders(response.headers())) .statusCode(response.status().code()) @@ -67,22 +69,56 @@ protected void channelRead0(ChannelHandlerContext channelContext, HttpObject msg .build(); channelContext.channel().attr(KEEP_ALIVE).set(HttpUtil.isKeepAlive(response)); requestContext.handler().headersReceived(sdkResponse); - requestContext.handler().onStream(new PublisherAdapter(response, channelContext, requestContext)); } + + if (msg instanceof StreamedHttpResponse) { + requestContext.handler().onStream(new PublisherAdapter((StreamedHttpResponse) msg, channelContext, requestContext)); + } else if (msg instanceof FullHttpResponse) { + requestContext.handler().onStream(new EmptyRequestPublisher(msg, channelContext)); + } + + if (msg instanceof LastHttpContent) { + Subscriber subscriber = channelContext.channel().attr(ChannelAttributeKeys.SUBSCRIBER_KEY).get(); + try { + subscriber.onComplete(); + requestContext.handler().complete(); + } catch (RuntimeException e) { + subscriber.onError(e); + requestContext.handler().exceptionOccurred(e); + throw e; + } finally { + finalizeRequest(requestContext, channelContext); + } + } + } + + private static void finalizeRequest(RequestContext requestContext, ChannelHandlerContext channelContext) { + if (!channelContext.channel().attr(KEEP_ALIVE).get()) { + closeAndRelease(channelContext); + } else { + requestContext.channelPool().release(channelContext.channel()); + } + } + + /** + * Close the channel and release it back into the pool. + * + * @param ctx Context for channel + */ + private static void closeAndRelease(ChannelHandlerContext ctx) { + RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get(); + ctx.channel().close() + .addListener(channelFuture -> requestContext.channelPool().release(ctx.channel())); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get(); - try { - log.error("Exception processing request: {}", requestContext.sdkRequest(), cause); - ctx.fireExceptionCaught(cause); - } finally { - runAndLogError("SdkHttpResponseHandler threw an exception", - () -> requestContext.handler().exceptionOccurred(cause)); - runAndLogError("Could not release channel back to the pool", - () -> requestContext.channelPool().release(ctx.channel())); - } + log.error("Exception processing request: {}", requestContext.sdkRequest(), cause); + + runAndLogError("SdkHttpResponseHandler threw an exception", + () -> requestContext.handler().exceptionOccurred(cause)); + runAndLogError("Could not release channel back to the pool", () -> closeAndRelease(ctx)); } /** @@ -134,22 +170,39 @@ public void onNext(HttpContent httpContent) { @Override public void onError(Throwable t) { - subscriber.onError(t); + runAndLogError(String.format("Subscriber %s threw an exception in onError.", subscriber.toString()), + () -> subscriber.onError(t)); requestContext.handler().exceptionOccurred(t); } @Override public void onComplete() { - subscriber.onComplete(); - requestContext.handler().complete(); - if (!channelContext.channel().attr(KEEP_ALIVE).get()) { - channelContext.channel().close(); + try { + runAndLogError(String.format("Subscriber %s threw an exception in onComplete.", subscriber.toString()), + subscriber::onComplete); + requestContext.handler().complete(); + } finally { + finalizeRequest(requestContext, channelContext); } - channelContext.pipeline().remove(HttpStreamsClientHandler.class); - channelContext.pipeline().remove(ResponseHandler.class); - requestContext.channelPool().release(channelContext.channel()); } }); } } + + private static class EmptyRequestPublisher implements Publisher { + private final HttpObject msg; + private final ChannelHandlerContext channelContext; + + private EmptyRequestPublisher(HttpObject msg, ChannelHandlerContext channelContext) { + this.msg = msg; + this.channelContext = channelContext; + } + + @Override + public void subscribe(Subscriber subscriber) { + subscriber.onNext(((FullHttpResponse) msg).content().nioBuffer()); + channelContext.channel().attr(ChannelAttributeKeys.SUBSCRIBER_KEY) + .set(subscriber); + } + } } diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectAsyncIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectAsyncIntegrationTest.java index a88814928542..a7209b7b1db8 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectAsyncIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectAsyncIntegrationTest.java @@ -22,6 +22,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -80,6 +82,18 @@ public void toFile() throws Exception { } } + @Test + public void dumpToString() throws IOException { + String returned = s3Async.getObject(getObjectRequest, AsyncResponseHandler.toUtf8String()).join(); + assertThat(returned).isEqualTo(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)); + } + + @Test + public void toByteArray() throws IOException { + byte[] returned = s3Async.getObject(getObjectRequest, AsyncResponseHandler.toByteArray()).join(); + assertThat(returned).isEqualTo(Files.readAllBytes(file.toPath())); + } + @Test public void customResponseHandler_InterceptorRecievesResponsePojo() throws Exception { try (S3AsyncClient asyncWithInterceptor = createClientWithInterceptor(new AssertingExecutionInterceptor())) { From 6ebedbe411b9bf65bae1aa6183dc7cd99bb79c52 Mon Sep 17 00:00:00 2001 From: Andrew Shore Date: Fri, 15 Sep 2017 16:14:38 -0700 Subject: [PATCH 2/2] Fixes for tests --- .../codegen-resources/customization.config | 3 +- .../s3/BucketAccelerateIntegrationTest.java | 6 +++ .../s3/handlers/CreateBucketInterceptor.java | 14 +++-- .../handlers/CreateBucketInterceptorTest.java | 51 ++++++++++++++----- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/services/elasticbeanstalk/src/main/resources/codegen-resources/customization.config b/services/elasticbeanstalk/src/main/resources/codegen-resources/customization.config index 1ba9f033934b..3ee63529d911 100644 --- a/services/elasticbeanstalk/src/main/resources/codegen-resources/customization.config +++ b/services/elasticbeanstalk/src/main/resources/codegen-resources/customization.config @@ -178,7 +178,8 @@ "describeEnvironmentManagedActionHistory", "describeEnvironmentManagedActions", "describeInstancesHealth", - "describeEnvironmentHealth" + "describeEnvironmentHealth", + "describeConfigurationOptions" ], "verifiedSimpleMethods" : ["createStorageLocation"] } diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/BucketAccelerateIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/BucketAccelerateIntegrationTest.java index e02f11b358cf..986764184d37 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/BucketAccelerateIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/BucketAccelerateIntegrationTest.java @@ -21,7 +21,9 @@ import java.util.List; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; +import software.amazon.awssdk.annotation.ReviewBeforeRelease; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.model.AccelerateConfiguration; import software.amazon.awssdk.services.s3.model.BucketAccelerateStatus; @@ -50,6 +52,10 @@ /** * Integration tests for S3 bucket accelerate configuration. */ +@ReviewBeforeRelease("These tests are a bit flaky. Looks like S3 returns 307 Temporary Redirect occasionally " + + "for a newly accelerated bucket. Not sure what the right fix is without following redirects " + + "which we don't want to do for other reasons.") +@Ignore public class BucketAccelerateIntegrationTest extends S3IntegrationTestBase { private static final String US_BUCKET_NAME = "s3-accelerate-us-east-1-" + System.currentTimeMillis(); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptor.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptor.java index a42b2364d6ed..f8063cafeb70 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptor.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptor.java @@ -38,9 +38,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut if (request.createBucketConfiguration() == null || request.createBucketConfiguration().locationConstraint() == null) { Region region = executionAttributes.getAttribute(AwsExecutionAttributes.AWS_REGION); sdkRequest = request.toBuilder() - .createBucketConfiguration(CreateBucketConfiguration.builder() - .locationConstraint(region.value()) - .build()) + .createBucketConfiguration(toLocationConstraint(region)) .build(); } } @@ -48,6 +46,16 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut return sdkRequest; } + private CreateBucketConfiguration toLocationConstraint(Region region) { + if (region.equals(Region.US_EAST_1)) { + // us-east-1 requires no location restraint. See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html + return null; + } + return CreateBucketConfiguration.builder() + .locationConstraint(region.value()) + .build(); + } + /** * Validates that the name of the bucket being requested to be created * is a valid S3 bucket name according to their guidelines. If the bucket diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptorTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptorTest.java index 9100a1836a1e..75054bc074df 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptorTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/handlers/CreateBucketInterceptorTest.java @@ -32,12 +32,14 @@ public void modifyRequest_DoesNotOverrideExistingLocationConstraint() { CreateBucketRequest request = CreateBucketRequest.builder() .bucket("test-bucket") .createBucketConfiguration(CreateBucketConfiguration.builder() - .locationConstraint("us-west-2") + .locationConstraint( + "us-west-2") .build()) .build(); Context.ModifyRequest context = () -> request; - ExecutionAttributes attributes = new ExecutionAttributes().putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_EAST_1); + ExecutionAttributes attributes = new ExecutionAttributes() + .putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_EAST_1); SdkRequest modifiedRequest = new CreateBucketInterceptor().modifyRequest(context, attributes); String locationConstraint = ((CreateBucketRequest) modifiedRequest).createBucketConfiguration().locationConstraint(); @@ -47,33 +49,54 @@ public void modifyRequest_DoesNotOverrideExistingLocationConstraint() { @Test public void modifyRequest_UpdatesLocationConstraint_When_NullCreateBucketConfiguration() { - CreateBucketRequest request = CreateBucketRequest.builder() - .bucket("test-bucket") - .build(); + CreateBucketRequest request = CreateBucketRequest.builder() + .bucket("test-bucket") + .build(); Context.ModifyRequest context = () -> request; - ExecutionAttributes attributes = new ExecutionAttributes().putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_EAST_1); + ExecutionAttributes attributes = new ExecutionAttributes() + .putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_EAST_2); SdkRequest modifiedRequest = new CreateBucketInterceptor().modifyRequest(context, attributes); String locationConstraint = ((CreateBucketRequest) modifiedRequest).createBucketConfiguration().locationConstraint(); - assertThat(locationConstraint).isEqualToIgnoringCase("us-east-1"); + assertThat(locationConstraint).isEqualToIgnoringCase("us-east-2"); } @Test public void modifyRequest_UpdatesLocationConstraint_When_NullLocationConstraint() { - CreateBucketRequest request = CreateBucketRequest.builder() - .bucket("test-bucket") - .createBucketConfiguration(CreateBucketConfiguration.builder() - .build()) - .build(); + CreateBucketRequest request = CreateBucketRequest.builder() + .bucket("test-bucket") + .createBucketConfiguration(CreateBucketConfiguration.builder() + .build()) + .build(); Context.ModifyRequest context = () -> request; - ExecutionAttributes attributes = new ExecutionAttributes().putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_EAST_1); + ExecutionAttributes attributes = new ExecutionAttributes() + .putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_WEST_2); SdkRequest modifiedRequest = new CreateBucketInterceptor().modifyRequest(context, attributes); String locationConstraint = ((CreateBucketRequest) modifiedRequest).createBucketConfiguration().locationConstraint(); - assertThat(locationConstraint).isEqualToIgnoringCase("us-east-1"); + assertThat(locationConstraint).isEqualToIgnoringCase("us-west-2"); + } + + /** + * For us-east-1 there must not be a location constraint (or containing CreateBucketConfiguration) sent. + */ + @Test + public void modifyRequest_UsEast1_UsesNullBucketConfiguration() { + CreateBucketRequest request = CreateBucketRequest.builder() + .bucket("test-bucket") + .createBucketConfiguration(CreateBucketConfiguration.builder() + .build()) + .build(); + + Context.ModifyRequest context = () -> request; + ExecutionAttributes attributes = new ExecutionAttributes() + .putAttribute(AwsExecutionAttributes.AWS_REGION, Region.US_EAST_1); + + SdkRequest modifiedRequest = new CreateBucketInterceptor().modifyRequest(context, attributes); + assertThat(((CreateBucketRequest) modifiedRequest).createBucketConfiguration()).isNull(); } }