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

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

Go to download

The AWS Java SDK for Amazon DynamoDB module holds the client classes that are used for communicating with Amazon DynamoDB Service

There is a newer version: 1.9.11
Show newest version
/*
 * Copyright 2016-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 static com.amazonaws.services.dynamodbv2.model.KeyType.HASH;
import static com.amazonaws.services.dynamodbv2.model.KeyType.RANGE;

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.SaveBehavior;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.Id;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.LocalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProjectionType;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;

/**
 * Table model.
 *
 * @param  The object type.
 */
public final class DynamoDBMapperTableModel implements DynamoDBTypeConverter,T>  {

    private final Map> fields;
    private final Map> keys;
    private final Map gsis;
    private final Map lsis;
    private final Properties properties;

    /**
     * Constructs a new table model for the specified class.
     * @param builder The builder.
     */
    private DynamoDBMapperTableModel(final DynamoDBMapperTableModel.Builder builder) {
        this.properties = builder.properties;
        this.fields = builder.fields;
        this.keys = builder.keys;
        this.gsis = builder.globalSecondaryIndexes();
        this.lsis = builder.localSecondaryIndexes();
    }

    /**
     * Gets the object type.
     * @return The object type.
     */
    public Class targetType() {
        return properties.targetType();
    }

    /**
     * Gets the table name.
     * @return The table name.
     */
    public String tableName() {
        return properties.tableName();
    }

    /**
     * Gets all the field models for the given class.
     * @return The field models.
     */
    public Collection> fields() {
        return fields.values();
    }

    /**
     * Gets the field model for a given attribute.
     * @param  The field model's value type.
     * @param attributeName The attribute name.
     * @return The field model.
     */
    public  DynamoDBMapperFieldModel field(final String attributeName) {
        if (!fields.containsKey(attributeName)) {
            throw new DynamoDBMappingException((new Id(targetType(), attributeName)).format("not mapped on object model"));
        }
        return (DynamoDBMapperFieldModel)fields.get(attributeName);
    }

    /**
     * Gets the field model for a given key type.
     * @param  The field model's value type.
     * @param keyType The key type.
     * @return The field model.
     */
    public  DynamoDBMapperFieldModel field(final KeyType keyType) {
        if (!keys.containsKey(keyType)) {
            throw new DynamoDBMappingException(new Id(targetType(), keyType.name()).format("not mapped on object model"));
        }
        return (DynamoDBMapperFieldModel)keys.get(keyType);
    }

    /**
     * Gets all the key field models for the given class.
     * @return The field models.
     */
    public Collection> keys() {
        return keys.values();
    }

    /**
     * Gets the hash key field model for the specified type.
     * @param  The hash key type.
     * @return The hash key field model.
     * @throws DynamoDBMappingException If the hash key is not present.
     */
    public  DynamoDBMapperFieldModel hashKey() {
        return (DynamoDBMapperFieldModel)field(HASH);
    }

    /**
     * Gets the range key field model for the specified type.
     * @param  The range key type.
     * @return The range key field model.
     * @throws DynamoDBMappingException If the range key is not present.
     */
    public  DynamoDBMapperFieldModel rangeKey() {
        return (DynamoDBMapperFieldModel)field(RANGE);
    }

    /**
     * Gets the range key field model for the specified type.
     * @param  The range key type.
     * @return The range key field model, or null if not present.
     */
    public  DynamoDBMapperFieldModel rangeKeyIfExists() {
        return (DynamoDBMapperFieldModel)keys.get(RANGE);
    }

    /**
     * Gets the global secondary indexes for the given class.
     * @return The map of index name to GlobalSecondaryIndexes.
     */
    public Collection globalSecondaryIndexes() {
        if (gsis.isEmpty()) {
            return Collections.emptyList();
        }
        final Collection copies = new ArrayList(gsis.size());
        for (final String indexName : gsis.keySet()) {
            copies.add(globalSecondaryIndex(indexName));
        }
        return copies;
    }

    /**
     * Gets the global secondary index.
     * @param indexName The index name.
     * @return The global secondary index or null.
     */
    public GlobalSecondaryIndex globalSecondaryIndex(final String indexName) {
        if (!gsis.containsKey(indexName)) {
            return null;
        }
        final GlobalSecondaryIndex gsi = gsis.get(indexName);
        final GlobalSecondaryIndex copy = new GlobalSecondaryIndex().withIndexName(gsi.getIndexName());
        copy.withProjection(new Projection().withProjectionType(gsi.getProjection().getProjectionType()));
        for (final KeySchemaElement key : gsi.getKeySchema()) {
            copy.withKeySchema(new KeySchemaElement(key.getAttributeName(), key.getKeyType()));
        }
        return copy;
    }

    /**
     * Gets the local secondary indexes for the given class.
     * @param indexNames The index names.
     * @return The map of index name to LocalSecondaryIndexes.
     */
    public Collection localSecondaryIndexes() {
        if (lsis.isEmpty()) {
            return Collections.emptyList();
        }
        final Collection copies = new ArrayList(lsis.size());
        for (final String indexName : lsis.keySet()) {
            copies.add(localSecondaryIndex(indexName));
        }
        return copies;
    }

    /**
     * Gets the local secondary index by name.
     * @param indexNames The index name.
     * @return The local secondary index, or null.
     */
    public LocalSecondaryIndex localSecondaryIndex(final String indexName) {
        if (!lsis.containsKey(indexName)) {
            return null;
        }
        final LocalSecondaryIndex lsi = lsis.get(indexName);
        final LocalSecondaryIndex copy = new LocalSecondaryIndex().withIndexName(lsi.getIndexName());
        copy.withProjection(new Projection().withProjectionType(lsi.getProjection().getProjectionType()));
        for (final KeySchemaElement key : lsi.getKeySchema()) {
            copy.withKeySchema(new KeySchemaElement(key.getAttributeName(), key.getKeyType()));
        }
        return copy;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map convert(final T object) {
        if (object == null) {
            return null;
        }
        final Map map = new LinkedHashMap();
        for (final DynamoDBMapperFieldModel field : fields()) {
            final AttributeValue value = field.getAndConvert(object);
            if (value != null) {
                map.put(field.name(), value);
            }
        }
        return map;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T unconvert(final Map object) {
        final T result;
        try {
            result = targetType().newInstance();
        } catch (final Exception e) {
            throw new DynamoDBMappingException("could not instantiate " + targetType(), e);
        }
        if (object != null && !object.isEmpty()) {
            for (final DynamoDBMapperFieldModel field : fields()) {
                final AttributeValue value = object.get(field.name());
                if (value != null) {
                    field.unconvertAndSet(result, value);
                }
            }
        }
        return result;
    }

    /**
     * Creates a new object instance with the keys populated.
     * @param  The hash key type.
     * @param  The range key type.
     * @param hashKey The hash key.
     * @param rangeKey The range key (optional if not present on table).
     * @return The new instance.
     */
    public  T newKey(final H hashKey, final R rangeKey) {
        final T key;
        try {
            key = targetType().newInstance();
        } catch (final Exception e) {
            throw new DynamoDBMappingException("could not instantiate " + targetType(), e);
        }
        if (hashKey != null) {
            final DynamoDBMapperFieldModel hk = hashKey();
            hk.set(key, hashKey);
        }
        if (rangeKey != null) {
            final DynamoDBMapperFieldModel rk = rangeKey();
            rk.set(key, rangeKey);
        }
        return key;
    }

    /**
     * Creates a new key map from the specified hash and range key.
     * @param  The hash key type.
     * @param  The range key type.
     * @param hashKey The hash key.
     * @param rangeKey The range key (optional if not present on table).
     * @return The key map.
     */
    public  Map mapKey(final H hashKey, final R rangeKey) {
        final Map key = new LinkedHashMap(4);
        if (hashKey != null) {
            final DynamoDBMapperFieldModel hk = hashKey();
            key.put(hk.name(), hk.convert(hashKey));
        }
        if (rangeKey != null) {
            final DynamoDBMapperFieldModel rk = rangeKey();
            key.put(rk.name(), rk.convert(rangeKey));
        }
        if (key.isEmpty()) {
            throw new DynamoDBMappingException("no key(s) present on " + targetType());
        }
        return key;
    }

    /**
     * Creates a new key map from the specified object.
     * @param  The hash key type.
     * @param  The range key type.
     * @param object The object instance.
     * @return The key map.
     */
    public  Map mapKey(final T object) {
        final Map key = new LinkedHashMap(4);
        for (final DynamoDBMapperFieldModel field : keys()) {
            final AttributeValue value = field.getAndConvert(object);
            if (value == null) {
                throw new DynamoDBMappingException(field.id().format("must not have null value for %s key", field.keyType()));
            }
            key.put(field.name(), value);
        }
        if (key.isEmpty()) {
            throw new DynamoDBMappingException("no key(s) present on " + targetType());
        }
        return key;
    }

    /**
     * Determnes if any of the primary keys require auto-generation.
     * @param object The object instance.
     * @param saveBehavior The save behaviour.
     * @return True if any keys should be auto-generated.
     */
    public boolean anyKeyGeneratable(final T object, final SaveBehavior saveBehavior) {
        if (keys().isEmpty()) {
            throw new DynamoDBMappingException("no key(s) present on " + targetType());
        }
        for (final DynamoDBMapperFieldModel field : keys()) {
            if (field.canGenerate(object, saveBehavior, null)) {
                return true;
            }
        }
        return false;
    }

    /**
     * The table model properties.
     */
    static interface Properties {
        public Class targetType();
        public String tableName();

         /**
          * Properties builder.
          */
        static final class Builder implements Properties {
            private Class targetType;
            private String tableName;

            /**
             * Populates the builder properties with the specified defaults.
             * @param defaults The default properties.
             * @return This builder instance for chaining.
             */
            public Builder with(final Properties defaults) {
                this.targetType = defaults.targetType();
                this.tableName = defaults.tableName();
                return this;
            }

            /**
             * Sets the target type.
             * @param targetType The target type.
             * @return This builder instance for chaining.
             */
            public Builder withTargetType(final Class targetType) {
                this.targetType = targetType;
                return this;
            }

            /**
             * Sets the table name.
             * @param tableName The table name.
             * @return This builder instance for chaining.
             */
            public Builder withTableName(final String tableName) {
                this.tableName = tableName;
                return this;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public Class targetType() {
                return targetType;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public String tableName() {
                return tableName;
            }
        }
    }

    /**
     * {@link DynamoDBMapperTableModel} builder.
     */
    static class Builder {
        private final Map> fields;
        private final Map> keys;
        private final Properties.Builder properties;

        /**
         * Construts a new builder.
         * @param defaults The default properties.
         */
        public Builder(final Properties defaults) {
            this.fields = new LinkedHashMap>();
            this.keys = new LinkedHashMap>(4);
            this.properties = new Properties.Builder().with(defaults);
        }

        /**
         * Sets the table name.
         * @param tableName The table name.
         * @return This builder instance for chaining.
         */
        public Builder withTableName(final String tableName) {
            this.properties.withTableName(tableName);
            return this;
        }

        /**
         * Adds a field model to this builder.
         * @param field The field model.
         * @return This builder instance for chaining.
         */
        public Builder with(final DynamoDBMapperFieldModel field) {
            if (fields.put(field.name(), field) != null) {
                throw new DynamoDBMappingException(field.id().format("must not duplicate attribute name"));
            }
            if (field.keyType() != null && keys.put(field.keyType(), field) != null) {
                throw new DynamoDBMappingException(field.id().format("must not specify multiple %s key(s)", field.keyType()));
            }
            return this;
        }

        /**
         * Builds the GSI mappings.
         * @return The mappings.
         */
        public Map globalSecondaryIndexes() {
            final Map map = new LinkedHashMap();
            for (final DynamoDBMapperFieldModel field : fields.values()) {
                for (final String indexName : field.globalSecondaryIndexNames(HASH)) {
                    final GlobalSecondaryIndex gsi = new GlobalSecondaryIndex().withIndexName(indexName);
                    if (map.put(indexName, gsi) != null) {
                        throw new DynamoDBMappingException(field.id().format("must not contain duplicate GSI named %s", indexName));
                    }
                    gsi.withProjection(new Projection().withProjectionType(ProjectionType.KEYS_ONLY));
                    gsi.withKeySchema(new KeySchemaElement(field.name(), HASH));
                }
            }
            for (final DynamoDBMapperFieldModel field : fields.values()) {
                for (final String indexName : field.globalSecondaryIndexNames(RANGE)) {
                    final GlobalSecondaryIndex gsi = map.get(indexName);
                    if (gsi == null) {
                        throw new DynamoDBMappingException(field.id().format("no HASH key present for GSI named %s", indexName));
                    }
                    gsi.withKeySchema(new KeySchemaElement(field.name(), RANGE));
                }
            }
            return Collections.unmodifiableMap(map);
        }

        /**
         * Builds the LSI mappings.
         * @return The mappings.
         */
        public Map localSecondaryIndexes() {
            final Map map = new LinkedHashMap();
            for (final DynamoDBMapperFieldModel field : fields.values()) {
                for (final String indexName : field.localSecondaryIndexNames()) {
                    final LocalSecondaryIndex lsi = new LocalSecondaryIndex().withIndexName(indexName);
                    if (map.put(indexName, lsi) != null) {
                        throw new DynamoDBMappingException(field.id().format("must not contain duplicate LocalSecondaryIndexes named %s", indexName));
                    }
                    lsi.withProjection(new Projection().withProjectionType(ProjectionType.KEYS_ONLY));
                    lsi.withKeySchema(new KeySchemaElement(keys.get(HASH).name(), HASH));
                    lsi.withKeySchema(new KeySchemaElement(field.name(), RANGE));
                }
            }
            return Collections.unmodifiableMap(map);
        }

        /**
         * Builds the instance.
         * @return The built instance.
         */
        public final DynamoDBMapperTableModel build() {
            return new DynamoDBMapperTableModel(this);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy