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

software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata Maven / Gradle / Ivy

/*
 * Copyright 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.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. 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 software.amazon.awssdk.enhanced.dynamodb.mapper;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;

/**
 * Implementation of {@link TableMetadata} that can be constructed directly using literal values for metadata objects.
 * This implementation is used by {@link StaticTableSchema} and associated interfaces such as {@link StaticAttributeTag}
 * and {@link StaticTableTag} which permit manipulation of the table metadata.
 */
@SdkPublicApi
public final class StaticTableMetadata implements TableMetadata {
    private final Map customMetadata;
    private final Map indexByNameMap;
    private final Map keyAttributes;

    private StaticTableMetadata(Builder builder) {
        this.customMetadata = Collections.unmodifiableMap(builder.customMetadata);
        this.indexByNameMap = Collections.unmodifiableMap(builder.indexByNameMap);
        this.keyAttributes = Collections.unmodifiableMap(builder.keyAttributes);
    }

    /**
     * Create a new builder for this class
     * @return A newly initialized {@link Builder} for building a {@link StaticTableMetadata} object.
     */
    public static Builder builder() {
        return new Builder();
    }

    @Override
    public  Optional customMetadataObject(String key, Class objectClass) {
        Object genericObject = customMetadata.get(key);

        if (genericObject == null) {
            return Optional.empty();
        }

        if (!objectClass.isAssignableFrom(genericObject.getClass())) {
            throw new IllegalArgumentException("Attempt to retrieve a custom metadata object as a type that is not "
                                               + "assignable for that object. Custom metadata key: " + key + "; "
                                               + "requested object class: " + objectClass.getCanonicalName() + "; "
                                               + "found object class: " + genericObject.getClass().getCanonicalName());
        }

        return Optional.of(objectClass.cast(genericObject));
    }

    @Override
    public String indexPartitionKey(String indexName) {
        Index index = getIndex(indexName);

        if (index.getIndexPartitionKey() == null) {
            if (!TableMetadata.primaryIndexName().equals(indexName) && index.getIndexSortKey() != null) {
                // Local secondary index, use primary partition key
                return primaryPartitionKey();
            }

            throw new IllegalArgumentException("Attempt to execute an operation against an index that requires a "
                                               + "partition key without assigning a partition key to that index. "
                                               + "Index name: " + indexName);
        }

        return index.getIndexPartitionKey();
    }

    @Override
    public Optional indexSortKey(String indexName) {
        Index index = getIndex(indexName);

        return Optional.ofNullable(index.getIndexSortKey());
    }

    @Override
    public Collection indexKeys(String indexName) {
        Index index = getIndex(indexName);

        if (index.getIndexSortKey() != null) {
            if (!TableMetadata.primaryIndexName().equals(indexName) && index.getIndexPartitionKey() == null) {
                // Local secondary index, use primary index for partition key
                return Collections.unmodifiableList(Arrays.asList(primaryPartitionKey(), index.getIndexSortKey()));
            }
            return Collections.unmodifiableList(Arrays.asList(index.getIndexPartitionKey(), index.getIndexSortKey()));
        } else {
            return Collections.singletonList(index.getIndexPartitionKey());
        }
    }

    @Override
    public Collection allKeys() {
        return this.keyAttributes.keySet();
    }

    private Index getIndex(String indexName) {
        Index index = indexByNameMap.get(indexName);

        if (index == null) {
            if (TableMetadata.primaryIndexName().equals(indexName)) {
                throw new IllegalArgumentException("Attempt to execute an operation that requires a primary index "
                                                   + "without defining any primary key attributes in the table "
                                                   + "metadata.");
            } else {
                throw new IllegalArgumentException("Attempt to execute an operation that requires a secondary index "
                                                   + "without defining the index attributes in the table metadata. "
                                                   + "Index name: " + indexName);
            }
        }

        return index;
    }

