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 AWS SDK for Java with support for OSGi. The AWS 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).

There is a newer version: 1.11.60
Show newest version
/*
 * 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.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
        AttributeDefinition pHashAttrDefinition = getKeyAttributeDefinition(mappings.getHashKey(), converter);
        createTableRequest.withKeySchema(new KeySchemaElement(pHashAttrDefinition.getAttributeName(), KeyType.HASH));
        // Primary range
        AttributeDefinition pRangeAttrDefinition = null;
        if (mappings.hasRangeKey()) {
            pRangeAttrDefinition = getKeyAttributeDefinition(mappings.getRangeKey(), 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 (mappings.hasRangeKey()) {
            putAfterCheckConflict(attrDefinitions, pRangeAttrDefinition);
        }
        for (DynamoDBMappingsRegistry.Mapping indexKeyMapping : indexesInfo.getIndexKeyMappings()) {
            AttributeDefinition indexKeyAttrDefinition = getKeyAttributeDefinition(indexKeyMapping, 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()) {
                    for (String gsi : mapping.bean().annotations().globalSecondaryIndexNames(KeyType.HASH)) {
                        tableIndexInfo.addGsiKeys(gsi, mapping.getAttributeName(), null);
                        tableIndexInfo.addIndexKeyMapping(mapping);
                    }
                    for (String gsi : mapping.bean().annotations().globalSecondaryIndexNames(KeyType.RANGE)) {
                        tableIndexInfo.addGsiKeys(gsi, null, mapping.getAttributeName());
                        tableIndexInfo.addIndexKeyMapping(mapping);
                    }
                    for (String lsi : mapping.bean().annotations().localSecondaryIndexNames()) {
                        tableIndexInfo.addLsiRangeKey(lsi, pHashName, mapping.getAttributeName());
                        tableIndexInfo.addIndexKeyMapping(mapping);
                    }
                } // 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(
            DynamoDBMappingsRegistry.Mapping keyMapping,
            ItemConverter converter) {

        DynamoDBMapperFieldModel fieldModel = converter.getFieldModel(keyMapping.getter());

        DynamoDBAttributeType keyType = fieldModel.getDynamoDBAttributeType();

        if (keyType == DynamoDBAttributeType.S ||
            keyType == DynamoDBAttributeType.N ||
            keyType == DynamoDBAttributeType.B) {
            return new AttributeDefinition(keyMapping.getAttributeName(), 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 indexKeyMappings = 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 addIndexKeyMapping(DynamoDBMappingsRegistry.Mapping indexKeyMapping) {
            indexKeyMappings.add(indexKeyMapping);
        }

        private Set getIndexKeyMappings() {
            return Collections.unmodifiableSet(indexKeyMappings);
        }

        private Collection getLocalSecondaryIndexes() {
            return Collections.unmodifiableCollection(lsiNameToLsiDefinition.values());
        }

        private Collection getGlobalSecondaryIndexes() {
            return Collections.unmodifiableCollection(gsiNameToGsiDefinition.values());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy