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

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

package com.nimbusds.infinispan.persistence.dynamodb;


import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;

import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import com.amazonaws.services.dynamodbv2.document.RangeKeyCondition;
import com.amazonaws.services.dynamodbv2.document.Table;
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 com.nimbusds.infinispan.persistence.common.InfinispanEntry;
import org.apache.commons.lang3.StringUtils;
import org.infinispan.persistence.spi.PersistenceException;


/**
 * DynamoDB request factory. This class is thread-safe.
 */
class RequestFactory {
	
	
	/**
	 * The 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;
	
	
	/**
	 * The DynamoDB provisioned read and write capacity.
	 */
	private final ProvisionedThroughput rwCapacity;
	
	
	/**
	 * The name of the optional range key to apply to all DynamoDB
	 * operations.
	 */
	private final String rangeKeyName;
	
	
	/**
	 * The value of the optional range key.
	 */
	private final String rangeKeyValue;
	
	
	/**
	 * Determines if the optional range key must be applied to all DynamoDB
	 * operations.
	 */
	private final boolean applyRangeKey;
	
	
	/**
	 * Creates a new DynamoDB request factory.
	 *
	 * @param itemTransformer The DynamoDB item transformer.
	 * @param indexAttributes The indexed DynamoDB attributes, {@code null}
	 *                        if not specified.
	 * @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.
	 * @param rangeKeyValue   The value of the optional range key, {@code
	 *                        null} if not specified.
	 */
	RequestFactory(final DynamoDBItemTransformer itemTransformer,
		       final Set indexAttributes,
		       final ProvisionedThroughput rwCapacity,
		       final String tablePrefix,
		       final String rangeKeyName,
		       final String rangeKeyValue) {
		
		assert itemTransformer != null;
		this.itemTransformer = itemTransformer;
		
		this.indexAttributes = indexAttributes;
		
		assert tablePrefix != null;
		this.tableName = tablePrefix + itemTransformer.getTableName();
		
		assert rwCapacity != null;
		this.rwCapacity = rwCapacity;
		
		this.rangeKeyName = rangeKeyName;
		
		this.rangeKeyValue = rangeKeyValue;
		
		applyRangeKey = StringUtils.isNotBlank(rangeKeyName);
	}
	
	
	/**
	 * Returns {@code true} if a range key is configured to be applied to
	 * all DynamoDB operations.
	 *
	 * @return {@code true} if a range key is configured, else {@code
	 * false}.
	 */
	boolean appliesRangeKey() {
		
		return applyRangeKey;
	}
	
	
	/**
	 * 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 name of the global secondary index (GSI) for the optional
	 * range key if configured.
	 *
	 * @return The range key GSI name, {@code null} if a range key is not
	 * configured.
	 */
	String getRangeKeyGSIName() {
		
		return applyRangeKey ? tableName + "-" + rangeKeyName + "-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 (applyRangeKey) {
			keyAttrs.add(new KeySchemaElement(rangeKeyName, KeyType.RANGE));
			
			attrs.add(new AttributeDefinition(rangeKeyName, ScalarAttributeType.S));
			
			gsIndices.add(new GlobalSecondaryIndex()
				.withIndexName(getRangeKeyGSIName())
				.withKeySchema(new KeySchemaElement(rangeKeyName, 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)
			.withKeySchema(keyAttrs)
			.withAttributeDefinitions(attrs);
		
		if (!gsIndices.isEmpty()) {
			createTableRequest.withGlobalSecondaryIndexes(gsIndices);
		}
		
		return createTableRequest;
	}
	
	
	/**
	 * Resolves the DynamoDB hash key.
	 *
	 * @param key The Infinispan entry key.
	 *
	 * @return The DynamoDB hash key.
	 */
	@SuppressWarnings("unchecked")
	private String resolveHashKey(final Object key) {
		
		if (key instanceof byte[]) {
			throw new PersistenceException("Cannot resolve " + itemTransformer.getTableName() + " key from byte[], enable compatibility mode");
		}
		
		return itemTransformer.resolveHashKey((K) key);
	}
	
	
	/**
	 * Returns the DynamoDB get item spec for the specified key.
	 *
	 * @param key The key.
	 *
	 * @return The get item spec.
	 */
	GetItemSpec resolveGetItemSpec(final Object key) {
		
		if (applyRangeKey) {
			return new GetItemSpec().withPrimaryKey(
				itemTransformer.getHashKeyAttributeName(),
				resolveHashKey(key),
				rangeKeyName,
				rangeKeyValue);
		}
		
		return new GetItemSpec().withPrimaryKey(
			itemTransformer.getHashKeyAttributeName(),
			resolveHashKey(key));
	}
	
	
	/**
	 * 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 = applyOptionalRangeKey(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) {
		
		DeleteItemSpec spec = new DeleteItemSpec();
		
		if (applyRangeKey) {
			spec.withPrimaryKey(
				itemTransformer.getHashKeyAttributeName(),
				resolveHashKey(key),
				rangeKeyName,
				rangeKeyValue);
		} else {
			spec.withPrimaryKey(
				itemTransformer.getHashKeyAttributeName(),
				resolveHashKey(key));
		}
		
		return spec.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 (applyRangeKey) {
			
			RangeKeyCondition c = new RangeKeyCondition(rangeKeyName).eq(rangeKeyValue);
			return table.getIndex(getRangeKeyGSIName()).query(new QuerySpec().withRangeKeyCondition(c));
		}
		
		return table.scan(new ScanSpec());
	}
	
	
	/**
	 * Applies the optional range key to the specified DynamoDB item (for
	 * writing).
	 *
	 * @param item The DynamoDB item.
	 *
	 * @return The resulting DynamoDB item.
	 */
	Item applyOptionalRangeKey(final Item item) {
		
		if (!applyRangeKey) {
			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,
			rangeKeyName,
			rangeKeyValue
		);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy