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 static com.amazonaws.services.dynamodbv2.model.ProjectionType.KEYS_ONLY;

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.EnumMap;
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 globalSecondaryIndexes;
    private final Map localSecondaryIndexes;
    private final Map> fields;
    private final Map> keys;
    private final DynamoDBMapperTableModel.Properties properties;

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

    /**
     * Gets the ID.
     * @return The ID.
     */
    final DynamoDBMapperTableModel.Id id() {
        return properties.id();
    }

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

    /**
     * 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(id().err("does not map %s on model", attributeName));
        }
        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(id().err("does not map %s key on model", keyType));
        }
        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 (globalSecondaryIndexes.isEmpty()) {
            return null;
        }
        final Collection copies = new ArrayList(globalSecondaryIndexes.size());
        for (final String indexName : globalSecondaryIndexes.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 (!globalSecondaryIndexes.containsKey(indexName)) {
            return null;
        }
        final GlobalSecondaryIndex gsi = globalSecondaryIndexes.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 (localSecondaryIndexes.isEmpty()) {
            return null;
        }
        final Collection copies = new ArrayList(localSecondaryIndexes.size());
        for (final String indexName : localSecondaryIndexes.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 (!localSecondaryIndexes.containsKey(indexName)) {
            return null;
        }
        final LocalSecondaryIndex lsi = localSecondaryIndexes.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) {
        final Map map = new LinkedHashMap();
        for (final DynamoDBMapperFieldModel field : fields()) {
            final AttributeValue value = field.convert(field.get(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(id().err("could not instantiate %s", targetType()));
        }
        if (!object.isEmpty()) {
            for (final DynamoDBMapperFieldModel field : fields()) {
                final AttributeValue value = object.get(field.name());
                if (value != null) {
                    field.set(result, field.unconvert(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 asKey(final H hashKey, final R rangeKey) {
        final T object;
        try {
            object = targetType().newInstance();
        } catch (final Exception e) {
            throw new DynamoDBMappingException(id().err("could not instantiate %s", targetType()));
        }
        if (hashKey != null) {
            final DynamoDBMapperFieldModel hk = hashKey();
            hk.set(object, hashKey);
        }
        if (rangeKey != null) {
            final DynamoDBMapperFieldModel rk = rangeKey();
            rk.set(object, rangeKey);
        }
        return object;
    }

    /**
     * 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  DynamoDBMapperTableModel.Key convertKey(final T key) {
        final DynamoDBMapperFieldModel hk = hashKey();
        final DynamoDBMapperFieldModel rk = rangeKeyIfExists();
        return new Key().withHashKey(hk.get(key)).withRangeKey(rk == null ? (R)null : rk.get(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  DynamoDBMapperTableModel.Key convertKey(final H hashKey, final R rangeKey) {
        return new Key().withHashKey(hashKey).withRangeKey(rangeKey);
    }

    /**
     * Holds the hash and range key attribute value map.
     */
    public final class Key extends LinkedHashMap {
        private Key() {
            super(4);
        }
        private Key withHashKey(final H hashKey) {
            if (hashKey != null) {
                final DynamoDBMapperFieldModel hk = DynamoDBMapperTableModel.this.hashKey();
                put(hk.name(), hk.convert(hashKey));
            } else {
                throw new DynamoDBMappingException(hashKey().id().err("no HASH key value present"));
            }
            return this;
        }
        private Key withRangeKey(final R rangeKey) {
            if (rangeKey != null) {
                final DynamoDBMapperFieldModel rk = DynamoDBMapperTableModel.this.rangeKey();
                put(rk.name(), rk.convert(rangeKey));
            } else if (DynamoDBMapperTableModel.this.rangeKeyIfExists() != null) {
                throw new DynamoDBMappingException(rangeKey().id().err("no RANGE key value present"));
            }
            return this;
        }
    }

    /**
     * The table identifier for formatting error messages.
     */
    public static class Id {
        private final Class type;

        /**
         * Constructs a new identifier.
         */
        public Id(final Class type) {
            this.type = type;
        }

        /**
         * Constructs a new identifier from an existing.
         */
        public Id(final Id id) {
            this(id.type);
        }

        /**
         * Formats an exception message with the identifier.
         */
        public final String err(String message, final Object ... args) {
            if (message != null && args.length > 0) {
                try {
                    message = String.format(message, args);
                } catch (final RuntimeException no) {}
            }
            return new StringBuilder().append(this).append(": ").append(message).toString();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(final Object o) {
            return o instanceof Id && ((Id)o).type == type;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return type.hashCode();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return type.getSimpleName();
        }
    }

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

        /**
         * Constructs a new builder with the optional defaults.
         */
        public Builder(final DynamoDBMapperTableModel.Properties ... defaults) {
            super(defaults);
            this.fields = new LinkedHashMap>();
            this.keys = new EnumMap>(KeyType.class);
        }

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

        /**
         * Builds the GSI mappings.
         */
        public final 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().err(
                            "must not contain duplicate GSI named %s", indexName));
                    }
                    gsi.withProjection(new Projection().withProjectionType(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().err(
                            "no HASH key present for GSI named %s", indexName));
                    }
                    gsi.withKeySchema(new KeySchemaElement(field.name(), RANGE));
                }
            }
            if (!map.isEmpty()) {
                return Collections.unmodifiableMap(map);
            }
            return Collections.emptyMap();
        }

        /**
         * Builds the LSI mappings.
         */
        public final 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().err(
                            "must not contain duplicate LSI named %s", indexName));
                    }
                    lsi.withProjection(new Projection().withProjectionType(KEYS_ONLY));
                    lsi.withKeySchema(new KeySchemaElement(keys.get(HASH).name(), HASH));
                    lsi.withKeySchema(new KeySchemaElement(field.name(), RANGE));
                }
            }
            if (!map.isEmpty()) {
                return Collections.unmodifiableMap(map);
            }
            return Collections.emptyMap();
        }

        /**
         * Builds the instance.
         */
        public final DynamoDBMapperTableModel build() {
            if (tableName() != null && keys.get(HASH) == null) {
                throw new DynamoDBMappingException(id().err("does not map HASH key on model"));
            }
            return new DynamoDBMapperTableModel(this);
        }
    }

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

        /**
         * Immutable properties.
         */
        static class Immutable implements Properties {
            private DynamoDBMapperTableModel.Id id;
            private Class targetType;
            private String tableName;

            /**
             * Initialize this properties with the specified defaults.
             */
            public Immutable(final Properties ... defaults) {
                for (final Properties d : defaults) {
                    this.targetType = d.targetType();
                    this.tableName = d.tableName();
                    this.id = d.id();
                }
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public final DynamoDBMapperTableModel.Id id() {
                return this.id;
            }

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

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

        /**
         * Properties builder.
         */
        static class Buildable extends Immutable {
            /**
             * Populates the builder properties with the specified defaults.
             */
            public Buildable(final Properties ... defaults) {
                super(defaults);
            }

            /**
             * Sets the target type.
             */
            public final Buildable withId(final DynamoDBMapperTableModel.Id id) {
                super.id = id;
                return this;
            }

            /**
             * Sets the target type.
             */
            public final Buildable withTargetType(final Class targetType) {
                super.targetType = targetType;
                return this;
            }

            /**
             * Sets the table name.
             */
            public final Buildable withTableName(final String tableName) {
                super.tableName = tableName;
                return this;
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy