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

com.amazonaws.services.dynamodbv2.datamodeling.StandardAnnotationMaps 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.datamodeling.DynamoDBAutoGenerateStrategy.CREATE;
import static com.amazonaws.services.dynamodbv2.model.KeyType.HASH;
import static com.amazonaws.services.dynamodbv2.model.KeyType.RANGE;

import com.amazonaws.annotation.SdkInternalApi;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Map of DynamoDB annotations.
 */
@SdkInternalApi
final class StandardAnnotationMaps {

    /**
     * Gets all the DynamoDB annotations for a given class.
     */
    static final  TableMap of(Class clazz) {
        final TableMap annotations = new TableMap(clazz);
        annotations.putAll(clazz);
        return annotations;
    }

    /**
     * Gets all the DynamoDB annotations; method annotations override field
     * level annotations which override class/type level annotations.
     */
    static final  FieldMap of(Method getter, String defaultName) {
        final Class targetType = (Class)getter.getReturnType();
        final String fieldName = StandardBeanProperties.fieldNameOf(getter);

        Field declaredField = null;
        try {
            declaredField = getter.getDeclaringClass().getDeclaredField(fieldName);
        } catch (final SecurityException e) {
            throw new DynamoDBMappingException("no access to field for " + getter, e);
        } catch (final NoSuchFieldException no) {}

        if (defaultName == null) {
            defaultName = fieldName;
        }

        final FieldMap annotations = new FieldMap(targetType, defaultName);
        annotations.putAll(targetType);
        annotations.putAll(declaredField);
        annotations.putAll(getter);
        return annotations;
    }

    /**
     * Common type-conversions properties.
     */
    private static abstract class AbstractAnnotationMap {
        private final Annotations map = new Annotations();

        /**
         * Gets the actual annotation by type; if the type is not directly
         * mapped then the meta-annotation is returned.
         */
        final  A actualOf(final Class annotationType) {
            final Annotation annotation = this.map.get(annotationType);
            if (annotation == null || annotation.annotationType() == annotationType) {
                return (A)annotation;
            } else if (annotation.annotationType().isAnnotationPresent(annotationType)) {
                return annotation.annotationType().getAnnotation(annotationType);
            }
            throw new DynamoDBMappingException(
                "could not resolve annotation by type" +
                "; @" + annotationType.getSimpleName() + " not present on " + annotation
            );
        }

        /**
         * Puts all DynamoDB annotations into the map.
         */
        final void putAll(AnnotatedElement annotated) {
            if (annotated != null) {
                this.map.putAll(new Annotations().putAll(annotated.getAnnotations()));
            }
        }
    }

    /**
     * Common type-conversions properties.
     */
    static abstract class TypedMap extends AbstractAnnotationMap {
        private final Class targetType;

        private TypedMap(final Class targetType) {
            this.targetType = targetType;
        }

        /**
         * Gets the target type.
         */
        final Class targetType() {
            return this.targetType;
        }

        /**
         * Gets the attribute type from the {@link DynamoDBTyped} annotation
         * if present.
         */
        public DynamoDBAttributeType attributeType() {
            final DynamoDBTyped annotation = actualOf(DynamoDBTyped.class);
            if (annotation != null) {
                return annotation.value();
            }
            return null;
        }

        /**
         * Creates a new type-converter form the {@link DynamoDBTypeConverted}
         * annotation if present.
         */
        public  DynamoDBTypeConverter typeConverter() {
            Annotation annotation = super.map.get(DynamoDBTypeConverted.class);
            if (annotation != null) {
                final DynamoDBTypeConverted converted = actualOf(DynamoDBTypeConverted.class);
                annotation = (converted == annotation ? null : annotation);
                return overrideOf(converted.converter(), targetType, annotation);
            }
            return null;
        }

        /**
         * Creates a new auto-generator from the {@link DynamoDBAutoGenerated}
         * annotation if present.
         */
        public DynamoDBAutoGenerator autoGenerator() {
            Annotation annotation = super.map.get(DynamoDBAutoGenerated.class);
            if (annotation != null) {
                final DynamoDBAutoGenerated generated = actualOf(DynamoDBAutoGenerated.class);
                annotation = (generated == annotation ? null : annotation);
                DynamoDBAutoGenerator generator = overrideOf(generated.generator(), targetType, annotation);
                if (generator.getGenerateStrategy() == CREATE && targetType.isPrimitive()) {
                    throw new DynamoDBMappingException(
                        "type [" + targetType + "] is not supported for auto-generation" +
                        "; primitives are not allowed when auto-generate strategy is CREATE"
                    );
                }
                return generator;
            }
            return null;
        }

