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

com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTableSchemaParser Maven / Gradle / Ivy

Go to download

The Amazon Web Services SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

The newest version!
/*
 * Copyright 2011-2014 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.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.
 */
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 ) {
            // Add the primary hash key element into each LSI
            for (LocalSecondaryIndex lsi : indexesInfo.getLocalSecondaryIndexes()) {
                lsi.withKeySchema(new KeySchemaElement(pHashAttrDefinition.getAttributeName(), KeyType.HASH));
            }
            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();
                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, attributeName);
                        } else if (multipleLsiNames) {
                            for (String lsi : lsiNames) {
                                tableIndexInfo.addLsiRangeKey(lsi, 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) {
                gsi.withKeySchema(new KeySchemaElement(gsiHashKeyName, KeyType.HASH));
                mapGsiHashKeyToIndexName(gsiHashKeyName, gsiName);
            }
            if (gsiRangeKeyName != null) {
                gsi.withKeySchema(new KeySchemaElement(gsiRangeKeyName, KeyType.RANGE));
                mapGsiRangeKeyToIndexName(gsiRangeKeyName, gsiName);
            }
        }

        private void addLsiRangeKey(String lsiName, String lsiRangeKeyName) {
            if (lsiNameToLsiDefinition.containsKey(lsiName)) {
                LocalSecondaryIndex existingLsi = lsiNameToLsiDefinition.get(lsiName);
                if ( !lsiName.equals(existingLsi.getIndexName()) 
                        || existingLsi.getKeySchema() == null
                        || existingLsi.getKeySchema().size() != 1
                        || !KeyType.RANGE.toString().equals(existingLsi.getKeySchema().get(0).getKeyType()) ) {
                    throw new IllegalStateException("Found invalid state of an existing LocalSecondaryIndex object " +
                            "associated with the LSI [" + lsiName + "].");
                }

                String existingLsiRangeKeyName = existingLsi.getKeySchema().get(0).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(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