下面列出了怎么用com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue的API类实例代码及写法,或者点击链接到github查看源代码。
@Override
public void create(Entry entry) {
readWriteLock.writeLock().lock();
try {
Map<String, AttributeValue> keys = createKey(entry);
Map<String, AttributeValueUpdate> attributes = createAttributes(entry);
Map<String, ExpectedAttributeValue> expected = expectNotExists();
try {
executeUpdate(keys, attributes, expected);
} catch (ConditionalCheckFailedException e) {
throw new AlreadyExistsException("DynamoDB store entry already exists:" + keys.toString());
}
} finally {
readWriteLock.writeLock().unlock();
}
}
@Override
public void update(Entry entry, Entry existingEntry) {
readWriteLock.writeLock().lock();
try {
Map<String, AttributeValue> keys = createKey(entry);
Map<String, AttributeValueUpdate> attributes = createAttributes(entry);
Map<String, ExpectedAttributeValue> expected = expectExists(existingEntry);
try {
executeUpdate(keys, attributes, expected);
} catch (ConditionalCheckFailedException e) {
throw new DoesNotExistException("Precondition to update entry in DynamoDB failed:" + keys.toString());
}
} finally {
readWriteLock.writeLock().unlock();
}
}
private void addExpectedValueIfPresent(final StaticBuffer column, final Map<String, ExpectedAttributeValue> expectedValueMap) {
final String dynamoDbColumn = encodeKeyBuffer(column);
if (expectedValueMap.containsKey(dynamoDbColumn)) {
return;
}
if (transaction.contains(store, key, column)) {
final StaticBuffer expectedValue = transaction.get(store, key, column);
final ExpectedAttributeValue expectedAttributeValue;
if (expectedValue == null) {
expectedAttributeValue = new ExpectedAttributeValue().withExists(false);
} else {
final AttributeValue attributeValue = encodeValue(expectedValue);
expectedAttributeValue = new ExpectedAttributeValue().withValue(attributeValue)
.withComparisonOperator(ComparisonOperator.EQ);
}
expectedValueMap.put(dynamoDbColumn, expectedAttributeValue);
}
}
/**
* Create a new MetaStore with specified table name and extra data supplier.
*
* @param ddb Interface for accessing DynamoDB.
* @param tableName DynamoDB table name for this {@link MetaStore}.
* @param encryptor used to perform crypto operations on the record attributes
* @param extraDataSupplier provides extra data that should be stored along with the material.
*/
public MetaStore(final AmazonDynamoDB ddb, final String tableName,
final DynamoDBEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) {
this.ddb = checkNotNull(ddb, "ddb must not be null");
this.tableName = checkNotNull(tableName, "tableName must not be null");
this.encryptor = checkNotNull(encryptor, "encryptor must not be null");
this.extraDataSupplier = checkNotNull(extraDataSupplier, "extraDataSupplier must not be null");
this.ddbCtx = new EncryptionContext.Builder().withTableName(this.tableName)
.withHashKeyName(DEFAULT_HASH_KEY).withRangeKeyName(DEFAULT_RANGE_KEY).build();
final Map<String, ExpectedAttributeValue> tmpExpected = new HashMap<>();
tmpExpected.put(DEFAULT_HASH_KEY, new ExpectedAttributeValue().withExists(false));
tmpExpected.put(DEFAULT_RANGE_KEY, new ExpectedAttributeValue().withExists(false));
doesNotExist = Collections.unmodifiableMap(tmpExpected);
this.doNotEncrypt = getSignedOnlyFields(extraDataSupplier);
}
/**
* Inserts a new transaction item into the table. Assumes txKey is already initialized.
* @return the txItem
* @throws TransactionException if the transaction already exists
*/
private Map<String, AttributeValue> insert() {
Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
item.put(AttributeName.STATE.toString(), new AttributeValue(STATE_PENDING));
item.put(AttributeName.VERSION.toString(), new AttributeValue().withN(Integer.toString(1)));
item.put(AttributeName.DATE.toString(), txManager.getCurrentTimeAttribute());
item.putAll(txKey);
Map<String, ExpectedAttributeValue> expectNotExists = new HashMap<String, ExpectedAttributeValue>(2);
expectNotExists.put(AttributeName.TXID.toString(), new ExpectedAttributeValue(false));
expectNotExists.put(AttributeName.STATE.toString(), new ExpectedAttributeValue(false));
PutItemRequest request = new PutItemRequest()
.withTableName(txManager.getTransactionTableName())
.withItem(item)
.withExpected(expectNotExists);
try {
txManager.getClient().putItem(request);
return item;
} catch (ConditionalCheckFailedException e) {
throw new TransactionException("Failed to create new transaction with id " + txId, e);
}
}
/**
* Checks a map of expected values against a map of actual values in a way
* that's compatible with how the DynamoDB service interprets the Expected
* parameter of PutItem, UpdateItem and DeleteItem.
*
* @param expectedValues
* A description of the expected values.
* @param item
* The actual values.
* @throws ConditionalCheckFailedException
* Thrown if the values do not match the expected values.
*/
public static void checkExpectedValues(Map<String, ExpectedAttributeValue> expectedValues, Map<String, AttributeValue> item) {
for (Map.Entry<String, ExpectedAttributeValue> entry : expectedValues.entrySet()) {
// if the attribute is expected to exist (null for isExists means
// true)
if ((entry.getValue().isExists() == null || entry.getValue().isExists() == true)
// but the item doesn't
&& (item == null
// or the attribute doesn't
|| item.get(entry.getKey()) == null
// or it doesn't have the expected value
|| !expectedValueMatches(entry.getValue().getValue(), item.get(entry.getKey())))) {
throw new ConditionalCheckFailedException(
"expected attribute(s) " + expectedValues
+ " but found " + item);
} else if (entry.getValue().isExists() != null
&& !entry.getValue().isExists()
&& item != null && item.get(entry.getKey()) != null) {
// the attribute isn't expected to exist, but the item exists
// and the attribute does too
throw new ConditionalCheckFailedException(
"expected attribute(s) " + expectedValues
+ " but found " + item);
}
}
}
private void executeUpdate(Map<String, AttributeValue> keys, Map<String, AttributeValueUpdate> attributes, Map<String, ExpectedAttributeValue> expected) {
UpdateItemRequest updateEntry = new UpdateItemRequest()
.withTableName(tableName)
.withKey(keys)
.withAttributeUpdates(attributes)
.withExpected(expected);
client.updateItem(updateEntry);
}
private Map<String, ExpectedAttributeValue> expectExists(Entry entry) {
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
ExpectedAttributeValue expectedAttributeValue = new ExpectedAttributeValue(true);
expectedAttributeValue.setValue(new AttributeValue(getPartitionKeyValue(entry)));
expected.put(partitionKeyName.toString(), expectedAttributeValue);
// FIXME: hardcode whole file, or make generic
ExpectedAttributeValue expectedSha = new ExpectedAttributeValue(true);
expectedSha.setValue(new AttributeValue(sha(entry)));
expected.put(OPTIMISTIC_LOCK_FIELD_NAME, expectedSha);
return expected;
}
@Override
public Collection<MutateWorker> createMutationWorkers(final Map<StaticBuffer, KCVMutation> mutationMap, final DynamoDbStoreTransaction txh) {
final List<MutateWorker> workers = Lists.newLinkedList();
for (Map.Entry<StaticBuffer, KCVMutation> entry : mutationMap.entrySet()) {
final StaticBuffer hashKey = entry.getKey();
final KCVMutation mutation = entry.getValue();
final Map<String, AttributeValue> key = new ItemBuilder().hashKey(hashKey)
.build();
// Using ExpectedAttributeValue map to handle large mutations in a single request
// Large mutations would require multiple requests using expressions
final Map<String, ExpectedAttributeValue> expected =
new SingleExpectedAttributeValueBuilder(this, txh, hashKey).build(mutation);
final Map<String, AttributeValueUpdate> attributeValueUpdates =
new SingleUpdateBuilder().deletions(mutation.getDeletions())
.additions(mutation.getAdditions())
.build();
final UpdateItemRequest request = super.createUpdateItemRequest()
.withKey(key)
.withReturnValues(ReturnValue.ALL_NEW)
.withAttributeUpdates(attributeValueUpdates)
.withExpected(expected);
final MutateWorker worker;
if (mutation.hasDeletions() && !mutation.hasAdditions()) {
worker = new SingleUpdateWithCleanupWorker(request, client.getDelegate());
} else {
worker = new UpdateItemWorker(request, client.getDelegate());
}
workers.add(worker);
}
return workers;
}
public Map<String, ExpectedAttributeValue> build(final KCVMutation mutation) {
final Map<String, ExpectedAttributeValue> expected = Maps.newHashMapWithExpectedSize(mutation.getTotalMutations());
for (Entry addedColumn : mutation.getAdditions()) {
final StaticBuffer columnKey = addedColumn.getColumn();
addExpectedValueIfPresent(columnKey, expected);
}
for (StaticBuffer deletedKey : mutation.getDeletions()) {
addExpectedValueIfPresent(deletedKey, expected);
}
return expected;
}
/**
* Releases the lock for the item. If the item was inserted only to acquire the lock (if the item didn't exist before
* for a DeleteItem or LockItem), it will be deleted now.
*
* Otherwise, all of the attributes uses for the transaction (tx id, transient flag, applied flag) will be removed.
*
* Conditions on our transaction id owning the item
*
* To be used once the transaction has committed only.
* @param request
*/
protected void unlockItemAfterCommit(Request request) {
try {
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>();
expected.put(AttributeName.TXID.toString(), new ExpectedAttributeValue().withValue(new AttributeValue(txId)));
if(request instanceof PutItem || request instanceof UpdateItem) {
Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
updates.put(AttributeName.TXID.toString(), new AttributeValueUpdate().withAction(AttributeAction.DELETE));
updates.put(AttributeName.TRANSIENT.toString(), new AttributeValueUpdate().withAction(AttributeAction.DELETE));
updates.put(AttributeName.APPLIED.toString(), new AttributeValueUpdate().withAction(AttributeAction.DELETE));
updates.put(AttributeName.DATE.toString(), new AttributeValueUpdate().withAction(AttributeAction.DELETE));
UpdateItemRequest update = new UpdateItemRequest()
.withTableName(request.getTableName())
.withKey(request.getKey(txManager))
.withAttributeUpdates(updates)
.withExpected(expected);
txManager.getClient().updateItem(update);
} else if(request instanceof DeleteItem) {
DeleteItemRequest delete = new DeleteItemRequest()
.withTableName(request.getTableName())
.withKey(request.getKey(txManager))
.withExpected(expected);
txManager.getClient().deleteItem(delete);
} else if(request instanceof GetItem) {
releaseReadLock(request.getTableName(), request.getKey(txManager));
} else {
throw new TransactionAssertionException(txId, "Unknown request type: " + request.getClass());
}
} catch (ConditionalCheckFailedException e) {
// ignore, unlock already happened
// TODO if we really want to be paranoid we could condition on applied = 1, and then here
// we would have to read the item again and make sure that applied was 1 if we owned the lock (and assert otherwise)
}
}
protected static void releaseReadLock(String txId, TransactionManager txManager, String tableName, Map<String, AttributeValue> key) {
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>();
expected.put(AttributeName.TXID.toString(), new ExpectedAttributeValue().withValue(new AttributeValue(txId)));
expected.put(AttributeName.TRANSIENT.toString(), new ExpectedAttributeValue().withExists(false));
expected.put(AttributeName.APPLIED.toString(), new ExpectedAttributeValue().withExists(false));
try {
Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>(1);
updates.put(AttributeName.TXID.toString(), new AttributeValueUpdate().withAction(AttributeAction.DELETE));
updates.put(AttributeName.DATE.toString(), new AttributeValueUpdate().withAction(AttributeAction.DELETE));
UpdateItemRequest update = new UpdateItemRequest()
.withTableName(tableName)
.withAttributeUpdates(updates)
.withKey(key)
.withExpected(expected);
txManager.getClient().updateItem(update);
} catch (ConditionalCheckFailedException e) {
try {
expected.put(AttributeName.TRANSIENT.toString(), new ExpectedAttributeValue().withValue(new AttributeValue().withS(BOOLEAN_TRUE_ATTR_VAL)));
DeleteItemRequest delete = new DeleteItemRequest()
.withTableName(tableName)
.withKey(key)
.withExpected(expected);
txManager.getClient().deleteItem(delete);
} catch (ConditionalCheckFailedException e1) {
// Ignore, means it was definitely rolled back
// Re-read to ensure that it wasn't applied
Map<String, AttributeValue> item = getItem(txManager, tableName, key);
txAssert(! (item != null && txId.equals(getOwner(item)) && item.containsKey(AttributeName.APPLIED.toString())),
"Item should not have been applied. Unable to release lock", "item", item);
}
}
}
/**
* Saves the old copy of the item. Does not mutate the item, unless an exception is thrown.
*
* @param item
* @param rid
*/
public void saveItemImage(Map<String, AttributeValue> item, int rid) {
txAssert(! item.containsKey(AttributeName.APPLIED.toString()), txId, "The transaction has already applied this item image, it should not be saving over the item image with it");
AttributeValue existingTxId = item.put(AttributeName.TXID.toString(), new AttributeValue(txId));
if(existingTxId != null && ! txId.equals(existingTxId.getS())) {
throw new TransactionException(txId, "Items in transactions may not contain the attribute named " + AttributeName.TXID.toString());
}
// Don't save over the already saved item. Prevents us from saving the applied image instead of the previous image in the case
// of a re-drive.
// If we want to be extremely paranoid, we could expect every attribute to be set exactly already in a second write step, and assert
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>(1);
expected.put(AttributeName.IMAGE_ID.toString(), new ExpectedAttributeValue().withExists(false));
AttributeValue existingImageId = item.put(AttributeName.IMAGE_ID.toString(), new AttributeValue(txId + "#" + rid));
if(existingImageId != null) {
throw new TransactionException(txId, "Items in transactions may not contain the attribute named " + AttributeName.IMAGE_ID.toString() + ", value was already " + existingImageId);
}
// TODO failures? Size validation?
try {
txManager.getClient().putItem(new PutItemRequest()
.withTableName(txManager.getItemImageTableName())
.withExpected(expected)
.withItem(item));
} catch (ConditionalCheckFailedException e) {
// Already was saved
}
// do not mutate the item for the customer unless if there aren't exceptions
item.remove(AttributeName.IMAGE_ID.toString());
}
/**
* Marks the transaction item as either COMMITTED or ROLLED_BACK, but only if it was in the PENDING state.
* It will also condition on the expected version.
*
* @param targetState
* @param expectedVersion
* @throws ConditionalCheckFailedException if the transaction doesn't exist, isn't PENDING, is finalized,
* or the expected version doesn't match (if specified)
*/
public void finish(final State targetState, final int expectedVersion) throws ConditionalCheckFailedException {
txAssert(State.COMMITTED.equals(targetState) || State.ROLLED_BACK.equals(targetState),"Illegal state in finish(): " + targetState, "txItem", txItem);
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>(2);
expected.put(AttributeName.STATE.toString(), new ExpectedAttributeValue().withValue(new AttributeValue().withS(STATE_PENDING)));
expected.put(AttributeName.FINALIZED.toString(), new ExpectedAttributeValue().withExists(false));
expected.put(AttributeName.VERSION.toString(), new ExpectedAttributeValue().withValue(new AttributeValue().withN(Integer.toString(expectedVersion))));
Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
updates.put(AttributeName.STATE.toString(), new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue(stateToString(targetState))));
updates.put(AttributeName.DATE.toString(), new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(txManager.getCurrentTimeAttribute()));
UpdateItemRequest finishRequest = new UpdateItemRequest()
.withTableName(txManager.getTransactionTableName())
.withKey(txKey)
.withAttributeUpdates(updates)
.withReturnValues(ReturnValue.ALL_NEW)
.withExpected(expected);
UpdateItemResult finishResult = txManager.getClient().updateItem(finishRequest);
txItem = finishResult.getAttributes();
if(txItem == null) {
throw new TransactionAssertionException(txId, "Unexpected null tx item after committing " + targetState);
}
}
/**
* Completes a transaction by marking its "Finalized" attribute. This leaves the completed transaction item around
* so that the party who created the transaction can see whether it was completed or rolled back. They can then either
* delete the transaction record when they're done, or they can run a sweeper process to go and delete the completed transactions
* later on.
*
* @param expectedCurrentState
* @throws ConditionalCheckFailedException if the transaction is completed, doesn't exist anymore, or even if it isn't committed or rolled back
*/
public void complete(final State expectedCurrentState) throws ConditionalCheckFailedException {
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>(2);
if(State.COMMITTED.equals(expectedCurrentState)) {
expected.put(AttributeName.STATE.toString(), new ExpectedAttributeValue(new AttributeValue(STATE_COMMITTED)));
} else if(State.ROLLED_BACK.equals(expectedCurrentState)) {
expected.put(AttributeName.STATE.toString(), new ExpectedAttributeValue(new AttributeValue(STATE_ROLLED_BACK)));
} else {
throw new TransactionAssertionException(txId, "Illegal state in finish(): " + expectedCurrentState + " txItem " + txItem);
}
Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
updates.put(AttributeName.FINALIZED.toString(), new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue(Transaction.BOOLEAN_TRUE_ATTR_VAL)));
updates.put(AttributeName.DATE.toString(), new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(txManager.getCurrentTimeAttribute()));
UpdateItemRequest completeRequest = new UpdateItemRequest()
.withTableName(txManager.getTransactionTableName())
.withKey(txKey)
.withAttributeUpdates(updates)
.withReturnValues(ReturnValue.ALL_NEW)
.withExpected(expected);
txItem = txManager.getClient().updateItem(completeRequest).getAttributes();
}
/**
* Deletes the tx item, only if it was in the "finalized" state.
*
* @throws ConditionalCheckFailedException if the item does not exist or is not finalized
*/
public void delete() throws ConditionalCheckFailedException {
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>(1);
expected.put(AttributeName.FINALIZED.toString(), new ExpectedAttributeValue().withValue(new AttributeValue(Transaction.BOOLEAN_TRUE_ATTR_VAL)));
DeleteItemRequest completeRequest = new DeleteItemRequest()
.withTableName(txManager.getTransactionTableName())
.withKey(txKey)
.withExpected(expected);
txManager.getClient().deleteItem(completeRequest);
}
/**
* Returns a new copy of Map that can be used in a write on the item to ensure it does not exist
* @param txManager
* @return a map for use in an expected clause to ensure the item does not exist
*/
@JsonIgnore
protected Map<String, ExpectedAttributeValue> getExpectNotExists(TransactionManager txManager) {
Map<String, AttributeValue> key = getKey(txManager);
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>(key.size());
for(Map.Entry<String, AttributeValue> entry : key.entrySet()) {
expected.put(entry.getKey(), new ExpectedAttributeValue().withExists(false));
}
return expected;
}
/**
* Returns a new copy of Map that can be used in a write on the item to ensure it exists
* @param txManager
* @return a map for use in an expected clause to ensure the item exists
*/
@JsonIgnore
protected Map<String, ExpectedAttributeValue> getExpectExists(TransactionManager txManager) {
Map<String, AttributeValue> key = getKey(txManager);
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>(key.size());
for(Map.Entry<String, AttributeValue> entry : key.entrySet()) {
expected.put(entry.getKey(), new ExpectedAttributeValue().withValue(entry.getValue()));
}
return expected;
}
@Override
public DeleteItemResult deleteItem(DeleteItemRequest request)
throws AmazonServiceException, AmazonClientException {
Map<String, ExpectedAttributeValue> expectedValues = request.getExpected();
checkExpectedValues(request.getTableName(), request.getKey(), expectedValues);
// conditional checks are handled by the above call
request.setExpected(null);
return txn.deleteItem(request);
}
@Override
public PutItemResult putItem(PutItemRequest request)
throws AmazonServiceException, AmazonClientException {
Map<String, ExpectedAttributeValue> expectedValues = request.getExpected();
checkExpectedValues(request.getTableName(), Request.getKeyFromItem(request.getTableName(),
request.getItem(), txManager), expectedValues);
// conditional checks are handled by the above call
request.setExpected(null);
return txn.putItem(request);
}
@Override
public UpdateItemResult updateItem(UpdateItemRequest request)
throws AmazonServiceException, AmazonClientException {
Map<String, ExpectedAttributeValue> expectedValues = request.getExpected();
checkExpectedValues(request.getTableName(), request.getKey(), expectedValues);
// conditional checks are handled by the above call
request.setExpected(null);
return txn.updateItem(request);
}
private void checkExpectedValues(String tableName,
Map<String, AttributeValue> itemKey,
Map<String, ExpectedAttributeValue> expectedValues) {
if (expectedValues != null && !expectedValues.isEmpty()) {
for (Map.Entry<String, ExpectedAttributeValue> entry : expectedValues.entrySet()) {
if ((entry.getValue().isExists() == null || entry.getValue().isExists() == true)
&& entry.getValue().getValue() == null) {
throw new IllegalArgumentException("An explicit value is required when Exists is null or true, "
+ "but none was found in expected values for item with key " + itemKey +
": " + expectedValues);
}
}
// simulate by loading the item and checking the values;
// this also has the effect of locking the item, which gives the
// same behavior
GetItemResult result = getItem(new GetItemRequest()
.withAttributesToGet(expectedValues.keySet())
.withKey(itemKey)
.withTableName(tableName));
Map<String, AttributeValue> item = result.getItem();
try {
checkExpectedValues(expectedValues, item);
} catch (ConditionalCheckFailedException e) {
throw new ConditionalCheckFailedException("Item " + itemKey + " had unexpected attributes: " + e.getMessage());
}
}
}
@Test
public void testCheckExpectedStringValueWithMatchingItem() {
Map<String, AttributeValue> item = Collections.singletonMap("Foo", new AttributeValue("Bar"));
Map<String, ExpectedAttributeValue> expected = Collections.singletonMap("Foo", new ExpectedAttributeValue(new AttributeValue("Bar")));
TransactionDynamoDBFacade.checkExpectedValues(expected, item);
// no exception expected
}
@Test
public void testCheckExpectedBinaryValueWithMatchingItem() {
Map<String, AttributeValue> item = Collections.singletonMap("Foo", new AttributeValue().withB(ByteBuffer.wrap(new byte[] { 1, 127, -127 })));
Map<String, ExpectedAttributeValue> expected = Collections.singletonMap("Foo", new ExpectedAttributeValue(new AttributeValue().withB(ByteBuffer.wrap(new byte[] { 1, 127, -127 }))));
TransactionDynamoDBFacade.checkExpectedValues(expected, item);
// no exception expected
}
@Test
public void testCheckExpectedNumericValueWithMatchingItem() {
Map<String, AttributeValue> item = Collections.singletonMap("Foo", new AttributeValue().withN("3.14"));
Map<String, ExpectedAttributeValue> expected = Collections.singletonMap("Foo", new ExpectedAttributeValue(new AttributeValue().withN("3.14")));
TransactionDynamoDBFacade.checkExpectedValues(expected, item);
// no exception expected
}
@Test
public void testCheckExpectedNumericValueWithMatchingNotStringEqualItem() {
Map<String, AttributeValue> item = Collections.singletonMap("Foo", new AttributeValue().withN("3.140"));
Map<String, ExpectedAttributeValue> expected = Collections.singletonMap("Foo", new ExpectedAttributeValue(new AttributeValue().withN("3.14")));
TransactionDynamoDBFacade.checkExpectedValues(expected, item);
// no exception expected
}
private Map<String, ExpectedAttributeValue> expectNotExists() {
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
expected.put(partitionKeyName.toString(), new ExpectedAttributeValue(false));
return expected;
}
private UpdateItemRequest constructUpdateItemRequest(RawSecretEntry rawSecretEntry, boolean expectExists, Optional<RawSecretEntry> expectedRawSecretEntry) {
// Create item key.
Map<String, AttributeValue> key = new HashMap<>();
key.put(KEY_ATTRIBUTE_NAME.toString(), new AttributeValue().withS(rawSecretEntry.secretIdentifier.name));
key.put(VERSION_ATTRIBUTE_NAME.toString(), new AttributeValue().withN(String.valueOf(rawSecretEntry.version)));
// Create item attributes.
Map<String, AttributeValueUpdate> attributes = new HashMap<>();
attributes.put(SCHEMA_VERSION_FIELD_NAME,
new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue()
.withN(SCHEMA_VERSION)));
attributes.put(NOT_BEFORE_ATTRIBUTE_NAME.toString(),
new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue()
.withN(FormattedTimestamp.epoch(rawSecretEntry.notBefore.get()).toString())));
attributes.put(STATE_ATTRIBUTE_NAME.toString(),
new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue()
.withN(Byte.toString(rawSecretEntry.state.asByte()))));
attributes.put(VALUE_ATTRIBUTE_NAME.toString(),
new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue()
.withS(Encoder.base64encode(rawSecretEntry.encryptedPayload))));
attributes.put(OPTIMISTIC_LOCKING_ATTRIBUTE_NAME,
new AttributeValueUpdate()
.withAction(AttributeAction.PUT)
.withValue(new AttributeValue()
.withS(Encoder.base64encode(rawSecretEntry.sha1OfEncryptionPayload()))));
// Create the expected conditions map.
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
if (expectExists) {
expected.put(KEY_ATTRIBUTE_NAME.toString(), new ExpectedAttributeValue(true).withValue(
new AttributeValue(rawSecretEntry.secretIdentifier.name)));
expected.put(OPTIMISTIC_LOCKING_ATTRIBUTE_NAME, new ExpectedAttributeValue(true).withValue(
new AttributeValue(Encoder.sha1(expectedRawSecretEntry.get().encryptedPayload))));
} else {
expected.put(KEY_ATTRIBUTE_NAME.toString(), new ExpectedAttributeValue(false));
}
return new UpdateItemRequest(tableName, key, attributes).withExpected(expected);
}
/**
* Adds a request object (input param) to the transaction item. Enforces that request are unique for a given table name and primary key.
* Doesn't let you do more than one write per item. However you can upgrade a read lock to a write.
* @param callerRequest
* @throws ConditionalCheckFailedException if the tx item changed out from under us. If you get this you must throw this TransactionItem away.
* @throws DuplicateRequestException If you get this you do not need to throw away the item.
* @throws InvalidRequestException If the request would add too much data to the transaction
* @return true if the request was added, false if it didn't need to be added (because it was a duplicate lock request)
*/
public synchronized boolean addRequest(Request callerRequest) throws ConditionalCheckFailedException, DuplicateRequestException {
// 1. Ensure the request is unique (modifies the internal data structure if it is unique)
// However, do not not short circuit. If we're doing a read in a resumed transaction, it's important to ensure we're returning
// any writes that happened before.
addRequestToMap(callerRequest);
callerRequest.setRid(version);
// 2. Write request to transaction item
ByteBuffer requestBytes = Request.serialize(txId, callerRequest);
AttributeValueUpdate txItemUpdate = new AttributeValueUpdate()
.withAction(AttributeAction.ADD)
.withValue(new AttributeValue().withBS(Arrays.asList(requestBytes)));
Map<String, AttributeValueUpdate> txItemUpdates = new HashMap<String, AttributeValueUpdate>();
txItemUpdates.put(AttributeName.REQUESTS.toString(), txItemUpdate);
txItemUpdates.put(AttributeName.VERSION.toString(), new AttributeValueUpdate().withAction(AttributeAction.ADD).withValue(new AttributeValue().withN("1")));
txItemUpdates.put(AttributeName.DATE.toString(), new AttributeValueUpdate().withAction(AttributeAction.PUT).withValue(txManager.getCurrentTimeAttribute()));
Map<String, ExpectedAttributeValue> expected = new HashMap<String, ExpectedAttributeValue>();
expected.put(AttributeName.STATE.toString(), new ExpectedAttributeValue(new AttributeValue(STATE_PENDING)));
expected.put(AttributeName.VERSION.toString(), new ExpectedAttributeValue(new AttributeValue().withN(Integer.toString(version))));
UpdateItemRequest txItemUpdateRequest = new UpdateItemRequest()
.withTableName(txManager.getTransactionTableName())
.withKey(txKey)
.withExpected(expected)
.withReturnValues(ReturnValue.ALL_NEW)
.withAttributeUpdates(txItemUpdates);
try {
txItem = txManager.getClient().updateItem(txItemUpdateRequest).getAttributes();
int newVersion = Integer.parseInt(txItem.get(AttributeName.VERSION.toString()).getN());
txAssert(newVersion == version + 1, txId, "Unexpected version number from update result");
version = newVersion;
} catch (AmazonServiceException e) {
if("ValidationException".equals(e.getErrorCode())) {
removeRequestFromMap(callerRequest);
throw new InvalidRequestException("The amount of data in the transaction cannot exceed the DynamoDB item size limit",
txId, callerRequest.getTableName(), callerRequest.getKey(txManager), callerRequest);
} else {
throw e;
}
}
return true;
}