/*
* Copyright 2012-2013 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.Threading;
#if AWS_ASYNC_API
using System.Threading.Tasks;
#endif
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
namespace Amazon.DynamoDBv2.DataModel
{
///
/// Context object for using the DataModel mode of DynamoDB.
/// Used to interact with the service, save/load objects, etc.
///
public partial class DynamoDBContext : IDynamoDBContext
{
#region Private members
private bool disposed;
private bool ownClient;
internal IAmazonDynamoDB Client { get; private set; }
private Dictionary tablesMap;
private readonly object tablesMapLock = new object();
internal DynamoDBContextConfig Config { get; private set; }
internal ItemStorageConfigCache StorageConfigCache { get; private set; }
#endregion
#region Public properties
///
/// This cache is a way to store Converters for objects which provides a way to expand Context
///
public Dictionary ConverterCache { get; private set; }
#endregion
#region Constructors
#if !(PCL || UNITY || NETSTANDARD)
///
/// Constructs a DynamoDBContext object with a default AmazonDynamoDBClient
/// client and a default DynamoDBContextConfig object for configuration.
///
public DynamoDBContext()
: this(new AmazonDynamoDBClient()) { }
///
/// Constructs a DynamoDBContext object with a default AmazonDynamoDBClient
/// client and a default DynamoDBContextConfig object for configuration.
///
/// The region to configure the AmazonDynamoDBClient to use.
public DynamoDBContext(RegionEndpoint region)
: this(new AmazonDynamoDBClient(region), true, new DynamoDBContextConfig()) { }
///
/// Constructs a DynamoDBContext object with the specified configuration.
/// Uses a default AmazonDynamoDBClient as the client.
///
///
public DynamoDBContext(DynamoDBContextConfig config)
: this(new AmazonDynamoDBClient(), config) { }
///
/// Constructs a DynamoDBContext object with the specified configuration.
/// Uses a default AmazonDynamoDBClient as the client.
///
/// The region to configure the AmazonDynamoDBClient to use.
///
public DynamoDBContext(RegionEndpoint region, DynamoDBContextConfig config)
: this(new AmazonDynamoDBClient(region), true, config) { }
#endif
///
/// Constructs a DynamoDBContext object with the specified DynamoDB client.
/// Uses default DynamoDBContextConfig object for configuration.
///
/// Client to use for making calls
public DynamoDBContext(IAmazonDynamoDB client)
: this(client, false, new DynamoDBContextConfig()) { }
///
/// Constructs a DynamoDBContext object with the specified DynamoDB client
/// and configuration.
///
/// Client to use for making calls
/// Configuration to use
public DynamoDBContext(IAmazonDynamoDB client, DynamoDBContextConfig config)
: this(client, false, config) { }
private DynamoDBContext(IAmazonDynamoDB client, bool ownClient, DynamoDBContextConfig config)
{
if (client == null) throw new ArgumentNullException("client");
this.ConverterCache = new Dictionary();
#if !UNITY
this.ConverterCache.Add(typeof(S3Link), new S3Link.S3LinkConverter(this));
#endif
this.Client = client;
this.tablesMap = new Dictionary();
this.ownClient = ownClient;
this.Config = config ?? new DynamoDBContextConfig();
this.StorageConfigCache = new ItemStorageConfigCache(this);
}
#endregion
#region Dispose Pattern Implementation
///
/// Implements the Dispose pattern
///
/// Whether this object is being disposed via a call to Dispose
/// or garbage collected.
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing && Client != null)
{
if (ownClient)
{
Client.Dispose();
}
Client = null;
}
this.disposed = true;
}
}
///
/// Disposes of all managed and unmanaged resources.
///
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
///
/// The destructor for the client class.
///
~DynamoDBContext()
{
this.Dispose(false);
}
#endregion
#region Factory Creates
///
/// Creates a strongly-typed BatchGet object, allowing
/// a batch-get operation against DynamoDB.
///
/// Type of objects to get
/// Empty strongly-typed BatchGet object
public BatchGet CreateBatchGet()
{
return CreateBatchGet(null);
}
///
/// Creates a strongly-typed BatchGet object, allowing
/// a batch-get operation against DynamoDB.
///
/// Type of objects to get
/// Config object which can be used to override that table used.
/// Empty strongly-typed BatchGet object
public BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig)
{
DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config);
return new BatchGet(this, config);
}
///
/// Creates a MultiTableBatchGet object, composed of multiple
/// individual BatchGet objects.
///
/// Individual BatchGet objects
/// Composite MultiTableBatchGet object
public MultiTableBatchGet CreateMultiTableBatchGet(params BatchGet[] batches)
{
return new MultiTableBatchGet(batches);
}
///
/// Creates a strongly-typed BatchWrite object, allowing
/// a batch-write operation against DynamoDB.
///
/// Type of objects to write
/// Empty strongly-typed BatchWrite object
public BatchWrite CreateBatchWrite()
{
return CreateBatchWrite(null);
}
///
/// Creates a strongly-typed BatchWrite object, allowing
/// a batch-write operation against DynamoDB.
///
/// Type of objects to write
/// Config object which can be used to override that table used.
/// Empty strongly-typed BatchWrite object
public BatchWrite CreateBatchWrite(DynamoDBOperationConfig operationConfig)
{
DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config);
return new BatchWrite(this, config);
}
///
/// Creates a MultiTableBatchWrite object, composed of multiple
/// individual BatchWrite objects.
///
/// Individual BatchWrite objects
/// Composite MultiTableBatchWrite object
public MultiTableBatchWrite CreateMultiTableBatchWrite(params BatchWrite[] batches)
{
return new MultiTableBatchWrite(batches);
}
#endregion
#region Save/serialize
private void SaveHelper(T value, DynamoDBOperationConfig operationConfig)
{
if (value == null) return;
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorage storage = ObjectToItemStorage(value, false, flatConfig);
if (storage == null) return;
Table table = GetTargetTable(storage.Config, flatConfig);
if (
(flatConfig.SkipVersionCheck.HasValue && flatConfig.SkipVersionCheck.Value)
|| !storage.Config.HasVersion)
{
table.UpdateHelper(storage.Document, table.MakeKey(storage.Document), null);
}
else
{
Document expectedDocument = CreateExpectedDocumentForVersion(storage);
SetNewVersion(storage);
table.UpdateHelper(
storage.Document,
table.MakeKey(storage.Document),
new UpdateItemOperationConfig { Expected = expectedDocument, ReturnValues = ReturnValues.None });
PopulateInstance(storage, value, flatConfig);
}
}
#if AWS_ASYNC_API
private async Task SaveHelperAsync(T value, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken)
{
if (value == null) return;
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorage storage = ObjectToItemStorage(value, false, flatConfig);
if (storage == null) return;
Table table = GetTargetTable(storage.Config, flatConfig);
if (
(flatConfig.SkipVersionCheck.HasValue && flatConfig.SkipVersionCheck.Value)
|| !storage.Config.HasVersion)
{
await table.UpdateHelperAsync(storage.Document, table.MakeKey(storage.Document), null, cancellationToken).ConfigureAwait(false);
}
else
{
Document expectedDocument = CreateExpectedDocumentForVersion(storage);
SetNewVersion(storage);
await table.UpdateHelperAsync(
storage.Document,
table.MakeKey(storage.Document),
new UpdateItemOperationConfig { Expected = expectedDocument, ReturnValues = ReturnValues.None },
cancellationToken).ConfigureAwait(false);
PopulateInstance(storage, value, flatConfig);
}
}
#endif
///
/// Serializes an object to a Document.
///
/// Type to serialize as.
/// Object to serialize.
/// Document with attributes populated from object.
public Document ToDocument(T value)
{
return ToDocument(value, null);
}
///
/// Serializes an object to a Document.
///
/// Type to serialize as.
/// Object to serialize.
/// Config object which can be used to override the table used.
/// Document with attributes populated from object.
public Document ToDocument(T value, DynamoDBOperationConfig operationConfig)
{
if (value == null) return null;
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, Config);
ItemStorage storage = ObjectToItemStorage(value, false, flatConfig);
if (storage == null) return null;
return storage.Document;
}
#endregion
#region Load/deserialize
private T LoadHelper(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig)
{
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig);
Key key = MakeKey(hashKey, rangeKey, storageConfig, flatConfig);
return LoadHelper(key, flatConfig, storageConfig);
}
#if AWS_ASYNC_API
private Task LoadHelperAsync(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken)
{
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig);
Key key = MakeKey(hashKey, rangeKey, storageConfig, flatConfig);
return LoadHelperAsync(key, flatConfig, storageConfig, cancellationToken);
}
#endif
private T LoadHelper(T keyObject, DynamoDBOperationConfig operationConfig)
{
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig);
Key key = MakeKey(keyObject, storageConfig, flatConfig);
return LoadHelper(key, flatConfig, storageConfig);
}
#if AWS_ASYNC_API
private Task LoadHelperAsync(T keyObject, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken)
{
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig);
Key key = MakeKey(keyObject, storageConfig, flatConfig);
return LoadHelperAsync(key, flatConfig, storageConfig, cancellationToken);
}
#endif
private T LoadHelper(Key key, DynamoDBFlatConfig flatConfig, ItemStorageConfig storageConfig)
{
GetItemOperationConfig getConfig = new GetItemOperationConfig
{
ConsistentRead = flatConfig.ConsistentRead.Value,
AttributesToGet = storageConfig.AttributesToGet
};
Table table = GetTargetTable(storageConfig, flatConfig);
ItemStorage storage = new ItemStorage(storageConfig);
storage.Document = table.GetItemHelper(key, getConfig);
T instance = DocumentToObject(storage, flatConfig);
return instance;
}
#if AWS_ASYNC_API
private async Task LoadHelperAsync(Key key, DynamoDBFlatConfig flatConfig, ItemStorageConfig storageConfig, CancellationToken cancellationToken)
{
GetItemOperationConfig getConfig = new GetItemOperationConfig
{
ConsistentRead = flatConfig.ConsistentRead.Value,
AttributesToGet = storageConfig.AttributesToGet
};
Table table = GetTargetTable(storageConfig, flatConfig);
ItemStorage storage = new ItemStorage(storageConfig);
storage.Document = await table.GetItemHelperAsync(key, getConfig, cancellationToken).ConfigureAwait(false);
T instance = DocumentToObject(storage, flatConfig);
return instance;
}
#endif
///
/// Deserializes a document to an instance of type T.
///
/// Type to populate.
/// Document with properties to use.
///
/// Object of type T, populated with properties from the document.
///
public T FromDocument(Document document)
{
return FromDocument(document, null);
}
///
/// Deserializes a document to an instance of type T.
///
/// Type to populate.
/// Document with properties to use.
/// Config object which can be used to override the table used.
///
/// Object of type T, populated with properties from the document.
///
public T FromDocument(Document document, DynamoDBOperationConfig operationConfig)
{
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, Config);
return FromDocumentHelper(document, flatConfig);
}
internal T FromDocumentHelper(Document document, DynamoDBFlatConfig flatConfig)
{
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(flatConfig);
ItemStorage storage = new ItemStorage(storageConfig);
storage.Document = document;
T instance = DocumentToObject(storage, flatConfig);
return instance;
}
///
/// Deserializes a collections of documents to a collection of instances of type T.
///
/// Type to populate.
/// Documents to deserialize.
///
/// Collection of items of type T, each populated with properties from a corresponding document.
///
public IEnumerable FromDocuments(IEnumerable documents)
{
return FromDocuments(documents, null);
}
///
/// Deserializes a collections of documents to a collection of instances of type T.
///
/// Type to populate.
/// Documents to deserialize.
/// Config object which can be used to override the table used.
///
/// Collection of items of type T, each populated with properties from a corresponding document.
///
public IEnumerable FromDocuments(IEnumerable documents, DynamoDBOperationConfig operationConfig)
{
foreach (var document in documents)
{
T item = FromDocument(document, operationConfig);
yield return item;
}
}
internal IEnumerable FromDocumentsHelper(IEnumerable documents, DynamoDBFlatConfig flatConfig)
{
foreach (var document in documents)
{
T item = FromDocumentHelper(document, flatConfig);
yield return item;
}
}
#endregion
#region Delete
private void DeleteHelper(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig)
{
DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(config);
Key key = MakeKey(hashKey, rangeKey, storageConfig, config);
Table table = GetTargetTable(storageConfig, config);
table.DeleteHelper(key, null);
}
#if AWS_ASYNC_API
private Task DeleteHelperAsync(object hashKey, object rangeKey, DynamoDBOperationConfig operationConfig, CancellationToken cancellationToken)
{
DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config);
ItemStorageConfig storageConfig = StorageConfigCache.GetConfig(config);
Key key = MakeKey(hashKey, rangeKey, storageConfig, config);
Table table = GetTargetTable(storageConfig, config);
return table.DeleteHelperAsync(key, null, cancellationToken);
}
#endif
private void DeleteHelper(T value, DynamoDBOperationConfig operationConfig)
{
if (value == null) throw new ArgumentNullException("value");
DynamoDBFlatConfig flatConfig = new DynamoDBFlatConfig(operationConfig, this.Config);
flatConfig.IgnoreNullValues = true;
ItemStorage storage = ObjectToItemStorage(value, true, flatConfig);
if (storage == null) return;
Table table = GetTargetTable(storage.Config, flatConfig);
if (flatConfig.SkipVersionCheck.Value || !storage.Config.HasVersion)
{
table.DeleteHelper(table.MakeKey(storage.Document), null);
}
else
{
Document expectedDocument = CreateExpectedDocumentForVersion(storage);
table.DeleteHelper(
table.MakeKey(storage.Document),
new DeleteItemOperationConfig { Expected = expectedDocument });
}
}
#if AWS_ASYNC_API
private static readonly Task CompletedTask = Task.FromResult