org.lunarray.model.descriptor.scanner.AnnotationMetaModelProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.lunarray.model.descriptor.scanner.AnnotationMetaModelProcessor.java

Source

/* 
 * Model Tools.
 * Copyright (C) 2013 Pal Hargitai (pal@lunarray.org)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.lunarray.model.descriptor.scanner;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.Validate;
import org.lunarray.common.check.CheckUtil;
import org.lunarray.model.descriptor.accessor.entity.DescribedEntity;
import org.lunarray.model.descriptor.accessor.exceptions.ValueAccessException;
import org.lunarray.model.descriptor.accessor.operation.DescribedOperation;
import org.lunarray.model.descriptor.accessor.property.DescribedProperty;
import org.lunarray.model.descriptor.builder.annotation.simple.SimpleBuilder;
import org.lunarray.model.descriptor.converter.ConverterTool;
import org.lunarray.model.descriptor.converter.def.DefaultConverterTool;
import org.lunarray.model.descriptor.converter.def.DelegatingEnumConverterTool;
import org.lunarray.model.descriptor.converter.exceptions.ConverterException;
import org.lunarray.model.descriptor.creational.CreationException;
import org.lunarray.model.descriptor.model.Model;
import org.lunarray.model.descriptor.model.ModelProcessor;
import org.lunarray.model.descriptor.model.entity.EntityDescriptor;
import org.lunarray.model.descriptor.model.entity.EntityExtension;
import org.lunarray.model.descriptor.model.extension.Extension;
import org.lunarray.model.descriptor.model.extension.ExtensionContainer;
import org.lunarray.model.descriptor.model.member.Cardinality;
import org.lunarray.model.descriptor.model.operation.OperationExtension;
import org.lunarray.model.descriptor.model.property.CollectionPropertyDescriptor;
import org.lunarray.model.descriptor.model.property.PropertyDescriptor;
import org.lunarray.model.descriptor.model.property.PropertyExtension;
import org.lunarray.model.descriptor.objectfactory.simple.SimpleObjectFactory;
import org.lunarray.model.descriptor.resource.ResourceException;
import org.lunarray.model.descriptor.resource.simpleresource.SimpleClazzResource;
import org.lunarray.model.descriptor.scanner.impl.exception.MappingException;
import org.lunarray.model.descriptor.scanner.impl.extensions.AnnotationMetaModelImpl;
import org.lunarray.model.descriptor.scanner.impl.inner.AnnotationDescriptor;
import org.lunarray.model.descriptor.scanner.impl.inner.AnnotationMetaValues;
import org.lunarray.model.descriptor.scanner.impl.inner.DescriptorProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A processor that processes annotations of a model to an annotation meta model
 * for easier processing.
 * 
 * @author Pal Hargitai (pal@lunarray.org)
 * @param <S>
 *            The model super type.
 */
public final class AnnotationMetaModelProcessor<S> implements ModelProcessor<S> {

    /** The logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationMetaModelProcessor.class);
    /**
     * The converter, by default supports most Java platform types usable within
     * annotations.
     */
    private ConverterTool converterTool;
    /** The inner model. */
    private Model<Serializable> model;
    /** The inner model builder. */
    private SimpleBuilder<Serializable> modelBuilder;
    /** The inner model descriptor processor. */
    private DescriptorProcessor processor;
    /** Model resources. */
    private SimpleClazzResource<Serializable> resource;

    /**
     * Default constructor.
     */
    @SuppressWarnings("unchecked")
    public AnnotationMetaModelProcessor() {
        this.processor = new DescriptorProcessor();
        this.modelBuilder = SimpleBuilder.createBuilder();
        this.modelBuilder.postProcessors(this.processor);
        this.resource = new SimpleClazzResource<Serializable>();
        this.converterTool = new DelegatingEnumConverterTool(new DefaultConverterTool());
    }

    /**
     * Gets the annotation meta values.
     * 
     * @return The meta values.
     */
    public AnnotationMetaValues createMetaValues() {
        return this.createAnnotationMetaValues();
    }

    /**
     * Gets the value for the converterTool field.
     * 
     * @return The value for the converterTool field.
     */
    public ConverterTool getConverterTool() {
        return this.converterTool;
    }

    /**
     * Gets the value for the model field.
     * 
     * @return The value for the model field.
     */
    public Model<Serializable> getModel() {
        return this.model;
    }

    /**
     * Gets the value for the modelBuilder field.
     * 
     * @return The value for the modelBuilder field.
     */
    public SimpleBuilder<Serializable> getModelBuilder() {
        return this.modelBuilder;
    }

    /**
     * Gets the value for the processor field.
     * 
     * @return The value for the processor field.
     */
    public DescriptorProcessor getProcessor() {
        return this.processor;
    }

    /**
     * Gets the value for the resource field.
     * 
     * @return The value for the resource field.
     */
    public SimpleClazzResource<Serializable> getResource() {
        return this.resource;
    }

    /** {@inheritDoc} */
    @Override
    public <E extends S> EntityExtension<E> process(final DescribedEntity<E> entityType,
            final ExtensionContainer extensionContainer) {
        Validate.notNull(entityType, "Entity may not be null.");
        EntityExtension<E> extension = null;
        try {
            extension = this.processNode(entityType.getAnnotations());
        } catch (final MappingException e) {
            AnnotationMetaModelProcessor.LOGGER.warn("Could not process entity.", e);
        }
        return extension;
    }

    /** {@inheritDoc} */
    @Override
    public <E extends S> OperationExtension<E> process(final DescribedOperation entityType,
            final ExtensionContainer extensionContainer) {
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public <E extends S, P> PropertyExtension<P, E> process(final DescribedProperty<P> property,
            final ExtensionContainer extensionContainer) {
        Validate.notNull(property, "Property may not be null.");
        PropertyExtension<P, E> extension = null;
        try {
            extension = this.processNode(property.getAnnotations());
        } catch (final MappingException e) {
            AnnotationMetaModelProcessor.LOGGER.warn("Could not process property.", e);
        }
        return extension;
    }

    /** {@inheritDoc} */
    @Override
    public Extension process(final ExtensionContainer extensionContainer) {
        if (CheckUtil.isNull(this.model)) {
            try {
                this.model = this.modelBuilder.extensions(new SimpleObjectFactory(), this.converterTool)
                        .resources(this.resource).build();
            } catch (final ResourceException e) {
                AnnotationMetaModelProcessor.LOGGER.warn("Could not process model.", e);
            }
        }
        return null;
    }

    /**
     * Register an annotation entity.
     * 
     * @param domainType
     *            The domain type.
     */
    public void registerAnnotationEntity(final Class<? extends Serializable> domainType) {
        this.resource.addClazz(domainType);
    }

    /**
     * Sets a new value for the converterTool field.
     * 
     * @param converterTool
     *            The new value for the converterTool field.
     */
    public void setConverterTool(final ConverterTool converterTool) {
        this.converterTool = converterTool;
    }

    /**
     * Sets a new value for the model field.
     * 
     * @param model
     *            The new value for the model field.
     */
    public void setModel(final Model<Serializable> model) {
        this.model = model;
    }

    /**
     * Sets a new value for the modelBuilder field.
     * 
     * @param modelBuilder
     *            The new value for the modelBuilder field.
     */
    public void setModelBuilder(final SimpleBuilder<Serializable> modelBuilder) {
        this.modelBuilder = modelBuilder;
    }

    /**
     * Sets a new value for the processor field.
     * 
     * @param processor
     *            The new value for the processor field.
     */
    public void setProcessor(final DescriptorProcessor processor) {
        this.processor = processor;
    }

    /**
     * Sets a new value for the resource field.
     * 
     * @param resource
     *            The new value for the resource field.
     */
    public void setResource(final SimpleClazzResource<Serializable> resource) {
        this.resource = resource;
    }

    /**
     * Copy array values.
     * 
     * @param values
     *            The array values.
     * @param name
     *            The property name.
     * @param obj
     *            The object value.
     * @param type
     *            The array type.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private void arrayCopy(final AnnotationMetaValues values, final String name, final Object obj,
            final Class<?> type) throws MappingException {
        try {
            final Class<?> compType = type.getComponentType();
            if (Integer.TYPE.equals(compType)) {
                final int[] array = int[].class.cast(obj);
                for (final int a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Integer.TYPE, a));
                }
            } else if (Long.TYPE.equals(compType)) {
                final long[] array = long[].class.cast(obj);
                for (final long a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Long.TYPE, a));
                }
            } else {
                this.copyShortNumbers(values, name, obj, type, compType);
            }
        } catch (final ConverterException e) {
            throw new MappingException(e);
        }
    }

    /**
     * Convert an array to string types.
     * 
     * @param array
     *            The array to convert.
     * @param type
     *            The array type.
     * @param tail
     *            The result tail.
     * @throws ConverterException
     *             Thrown if a value could not be converted.
     * @param <T>
     *            The array type.
     */
    private <T> void convert(final T[] array, final Class<T> type, final List<String> tail)
            throws ConverterException {
        for (final T t : array) {
            if (!CheckUtil.isNull(t)) {
                tail.add(this.converterTool.convertToString(type, t));
            }
        }
    }

    /**
     * Copy array values.
     * 
     * @param values
     *            The array values.
     * @param name
     *            The property name.
     * @param obj
     *            The object value.
     * @param type
     *            The array type.
     * @param compType
     *            The component type.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private void copyNonDiscreteNumbers(final AnnotationMetaValues values, final String name, final Object obj,
            final Class<?> type, final Class<?> compType) throws MappingException {
        try {
            if (Double.TYPE.equals(compType)) {
                final double[] array = double[].class.cast(obj);
                for (final double a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Double.TYPE, a));
                }
            } else if (Float.TYPE.equals(compType)) {
                final float[] array = float[].class.cast(obj);
                for (final float a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Float.TYPE, a));
                }
            } else {
                this.extractNonFloats(values, name, obj, type, compType);
            }
        } catch (final ConverterException e) {
            throw new MappingException(e);
        }
    }

    /**
     * Copy array values.
     * 
     * @param values
     *            The array values.
     * @param name
     *            The property name.
     * @param obj
     *            The object value.
     * @param type
     *            The array type.
     * @param compType
     *            The component type.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private void copyObjectTypes(final AnnotationMetaValues values, final String name, final Object obj,
            final Class<?> type, final Class<?> compType) throws MappingException {
        if (Annotation.class.isAssignableFrom(compType)) {
            final Annotation[] array = Annotation[].class.cast(obj);
            for (final Annotation element : array) {
                final AnnotationMetaValues innerValues = this.createMetaValues();
                values.getMetaAnnotationList(name).add(innerValues);
                this.processAnnotation(innerValues, element);
            }
        } else {
            final Object[] array = Object[].class.cast(obj);
            @SuppressWarnings("unchecked")
            final Class<Object> aType = (Class<Object>) type.getComponentType();
            try {
                this.convert(array, aType, values.getMetaValueList(name));
            } catch (final ConverterException e) {
                throw new MappingException(e);
            }
        }
    }

    /**
     * Copy array values.
     * 
     * @param values
     *            The array values.
     * @param name
     *            The property name.
     * @param obj
     *            The object value.
     * @param type
     *            The array type.
     * @param compType
     *            The component type.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private void copyShortNumbers(final AnnotationMetaValues values, final String name, final Object obj,
            final Class<?> type, final Class<?> compType) throws MappingException {
        try {
            if (Short.TYPE.equals(compType)) {
                final short[] array = short[].class.cast(obj);
                for (final short a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Short.TYPE, a));
                }
            } else if (Byte.TYPE.equals(compType)) {
                final byte[] array = byte[].class.cast(obj);
                for (final byte a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Byte.TYPE, a));
                }
            } else {
                this.copyNonDiscreteNumbers(values, name, obj, type, compType);
            }
        } catch (final ConverterException e) {
            throw new MappingException(e);
        }
    }

    /**
     * Copy a value.
     * 
     * @param values
     *            The values to copy to.
     * @param annotation
     *            The annotation to copy for.
     * @param method
     *            The method of the annotation to copy.
     * @throws MappingException
     *             Thrown if the value could not be related.
     */
    private void copyValue(final AnnotationMetaValues values, final Annotation annotation, final Method method)
            throws MappingException {
        final String name = method.getName();
        final Object defaultValue = method.getDefaultValue();
        try {
            final Object obj = method.invoke(annotation);
            if (!CheckUtil.isNull(obj) && !obj.equals(defaultValue)) {
                values.getMetaAnnotationList(name);
                final Class<?> type = obj.getClass();
                this.processType(values, name, obj, type);
            }
        } catch (final IllegalArgumentException e) {
            throw new MappingException(e);
        } catch (final IllegalAccessException e) {
            throw new MappingException(e);
        } catch (final InvocationTargetException e) {
            throw new MappingException(e);
        }
    }

    /**
     * Create annotation meta value.
     * 
     * @return The meta value.
     */
    private AnnotationMetaValues createAnnotationMetaValues() {
        return new AnnotationMetaValues();
    }

    /**
     * Extract annotation meta data.
     * 
     * @param annotations
     *            The annotations.
     * @return The meta data.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private AnnotationMetaValues extractCompoundMetadata(final List<Annotation> annotations)
            throws MappingException {
        final AnnotationMetaValues values = this.createMetaValues();
        for (final Annotation a : annotations) {
            this.processAnnotation(values, a);
        }
        return values;
    }

    /**
     * Extract annotation methods.
     * 
     * @param annotationType
     *            The annotation type.
     * @return The methods of the annotation type.
     */
    private List<Method> extractMethods(final Class<? extends Annotation> annotationType) {
        final List<Method> result = new LinkedList<Method>();
        for (final Method m : annotationType.getMethods()) {
            if ((m.getParameterTypes().length == 0) && annotationType.equals(m.getDeclaringClass())) {
                result.add(m);
            }
        }
        return result;
    }

    /**
     * Copy array values.
     * 
     * @param values
     *            The array values.
     * @param name
     *            The property name.
     * @param obj
     *            The object value.
     * @param type
     *            The array type.
     * @param compType
     *            The component type.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private void extractNonFloats(final AnnotationMetaValues values, final String name, final Object obj,
            final Class<?> type, final Class<?> compType) throws MappingException {
        try {
            if (Boolean.TYPE.equals(compType)) {
                final boolean[] array = boolean[].class.cast(obj);
                for (final boolean a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Boolean.TYPE, a));
                }
            } else if (Character.TYPE.equals(compType)) {
                final char[] array = char[].class.cast(obj);
                for (final char a : array) {
                    values.getMetaValueList(name).add(this.converterTool.convertToString(Character.TYPE, a));
                }
            } else {
                this.copyObjectTypes(values, name, obj, type, compType);
            }
        } catch (final ConverterException e) {
            throw new MappingException(e);
        }
    }

    /**
     * Process an annotation.
     * 
     * @param values
     *            The values to copy to.
     * @param anno
     *            The annotation to copy.
     * @throws MappingException
     *             Thrown if the mapping was unsuccessful.
     */
    private void processAnnotation(final AnnotationMetaValues values, final Annotation anno)
            throws MappingException {
        for (final Method m : this.extractMethods(anno.annotationType())) {
            this.copyValue(values, anno, m);
        }
    }

    /**
     * Process an annotation to an entity.
     * 
     * @param annotationMapping
     *            The annotation type to annotation mapping.
     * @param annotationType
     *            The annotation type.
     * @param targetEntity
     *            The target entity.
     * @throws MappingException
     *             Thrown if the mapping could not be done.
     * @param <P>
     *            The property type.
     * @param <E>
     *            The entity type.
     * @return The entity.
     */
    private <P, E extends Serializable> E processAnnotationEntity(
            final Map.Entry<Class<? extends Annotation>, List<Annotation>> annotationMapping,
            final Class<? extends Annotation> annotationType, final Class<E> targetEntity) throws MappingException {
        @SuppressWarnings("unchecked")
        final AnnotationDescriptor<?> descriptor = this.model.getEntity(targetEntity)
                .extension(AnnotationDescriptor.class);
        Set<Class<? extends Annotation>> aggregates = Collections.emptySet();
        if (!CheckUtil.isNull(descriptor)) {
            aggregates = descriptor.getAggregates();
        }
        final Set<Annotation> process = new LinkedHashSet<Annotation>();
        for (final Annotation ann : annotationMapping.getValue()) {
            process.addAll(AnnotationScannerUtil.getMarked(annotationType, ann, true));
            for (final Class<? extends Annotation> aggregate : aggregates) {
                process.addAll(AnnotationScannerUtil.getMarked(aggregate, ann, true));
            }
        }
        return this.processMetaValues(this.extractCompoundMetadata(new LinkedList<Annotation>(process)),
                targetEntity);
    }

    /**
     * Process annotation types.
     * 
     * @param extension
     *            The extension.
     * @param annotationMapping
     *            The annotation type to annotations mapping.
     * @throws MappingException
     *             Thrown if the mapping could not be done.
     * @param <P>
     *            The property type.
     * @param <E>
     *            The entity type.
     */
    private <P, E extends S> void processAnnotationTypes(final AnnotationMetaModelImpl<P, E> extension,
            final Map.Entry<Class<? extends Annotation>, List<Annotation>> annotationMapping)
            throws MappingException {
        for (final Map.Entry<Class<? extends Annotation>, List<Class<? extends Serializable>>> annotationEntity : this.processor
                .getAnnotationEntityMapping().entrySet()) {
            final Class<? extends Annotation> annotationType = annotationMapping.getKey();
            if (AnnotationScannerUtil.isMarked(annotationEntity.getKey(), annotationType, true)
                    && !annotationMapping.getValue().isEmpty()) {
                for (final Class<? extends Serializable> c : annotationEntity.getValue()) {
                    extension.addEntity(this.processAnnotationEntity(annotationMapping, annotationType, c));
                }
            }
        }
    }

    /**
     * Process a visitor.
     * 
     * @param metadata
     *            The meta model.
     * @param entityType
     *            The entity type.
     * @return The entity.
     * @throws MappingException
     *             Thrown if the mapping could not be done.
     * @param <E>
     *            The entity type.
     */
    private <E extends Serializable> E processMetaValues(final AnnotationMetaValues metadata,
            final Class<E> entityType) throws MappingException {
        final EntityDescriptor<E> visitorDiscriptor = this.model.getEntity(entityType);
        E visitorValue;
        try {
            visitorValue = visitorDiscriptor.createEntity();
        } catch (final CreationException e) {
            throw new MappingException(e);
        }
        for (final Map.Entry<String, List<String>> metadataValue : metadata.getMetaValues().entrySet()) {
            this.updateValue(visitorDiscriptor, visitorValue, metadataValue);
        }
        for (final Map.Entry<String, List<AnnotationMetaValues>> annotationMetadataValue : metadata
                .getMetaAnnotations().entrySet()) {
            this.updateAnnotationValue(visitorDiscriptor, visitorValue, annotationMetadataValue);
        }
        return visitorValue;
    }

    /**
     * Process a node.
     * 
     * @param annotations
     *            The nodes' annotations.
     * @return The extension.
     * @throws MappingException
     *             Thrown if the mapping could not be done.
     * @param <P>
     *            The property type.
     * @param <E>
     *            The entity type.
     */
    private <P, E extends S> AnnotationMetaModelImpl<P, E> processNode(
            final Map<Class<? extends Annotation>, List<Annotation>> annotations) throws MappingException {
        Validate.notNull(annotations, "Annotations must be present.");
        final AnnotationMetaModelImpl<P, E> extension = new AnnotationMetaModelImpl<P, E>();
        for (final Map.Entry<Class<? extends Annotation>, List<Annotation>> a : annotations.entrySet()) {
            this.processAnnotationTypes(extension, a);
        }
        return extension;
    }

    /**
     * Process type.
     * 
     * @param values
     *            The values.
     * @param name
     *            The name.
     * @param obj
     *            The object.
     * @param type
     *            The type.
     * @throws MappingException
     *             Thrown if the mapping failed.
     */
    @SuppressWarnings("unchecked")
    private void processType(final AnnotationMetaValues values, final String name, final Object obj,
            final Class<?> type) throws MappingException {
        if (type.isArray()) {
            this.arrayCopy(values, name, obj, type);
        } else if (Annotation.class.isAssignableFrom(type)) {
            final AnnotationMetaValues innerValues = this.createMetaValues();
            values.getMetaAnnotationList(name).add(innerValues);
            this.processAnnotation(innerValues, (Annotation) obj);
        } else {
            try {
                values.getMetaValueList(name)
                        .add(this.converterTool.convertToString((Class<Object>) obj.getClass(), obj));
            } catch (final ConverterException e) {
                throw new MappingException(e);
            }
        }
    }

    /**
     * Update the actual value.
     * 
     * @param entity
     *            The entity to update.
     * @param prop
     *            The property.
     * @param values
     *            The values.
     * @param collectionDescriptor
     *            The collection property.
     * @throws MappingException
     *             Thrown if the mapping could not be successful.
     * @param <P>
     *            The property type.
     * @param <E>
     *            The entity type.
     */
    private <E, P extends Serializable> void updateActualValue(final E entity, final PropertyDescriptor<P, E> prop,
            final List<String> values, final CollectionPropertyDescriptor<P, ?, E> collectionDescriptor)
            throws MappingException {
        try {
            if (prop.getCardinality() == Cardinality.MULTIPLE) {
                for (final String value : values) {
                    final P convertedValue = this.converterTool
                            .convertToInstance(collectionDescriptor.getCollectionType(), value);
                    collectionDescriptor.addValue(entity, convertedValue);
                }
            } else {
                final P singleValue = this.converterTool.convertToInstance(prop.getPropertyType(),
                        values.iterator().next());
                prop.setValue(entity, singleValue);
            }
        } catch (final ConverterException e) {
            throw new MappingException(e);
        } catch (final ValueAccessException e) {
            throw new MappingException(e);
        }
    }

    /**
     * Update the annotation meta values.
     * 
     * @param entityDescriptor
     *            The entity descriptor.
     * @param entity
     *            The entity.
     * @param metadataValue
     *            The meta value.
     * @throws MappingException
     *             Thrown if the mapping could not be done.
     * @param <P>
     *            The property type.
     * @param <E>
     *            The entity type.
     */
    @SuppressWarnings("unchecked")
    private <P extends Serializable, E> void updateAnnotationValue(final EntityDescriptor<E> entityDescriptor,
            final E entity, final Map.Entry<String, List<AnnotationMetaValues>> metadataValue)
            throws MappingException {
        final String key = metadataValue.getKey();
        final PropertyDescriptor<P, E> prop = (PropertyDescriptor<P, E>) entityDescriptor.getProperty(key);
        final List<AnnotationMetaValues> values = metadataValue.getValue();
        if (!CheckUtil.isNull(prop) && !values.isEmpty()) {
            CollectionPropertyDescriptor<P, ?, E> collectionDescriptor = null;
            Class<P> innerType = prop.getPropertyType();
            if (Cardinality.MULTIPLE == prop.getCardinality()) {
                collectionDescriptor = prop.adapt(CollectionPropertyDescriptor.class);
                innerType = collectionDescriptor.getCollectionType();
            }
            try {
                if (Cardinality.MULTIPLE == prop.getCardinality()) {
                    for (final AnnotationMetaValues metaValue : values) {
                        final P innerEntity = this.processMetaValues(metaValue, innerType);
                        collectionDescriptor.addValue(entity, innerEntity);
                    }
                } else {
                    final P innerEntity = this.processMetaValues(values.iterator().next(), innerType);
                    prop.setValue(entity, innerEntity);
                }
            } catch (final ValueAccessException e) {
                throw new MappingException(e);
            }
        }
    }

    /**
     * Update the annotation values.
     * 
     * @param entityDescriptor
     *            The entity descriptor.
     * @param entity
     *            The entity.
     * @param metadataValue
     *            The assignable values.
     * @throws MappingException
     *             Thrown if the mapping could not be done.
     * @param <P>
     *            The property type.
     * @param <E>
     *            The entity type.
     */
    @SuppressWarnings("unchecked")
    private <P extends Serializable, E> void updateValue(final EntityDescriptor<E> entityDescriptor, final E entity,
            final Map.Entry<String, List<String>> metadataValue) throws MappingException {
        final String key = metadataValue.getKey();
        final PropertyDescriptor<P, E> prop = (PropertyDescriptor<P, E>) entityDescriptor.getProperty(key);
        final List<String> values = metadataValue.getValue();
        if (!CheckUtil.isNull(prop) && !values.isEmpty()) {
            CollectionPropertyDescriptor<P, ?, E> collectionDescriptor = null;
            if (Cardinality.MULTIPLE == prop.getCardinality()) {
                collectionDescriptor = prop.adapt(CollectionPropertyDescriptor.class);
            }
            this.updateActualValue(entity, prop, values, collectionDescriptor);
        }
    }
}