Package info.archinnov.achilles.internal.metadata.parsing

Source Code of info.archinnov.achilles.internal.metadata.parsing.PropertyParser

/*
* Copyright (C) 2012-2014 DuyHai DOAN
*
*  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://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License 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 info.archinnov.achilles.internal.metadata.parsing;

import static info.archinnov.achilles.internal.metadata.holder.PropertyMetaBuilder.factory;
import static info.archinnov.achilles.internal.metadata.holder.PropertyType.EMBEDDED_ID;
import static info.archinnov.achilles.internal.metadata.holder.PropertyType.ID;
import static info.archinnov.achilles.internal.metadata.holder.PropertyType.LIST;
import static info.archinnov.achilles.internal.metadata.holder.PropertyType.MAP;
import static info.archinnov.achilles.internal.metadata.holder.PropertyType.SET;
import static info.archinnov.achilles.internal.metadata.holder.PropertyType.SIMPLE;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.validation.constraints.NotNull;

import info.archinnov.achilles.codec.Codec;
import info.archinnov.achilles.internal.metadata.codec.ListCodec;
import info.archinnov.achilles.internal.metadata.codec.MapCodec;
import info.archinnov.achilles.internal.metadata.codec.SetCodec;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.archinnov.achilles.annotations.Column;
import info.archinnov.achilles.annotations.Consistency;
import info.archinnov.achilles.annotations.EmptyCollectionIfNull;
import info.archinnov.achilles.annotations.Id;
import info.archinnov.achilles.annotations.Index;
import info.archinnov.achilles.annotations.TimeUUID;
import info.archinnov.achilles.interceptor.Interceptor;
import info.archinnov.achilles.internal.metadata.holder.CounterProperties;
import info.archinnov.achilles.internal.metadata.holder.EmbeddedIdProperties;
import info.archinnov.achilles.internal.metadata.holder.IndexProperties;
import info.archinnov.achilles.internal.metadata.holder.PropertyMeta;
import info.archinnov.achilles.internal.metadata.holder.PropertyType;
import info.archinnov.achilles.internal.metadata.parsing.context.PropertyParsingContext;
import info.archinnov.achilles.internal.metadata.parsing.validator.PropertyParsingValidator;
import info.archinnov.achilles.internal.validation.Validator;
import info.archinnov.achilles.type.ConsistencyLevel;
import info.archinnov.achilles.type.Counter;
import info.archinnov.achilles.type.Pair;

public class PropertyParser {
    private static final Logger log = LoggerFactory.getLogger(PropertyFilter.class);

    public static Set<Class<?>> allowedTypes = new HashSet<>();

    static {
        // Bytes
        allowedTypes.add(byte.class);
        allowedTypes.add(Byte.class);
        allowedTypes.add(byte[].class);
        allowedTypes.add(ByteBuffer.class);

        // Boolean
        allowedTypes.add(Boolean.class);
        allowedTypes.add(boolean.class);

        // Date
        allowedTypes.add(Date.class);

        // Double
        allowedTypes.add(Double.class);
        allowedTypes.add(double.class);

        // Float
        allowedTypes.add(BigDecimal.class);
        allowedTypes.add(Float.class);
        allowedTypes.add(float.class);

        // InetAddress
        allowedTypes.add(InetAddress.class);

        // Integer
        allowedTypes.add(BigInteger.class);
        allowedTypes.add(Integer.class);
        allowedTypes.add(int.class);

        // Long
        allowedTypes.add(Long.class);
        allowedTypes.add(long.class);

        // String
        allowedTypes.add(String.class);

        // UUID
        allowedTypes.add(UUID.class);
    }

    private EntityIntrospector entityIntrospector = EntityIntrospector.Singleton.INSTANCE.get();
    private PropertyParsingValidator validator = PropertyParsingValidator.Singleton.INSTANCE.get();
    private PropertyFilter filter = PropertyFilter.Singleton.INSTANCE.get();
    private CodecFactory codecFactory = CodecFactory.Singleton.INSTANCE.get();

    public static String getIndexName(Field field) {
        log.debug("Check @Index annotation on field {} of class {}", field.getName(), field.getDeclaringClass().getCanonicalName());
        String indexName = null;
        Index index = field.getAnnotation(Index.class);
        if (index != null) {
            indexName = index.name();
        }
        return indexName;
    }

//    public boolean hasConsistencyAnnotation(Field field) {
//        log.debug("Check @Consistency annotation on field {} of class {}", field.getName(), field.getDeclaringClass()
//                .getCanonicalName());
//
//        boolean consistency = false;
//        if (field.getAnnotation(Consistency.class) != null) {
//            consistency = true;
//        }
//        return consistency;
//    }

    public static <T> boolean isSupportedNativeType(Class<T> valueClass) {
        return valueClass != null && (allowedTypes.contains(valueClass) || (ByteBuffer.class.isAssignableFrom(valueClass)));
    }

    public static <T> boolean isAssignableFromNativeType(Class<T> valueClass) {
        for (Class<?> allowedType : allowedTypes) {
            if (allowedType.isAssignableFrom(valueClass)) {
                return true;
            }
        }
        return false;
    }

    public static <T> boolean isSupportedType(Class<T> valueClass) {
        return isSupportedNativeType(valueClass) || (valueClass != null && valueClass.isEnum());
    }


    public Pair<ConsistencyLevel, ConsistencyLevel> findConsistencyLevels(Field field, Pair<ConsistencyLevel, ConsistencyLevel> defaultConsistencyLevels) {
        log.debug("Find consistency configuration for field {} of class {}", field.getName(), field.getDeclaringClass()
                .getCanonicalName());

        Consistency clevel = field.getAnnotation(Consistency.class);

        ConsistencyLevel defaultGlobalRead = defaultConsistencyLevels.left;
        ConsistencyLevel defaultGlobalWrite = defaultConsistencyLevels.right;

        if (clevel != null) {
            defaultGlobalRead = clevel.read();
            defaultGlobalWrite = clevel.write();
        }

        log.trace("Found consistency levels : {} / {}", defaultGlobalRead, defaultGlobalWrite);
        return Pair.create(defaultGlobalRead, defaultGlobalWrite);
    }

    public Class<?> inferEntityClassFromInterceptor(Interceptor<?> interceptor) {
        for (Type type : interceptor.getClass().getGenericInterfaces()) {
            if (type instanceof ParameterizedType) {
                final ParameterizedType parameterizedType = (ParameterizedType) type;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                return TypeParser.getClassFromType(actualTypeArguments[0]);
            }
        }
        return null;
    }

    public PropertyMeta parse(PropertyParsingContext context) {
        log.debug("Parsing property {} of entity class {}", context.getCurrentPropertyName(), context
                .getCurrentEntityClass().getCanonicalName());

        Field field = context.getCurrentField();
        context.setCustomConsistencyLevels(filter.hasAnnotation(context.getCurrentField(), Consistency.class));

        validator.validateNoDuplicatePropertyName(context);
        validator.validateIndexIfSet(context);

        Class<?> fieldType = field.getType();
        PropertyMeta propertyMeta;

        if (List.class.isAssignableFrom(fieldType)) {
            propertyMeta = parseListProperty(context);
        } else if (Set.class.isAssignableFrom(fieldType)) {
            propertyMeta = parseSetProperty(context);
        } else if (Map.class.isAssignableFrom(fieldType)) {
            propertyMeta = parseMapProperty(context);
        } else if (Counter.class.isAssignableFrom(fieldType)) {
            propertyMeta = parseCounterProperty(context);
        } else if (context.isEmbeddedId()) {
            propertyMeta = parseEmbeddedId(context);
        } else if (context.isPrimaryKey()) {
            propertyMeta = parseId(context);
        } else {
            propertyMeta = parseSimpleProperty(context);
            String indexName = getIndexName(field);
            if (indexName != null) {
                propertyMeta.setIndexProperties(new IndexProperties(indexName, propertyMeta.getCQL3ColumnName()));
            }
        }
        context.getPropertyMetas().put(context.getCurrentPropertyName(), propertyMeta);

        validator.validateNoDuplicateCQLName(context);
        return propertyMeta;
    }

    protected PropertyMeta parseId(PropertyParsingContext context) {
        log.debug("Parsing property {} as id of entity class {}", context.getCurrentPropertyName(), context
                .getCurrentEntityClass().getCanonicalName());

        PropertyMeta idMeta = parseSimpleProperty(context);
        idMeta.setType(ID);
        Id id = context.getCurrentField().getAnnotation(Id.class);
        String propertyName = StringUtils.isNotBlank(id.name()) ? id.name() : context.getCurrentPropertyName();
        idMeta.setPropertyName(propertyName);

        return idMeta;
    }

    protected PropertyMeta parseEmbeddedId(PropertyParsingContext context) {
        log.debug("Parsing property {} as embedded id of entity class {}", context.getCurrentPropertyName(), context
                .getCurrentEntityClass().getCanonicalName());

        Class<?> entityClass = context.getCurrentEntityClass();
        Field field = context.getCurrentField();
        final Class<?> embeddedIdClass = field.getType();

        Validator.validateInstantiable(embeddedIdClass);

        Method[] accessors = entityIntrospector.findAccessors(entityClass, field);
        PropertyType type = EMBEDDED_ID;

        EmbeddedIdProperties embeddedIdProperties = extractEmbeddedIdProperties(embeddedIdClass, context);
        PropertyMeta propertyMeta = factory().objectMapper(context.getCurrentObjectMapper()).type(type)
                .propertyName(context.getCurrentPropertyName()).embeddedIdProperties(embeddedIdProperties)
                .entityClassName(context.getCurrentEntityClass().getCanonicalName()).accessors(accessors).field(field)
                .consistencyLevels(context.getCurrentConsistencyLevels()).build(Void.class, embeddedIdClass);

        log.trace("Built embedded id property meta for property {} of entity class {} : {}",
                propertyMeta.getPropertyName(), context.getCurrentEntityClass().getCanonicalName(), propertyMeta);
        return propertyMeta;
    }

    protected PropertyMeta parseSimpleProperty(PropertyParsingContext context) {
        log.debug("Parsing property {} as simple property of entity class {}", context.getCurrentPropertyName(),
                context.getCurrentEntityClass().getCanonicalName());

        Class<?> entityClass = context.getCurrentEntityClass();
        Field field = context.getCurrentField();
        final boolean staticColumn = isStaticColumn(field);
        boolean timeUUID = isTimeUUID(context, field);

        Method[] accessors = entityIntrospector.findAccessors(entityClass, field);
        final Codec simpleCodec = codecFactory.parseSimpleField(context);
        final Class<?> cql3ValueType = codecFactory.determineCQL3ValueType(simpleCodec, timeUUID);
        PropertyType type = SIMPLE;

        PropertyMeta propertyMeta = factory().objectMapper(context.getCurrentObjectMapper()).type(type)
                .propertyName(context.getCurrentPropertyName()).cqlColumnName(context.getCurrentCQL3ColumnName())
                .entityClassName(context.getCurrentEntityClass().getCanonicalName()).accessors(accessors)
                .consistencyLevels(context.getCurrentConsistencyLevels()).field(field).timeuuid(timeUUID)
                .staticColumn(staticColumn).simpleCodec(simpleCodec).cql3ValueClass(cql3ValueType)
                .build(Void.class, field.getType());

        log.trace("Built simple property meta for property {} of entity class {} : {}", propertyMeta.getPropertyName(),
                context.getCurrentEntityClass().getCanonicalName(), propertyMeta);
        return propertyMeta;
    }

    protected PropertyMeta parseCounterProperty(PropertyParsingContext context) {
        log.debug("Parsing property {} as counter property of entity class {}", context.getCurrentPropertyName(),
                context.getCurrentEntityClass().getCanonicalName());

        Class<?> entityClass = context.getCurrentEntityClass();
        Field field = context.getCurrentField();

        Method[] accessors = entityIntrospector.findAccessors(entityClass, field);
        final boolean staticColumn = isStaticColumn(field);
        PropertyType type = PropertyType.COUNTER;

        CounterProperties counterProperties = new CounterProperties(context.getCurrentEntityClass().getCanonicalName());

        PropertyMeta propertyMeta = factory().objectMapper(context.getCurrentObjectMapper()).type(type)
                .propertyName(context.getCurrentPropertyName()).cqlColumnName(context.getCurrentCQL3ColumnName())
                .entityClassName(context.getCurrentEntityClass().getCanonicalName()).accessors(accessors).field(field)
                .counterProperties(counterProperties).consistencyLevels(context.getCurrentConsistencyLevels()).staticColumn(staticColumn)
                .cql3ValueClass(Long.class)
                .build(Void.class, field.getType());

        context.hasSimpleCounterType();
        context.getCounterMetas().add(propertyMeta);
        if (context.isCustomConsistencyLevels()) {
            parseSimpleCounterConsistencyLevel(context, propertyMeta);
        }

        log.trace("Built simple property meta for property {} of entity class {} : {}", propertyMeta.getPropertyName(),
                context.getCurrentEntityClass().getCanonicalName(), propertyMeta);
        return propertyMeta;
    }

    public <V> PropertyMeta parseListProperty(PropertyParsingContext context) {

        log.debug("Parsing property {} as list property of entity class {}", context.getCurrentPropertyName(), context
                .getCurrentEntityClass().getCanonicalName());

        Class<?> entityClass = context.getCurrentEntityClass();
        Field field = context.getCurrentField();
        final boolean timeUUID = isTimeUUID(context, field);
        final boolean emptyCollectionIfNull = mapNullCollectionAndMapToEmpty(field);
        final boolean staticColumn = isStaticColumn(field);
        Class<V> valueClass;
        Type genericType = field.getGenericType();
        valueClass = TypeParser.inferValueClassForListOrSet(genericType, entityClass);

        Method[] accessors = entityIntrospector.findAccessors(entityClass, field);
        final ListCodec listCodec = codecFactory.parseListField(context);
        final Class<?> cql3ValueType = codecFactory.determineCQL3ValueType(listCodec, timeUUID);

        PropertyType type = LIST;

        PropertyMeta listMeta = factory().objectMapper(context.getCurrentObjectMapper()).type(type)
                .propertyName(context.getCurrentPropertyName()).cqlColumnName(context.getCurrentCQL3ColumnName())
                .entityClassName(context.getCurrentEntityClass().getCanonicalName())
                .consistencyLevels(context.getCurrentConsistencyLevels()).accessors(accessors).field(field)
                .timeuuid(timeUUID).emptyCollectionAndMapIfNull(emptyCollectionIfNull).staticColumn(staticColumn)
                .listCodec(listCodec).cql3ValueClass(cql3ValueType)
                .build(Void.class, valueClass);

        log.trace("Built list property meta for property {} of entity class {} : {}", listMeta.getPropertyName(),
                context.getCurrentEntityClass().getCanonicalName(), listMeta);

        return listMeta;

    }

    public <V> PropertyMeta parseSetProperty(PropertyParsingContext context) {
        log.debug("Parsing property {} as set property of entity class {}", context.getCurrentPropertyName(), context
                .getCurrentEntityClass().getCanonicalName());

        Class<?> entityClass = context.getCurrentEntityClass();
        Field field = context.getCurrentField();
        final boolean timeUUID = isTimeUUID(context, field);
        final boolean emptyCollectionIfNull = mapNullCollectionAndMapToEmpty(field);
        final boolean staticColumn = isStaticColumn(field);
        Class<V> valueClass;
        Type genericType = field.getGenericType();

        valueClass = TypeParser.inferValueClassForListOrSet(genericType, entityClass);
        Method[] accessors = entityIntrospector.findAccessors(entityClass, field);
        final SetCodec setCodec = codecFactory.parseSetField(context);
        final Class<?> cql3ValueType = codecFactory.determineCQL3ValueType(setCodec, timeUUID);

        PropertyType type = SET;

        PropertyMeta setMeta = factory().objectMapper(context.getCurrentObjectMapper()).type(type)
                .propertyName(context.getCurrentPropertyName()).cqlColumnName(context.getCurrentCQL3ColumnName())
                .entityClassName(context.getCurrentEntityClass().getCanonicalName())
                .consistencyLevels(context.getCurrentConsistencyLevels()).accessors(accessors).field(field)
                .timeuuid(timeUUID).emptyCollectionAndMapIfNull(emptyCollectionIfNull).staticColumn(staticColumn)
                .setCodec(setCodec).cql3ValueClass(cql3ValueType)
                .build(Void.class, valueClass);

        log.trace("Built set property meta for property {} of  entity class {} : {}", setMeta.getPropertyName(),
                context.getCurrentEntityClass().getCanonicalName(), setMeta);

        return setMeta;
    }

    protected <K, V> PropertyMeta parseMapProperty(PropertyParsingContext context) {
        log.debug("Parsing property {} as map property of entity class {}", context.getCurrentPropertyName(), context
                .getCurrentEntityClass().getCanonicalName());

        Class<?> entityClass = context.getCurrentEntityClass();
        Field field = context.getCurrentField();
        final boolean timeUUID = isTimeUUID(context, field);
        final boolean emptyCollectionIfNull = mapNullCollectionAndMapToEmpty(field);
        final boolean staticColumn = isStaticColumn(field);
        validator.validateMapGenerics(field, entityClass);

        Pair<Class<K>, Class<V>> types = TypeParser.determineMapGenericTypes(field);
        Class<K> keyClass = types.left;
        Class<V> valueClass = types.right;

        Method[] accessors = entityIntrospector.findAccessors(entityClass, field);
        final MapCodec mapCodec = codecFactory.parseMapField(context);
        final Class<?> cql3KeyType = codecFactory.determineCQL3KeyType(mapCodec, timeUUID);
        final Class<?> cql3ValueType = codecFactory.determineCQL3ValueType(mapCodec, timeUUID);

        PropertyType type = MAP;

        PropertyMeta mapMeta = factory().objectMapper(context.getCurrentObjectMapper()).type(type)
                .propertyName(context.getCurrentPropertyName()).cqlColumnName(context.getCurrentCQL3ColumnName())
                .entityClassName(context.getCurrentEntityClass().getCanonicalName())
                .consistencyLevels(context.getCurrentConsistencyLevels()).accessors(accessors).field(field)
                .timeuuid(timeUUID).emptyCollectionAndMapIfNull(emptyCollectionIfNull).staticColumn(staticColumn)
                .mapCodec(mapCodec).cql3KeyClass(cql3KeyType).cql3ValueClass(cql3ValueType)
                .build(keyClass, valueClass);

        log.trace("Built map property meta for property {} of entity class {} : {}", mapMeta.getPropertyName(), context
                .getCurrentEntityClass().getCanonicalName(), mapMeta);

        return mapMeta;

    }

    private EmbeddedIdProperties extractEmbeddedIdProperties(Class<?> keyClass, PropertyParsingContext context) {
        log.trace("Parsing compound key class", keyClass.getCanonicalName());
        EmbeddedIdProperties embeddedIdProperties = new EmbeddedIdParser(context).parseEmbeddedId(keyClass, this);
        log.trace("Built compound key properties", embeddedIdProperties);
        return embeddedIdProperties;
    }

    private void parseSimpleCounterConsistencyLevel(PropertyParsingContext context, PropertyMeta propertyMeta) {

        log.trace("Parse custom consistency levels for counter property {}", propertyMeta);
        Pair<ConsistencyLevel, ConsistencyLevel> consistencyLevels = findConsistencyLevels(context.getCurrentField(), context.getCurrentConsistencyLevels());

        validator.validateConsistencyLevelForCounter(context, consistencyLevels);

        log.trace("Found custom consistency levels : {}", consistencyLevels);
        propertyMeta.setConsistencyLevels(consistencyLevels);
    }

    private boolean isTimeUUID(PropertyParsingContext context, Field field) {
        boolean timeUUID = false;
        if (filter.hasAnnotation(field, TimeUUID.class)) {
            Validator.validateBeanMappingTrue(field.getType().equals(UUID.class),
                    "The field '%s' from class '%s' annotated with @TimeUUID should be of java.util.UUID type",
                    field.getName(), context.getCurrentEntityClass().getCanonicalName());
            timeUUID = true;
        }
        return timeUUID;
    }

    private boolean mapNullCollectionAndMapToEmpty(Field field) {
        return filter.hasAnnotation(field, EmptyCollectionIfNull.class) || filter.hasAnnotation(field, NotNull.class);
    }

    private boolean isStaticColumn(Field field) {
        Column column = field.getAnnotation(Column.class);
        return column!= null && column.staticColumn();
    }

    public static enum Singleton {
        INSTANCE;

        private final PropertyParser instance = new PropertyParser();

        public PropertyParser get() {
            return instance;
        }
    }
}
TOP

Related Classes of info.archinnov.achilles.internal.metadata.parsing.PropertyParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.