All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.enhanced.dynamodb.extensions.AtomicCounterExtension Maven / Gradle / Ivy

/*
 * Copyright 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.enhanced.dynamodb.extensions;

import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.keyRef;
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.valueRef;
import static software.amazon.awssdk.enhanced.dynamodb.internal.update.UpdateExpressionUtils.ifNotExists;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAtomicCounter;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AtomicCounterTag;
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.AtomicCounter;
import software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.update.SetAction;
import software.amazon.awssdk.enhanced.dynamodb.update.UpdateExpression;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.utils.CollectionUtils;

/**
 * This extension enables atomic counter attributes to be written to the database.
 * The extension is loaded by default when you instantiate a
 * {@link DynamoDbEnhancedClient} and only needs to be added to the client if you
 * are adding custom extensions to the client.
 * 

* To utilize atomic counters, first create a field in your model that will be used to store the counter. * This class field should of type {@link Long} and you need to tag it as an atomic counter: *

    *
  • If you are using the * {@link BeanTableSchema}, you should annotate with * {@link DynamoDbAtomicCounter}
  • *
  • If you are using the {@link StaticTableSchema}, * use the {@link StaticAttributeTags#atomicCounter()} static attribute tag.
  • *
*

* Every time a new update of the record is successfully written to the database, the counter will be updated automatically. * By default, the counter starts at 0 and increments by 1 for each update. The tags provide the capability of adjusting * the counter start and increment/decrement values such as described in * {@link DynamoDbAtomicCounter}. *

* Example 1: Using a bean based table schema *

 * {@code
 * @DynamoDbBean
 * public class CounterRecord {
 *     @DynamoDbAtomicCounter(delta = 5, startValue = 10)
 *     public Long getCustomCounter() {
 *         return customCounter;
 *     }
 * }
 * }
 * 
*

* Example 2: Using a static table schema *

 * {@code
 *     private static final StaticTableSchema ITEM_MAPPER =
 *         StaticTableSchema.builder(AtomicCounterItem.class)
 *                          .newItemSupplier(AtomicCounterItem::new)
 *                          .addAttribute(Long.class, a -> a.name("defaultCounter")
 *                                                          .getter(AtomicCounterItem::getDefaultCounter)
 *                                                          .setter(AtomicCounterItem::setDefaultCounter)
 *                                                          .addTag(StaticAttributeTags.atomicCounter()))
 *                          .build();
 * }
 * 
*

* NOTE: When using putItem, the counter will be reset to its start value. */ @SdkPublicApi public final class AtomicCounterExtension implements DynamoDbEnhancedClientExtension { private AtomicCounterExtension() { } public static AtomicCounterExtension.Builder builder() { return new AtomicCounterExtension.Builder(); } /** * @param context The {@link DynamoDbExtensionContext.BeforeWrite} context containing the state of the execution. * @return WriteModification contains an update expression representing the counters. */ @Override public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) { Map counters = AtomicCounterTag.resolve(context.tableMetadata()); WriteModification.Builder modificationBuilder = WriteModification.builder(); if (CollectionUtils.isNullOrEmpty(counters)) { return modificationBuilder.build(); } switch (context.operationName()) { case PUT_ITEM: modificationBuilder.transformedItem(addToItem(counters, context.items())); break; case UPDATE_ITEM: modificationBuilder.updateExpression(createUpdateExpression(counters)); break; default: break; } return modificationBuilder.build(); } private UpdateExpression createUpdateExpression(Map counters) { return UpdateExpression.builder() .actions(counters.entrySet().stream().map(this::counterAction).collect(Collectors.toList())) .build(); } private Map addToItem(Map counters, Map items) { Map itemToTransform = new HashMap<>(items); counters.forEach((attribute, counter) -> itemToTransform.put(attribute, attributeValue(counter.startValue().value()))); return Collections.unmodifiableMap(itemToTransform); } private SetAction counterAction(Map.Entry e) { String attributeName = e.getKey(); AtomicCounter counter = e.getValue(); String startValueName = attributeName + counter.startValue().name(); String deltaValueName = attributeName + counter.delta().name(); String valueExpression = ifNotExists(attributeName, startValueName) + " + " + valueRef(deltaValueName); AttributeValue startValue = attributeValue(counter.startValue().value() - counter.delta().value()); AttributeValue deltaValue = attributeValue(counter.delta().value()); return SetAction.builder() .path(keyRef(attributeName)) .value(valueExpression) .putExpressionName(keyRef(attributeName), attributeName) .putExpressionValue(valueRef(startValueName), startValue) .putExpressionValue(valueRef(deltaValueName), deltaValue) .build(); } private AttributeValue attributeValue(long value) { return AtomicCounter.CounterAttribute.resolvedValue(value); } public static final class Builder { private Builder() { } public AtomicCounterExtension build() { return new AtomicCounterExtension(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy