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

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

/*
 * Copyright 2016-2022 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> versions;
    private final Map> fields;
    private final Map> keys;
    private final DynamoDBMapperTableModel.Properties properties;
    private final Class targetType;

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

    /**
     * Gets the object type.
     * @return The object type.
     */
    public Class targetType() {
        return this.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.
     */
    @SuppressWarnings("unchecked")
    public  DynamoDBMapperFieldModel field(final String attributeName) {
        final DynamoDBMapperFieldModel field = (DynamoDBMapperFieldModel)fields.get(attributeName);
        if (field == null) {
            throw new DynamoDBMappingException(
                targetType.getSimpleName() + "[" + attributeName + "]; no mapping for attribute by name"
            );
        }
        return field;
    }

    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
    public  DynamoDBMapperFieldModel hashKey() {
        final DynamoDBMapperFieldModel field = (DynamoDBMapperFieldModel)keys.get(HASH);
        if (field == null) {
            throw new DynamoDBMappingException(
                targetType.getSimpleName() + "; no mapping for HASH key"
            );
        }
        return field;
    }

    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
    public  DynamoDBMapperFieldModel rangeKey() {
        final DynamoDBMapperFieldModel field = (DynamoDBMapperFieldModel)keys.get(RANGE);
        if (field == null) {
            throw new DynamoDBMappingException(
                targetType.getSimpleName() + "; no mapping for RANGE key"
            );
        }
        return field;
    }

    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
    public  DynamoDBMapperFieldModel rangeKeyIfExists() {
        return (DynamoDBMapperFieldModel)keys.get(RANGE);
    }

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

    /**
     * Indicates if this table has any versioned attributes.
     * @return True if any versioned attributes, false otherwise.
     */
    public boolean versioned() {
        return !versions.isEmpty();
    }

    /**
     * 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()) {
            try {
                final AttributeValue value = field.getAndConvert(object);
                if (value != null) {
                    map.put(field.name(), value);
                }
            } catch (final RuntimeException e) {
                throw new DynamoDBMappingException(
                    targetType.getSimpleName() + "[" + field.name() + "]; could not convert attribute", e
                );
            }
        }
        return map;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T unconvert(final Map object) {
        final T result = StandardBeanProperties.DeclaringReflect.newInstance(targetType);
        if (!object.isEmpty()) {
            for (final DynamoDBMapperFieldModel field : fields()) {
                try {
                    final AttributeValue value = object.get(field.name());
                    if (value != null) {
                        field.unconvertAndSet(result, value);
                    }
                } catch (final RuntimeException e) {
                    throw new DynamoDBMappingException(
                        targetType.getSimpleName() + "[" + field.name() + "]; could not unconvert attribute", e
                    );
                }
            }
        }
        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 createKey(final H hashKey, final R rangeKey) {
        final T key = StandardBeanProperties.DeclaringReflect.newInstance(targetType);
        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 object.
     * @param  The hash key type.
     * @param  The range key type.
     * @param object The object instance.
     * @return The key map.
     */
    public  Map convertKey(final T key) {
        final DynamoDBMapperFieldModel hk = this.hashKey();
        final DynamoDBMapperFieldModel rk = this.rangeKeyIfExists();
        return this.convertKey(hk.get(key), (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  Map convertKey(final H hashKey, final R rangeKey) {
        final Map key = new LinkedHashMap(4);
        final DynamoDBMapperFieldModel hk = this.hashKey();
        final AttributeValue hkValue = hashKey == null ? null : hk.convert(hashKey);
        if (hkValue != null) {
            key.put(hk.name(), hkValue);
        } else {
            throw new DynamoDBMappingException(
                targetType.getSimpleName() + "[" + hk.name() + "]; no HASH key value present"
            );
        }
        final DynamoDBMapperFieldModel rk = this.rangeKeyIfExists();
        final AttributeValue rkValue = rangeKey == null ? null : rk.convert(rangeKey);
        if (rkValue != null) {
            key.put(rk.name(), rkValue);
        } else if (rk != null) {
            throw new DynamoDBMappingException(
                targetType.getSimpleName() + "[" + rk.name() + "]; no RANGE key value present"
            );
        }
        return key;
    }

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

        public Builder(Class targetType, Properties properties) {
            this.versions = new LinkedHashMap>(4);
            this.fields = new LinkedHashMap>();
            this.keys = new EnumMap>(KeyType.class);
            this.properties = properties;
            this.targetType = targetType;
        }

        public Builder with(final DynamoDBMapperFieldModel field) {
            fields.put(field.name(), field);
            if (field.keyType() != null) {
                keys.put(field.keyType(), field);
            }
            if (field.versioned()) {
                versions.put(field.name(), field);
            }
            return this;
        }

        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(
                            targetType.getSimpleName() + "[" + field.name() + "]; must not duplicate GSI " + 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(
                            targetType.getSimpleName() + "[" + field.name() + "]; no HASH key for GSI " + indexName
                        );
                    }
                    gsi.withKeySchema(new KeySchemaElement(field.name(), RANGE));
                }
            }
            if (map.isEmpty()) {
                return Collections.emptyMap();
            }
            return Collections.unmodifiableMap(map);
        }

        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(
                            targetType.getSimpleName() + "[" + field.name() + "]; must not duplicate LSI " + 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.emptyMap();
            }
            return Collections.unmodifiableMap(map);
        }

        private Map> versions() {
            if (versions.isEmpty()) {
                return Collections.>emptyMap();
            }
            return Collections.unmodifiableMap(versions);
        }

        public Map> fields() {
            if (fields.isEmpty()) {
                return Collections.>emptyMap();
            }
            return Collections.unmodifiableMap(fields);
        }

        public Map> keys() {
            if (keys.isEmpty()) {
                return Collections.>emptyMap();
            }
            return Collections.unmodifiableMap(keys);
        }

        public DynamoDBMapperTableModel build() {
            final DynamoDBMapperTableModel result = new DynamoDBMapperTableModel(this);
            if (properties.tableName() != null) {
                result.hashKey(); //<- make sure the hash key is present
            }
            return result;
        }
    }

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

        static final class Immutable implements Properties {
            private final String tableName;

            public Immutable(final Properties properties) {
                this.tableName = properties.tableName();
            }

            @Override
            public String tableName() {
                return this.tableName;
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy