Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AWSSDKforJavav2-01696b7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Add allowDuplicateKeys to ImmutableMap Builder"
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ private CodeBlock dnsSuffixesByPartition(Partitions partitions) {
private CodeBlock hostnamesByRegion(Partitions partitions) {
Map<Partition, Service> services = getServiceData(partitions);

CodeBlock.Builder builder = CodeBlock.builder().add("$T.<$T, $T>builder()",
CodeBlock.Builder builder = CodeBlock.builder().add("$T.<$T, $T>builder().allowDuplicateKeys(true)",
ImmutableMap.class, serviceEndpointKeyClass(), String.class);

services.forEach((partition, service) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public final class S3ServiceMetadata implements ServiceMetadata {

private static final Map<ServiceEndpointKey, String> HOSTNAMES_BY_REGION = ImmutableMap
.<ServiceEndpointKey, String> builder()
.allowDuplicateKeys(true)
.put(ServiceEndpointKey.builder().region(Region.of("af-south-1")).tags(EndpointTag.of("dualstack")).build(),
"s3.dualstack.af-south-1.amazonaws.com")
.put(ServiceEndpointKey.builder().region(Region.of("ap-east-1")).tags(EndpointTag.of("dualstack")).build(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public final class StsServiceMetadata implements ServiceMetadata {

private static final Map<ServiceEndpointKey, String> HOSTNAMES_BY_REGION = ImmutableMap
.<ServiceEndpointKey, String> builder()
.allowDuplicateKeys(true)
.put(ServiceEndpointKey.builder().region(Region.of("aws-global")).build(), "sts.amazonaws.com")
.put(ServiceEndpointKey.builder().region(Region.of("us-east-1")).tags(EndpointTag.of("fips")).build(),
"sts-fips.us-east-1.amazonaws.com")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbConvertedBy;
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
import software.amazon.awssdk.enhanced.dynamodb.model.Record;
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity;
import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity;
import software.amazon.awssdk.services.dynamodb.model.Select;

public class ScanQueryIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {

Expand All @@ -57,6 +63,60 @@ public static void setup() {
mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA);
mappedTable.createTable();
dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME));

// sandbox

QueryConditional queryConditional = QueryConditional.keyEqualTo(Key.builder().partitionValue("val").build());

PageIterable<Record> pageIterable = mappedTable.query(QueryEnhancedRequest.builder()
.queryConditional(queryConditional)
.select(Select.COUNT)
.build());
Iterator<Page<Record>> iterator = pageIterable.iterator();

Page<Record> page = iterator.next();

int count = page.count();
}

public class NullifyEmptyStringConverter implements AttributeConverter<String> {
Comment thread
davidh44 marked this conversation as resolved.
Outdated
@Override
public AttributeValue transformFrom(String value) {
if (value == null || value.isEmpty()) {
return AttributeValue.builder().nul(true).build();
}
return AttributeValue.builder().s(value).build();
}

@Override
public String transformTo(AttributeValue attributeValue) {
if (attributeValue.nul()) {
return null;
}
return attributeValue.s();
}

@Override
public EnhancedType<String> type() {
return EnhancedType.of(String.class);
}

@Override
public AttributeValueType attributeValueType() {
return AttributeValueType.S;
}
}

// Usage:
@DynamoDbBean
public class Customer {
private String name;

@DynamoDbAttribute("name")
@DynamoDbConvertedBy(NullifyEmptyStringConverter.class)
public String getName() {
return name;
}
}

@AfterClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public class CodingConventionWithSuppressionTest {
ArchUtils.classNameToPattern(MakeHttpRequestStage.class),
ArchUtils.classNameToPattern("software.amazon.awssdk.services.s3.internal.crt.S3CrtResponseHandlerAdapter"),
ArchUtils.classNameToPattern(
"software.amazon.awssdk.services.s3.internal.crt.CrtResponseFileResponseTransformer")));
"software.amazon.awssdk.services.s3.internal.crt.CrtResponseFileResponseTransformer"),
ArchUtils.classNameToPattern("software.amazon.awssdk.utils.ImmutableMap")));
Comment thread
davidh44 marked this conversation as resolved.
Outdated

private static final Set<Pattern> ALLOWED_ERROR_LOG_SUPPRESSION = new HashSet<>(
Arrays.asList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
@SdkProtectedApi
public final class ImmutableMap<K, V> implements Map<K, V> {

private static final Logger log = Logger.loggerFor(ImmutableMap.class);
private static final String UNMODIFIABLE_MESSAGE = "This is an immutable map.";
private static final String DUPLICATED_KEY_MESSAGE = "Duplicate keys are provided.";

Expand Down Expand Up @@ -198,8 +199,18 @@ public static <K, V> ImmutableMap<K, V> of(K k0, V v0, K k1, V v1,

private static <K, V> void putAndWarnDuplicateKeys(Map<K, V> map, K key,
V value) {
putAndWarnDuplicateKeys(map, key, value, false);
}

private static <K, V> void putAndWarnDuplicateKeys(Map<K, V> map, K key, V value, boolean allowDuplicateKeys) {
Comment thread
davidh44 marked this conversation as resolved.
if (map.containsKey(key)) {
throw new IllegalArgumentException(DUPLICATED_KEY_MESSAGE);

if (allowDuplicateKeys) {
log.warn(() -> String.format("Duplicate keys are provided for [%s]. The newer value [%s] will be saved, and the "
Comment thread
davidh44 marked this conversation as resolved.
Outdated
+ "existing value [%s] will be overwritten.", key, value, map.get(key)));
} else {
throw new IllegalArgumentException(DUPLICATED_KEY_MESSAGE);
}
}
map.put(key, value);
}
Expand Down Expand Up @@ -289,6 +300,7 @@ public String toString() {
public static class Builder<K, V> {

private final Map<K, V> entries;
private boolean allowDuplicateKeys;

public Builder() {
this.entries = new HashMap<>();
Expand All @@ -297,13 +309,29 @@ public Builder() {
/**
* Add a key-value pair into the built map. The method will throw
* IllegalArgumentException immediately when duplicate keys are
* provided.
* provided and allowDuplicateKeys is false (default value).
*
*
* If duplicate keys are provided and allowDuplicateKeys is true, the latest value will overwrite the existing value, and
* the SDK will log a message at WARN level.
*
* @return Returns a reference to this object so that method calls can
* be chained together.
*/
public Builder<K, V> put(K key, V value) {
putAndWarnDuplicateKeys(entries, key, value);
putAndWarnDuplicateKeys(entries, key, value, allowDuplicateKeys);
return this;
}

/**
* Sets whether duplicate keys are allowed. If true, the latest value will overwrite the existing value. If
* false, an error will be thrown when attempting to put an entry with a duplicate key. Default value is false.
*
* @return Returns a reference to this object so that method calls can
* be chained together.
*/
public Builder<K, V> allowDuplicateKeys(boolean allowDuplicateKeys) {
this.allowDuplicateKeys = allowDuplicateKeys;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ public void testOfBuilder() {
public void testErrorOnDuplicateKeys() {
try {
Map<Integer, String> builtMap = new ImmutableMap.Builder<Integer, String>()
.put(1, "one")
.put(1, "two")
.build();
.put(1, "one")
.put(1, "two")
.build();
fail("IllegalArgumentException expected.");
} catch (IllegalArgumentException iae) {
// Ignored or expected.
Expand All @@ -85,6 +85,17 @@ public void testErrorOnDuplicateKeys() {
}
}

@Test
public void putDuplicateKeys_allowDuplicateKeysTrue_doesNotThrowErrorAndKeepsNewestValue() {
Map<Integer, String> builtMap = new ImmutableMap.Builder<Integer, String>()
.allowDuplicateKeys(true)
.put(1, "one")
.put(1, "two")
.build();

assertEquals("two", builtMap.get(1));
}

@Test
public void testMapOperations() {
Map<Integer, String> builtMap = new ImmutableMap.Builder<Integer, String>()
Expand Down
Loading