Skip to content

Commit 72dfe9f

Browse files
committed
Apply dynamodb enhanced client user agent for all requests made by dynamodbEnahncedClient via an interceptor. Updated javadocs
1 parent 3c21a57 commit 72dfe9f

File tree

9 files changed

+180
-10
lines changed

9 files changed

+180
-10
lines changed

services-custom/dynamodb-enhanced/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@
9090
</build>
9191

9292
<dependencies>
93+
<dependency>
94+
<groupId>software.amazon.awssdk</groupId>
95+
<artifactId>aws-core</artifactId>
96+
<version>${awsjavasdk.version}</version>
97+
</dependency>
98+
<dependency>
99+
<groupId>software.amazon.awssdk</groupId>
100+
<artifactId>http-client-spi</artifactId>
101+
<version>${awsjavasdk.version}</version>
102+
</dependency>
93103
<dependency>
94104
<groupId>software.amazon.awssdk</groupId>
95105
<artifactId>sdk-core</artifactId>

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import software.amazon.awssdk.annotations.SdkPublicApi;
2121
import software.amazon.awssdk.annotations.ThreadSafe;
22-
import software.amazon.awssdk.enhanced.dynamodb.TypeToken;
2322
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.InstantAsIntegerAttributeConverter;
2423
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.InstantAsStringAttributeConverter;
2524
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter;

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,22 @@
3737
* to automatically track each revision of the record as it is modified. To use this extension, first load it as part
3838
* of your MappedTable instantiation:
3939
*
40+
* <pre>
41+
* {@code
4042
* MappedTable.builder()
4143
* .extendWith(VersionedRecordExtension.builder().build())
4244
* .build();
45+
* }
46+
* </pre>
4347
*
48+
* <p>
4449
* Then create an attribute in your model that will be used to store the record version number. This attribute must
4550
* be an 'integer' type numeric (long or integer), and you need to tag it as the version attribute:
4651
*
52+
* <p>
4753
* ..., integerNumber("version", Customer::getVersion, Customer::setVersion).as(version()), ...
4854
*
55+
* <p>
4956
* Then, whenever a record is written the write operation will only succeed if the version number of the record has not
5057
* been modified since it was last read by the application. Every time a new version of the record is successfully
5158
* written to the database, the record version number will be automatically incremented.

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/WriteModification.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323

2424
/**
2525
* Simple object for storing a modification to a write operation.
26-
*
26+
* <p>
2727
* If a transformedItem is supplied then this item will be completely substituted in place of the item that was
2828
* previously going to be written.
29-
*
29+
* <p>
3030
* If an additionalConditionalExpression is supplied then this condition will be coalesced with any other conditions
3131
* and added as a parameter to the write operation.
3232
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.enhanced.dynamodb.internal;
17+
18+
import java.util.function.Consumer;
19+
import software.amazon.awssdk.annotations.SdkInternalApi;
20+
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
21+
import software.amazon.awssdk.core.ApiName;
22+
import software.amazon.awssdk.core.SdkRequest;
23+
import software.amazon.awssdk.core.interceptor.Context;
24+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
25+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
26+
import software.amazon.awssdk.services.dynamodb.model.DynamoDbRequest;
27+
28+
/**
29+
* Apply dynamodb enhanced client specific user agent to the request
30+
*/
31+
@SdkInternalApi
32+
public final class ApplyUserAgentInterceptor implements ExecutionInterceptor {
33+
private static final ApiName API_NAME =
34+
ApiName.builder().version("ddb-enh").name("hll").build();
35+
private static final Consumer<AwsRequestOverrideConfiguration.Builder> USER_AGENT_APPLIER =
36+
b -> b.addApiName(API_NAME);
37+
38+
@Override
39+
public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) {
40+
if (!(context.request() instanceof DynamoDbRequest)) {
41+
// should never happen
42+
return context.request();
43+
}
44+
45+
DynamoDbRequest request = (DynamoDbRequest) context.request();
46+
AwsRequestOverrideConfiguration overrideConfiguration =
47+
request.overrideConfiguration().map(c -> c.toBuilder()
48+
.applyMutation(USER_AGENT_APPLIER)
49+
.build())
50+
.orElse((AwsRequestOverrideConfiguration.builder()
51+
.applyMutation(USER_AGENT_APPLIER)
52+
.build()));
53+
54+
return request.toBuilder().overrideConfiguration(overrideConfiguration).build();
55+
}
56+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/PaginatedOperation.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,14 @@ ResultT transformResponse(ResponseT response,
100100
/**
101101
* Default implementation of a complete synchronous execution of this operation against either the primary or a
102102
* secondary index.
103+
* <p>
103104
* It performs three steps:
104-
* 1) Call generateRequest() to get the request object.
105-
* 2) Call getServiceCall() and call it using the request object generated in the previous step.
106-
* 3) Wraps the {@link SdkIterable} that was returned by the previous step with a transformation that turns each
107-
* object returned to a high level result.
105+
* <ol>
106+
* <li> Call {@link #generateRequest} to get the request object.</li>
107+
* <li> Call {@link #asyncServiceCall} and call it using the request object generated in the previous step.</li>
108+
* <li> Wraps the {@link SdkIterable} that was returned by the previous step with a transformation that turns each
109+
* object returned to a high level result.</li>
110+
* </ol>
108111
*
109112
* @param tableSchema A {@link TableSchema} that maps the table to a modelled object.
110113
* @param context An object containing the context, or target, of the command execution.
@@ -125,11 +128,14 @@ default SdkIterable<ResultT> execute(TableSchema<ItemT> tableSchema,
125128
/**
126129
* Default implementation of a complete non-blocking asynchronous execution of this operation against either the
127130
* primary or a secondary index.
131+
* <p>
128132
* It performs three steps:
129-
* 1) Call generateRequest() to get the request object.
130-
* 2) Call getServiceCall() and call it using the request object generated in the previous step.
131-
* 3) Wraps the {@link SdkPublisher} returned by the SDK in a new one that calls transformResponse() to
133+
* <ol>
134+
* <li> Call {@link #generateRequest} to get the request object.
135+
* <li> Call {@link #asyncServiceCall} and call it using the request object generated in the previous step.
136+
* <li> Wraps the {@link SdkPublisher} returned by the SDK in a new one that calls transformResponse() to
132137
* convert the response objects published to a high level result.
138+
* </ol>
133139
*
134140
* @param tableSchema A {@link TableSchema} that maps the table to a modelled object.
135141
* @param context An object containing the context, or target, of the command execution.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
software.amazon.awssdk.enhanced.dynamodb.internal.ApplyUserAgentInterceptor

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/LocalDynamoDb.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@
1414
*/
1515
package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
1616

17+
import static org.assertj.core.api.Assertions.assertThat;
18+
1719
import com.amazonaws.services.dynamodbv2.local.main.ServerRunner;
1820
import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer;
1921
import java.io.IOException;
2022
import java.net.ServerSocket;
2123
import java.net.URI;
24+
import java.util.List;
25+
import java.util.Optional;
2226
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
2327
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
28+
import software.amazon.awssdk.core.interceptor.Context;
29+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
30+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
31+
import software.amazon.awssdk.core.util.VersionInfo;
2432
import software.amazon.awssdk.regions.Region;
2533
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
2634
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@@ -71,6 +79,7 @@ DynamoDbClient createClient() {
7179
.region(Region.US_EAST_1)
7280
.credentialsProvider(StaticCredentialsProvider.create(
7381
AwsBasicCredentials.create("dummy-key", "dummy-secret")))
82+
.overrideConfiguration(o -> o.addExecutionInterceptor(new VerifyUserAgentInterceptor()))
7483
.build();
7584
}
7685

@@ -81,6 +90,7 @@ DynamoDbAsyncClient createAsyncClient() {
8190
.region(Region.US_EAST_1)
8291
.credentialsProvider(StaticCredentialsProvider.create(
8392
AwsBasicCredentials.create("dummy-key", "dummy-secret")))
93+
.overrideConfiguration(o -> o.addExecutionInterceptor(new VerifyUserAgentInterceptor()))
8494
.build();
8595
}
8696

@@ -120,4 +130,15 @@ private static RuntimeException propagate(Exception e) {
120130
}
121131
throw new RuntimeException(e);
122132
}
133+
134+
private static class VerifyUserAgentInterceptor implements ExecutionInterceptor {
135+
136+
@Override
137+
public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {
138+
Optional<String> headers = context.httpRequest().firstMatchingHeader("User-agent");
139+
assertThat(headers).isPresent();
140+
assertThat(headers.get()).contains("hll/ddb-enh");
141+
}
142+
}
143+
123144
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2010-2020 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.enhanced.dynamodb.internal;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.util.List;
21+
import java.util.Optional;
22+
import org.junit.Test;
23+
import software.amazon.awssdk.core.RequestOverrideConfiguration;
24+
import software.amazon.awssdk.core.SdkField;
25+
import software.amazon.awssdk.core.SdkRequest;
26+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
27+
import software.amazon.awssdk.core.util.VersionInfo;
28+
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
29+
30+
public class ApplyUserAgentInterceptorTest {
31+
32+
private ApplyUserAgentInterceptor interceptor = new ApplyUserAgentInterceptor();
33+
34+
@Test
35+
public void ddbRequest_shouldModifyRequest() {
36+
GetItemRequest getItemRequest = GetItemRequest.builder().build();
37+
SdkRequest sdkRequest = interceptor.modifyRequest(() -> getItemRequest, new ExecutionAttributes());
38+
39+
RequestOverrideConfiguration requestOverrideConfiguration = sdkRequest.overrideConfiguration().get();
40+
assertThat(requestOverrideConfiguration.apiNames()
41+
.stream()
42+
.filter(a -> a.name()
43+
.equals("hll") &&
44+
a.version().equals("ddb-enh")).findAny())
45+
.isPresent();
46+
}
47+
48+
@Test
49+
public void otherRequest_shouldNotModifyRequest() {
50+
SdkRequest someOtherRequest = new SdkRequest() {
51+
@Override
52+
public List<SdkField<?>> sdkFields() {
53+
return null;
54+
}
55+
56+
@Override
57+
public Optional<? extends RequestOverrideConfiguration> overrideConfiguration() {
58+
return Optional.empty();
59+
}
60+
61+
@Override
62+
public Builder toBuilder() {
63+
return null;
64+
}
65+
};
66+
SdkRequest sdkRequest = interceptor.modifyRequest(() -> someOtherRequest, new ExecutionAttributes());
67+
68+
assertThat(sdkRequest).isEqualTo(someOtherRequest);
69+
}
70+
}

0 commit comments

Comments
 (0)