        /**
         * Maps the attributes from the {@link DynamoDBFlattened} annotation.
         */
        public Map attributes() {
            final Map attributes = new LinkedHashMap();
            for (final DynamoDBAttribute a : actualOf(DynamoDBFlattened.class).attributes()) {
                if (a.mappedBy().isEmpty() || a.attributeName().isEmpty()) {
                    throw new DynamoDBMappingException("@DynamoDBFlattened must specify mappedBy and attributeName");
                } else if (attributes.put(a.mappedBy(), a.attributeName()) != null) {
                    throw new DynamoDBMappingException("@DynamoDBFlattened must not duplicate mappedBy=" + a.mappedBy());
                }
            }
            if (attributes.isEmpty()) {
                throw new DynamoDBMappingException("@DynamoDBFlattened must specify one or more attributes");
            }
            return attributes;
        }

        /**
         * Returns true if the {@link DynamoDBFlattened} annotation is present.
         */
        public boolean flattened() {
            return actualOf(DynamoDBFlattened.class) != null;
        }
    }

    /**
     * {@link DynamoDBMapperTableModel} annotations.
     */
    static final class TableMap extends TypedMap implements DynamoDBMapperTableModel.Properties {
        private TableMap(final Class targetType) {
            super(targetType);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public DynamoDBAttributeType attributeType() {
            DynamoDBAttributeType attributeType = super.attributeType();
            if (attributeType == null && actualOf(DynamoDBTable.class) != null) {
                attributeType = DynamoDBAttributeType.M;
            }
            return attributeType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String tableName() {
            final DynamoDBTable annotation = actualOf(DynamoDBTable.class);
            if (annotation != null && !annotation.tableName().isEmpty()) {
                return annotation.tableName();
            }
            return null;
        }
    }

    /**
     * {@link DynamoDBMapperFieldModel} annotations.
     */
    static final class FieldMap extends TypedMap implements DynamoDBMapperFieldModel.Properties {
        private final String defaultName;

        private FieldMap(Class targetType, String defaultName) {
            super(targetType);
            this.defaultName = defaultName;
        }

        /**
         * Returns true if the {@link DynamoDBIgnore} annotation is present.
         */
        public boolean ignored() {
            return actualOf(DynamoDBIgnore.class) != null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public DynamoDBAttributeType attributeType() {
            final DynamoDBScalarAttribute annotation = actualOf(DynamoDBScalarAttribute.class);
            if (annotation != null) {
                if (Set.class.isAssignableFrom(targetType())) {
                    return DynamoDBAttributeType.valueOf(annotation.type().name() + "S");
                } else {
                    return DynamoDBAttributeType.valueOf(annotation.type().name());
                }
            }
            return super.attributeType();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String attributeName() {
            final DynamoDBHashKey hashKey = actualOf(DynamoDBHashKey.class);
            if (hashKey != null && !hashKey.attributeName().isEmpty()) {
                return hashKey.attributeName();
            }
            final DynamoDBIndexHashKey indexHashKey = actualOf(DynamoDBIndexHashKey.class);
            if (indexHashKey != null && !indexHashKey.attributeName().isEmpty()) {
                return indexHashKey.attributeName();
            }
            final DynamoDBRangeKey rangeKey = actualOf(DynamoDBRangeKey.class);
            if (rangeKey != null && !rangeKey.attributeName().isEmpty()) {
                return rangeKey.attributeName();
            }
            final DynamoDBIndexRangeKey indexRangeKey = actualOf(DynamoDBIndexRangeKey.class);
            if (indexRangeKey != null && !indexRangeKey.attributeName().isEmpty()) {
                return indexRangeKey.attributeName();
            }
            final DynamoDBAttribute attribute = actualOf(DynamoDBAttribute.class);
            if (attribute != null && !attribute.attributeName().isEmpty()) {
                return attribute.attributeName();
            }
            final DynamoDBVersionAttribute versionAttribute = actualOf(DynamoDBVersionAttribute.class);
            if (versionAttribute != null && !versionAttribute.attributeName().isEmpty()) {
                return versionAttribute.attributeName();
            }
            final DynamoDBScalarAttribute scalarAttribute = actualOf(DynamoDBScalarAttribute.class);
            if (scalarAttribute != null && !scalarAttribute.attributeName().isEmpty()) {
                return scalarAttribute.attributeName();
            }
            final DynamoDBNamed annotation = actualOf(DynamoDBNamed.class);
            if (annotation != null && !annotation.value().isEmpty()) {
                return annotation.value();
            }
            return this.defaultName;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public KeyType keyType() {
            final DynamoDBKeyed annotation = actualOf(DynamoDBKeyed.class);
            if (annotation != null) {
                return annotation.value();
            }
            return null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean versioned() {
            return actualOf(DynamoDBVersioned.class) != null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Map> globalSecondaryIndexNames() {
            final Map> gsis = new EnumMap>(KeyType.class);
            final DynamoDBIndexHashKey indexHashKey = actualOf(DynamoDBIndexHashKey.class);
            if (indexHashKey != null) {
                if (!indexHashKey.globalSecondaryIndexName().isEmpty()) {
                    if (indexHashKey.globalSecondaryIndexNames().length > 0) {
                        throw new DynamoDBMappingException("@DynamoDBIndexHashKey must not specify both HASH GSI name/names");
                    }
                    gsis.put(HASH, Collections.singletonList(indexHashKey.globalSecondaryIndexName()));
                } else if (indexHashKey.globalSecondaryIndexNames().length > 0) {
                    gsis.put(HASH, Collections.unmodifiableList(Arrays.asList(indexHashKey.globalSecondaryIndexNames())));
                } else {
                    throw new DynamoDBMappingException("@DynamoDBIndexHashKey must specify one of HASH GSI name/names");
                }
            }
            final DynamoDBIndexRangeKey indexRangeKey = actualOf(DynamoDBIndexRangeKey.class);
            if (indexRangeKey != null) {
                if (!indexRangeKey.globalSecondaryIndexName().isEmpty()) {
                    if (indexRangeKey.globalSecondaryIndexNames().length > 0) {
                        throw new DynamoDBMappingException("@DynamoDBIndexRangeKey must not specify both RANGE GSI name/names");
                    }
                    gsis.put(RANGE, Collections.singletonList(indexRangeKey.globalSecondaryIndexName()));
                } else if (indexRangeKey.globalSecondaryIndexNames().length > 0) {
                    gsis.put(RANGE, Collections.unmodifiableList(Arrays.asList(indexRangeKey.globalSecondaryIndexNames())));
                } else if (localSecondaryIndexNames().isEmpty()) {
                    throw new DynamoDBMappingException("@DynamoDBIndexRangeKey must specify RANGE GSI and/or LSI name/names");
                }
            }
            if (!gsis.isEmpty()) {
                return Collections.unmodifiableMap(gsis);
            }
            return Collections.>emptyMap();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public List localSecondaryIndexNames() {
            final DynamoDBIndexRangeKey annotation = actualOf(DynamoDBIndexRangeKey.class);
            if (annotation != null) {
                if (!annotation.localSecondaryIndexName().isEmpty()) {
                    if (annotation.localSecondaryIndexNames().length > 0) {
                        throw new DynamoDBMappingException("@DynamoDBIndexRangeKey must not specify both LSI name/names");
                    }
                    return Collections.singletonList(annotation.localSecondaryIndexName());
                } else if (annotation.localSecondaryIndexNames().length > 0) {
                    return Collections.unmodifiableList(Arrays.asList(annotation.localSecondaryIndexNames()));
                }
            }
            return Collections.emptyList();
        }
    }

    /**
     * A map of annotation type to annotation. It will map any first level
     * custom annotations to any DynamoDB annotation types that are present.
     * It will support up to two levels of compounded DynamoDB annotations.
     */
    private static final class Annotations extends LinkedHashMap,Annotation> {
        private static final long serialVersionUID = -1L;

        /**
         * Puts the annotation if it's DynamoDB; ensures there are no conflicts.
         */
        public boolean putIfAnnotated(Class annotationType, Annotation annotation) {
            if (!annotationType.isAnnotationPresent(DynamoDB.class)) {
                return false;
            } else if ((annotation = put(annotationType, annotation)) == null) {
                return true;
            }
            throw new DynamoDBMappingException(
                "conflicting annotations " + annotation + " and " + get(annotationType) +
                "; allowed only one of @" + annotationType.getSimpleName()
            );
        }

        /**
         * Puts all DynamoDB annotations and meta-annotations in the map.
         */
        public Annotations putAll(Annotation ... annotations) {
            for (final Annotation a1 : annotations) {
                putIfAnnotated(a1.annotationType(), a1);
                for (final Annotation a2 : a1.annotationType().getAnnotations()) {
                    if (putIfAnnotated(a2.annotationType(), a1)) {
                        for (final Annotation a3 : a2.annotationType().getAnnotations()) {
                            putIfAnnotated(a3.annotationType(), a2);
                        }
                    }
                }
            }
            return this;
        }
    }

    /**
     * Creates a new instance of the clazz with the target type and annotation
     * as parameters if available.
     */
    private static  T overrideOf(Class clazz, Class targetType, Annotation annotation) {
        try {
            if (annotation != null) {
                try {
                    return clazz.getConstructor(Class.class, annotation.annotationType()).newInstance(targetType, annotation);
                } catch (final NoSuchMethodException no) {}
            }
            try {
                return clazz.getConstructor(Class.class).newInstance(targetType);
            } catch (final NoSuchMethodException no) {}
            return clazz.newInstance();
        } catch (final Exception e) {
            throw new DynamoDBMappingException("could not instantiate " + clazz, e);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy