
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTableSchemaParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-java-sdk-dynamodb Show documentation
Show all versions of aws-java-sdk-dynamodb Show documentation
The AWS Java SDK for Amazon DynamoDB module holds the client classes that are used for communicating with Amazon DynamoDB Service
/*
* Copyright 2011-2016 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.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* 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.
*/
package com.amazonaws.services.dynamodbv2.datamodeling;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.DeleteTableRequest;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.LocalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProjectionType;
/**
* A class responsible for parsing the primary key and index schema of a table
* POJO.
*/
class DynamoDBTableSchemaParser {
private final Map, TableIndexesInfo> tableIndexesInfoCache =
new HashMap, TableIndexesInfo>();
/**
* Parse the given POJO class and return the CreateTableRequest for the
* DynamoDB table it represents. Note that the returned request does not
* include the required ProvisionedThroughput parameters for the primary
* table and the GSIs, and that all secondary indexes are initialized with
* the default projection type - KEY_ONLY.
*
* @param clazz
* The POJO class.
* @param config
* The DynamoDBMapperConfig which contains the TableNameOverrides
* parameter used to determine the table name.
* @param registry
* The DynamoDBMappingsRegistry that provides all the relevant getters
* of the POJO.
*/
CreateTableRequest parseTablePojoToCreateTableRequest(
Class> clazz,
DynamoDBMapperConfig config,
DynamoDBMappingsRegistry registry,
ItemConverter converter) {
CreateTableRequest createTableRequest = new CreateTableRequest();
createTableRequest.setTableName(DynamoDBMapper.internalGetTableName(clazz, null, config));
final DynamoDBMappingsRegistry.Mappings mappings = registry.mappingsOf(clazz);
// Primary keys
Method pHashKeyGetter = mappings.getHashKey().getter();
AttributeDefinition pHashAttrDefinition = getKeyAttributeDefinition(pHashKeyGetter, converter);
createTableRequest.withKeySchema(new KeySchemaElement(pHashAttrDefinition.getAttributeName(), KeyType.HASH));
// Primary range
Method pRangeKeyGetter = null;
AttributeDefinition pRangeAttrDefinition = null;
if (mappings.hasRangeKey()) {
pRangeKeyGetter = mappings.getRangeKey().getter();
pRangeAttrDefinition = getKeyAttributeDefinition(pRangeKeyGetter, converter);
createTableRequest.withKeySchema(new KeySchemaElement(pRangeAttrDefinition.getAttributeName(), KeyType.RANGE));
}
// Parse the index schema
TableIndexesInfo indexesInfo = parseTableIndexes(clazz, registry);
if ( indexesInfo.getGlobalSecondaryIndexes().isEmpty() == false ) {
createTableRequest.setGlobalSecondaryIndexes(indexesInfo.getGlobalSecondaryIndexes());
}
if ( indexesInfo.getLocalSecondaryIndexes().isEmpty() == false ) {
createTableRequest.setLocalSecondaryIndexes(indexesInfo.getLocalSecondaryIndexes());
}
// Aggregate all key attribute definitions
Map attrDefinitions = new HashMap();
// Hash key definition
putAfterCheckConflict(attrDefinitions, pHashAttrDefinition);
// Range key definition
if (pRangeKeyGetter != null) {
putAfterCheckConflict(attrDefinitions, pRangeAttrDefinition);
}
for (Method indexKeyGetter : indexesInfo.getIndexKeyGetters()) {
AttributeDefinition indexKeyAttrDefinition = getKeyAttributeDefinition(indexKeyGetter, converter);
putAfterCheckConflict(attrDefinitions, indexKeyAttrDefinition);
}
createTableRequest.setAttributeDefinitions(attrDefinitions.values());
return createTableRequest;
}
/**
* Parse the given POJO class and return the DeleteTableRequest for the DynamoDB table it
* represents.
*
* @param clazz
* The POJO class.
* @param config
* The DynamoDBMapperConfig which contains the TableNameOverrides parameter used to
* determine the table name.
*/
DeleteTableRequest parseTablePojoToDeleteTableRequest(Class> clazz, DynamoDBMapperConfig config) {
DeleteTableRequest deleteTableRequest = new DeleteTableRequest();
deleteTableRequest.setTableName(DynamoDBMapper.internalGetTableName(clazz, null, config));
return deleteTableRequest;
}
TableIndexesInfo parseTableIndexes(final Class> clazz, final DynamoDBMappingsRegistry registry) {
synchronized(tableIndexesInfoCache) {
if ( !tableIndexesInfoCache.containsKey(clazz) ) {
final DynamoDBMappingsRegistry.Mappings mappings = registry.mappingsOf(clazz);
TableIndexesInfo tableIndexInfo = new TableIndexesInfo();
String pHashName = mappings.getHashKey().getAttributeName();
for (final DynamoDBMappingsRegistry.Mapping mapping : mappings.getMappings()) {
String attributeName = mapping.getAttributeName();
if (mapping.isIndexHashKey()) {
Collection gsiNames = mapping.getGlobalSecondaryIndexNamesOfIndexHashKey();
for (String gsi : gsiNames) {
tableIndexInfo.addGsiKeys(gsi, attributeName, null);
}
tableIndexInfo.addIndexKeyGetter(mapping.getter());
}
if (mapping.isIndexRangeKey()) {
Collection gsiNames = mapping.getGlobalSecondaryIndexNamesOfIndexRangeKey();
Collection lsiNames = mapping.getLocalSecondaryIndexNamesOfIndexRangeKey();
if (gsiNames.isEmpty() && lsiNames.isEmpty()) {
throw new DynamoDBMappingException(
"@DynamoDBIndexRangeKey annotation on getter " + mapping.getter() +
" doesn't contain any index name.");
}
for (String gsi : gsiNames) {
tableIndexInfo.addGsiKeys(gsi, null, attributeName);
}
for (String lsi : lsiNames) {
tableIndexInfo.addLsiRangeKey(lsi, pHashName, attributeName);
}
tableIndexInfo.addIndexKeyGetter(mapping.getter());
}
} // end of for loop
tableIndexesInfoCache.put(clazz, tableIndexInfo);
} // end of the if-cache-does-not-contain block
return tableIndexesInfoCache.get(clazz);
} // end of synchronized block
}
private static AttributeDefinition getKeyAttributeDefinition(
Method keyGetter,
ItemConverter converter) {
DynamoDBMapperFieldModel fieldModel = converter.getFieldModel(keyGetter);
String keyAttrName = fieldModel.getDynamoDBAttributeName();
DynamoDBAttributeType keyType = fieldModel.getDynamoDBAttributeType();
if (keyType == DynamoDBAttributeType.S ||
keyType == DynamoDBAttributeType.N ||
keyType == DynamoDBAttributeType.B) {
return new AttributeDefinition(keyAttrName, keyType.toString());
}
throw new DynamoDBMappingException(
"The key attribute must be in a scalar type "
+ "(String, Number or Binary).");
}
private static void putAfterCheckConflict(Map map,
AttributeDefinition attrDefinition) {
String attrName = attrDefinition.getAttributeName();
AttributeDefinition existingDefinition = map.get(attrName);
if (existingDefinition != null && !existingDefinition.equals(attrDefinition)) {
throw new DynamoDBMappingException(
"Found conflicting definitions for attribute [" + attrName + "]: " +
existingDefinition + " and " + attrDefinition + ".");
} else {
map.put(attrName, attrDefinition);
}
}
/**
* This class contains all the information about a table's index schema
* parsed from a table POJO class.
*/
static class TableIndexesInfo {
/** Used for mapping an index key name to all the applicable indexes. */
private final Map> lsiRangeKeyNameToIndexNames =
new HashMap>();
private final Map> gsiHashKeyNameToIndexNames =
new HashMap>();
private final Map> gsiRangeKeyNameToIndexNames =
new HashMap>();
/** Note that the KeySchema in each LocalSecondaryIndex does not include the hash key. */
private final Map lsiNameToLsiDefinition = new HashMap();
private final Map gsiNameToGsiDefinition = new HashMap();
/** All getter methods of index key attributes. */
private final Set indexKeyGetters = new HashSet();
/**
* Returns the names of all the annotated local secondary indexes that
* use the given attribute as the index range key.
*/
public Set getLsiNamesByIndexRangeKey(String indexRangeKeyName) {
Set lsiNames = lsiRangeKeyNameToIndexNames.get(indexRangeKeyName);
if (lsiNames != null) {
lsiNames = Collections.unmodifiableSet(lsiNames);
}
return lsiNames;
}
/**
* Returns the names of all the annotated global secondary indexes that
* use the given attribute as the index hash key.
*/
public Set getGsiNamesByIndexHashKey(String indexHashKeyName) {
Set gsiNames = gsiHashKeyNameToIndexNames.get(indexHashKeyName);
if (gsiNames != null) {
gsiNames = Collections.unmodifiableSet(gsiNames);
}
return gsiNames;
}
/**
* Returns the names of all the annotated global secondary indexes that
* use the given attribute as the index range key.
*/
public Set getGsiNamesByIndexRangeKey(String indexRangeKeyName) {
Set gsiNames = gsiRangeKeyNameToIndexNames.get(indexRangeKeyName);
if (gsiNames != null) {
gsiNames = Collections.unmodifiableSet(gsiNames);
}
return gsiNames;
}
/**
* Returns the names of all the annotated local secondary indexes of
* this POJO class.
*/
public Set getAllLsiNames() {
return Collections.unmodifiableSet(lsiNameToLsiDefinition.keySet());
}
/**
* Returns the names of all the annotated global secondary indexes of
* this POJO class.
*/
public Set getAllGsiNames() {
return Collections.unmodifiableSet(gsiNameToGsiDefinition.keySet());
}
/*
* Private interfaces
*/
private void addGsiKeys(String gsiName, String gsiHashKeyName, String gsiRangeKeyName) {
GlobalSecondaryIndex gsi;
if (gsiNameToGsiDefinition.containsKey(gsiName)) {
GlobalSecondaryIndex existingGsi = gsiNameToGsiDefinition.get(gsiName);
gsi = existingGsi;
if ( !gsiName.equals(existingGsi.getIndexName()) ) {
throw new IllegalStateException("Found invalid state of an existing GlobalSecondaryIndex object " +
"associated with the GSI [" + gsiName + "].");
}
for (KeySchemaElement existingKey : existingGsi.getKeySchema()) {
String existingKeyName = existingKey.getAttributeName();
String existingKeyType = existingKey.getKeyType();
if (KeyType.HASH.toString().equals(existingKeyType)) {
if (gsiHashKeyName != null && !gsiHashKeyName.equals(existingKeyName)) {
throw new DynamoDBMappingException("Multiple hash keys [" + existingKeyName + ", " + gsiHashKeyName +
"] are found for the GSI [" + gsiName + "]. " +
"Each index allows at most one range key attribute.");
}
} else if (KeyType.RANGE.toString().equals(existingKeyType)) {
if (gsiRangeKeyName != null && !gsiRangeKeyName.equals(existingKeyName)) {
throw new DynamoDBMappingException("Multiple range keys [" + existingKeyName + ", " + gsiRangeKeyName +
"] are found for the GSI [" + gsiName + "]. " +
"Each index allows at most one range key attribute.");
}
} else {
// Existing key element is neither HASH nor RANGE.
throw new IllegalStateException("Found invalid state of an existing GlobalSecondaryIndex object " +
"associated with the GSI [" + gsiName + "].");
}
}
} else {
gsi = new GlobalSecondaryIndex()
.withIndexName(gsiName)
.withProjection(new Projection().withProjectionType(ProjectionType.KEYS_ONLY));
gsiNameToGsiDefinition.put(gsiName, gsi);
}
if (gsiHashKeyName != null) {
// Make sure that the HASH key element is always inserted at the beginning of the list
if (gsi.getKeySchema() == null || gsi.getKeySchema().isEmpty()) {
gsi.withKeySchema(new KeySchemaElement(gsiHashKeyName, KeyType.HASH));
} else {
LinkedList orderedKeys = new LinkedList(gsi.getKeySchema());
orderedKeys.addFirst(new KeySchemaElement(gsiHashKeyName, KeyType.HASH));
gsi.setKeySchema(orderedKeys);
}
// Register the mapping from the hash key name to the GSI name
mapGsiHashKeyToIndexName(gsiHashKeyName, gsiName);
}
if (gsiRangeKeyName != null) {
gsi.withKeySchema(new KeySchemaElement(gsiRangeKeyName, KeyType.RANGE));
// Register the mapping from the range key name to the GSI name
mapGsiRangeKeyToIndexName(gsiRangeKeyName, gsiName);
}
}
private void addLsiRangeKey(String lsiName, String pHashKeyName, String lsiRangeKeyName) {
if (pHashKeyName == null) {
throw new IllegalArgumentException("The name of the primary hash key must be specified.");
}
if (lsiNameToLsiDefinition.containsKey(lsiName)) {
LocalSecondaryIndex existingLsi = lsiNameToLsiDefinition.get(lsiName);
if ( !lsiName.equals(existingLsi.getIndexName())
|| existingLsi.getKeySchema() == null
|| existingLsi.getKeySchema().size() != 2 // the hash key element should be already added
|| !KeyType.RANGE.toString().equals(existingLsi.getKeySchema().get(1).getKeyType()) ) {
throw new IllegalStateException("Found invalid state of an existing LocalSecondaryIndex object " +
"associated with the LSI [" + lsiName + "].");
}
String existingLsiRangeKeyName = existingLsi.getKeySchema().get(1).getAttributeName();
if ( !existingLsiRangeKeyName.equals(lsiRangeKeyName) ) {
throw new DynamoDBMappingException("Multiple range keys [" + existingLsiRangeKeyName + ", " + lsiRangeKeyName +
"] are found for the LSI [" + lsiName + "]. " +
"Each index allows at most one range key attribute.");
}
} else {
lsiNameToLsiDefinition.put(
lsiName,
new LocalSecondaryIndex()
.withIndexName(lsiName)
.withKeySchema(
new KeySchemaElement(pHashKeyName, KeyType.HASH),
new KeySchemaElement(lsiRangeKeyName, KeyType.RANGE))
.withProjection(new Projection().withProjectionType(ProjectionType.KEYS_ONLY)));
mapLsiRangeKeyToIndexName(lsiRangeKeyName, lsiName);
}
}
private void mapLsiRangeKeyToIndexName(String lsiRangeKeyName, String lsiName) {
mapIndexKeyToIndexName(lsiRangeKeyNameToIndexNames, lsiRangeKeyName, lsiName);
}
private void mapGsiHashKeyToIndexName(String gsiHashKeyName, String gsiName) {
mapIndexKeyToIndexName(gsiHashKeyNameToIndexNames, gsiHashKeyName, gsiName);
}
private void mapGsiRangeKeyToIndexName(String gsiRangeKeyName, String gsiName) {
mapIndexKeyToIndexName(gsiRangeKeyNameToIndexNames, gsiRangeKeyName, gsiName);
}
private void mapIndexKeyToIndexName(Map> indexKeyNameToIndexNames,
String indexKeyName,
String indexName) {
if (indexKeyNameToIndexNames.get(indexKeyName) == null) {
Set indexNames = new HashSet();
indexNames.add(indexName);
indexKeyNameToIndexNames.put(indexKeyName, indexNames);
} else {
indexKeyNameToIndexNames.get(indexKeyName).add(indexName);
}
}
private void addIndexKeyGetter(Method indexKeyGetter) {
indexKeyGetters.add(indexKeyGetter);
}
private Set getIndexKeyGetters() {
return Collections.unmodifiableSet(indexKeyGetters);
}
private Collection getLocalSecondaryIndexes() {
return Collections.unmodifiableCollection(lsiNameToLsiDefinition.values());
}
private Collection getGlobalSecondaryIndexes() {
return Collections.unmodifiableCollection(gsiNameToGsiDefinition.values());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy