/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Globalization;
#if AWS_ASYNC_API
using System.Threading;
using System.Threading.Tasks;
#endif
using Amazon.DynamoDBv2.DocumentModel;
namespace Amazon.DynamoDBv2.DataModel
{
///
/// Represents a non-generic object for writing/deleting/version-checking multiple items
/// in a single DynamoDB table in a transaction.
///
public abstract partial class TransactWrite
{
#region Internal/protected properties
internal DynamoDBContext Context { get; set; }
internal DynamoDBFlatConfig Config { get; set; }
internal DocumentTransactWrite DocumentTransaction { get; set; }
#endregion
#region Constructor
internal TransactWrite(DynamoDBContext context, DynamoDBFlatConfig config)
{
Context = context;
Config = config;
}
#endregion
#region Protected methods
///
/// Executes a server call to write/delete/version-check the items requested in a transaction.
///
protected internal abstract void ExecuteHelper();
#if AWS_ASYNC_API
///
/// Executes an asynchronous server call to write/delete/version-check the items requested in a transaction.
///
protected internal abstract Task ExecuteHelperAsync(CancellationToken cancellationToken);
#endif
#endregion
#region Internal methods
internal abstract void PopulateObjects();
#endregion
}
///
/// Represents a strongly-typed object for writing/deleting/version-checking multiple items
/// in a single DynamoDB table in a transaction.
///
public class TransactWrite : TransactWrite
{
#region Public Combine methods
///
/// Creates a MultiTableTransactWrite object that is a combination
/// of the current TransactWrite and the specified TransactWrites.
///
/// Other TransactWrite objects.
///
/// MultiTableTransactWrite consisting of the multiple TransactWrite objects:
/// the current TransactWrite object and the passed-in TransactWrite objects.
///
public MultiTableTransactWrite Combine(params TransactWrite[] otherTransactionParts)
{
return new MultiTableTransactWrite(this, otherTransactionParts);
}
#endregion
#region Public Save methods
///
/// Add a number of items to be saved in the current transaction operation.
///
/// Items to save.
public void AddSaveItems(IEnumerable values)
{
if (values == null) return;
foreach (T item in values)
{
AddSaveItem(item);
}
}
///
/// Add a single item to be saved in the current transaction operation.
///
/// Item to save.
public void AddSaveItem(T item)
{
if (item == null) return;
ItemStorage storage = Context.ObjectToItemStorageHelper(item, StorageConfig, Config, keysOnly: false, Config.IgnoreNullValues ?? false);
if (storage == null) return;
Expression conditionExpression = CreateConditionExpressionForVersion(storage);
SetNewVersion(storage);
DocumentTransaction.AddDocumentToUpdate(storage.Document, new TransactWriteItemOperationConfig
{
ConditionalExpression = conditionExpression,
ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None
});
var objectItem = new DynamoDBContext.ObjectWithItemStorage
{
OriginalObject = item,
ItemStorage = storage
};
objectItems.Add(objectItem);
}
#endregion
#region Public Delete methods
///
/// Add a number of items to be deleted in the current transaction operation.
///
/// Items to be deleted.
public void AddDeleteItems(IEnumerable values)
{
if (values == null) return;
foreach (T item in values)
{
AddDeleteItem(item);
}
}
///
/// Add a single item to be deleted in the current transaction operation.
///
/// Item to be deleted.
public void AddDeleteItem(T item)
{
if (item == null) return;
ItemStorage storage = Context.ObjectToItemStorageHelper(item, StorageConfig, Config, keysOnly: true, Config.IgnoreNullValues ?? false);
if (storage == null) return;
Expression conditionExpression = CreateConditionExpressionForVersion(storage);
DocumentTransaction.AddItemToDelete(storage.Document, new TransactWriteItemOperationConfig
{
ConditionalExpression = conditionExpression,
ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None
});
}
///
/// Add a single item to be deleted in the current transaction operation.
/// Item is identified by its hash primary key.
///
/// Hash key of the item to delete.
public void AddDeleteKey(object hashKey)
{
AddDeleteKey(hashKey, rangeKey: null);
}
///
/// Add a single item to be deleted in the current transaction operation.
/// Item is identified by its hash-and-range primary key.
///
/// Hash key of the item to delete.
/// Range key of the item to delete.
public void AddDeleteKey(object hashKey, object rangeKey)
{
DocumentTransaction.AddKeyToDeleteHelper(Context.MakeKey(hashKey, rangeKey, StorageConfig, Config));
}
#endregion
#region Public VersionCheck methods
///
/// Add a single item to be version checked in the current transaction operation.
/// The item must have a single property marked with the DynamoDBVersionAttribute.
///
/// Item to be version checked.
public void AddVersionCheckItem(T item)
{
CheckUseVersioning();
if (item == null) return;
ItemStorage storage = Context.ObjectToItemStorageHelper(item, StorageConfig, Config, keysOnly: true, Config.IgnoreNullValues ?? false);
if (storage == null) return;
Expression conditionExpression = CreateConditionExpressionForVersion(storage);
DocumentTransaction.AddItemToConditionCheck(storage.Document, new TransactWriteItemOperationConfig
{
ConditionalExpression = conditionExpression,
ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None
});
}
///
/// Add a number of items to be version checked in the current transaction operation.
/// All items must have a single property marked with the DynamoDBVersionAttribute.
///
/// Items to be version checked.
public void AddVersionCheckItems(IEnumerable items)
{
foreach (var item in items)
{
AddVersionCheckItem(item);
}
}
///
/// Add a single item to be version checked in the current transaction operation.
/// Item is identified by its hash primary key.
///
/// Hash key of the item to be version checked.
/// Version of the item.
public void AddVersionCheckKey(object hashKey, object version)
{
AddVersionCheckKey(hashKey, rangeKey: null, version);
}
///
/// Add a single item to be version checked in the current transaction operation.
/// Item is identified by its hash-and-range primary key.
///
/// Hash key of the item to be version checked.
/// Range key of the item to be version checked.
/// Version of the item.
public void AddVersionCheckKey(object hashKey, object rangeKey, object version)
{
CheckUseVersioning();
Key key = Context.MakeKey(hashKey, rangeKey, StorageConfig, Config);
DynamoDBEntry versionEntry = Context.ToDynamoDBEntry(StorageConfig.VersionPropertyStorage, version, Config);
Primitive versionPrimitive = versionEntry?.AsPrimitive();
if (versionEntry != null && versionPrimitive == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Version property {0} must be Primitive.",
StorageConfig.VersionPropertyName));
}
ItemStorage storage = new ItemStorage(StorageConfig)
{
CurrentVersion = versionPrimitive
};
Expression conditionExpression = CreateConditionExpressionForVersion(storage);
DocumentTransaction.AddKeyToConditionCheckHelper(key,
new TransactWriteItemOperationConfig
{
ConditionalExpression = conditionExpression,
ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None
});
}
#endregion
#region Constructor
internal TransactWrite(DynamoDBContext context, DynamoDBFlatConfig config)
: base(context, config)
{
StorageConfig = context.StorageConfigCache.GetConfig(config);
Table table = Context.GetTargetTable(StorageConfig, Config);
DocumentTransaction = table.CreateTransactWrite();
}
#endregion
#region Internal/protected/private members
private readonly List objectItems = new List();
internal ItemStorageConfig StorageConfig { get; set; }
///
/// Executes a server call to write/delete/version-check the items requested in a transaction.
///
protected internal override void ExecuteHelper()
{
DocumentTransaction.ExecuteHelper();
PopulateObjects();
}
#if AWS_ASYNC_API
///
/// Executes an asynchronous server call to write/delete/version-check the items requested in a transaction.
///
protected internal override async Task ExecuteHelperAsync(CancellationToken cancellationToken)
{
await DocumentTransaction.ExecuteHelperAsync(cancellationToken).ConfigureAwait(false);
PopulateObjects();
}
#endif
internal override void PopulateObjects()
{
foreach (var objectItem in objectItems)
{
objectItem.PopulateObject(Context, Config);
}
}
private bool ShouldUseVersioning()
{
var skipVersionCheck = Config.SkipVersionCheck ?? false;
return !skipVersionCheck && StorageConfig.HasVersion;
}
private void CheckUseVersioning()
{
if (Config.SkipVersionCheck == true)
{
throw new InvalidOperationException(
"Using DynamoDBContextConfig.SkipVersionCheck property with true value is not supported for this operation.");
}
if (!StorageConfig.HasVersion)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Object {0} does not have a versioning field, which is not supported for this operation.",
typeof(T).Name));
}
}
private Expression CreateConditionExpressionForVersion(ItemStorage storage)
{
if (!ShouldUseVersioning()) return null;
var conversionConfig = new DynamoDBEntry.AttributeConversionConfig(
DocumentTransaction.TargetTable.Conversion,
DocumentTransaction.TargetTable.IsEmptyStringValueEnabled);
return DynamoDBContext.CreateConditionExpressionForVersion(storage, conversionConfig);
}
private void SetNewVersion(ItemStorage storage)
{
if (!ShouldUseVersioning()) return;
DynamoDBContext.SetNewVersion(storage);
}
#endregion
}
///
/// Class for writing/deleting/version-checking multiple items in multiple DynamoDB tables,
/// using multiple strongly-typed TransactWrite objects.
///
public partial class MultiTableTransactWrite
{
#region Private members
private readonly List allTransactionParts;
#endregion
#region Constructor
///
/// Constructs a MultiTableTransactWrite object from a number of
/// TransactWrite objects
///
/// Collection of TransactWrite objects
public MultiTableTransactWrite(params TransactWrite[] transactionParts)
{
allTransactionParts = new List(transactionParts);
}
internal MultiTableTransactWrite(TransactWrite first, params TransactWrite[] rest)
{
allTransactionParts = new List();
allTransactionParts.Add(first);
allTransactionParts.AddRange(rest);
}
#endregion
#region Public methods
///
/// Add a TransactWrite object to the multi-table transaction request.
///
/// TransactWrite to add.
public void AddTransactionPart(TransactWrite transactionPart)
{
allTransactionParts.Add(transactionPart);
}
internal void ExecuteHelper()
{
MultiTableDocumentTransactWrite transaction = new MultiTableDocumentTransactWrite();
foreach (var transactionPart in allTransactionParts)
{
transaction.AddTransactionPart(transactionPart.DocumentTransaction);
}
transaction.ExecuteHelper();
foreach (var transactionPart in allTransactionParts)
{
transactionPart.PopulateObjects();
}
}
#if AWS_ASYNC_API
internal async Task ExecuteHelperAsync(CancellationToken cancellationToken)
{
MultiTableDocumentTransactWrite transaction = new MultiTableDocumentTransactWrite();
foreach (var transactionPart in allTransactionParts)
{
transaction.AddTransactionPart(transactionPart.DocumentTransaction);
}
await transaction.ExecuteHelperAsync(cancellationToken).ConfigureAwait(false);
foreach (var transactionPart in allTransactionParts)
{
transactionPart.PopulateObjects();
}
}
#endif
#endregion
}
}