    @Override
    public Optional scalarAttributeType(String keyAttribute) {
        AttributeValueType attributeValueType = this.keyAttributes.get(keyAttribute);

        if (attributeValueType == null) {
            throw new IllegalArgumentException("Key attribute '" + keyAttribute + "' not found in table metadata.");
        }

        return Optional.ofNullable(attributeValueType.scalarAttributeType());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        StaticTableMetadata that = (StaticTableMetadata) o;

        if (customMetadata != null ? ! customMetadata.equals(that.customMetadata) : that.customMetadata != null) {
            return false;
        }
        if (indexByNameMap != null ? ! indexByNameMap.equals(that.indexByNameMap) : that.indexByNameMap != null) {
            return false;
        }
        return keyAttributes != null ? keyAttributes.equals(that.keyAttributes) : that.keyAttributes == null;
    }

    @Override
    public int hashCode() {
        int result = customMetadata != null ? customMetadata.hashCode() : 0;
        result = 31 * result + (indexByNameMap != null ? indexByNameMap.hashCode() : 0);
        result = 31 * result + (keyAttributes != null ? keyAttributes.hashCode() : 0);
        return result;
    }

    /**
     * Builder for {@link StaticTableMetadata}
     */
    public static class Builder {
        private final Map customMetadata = new HashMap<>();
        private final Map indexByNameMap = new HashMap<>();
        private final Map keyAttributes = new HashMap<>();

        private Builder() {
        }

        /**
         * Builds an immutable instance of {@link StaticTableMetadata} from the values supplied to the builder.
         */
        public StaticTableMetadata build() {
            return new StaticTableMetadata(this);
        }

        /**
         * Adds a single custom object to the metadata, keyed by a string. Attempting to add a metadata object with a
         * key that matches one that has already been added will cause an exception to be thrown.
         * @param key a string key that will be used to retrieve the custom metadata
         * @param object an object that will be stored in the custom metadata map
         * @throws  IllegalArgumentException if the custom metadata map already contains an entry with the same key
         */
        public Builder addCustomMetadataObject(String key, Object object) {
            if (customMetadata.containsKey(key)) {
                throw new IllegalArgumentException("Attempt to set a custom metadata object that has already been set. "
                                                   + "Custom metadata object key: " + key);
            }

            customMetadata.put(key, object);
            return this;
        }

        /**
         * Adds information about a partition key associated with a specific index.
         * @param indexName the name of the index to associate the partition key with
         * @param attributeName the name of the attribute that represents the partition key
         * @param attributeValueType the {@link AttributeValueType} of the partition key
         * @throws IllegalArgumentException if a partition key has already been defined for this index
         */
        public Builder addIndexPartitionKey(String indexName, String attributeName, AttributeValueType attributeValueType) {
            Index index = indexByNameMap.computeIfAbsent(indexName, $ -> new Index(indexName));

            if (index.getIndexPartitionKey() != null) {
                throw new IllegalArgumentException("Attempt to set an index partition key that conflicts with an "
                                                   + "existing index partition key of the same name and index. Index "
                                                   + "name: " + indexName + "; attribute name: " + attributeName);
            }

            index.setIndexPartitionKey(attributeName);
            index.setIndexPartitionType(attributeValueType);
            markAttributeAsKey(attributeName, attributeValueType);
            return this;
        }

        /**
         * Adds information about a sort key associated with a specific index.
         * @param indexName the name of the index to associate the sort key with
         * @param attributeName the name of the attribute that represents the sort key
         * @param attributeValueType the {@link AttributeValueType} of the sort key
         * @throws IllegalArgumentException if a sort key has already been defined for this index
         */
        public Builder addIndexSortKey(String indexName, String attributeName, AttributeValueType attributeValueType) {
            Index index = indexByNameMap.computeIfAbsent(indexName, $ -> new Index(indexName));

            if (index.getIndexSortKey() != null) {
                throw new IllegalArgumentException("Attempt to set an index sort key that conflicts with an existing"
                                                   + " index sort key of the same name and index. Index name: "
                                                   + indexName + "; attribute name: " + attributeName);
            }

            index.setIndexSortKey(attributeName);
            index.setIndexSortType(attributeValueType);
            markAttributeAsKey(attributeName, attributeValueType);
            return this;
        }

        /**
         * Declares a 'key-like' attribute that is not an actual DynamoDB key. These pseudo-keys can then be recognized
         * by extensions and treated appropriately, often being protected from manipulations as those would alter the
         * meaning of the record. One example usage of this is a 'versioned record attribute': although the version is
         * not part of the primary key of the record, it effectively serves as such.
         * @param attributeName the name of the attribute to mark as a pseudo-key
         * @param attributeValueType the {@link AttributeValueType} of the pseudo-key
         */
        public Builder markAttributeAsKey(String attributeName, AttributeValueType attributeValueType) {
            AttributeValueType existing = keyAttributes.get(attributeName);

            if (existing != null && !existing.equals(attributeValueType)) {
                throw new IllegalArgumentException("Attempt to mark an attribute as a key with a different "
                                                   + "AttributeValueType than one that has already been recorded.");
            }

            if (existing == null) {
                keyAttributes.put(attributeName, attributeValueType);
            }

            return this;
        }

        /**
         * Package-private method to merge the contents of a constructed {@link TableMetadata} into this builder.
         */
        Builder mergeWith(StaticTableMetadata other) {
            other.indexByNameMap.forEach((key, index) -> {
                if (index.getIndexPartitionKey() != null) {
                    addIndexPartitionKey(index.getIndexName(),
                                         index.getIndexPartitionKey(),
                                         index.getIndexPartitionType());
                }

                if (index.getIndexSortKey() != null) {
                    addIndexSortKey(index.getIndexName(), index.getIndexSortKey(), index.getIndexSortType());
                }
            });

            other.customMetadata.forEach(this::addCustomMetadataObject);
            other.keyAttributes.forEach(this::markAttributeAsKey);
            return this;
        }
    }

    private static class Index {
        private final String indexName;
        private String indexPartitionKey;
        private String indexSortKey;
        private AttributeValueType indexPartitionType;
        private AttributeValueType indexSortType;

        private Index(String indexName) {
            this.indexName = indexName;
        }

        private String getIndexName() {
            return indexName;
        }

        private String getIndexPartitionKey() {
            return indexPartitionKey;
        }

        private String getIndexSortKey() {
            return indexSortKey;
        }

        private AttributeValueType getIndexPartitionType() {
            return indexPartitionType;
        }

        private AttributeValueType getIndexSortType() {
            return indexSortType;
        }

        private void setIndexPartitionKey(String indexPartitionKey) {
            this.indexPartitionKey = indexPartitionKey;
        }

        private void setIndexSortKey(String indexSortKey) {
            this.indexSortKey = indexSortKey;
        }

        private void setIndexPartitionType(AttributeValueType indexPartitionType) {
            this.indexPartitionType = indexPartitionType;
        }

        private void setIndexSortType(AttributeValueType indexSortType) {
            this.indexSortType = indexSortType;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            Index index = (Index) o;

            if (indexName != null ? ! indexName.equals(index.indexName) : index.indexName != null) {
                return false;
            }
            if (indexPartitionKey != null ? ! indexPartitionKey.equals(index.indexPartitionKey) :
                index.indexPartitionKey != null) {
                return false;
            }
            if (indexSortKey != null ? ! indexSortKey.equals(index.indexSortKey) : index.indexSortKey != null) {
                return false;
            }
            if (indexPartitionType != index.indexPartitionType) {
                return false;
            }
            return indexSortType == index.indexSortType;
        }

        @Override
        public int hashCode() {
            int result = indexName != null ? indexName.hashCode() : 0;
            result = 31 * result + (indexPartitionKey != null ? indexPartitionKey.hashCode() : 0);
            result = 31 * result + (indexSortKey != null ? indexSortKey.hashCode() : 0);
            result = 31 * result + (indexPartitionType != null ? indexPartitionType.hashCode() : 0);
            result = 31 * result + (indexSortType != null ? indexSortType.hashCode() : 0);
            return result;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy