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-sdk-android Show documentation
Show all versions of aws-sdk-android Show documentation
Gradle project for AWS SDK Android
The newest version!
/*
* Copyright 2011-2015 Amazon Technologies, Inc.
*
* 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.ArgumentMarshaller.BinaryAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.NumberAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.datamodeling.ArgumentMarshaller.StringAttributeMarshaller;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
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;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
/**
* A class responsible for parsing the primary key and index schema of a table
* POJO.
* @deprecated These classes have been deprecated, please use the classes in the com.amazonaws.mobileconnectors namespace.
*/
public 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 reflector
* The DynamoDBReflector that provides all the relevant getters
* of the POJO.
*/
CreateTableRequest parseTablePojoToCreateTableRequest(final Class clazz,
final DynamoDBMapperConfig config,
final DynamoDBReflector reflector) {
CreateTableRequest createTableRequest = new CreateTableRequest();
createTableRequest.setTableName(DynamoDBMapper.getTableName(clazz, config, reflector));
// Primary keys
Method pHashKeyGetter = reflector.getPrimaryHashKeyGetter(clazz);
AttributeDefinition pHashAttrDefinition = getKeyAttributeDefinition(pHashKeyGetter, reflector);
createTableRequest.withKeySchema(new KeySchemaElement(pHashAttrDefinition.getAttributeName(), KeyType.HASH));
// Primary range
Method pRangeKeyGetter = reflector.getPrimaryRangeKeyGetter(clazz);
AttributeDefinition pRangeAttrDefinition = null;
if (pRangeKeyGetter != null) {
pRangeAttrDefinition = getKeyAttributeDefinition(pRangeKeyGetter, reflector);
createTableRequest.withKeySchema(new KeySchemaElement(pRangeAttrDefinition.getAttributeName(), KeyType.RANGE));
}
// Parse the index schema
TableIndexesInfo indexesInfo = parseTableIndexes(clazz, reflector);
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, reflector);
putAfterCheckConflict(attrDefinitions, indexKeyAttrDefinition);
}
createTableRequest.setAttributeDefinitions(attrDefinitions.values());
return createTableRequest;
}
TableIndexesInfo parseTableIndexes(final Class clazz, final DynamoDBReflector reflector) {
synchronized(tableIndexesInfoCache) {
if ( !tableIndexesInfoCache.containsKey(clazz) ) {
TableIndexesInfo tableIndexInfo = new TableIndexesInfo();
String pHashName = reflector.getPrimaryHashKeyName(clazz);
for (Method getter : reflector.getRelevantGetters(clazz)) {
// Only consider 0-arg getters
if (getter.getParameterTypes().length != 0) {
continue;
}
String attributeName = reflector.getAttributeName(getter);
if (ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBIndexHashKey.class)) {
DynamoDBIndexHashKey indexHashKeyAnnotation = ReflectionUtils
.getAnnotationFromGetterOrField(getter, DynamoDBIndexHashKey.class);
String gsiName = indexHashKeyAnnotation.globalSecondaryIndexName();
String[] gsiNames = indexHashKeyAnnotation.globalSecondaryIndexNames();
boolean singleGsiName = gsiName != null &&
gsiName.length() != 0;
boolean multipleGsiNames = gsiNames != null &&
gsiNames.length != 0;
if ( singleGsiName && multipleGsiNames) {
throw new DynamoDBMappingException(
"@DynamoDBIndexHashKey annotation on getter " + getter +
" contains both globalSecondaryIndexName and globalSecondaryIndexNames.");
} else if ( (!singleGsiName) && (!multipleGsiNames) ) {
throw new DynamoDBMappingException(
"@DynamoDBIndexHashKey annotation on getter " + getter +
" doesn't contain any index name.");
}
if (singleGsiName) {
tableIndexInfo.addGsiKeys(gsiName, attributeName, null);
} else if (multipleGsiNames) {
for (String gsi : gsiNames) {
tableIndexInfo.addGsiKeys(gsi, attributeName, null);
}
}
tableIndexInfo.addIndexKeyGetter(getter);
}
if (ReflectionUtils.getterOrFieldHasAnnotation(getter, DynamoDBIndexRangeKey.class)) {
DynamoDBIndexRangeKey indexRangeKeyAnnotation = ReflectionUtils
.getAnnotationFromGetterOrField(getter, DynamoDBIndexRangeKey.class);
String gsiName = indexRangeKeyAnnotation.globalSecondaryIndexName();
String[] gsiNames = indexRangeKeyAnnotation.globalSecondaryIndexNames();
String lsiName = indexRangeKeyAnnotation.localSecondaryIndexName();
String[] lsiNames = indexRangeKeyAnnotation.localSecondaryIndexNames();
boolean singleGsiName = gsiName != null &&
gsiName.length() != 0;
boolean multipleGsiNames = gsiNames != null &&
gsiNames.length != 0;
boolean singleLsiName = lsiName != null &&
lsiName.length() != 0;
boolean multipleLsiNames = lsiNames != null &&
lsiNames.length != 0;
if ( singleGsiName && multipleGsiNames ) {
throw new DynamoDBMappingException(
"@DynamoDBIndexRangeKey annotation on getter " + getter +
" contains both globalSecondaryIndexName and globalSecondaryIndexNames.");
}
if ( singleLsiName && multipleLsiNames ) {
throw new DynamoDBMappingException(
"@DynamoDBIndexRangeKey annotation on getter " + getter +
" contains both localSecondaryIndexName and localSecondaryIndexNames.");
}
if ( (!singleGsiName) && (!multipleGsiNames) && (!singleLsiName) && (!multipleLsiNames) ) {
throw new DynamoDBMappingException(
"@DynamoDBIndexRangeKey annotation on getter " + getter +
" doesn't contain any index name.");
}
if (singleGsiName) {
tableIndexInfo.addGsiKeys(gsiName, null, attributeName);
} else if (multipleGsiNames) {
for (String gsi : gsiNames) {
tableIndexInfo.addGsiKeys(gsi, null, attributeName);
}
}
if (singleLsiName) {
tableIndexInfo.addLsiRangeKey(lsiName, pHashName, attributeName);
} else if (multipleLsiNames) {
for (String lsi : lsiNames) {
tableIndexInfo.addLsiRangeKey(lsi, pHashName, attributeName);
}
}
tableIndexInfo.addIndexKeyGetter(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, final DynamoDBReflector reflector) {
String keyAttrName = reflector.getAttributeName(keyGetter);
ArgumentMarshaller marshaller = reflector.getArgumentMarshaller(keyGetter);
if (marshaller instanceof StringAttributeMarshaller) {
return new AttributeDefinition(keyAttrName, ScalarAttributeType.S);
} else if (marshaller instanceof NumberAttributeMarshaller) {
return new AttributeDefinition(keyAttrName, ScalarAttributeType.N);
} else if (marshaller instanceof BinaryAttributeMarshaller) {
return new AttributeDefinition(keyAttrName, ScalarAttributeType.B);
}
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());
}
}
}