Skip to content

Feature/support auto generated timestamp in nested objects #5960

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Amazon DynamoDB Enhanced Client",
"contributor": "",
"description": "Added support for DynamoDbAutoGeneratedTimestampAttribute annotation in nested objects."
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.NotThreadSafe;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
Expand All @@ -30,6 +33,7 @@
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
Expand Down Expand Up @@ -126,28 +130,70 @@ public static AutoGeneratedTimestampRecordExtension create() {
*/
@Override
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
Collection customMetadataObject = context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);

Collection<String> customMetadataObject = context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);
Map<String, AttributeValue> itemToTransform = new HashMap<>(context.items());
if (customMetadataObject != null) {
customMetadataObject.forEach(
key -> insertTimestampInItemToTransform(itemToTransform, key.toString(),
context.tableSchema().converterForAttribute(key)));
}
itemToTransform.forEach((key, value) -> {
if (value.hasM() && value.m() != null) {
Optional<? extends TableSchema<?>> nestedSchema = getNestedSchema(context.tableSchema(), key);
if (nestedSchema != null && nestedSchema.isPresent()) {
itemToTransform.put(key, AttributeValue.builder().m(processNestedObject(value.m(), nestedSchema.get())).build());
}
}
});

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

private Map<String, AttributeValue> processNestedObject(Map<String, AttributeValue> nestedMap, TableSchema<?> nestedSchema) {
Map<String, AttributeValue> updatedNestedMap = new HashMap<>(nestedMap);
Collection customMetadataObject = nestedSchema.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);
for (Map.Entry<String, AttributeValue> entry : nestedMap.entrySet()) {
String nestedKey = entry.getKey();
AttributeValue nestedValue = entry.getValue();

if (nestedValue.hasM()) {
updatedNestedMap.put(nestedKey,
AttributeValue.builder().m(processNestedObject(nestedValue.m(), nestedSchema)).build());
} else if (nestedValue.hasL()) {
List<AttributeValue> updatedList = nestedValue.l().stream()
.map(listItem -> listItem.hasM() ?
AttributeValue.builder().m(processNestedObject(listItem.m(), nestedSchema)).build() : listItem)
.collect(Collectors.toList());
updatedNestedMap.put(nestedKey, AttributeValue.builder().l(updatedList).build());
} else {
AttributeConverter<?> converter = nestedSchema.converterForAttribute(nestedKey);
if (converter != null && customMetadataObject != null && customMetadataObject.contains(nestedKey)) {
insertTimestampInItemToTransform(updatedNestedMap, nestedKey, converter);
}
}
}
return updatedNestedMap;
}


private void insertTimestampInItemToTransform(Map<String, AttributeValue> itemToTransform,
String key,
AttributeConverter converter) {
itemToTransform.put(key, converter.transformFrom(clock.instant()));
}

private Optional<? extends TableSchema<?>> getNestedSchema(TableSchema<?> parentSchema, String attributeName) {
return parentSchema.converterForAttribute(attributeName).type().tableSchema();
}

/**
* Builder for a {@link AutoGeneratedTimestampRecordExtension}
*/
Expand Down
Loading