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

com.nimbusds.infinispan.persistence.dynamodb.RequestFactory Maven / Gradle / Ivy

There is a newer version: 7.0
Show newest version
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
		);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy