com.nimbusds.infinispan.persistence.dynamodb.RequestFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of infinispan-cachestore-dynamodb Show documentation
Show all versions of infinispan-cachestore-dynamodb Show documentation
Infinispan module for persisting data to an AWS DynamoDB table
package com.nimbusds.infinispan.persistence.dynamodb;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;
import com.amazonaws.services.dynamodbv2.document.*;
import com.amazonaws.services.dynamodbv2.document.spec.DeleteItemSpec;
import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.document.spec.ScanSpec;
import com.amazonaws.services.dynamodbv2.model.*;
import org.infinispan.persistence.spi.PersistenceException;
import com.nimbusds.infinispan.persistence.common.InfinispanEntry;
/**
* DynamoDB request factory. This class is thread-safe.
*/
class RequestFactory {
/**
* The final DynamoDB table name.
*/
private final String tableName;
/**
* The DynamoDB item transformer.
*/
private final DynamoDBItemTransformer itemTransformer;
/**
* The indexed (GSI) DynamoDB table attributes.
*/
private final Set indexAttributes;
/**
* {@code true} to enable consistent reads.
*/
private final boolean consistentReads;
/**
* The provisioned DynamoDB read and write capacity.
*/
private final ProvisionedThroughput rwCapacity;
/**
* {@code true} to create the DynamoDB table with encryption at rest.
*/
private final boolean tableEncryptionAtRest;
/**
* The resolved name of the optional range key to apply to all DynamoDB
* operations, {@code null} if none.
*/
private final String rangeKeyResolvedName;
/**
* The constant value of the optional range key, overridden by the
* DynamoDB item transformer, {@code null} if none.
*/
private final String rangeKeyConstValue;
/**
* {@code true} to create the DynamoDB table with an enabled stream.
*/
private final boolean enableStream;
/**
* Creates a new DynamoDB request factory.
*
* @param itemTransformer The DynamoDB item transformer.
* @param indexAttributes The indexed DynamoDB attributes,
* {@code null} if not specified.
* @param consistentReads {@code true} to enable consistent reads.
* @param rwCapacity The provisioned read and write capacity.
* @param tablePrefix The optional DynamoDB table prefix to use,
* empty string if none.
* @param rangeKeyName The name of the optional range key to
* apply to all DynamoDB operations,
* {@code null} if not specified. Overridden
* by the DynamoDB item transformer.
* @param rangeKeyConstValue Constant value of the optional range key,
* {@code null} if not specified.
* @param encryptionAtRest {@code true} to create the DynamoDB table
* with encryption at rest.
* @param enableStream {@code true} to create the DynamoDB table
* with an enabled stream (intended for a
* global table).
*/
RequestFactory(final DynamoDBItemTransformer itemTransformer,
final Set indexAttributes,
final boolean consistentReads,
final ProvisionedThroughput rwCapacity,
final boolean encryptionAtRest,
final String tablePrefix,
final String rangeKeyName,
final String rangeKeyConstValue,
final boolean enableStream) {
assert itemTransformer != null;
this.itemTransformer = itemTransformer;
this.indexAttributes = indexAttributes;
this.consistentReads = consistentReads;
assert tablePrefix != null;
this.tableName = tablePrefix + itemTransformer.getTableName();
assert rwCapacity != null;
this.rwCapacity = rwCapacity;
this.tableEncryptionAtRest = encryptionAtRest;
if (itemTransformer.getRangeKeyAttributeName() != null) {
// Range key set by transformer, value resolved for each item
this.rangeKeyResolvedName = itemTransformer.getRangeKeyAttributeName();
this.rangeKeyConstValue = null;
} else if (rangeKeyName != null && ! rangeKeyName.trim().isEmpty()) {
// Range key with const value set by config
this.rangeKeyResolvedName = rangeKeyName;
this.rangeKeyConstValue = rangeKeyConstValue;
} else {
// No range key
this.rangeKeyResolvedName = null;
this.rangeKeyConstValue = null;
}
this.enableStream = enableStream;
}
/**
* Returns the configured DynamoDB item transformer.
*
* @return The DynamoDB item transformer.
*/
DynamoDBItemTransformer getItemTransformer() {
return itemTransformer;
}
/**
* Returns the final table name.
*
* @return The table name.
*/
String getTableName() {
return tableName;
}
/**
* Returns the resolved name of the optional range key to apply to all
* DynamoDB operations.
*
* @return The resolved name of the range key to apply to all DynamoDB
* operations, {@code null} if none.
*/
public String getRangeKeyResolvedName() {
return rangeKeyResolvedName;
}
/**
* Returns the name of the global secondary index (GSI) for the
* optional range key.
*
* @return The range key GSI name, {@code null} if no range key.
*/
String getRangeKeyGSIName() {
return rangeKeyResolvedName != null ? tableName + "-" + rangeKeyResolvedName + "-gsi" : null;
}
/**
* Returns the name of the global secondary index (GSI) for the
* specified attribute.
*
* @param attr The attribute name.
*
* @return The GSI name.
*/
String getGSIName(final String attr) {
return tableName + "-" + attr + "-gsi";
}
/**
* Returns a create table request.
*
* @return The create table request.
*/
CreateTableRequest resolveCreateTableRequest() {
Collection keyAttrs = new LinkedList<>();
keyAttrs.add(new KeySchemaElement(itemTransformer.getHashKeyAttributeName(), KeyType.HASH));
Collection attrs = new LinkedList<>();
attrs.add(new AttributeDefinition(itemTransformer.getHashKeyAttributeName(), ScalarAttributeType.S));
Collection gsIndices = new LinkedList<>();
if (rangeKeyResolvedName != null) {
keyAttrs.add(new KeySchemaElement(rangeKeyResolvedName, KeyType.RANGE));
attrs.add(new AttributeDefinition(rangeKeyResolvedName, ScalarAttributeType.S));
gsIndices.add(new GlobalSecondaryIndex()
.withIndexName(getRangeKeyGSIName())
.withKeySchema(new KeySchemaElement(rangeKeyResolvedName, KeyType.HASH))
.withProjection(new Projection().withProjectionType(ProjectionType.ALL))
.withProvisionedThroughput(rwCapacity));
}
if (indexAttributes != null) {
// indexed attributes
for (String a : indexAttributes) {
attrs.add(new AttributeDefinition(a, ScalarAttributeType.S));
gsIndices.add(new GlobalSecondaryIndex()
.withIndexName(getGSIName(a))
.withKeySchema(new KeySchemaElement(a, KeyType.HASH))
.withProjection(new Projection().withProjectionType(ProjectionType.ALL))
.withProvisionedThroughput(rwCapacity)
);
}
}
CreateTableRequest createTableRequest = new CreateTableRequest()
.withTableName(tableName)
.withProvisionedThroughput(rwCapacity)
.withSSESpecification(new SSESpecification().withEnabled(tableEncryptionAtRest))
.withKeySchema(keyAttrs)
.withAttributeDefinitions(attrs);
if (enableStream) {
createTableRequest = createTableRequest
.withStreamSpecification(
new StreamSpecification()
.withStreamEnabled(true)
.withStreamViewType(StreamViewType.NEW_AND_OLD_IMAGES));
}
if (!gsIndices.isEmpty()) {
createTableRequest.withGlobalSecondaryIndexes(gsIndices);
}
return createTableRequest;
}
/**
* Resolves the DynamoDB item primary key.
*
* @param key The Infinispan entry key. Must not be {@code null}.
*
* @return The DynamoDB item primary key.
*/
private PrimaryKey resolvePrimaryKey(final Object key) {
if (key instanceof byte[]) {
throw new PersistenceException("Cannot resolve " + itemTransformer.getTableName() + " key from byte[], enable compatibility mode");
}
@SuppressWarnings("unchecked")
PrimaryKeyValue pkValue = itemTransformer.resolvePrimaryKey((K) key);
if (rangeKeyResolvedName != null) {
if (itemTransformer.getRangeKeyAttributeName() != null) {
// Range key set by transformer
return new PrimaryKey(
itemTransformer.getHashKeyAttributeName(),
pkValue.getHashKeyValue(),
rangeKeyResolvedName,
pkValue.getRangeKeyValue()
);
}
// Range key set in config
return new PrimaryKey(
itemTransformer.getHashKeyAttributeName(),
pkValue.getHashKeyValue(),
rangeKeyResolvedName,
rangeKeyConstValue
);
}
return new PrimaryKey(itemTransformer.getHashKeyAttributeName(), pkValue.getHashKeyValue());
}
/**
* Returns the DynamoDB get item spec for the specified key.
*
* @param key The key.
*
* @return The get item spec.
*/
GetItemSpec resolveGetItemSpec(final Object key) {
return new GetItemSpec()
.withPrimaryKey(resolvePrimaryKey(key))
.withConsistentRead(consistentReads);
}
/**
* Returns the DynamoDB item for the specified Infinispan entry.
*
* @param infinispanEntry The Infinispan entry.
*
* @return The DynamoDB item.
*/
Item resolveItem(final InfinispanEntry infinispanEntry) {
Item item = itemTransformer.toItem(infinispanEntry);
item = ItemSanitization.sanitize(item);
item = applyOptionalRangeKeyConstValue(item);
return item;
}
/**
* Returns the DynamoDB delete item spec for the specified key.
*
* @param key The key.
*
* @return The delete item spec.
*/
DeleteItemSpec resolveDeleteItemSpec(final Object key) {
return new DeleteItemSpec()
.withPrimaryKey(resolvePrimaryKey(key))
.withReturnValues(ReturnValue.ALL_OLD); // to confirm deletion
}
/**
* Returns the result of a request to get all items from the specified
* DynamoDB table.
*
* @param table The DynamoDB table.
*
* @return The iterable item collection.
*/
ItemCollection> getAllItems(final Table table) {
if (rangeKeyConstValue != null) {
RangeKeyCondition c = new RangeKeyCondition(rangeKeyResolvedName).eq(rangeKeyConstValue);
return table.getIndex(getRangeKeyGSIName()).query(new QuerySpec().withRangeKeyCondition(c));
}
return table.scan(new ScanSpec());
}
/**
* Applies the optional range key constant value to the specified
* DynamoDB item (for writing).
*
* @param item The DynamoDB item.
*
* @return The resulting DynamoDB item.
*/
Item applyOptionalRangeKeyConstValue(final Item item) {
if (rangeKeyConstValue == null) {
return item;
}
Object hashKeyValue = item.get(itemTransformer.getHashKeyAttributeName());
if (hashKeyValue == null) {
throw new PersistenceException("Missing hash key in transformed DynamoDB item: " + itemTransformer.getHashKeyAttributeName());
}
return item.withPrimaryKey(
itemTransformer.getHashKeyAttributeName(),
hashKeyValue,
rangeKeyResolvedName,
rangeKeyConstValue
);
}
}