aws / aws/aws-sdk-java-v2

DynamoDb enhanced TableSchema interface incorrectly types the AttributeConverter returned by `converterForAttribute`

Open
#4,106 1 comment 0 reactions 0 assignees View on GitHub
bug dynamodb-enhanced p2
Dominant language
Java
Stars
2.6k
Forks
1k
Avg merge
2d 9h
Merged PRs (30d)
51

Description

### Describe the bug

This was found while implementing a custom TableSchema in Kotlin. The `converterForAttribute` function is not needed for regular table operations or default extensions, but it is used in non-default extensions. In this case, the one that brought this to my attention is `AutoGeneratedTimestampRecordExtension`.

Relevant `TableSchema` code:
```java
/**
* ...
*
* @param The type of model object that is being mapped to records in the DynamoDb table.
*/
public interface TableSchema {
/**
* {@link AttributeConverter} that is applied to the given key.
*
* @param key Attribute of the modelled item.
* @return AttributeConverter defined for the given attribute key.
*/
default AttributeConverter converterForAttribute(Object key) {
throw new UnsupportedOperationException();
}
}
```

Relevant `AutoGeneratedTimestampRecordExtension` code:
```java
@Override
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
Collection customMetadataObject = context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);

if (customMetadataObject == null) {
return WriteModification.builder().build();
}
Map itemToTransform = new HashMap<>(context.items());
customMetadataObject.forEach(
key -> insertTimestampInItemToTransform(itemToTransform, key,
context.tableSchema().converterForAttribute(key)));
return WriteModification.builder()
.transformedItem(Collections.unmodifiableMap(itemToTransform))
.build();
}

private void insertTimestampInItemToTransform(Map itemToTransform,
String key,
AttributeConverter converter) {
itemToTransform.put(key, converter.transformFrom(clock.instant()));
}
```
The `AutoGeneratedTimestampRecordExtension` uses `converterForAttribute` to get the converter for the `Instant` field annotated with `DynamoDbAutoGeneratedTimestampAttribute`, which is `AttributeConverter` and not an `AttributeConverter`.

To implement this in Kotlin, we need to do an unchecked cast on the converter for the attribute to `AttributeConverter`, when the `AttributeConverter` that's returned is actually `AttributeConverter`. `AttrT` is the type of the attribute getting converted.
```kotlin
@Suppress("UNCHECKED_CAST")
override fun converterForAttribute(key: Any): AttributeConverter {
val attributeKey = key as String
val attribute: CustomAttribute = attributes[attributeKey]

return attribute.converter as AttributeConverter
}
```

### Expected Behavior

The signature of `TableSchema.converterForAttribute` returns an `AttributeConverter` for attributes of the item

### Current Behavior

The signature of `TableSchema.converterForAttribute` returns an `AttributeConverter` for the item itself

### Reproduction Steps

Difficult to create an SSCCE, but hopefully there is enough information to explain the issue

### Possible Solution

The current implementations work due to type erasure. The `StaticTableSchema` implementation uses `ResolvedImmutableAttribute.attributeConverter()` to return the appropriate `AttributeConverter`, however, the `ResolvedImmutableAttribute` `attributeConverter` is a raw `AttributeConverter` without a type specified so this is not caught by the compiler.

```java
private final AttributeConverter attributeConverter;

public AttributeConverter attributeConverter() {
return attributeConverter;
}
```

### Additional Information/Context

_No response_

### AWS Java SDK version used

2.20.40

### JDK version used

11

### Operating System and version

MacOS Ventura 13.4

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.