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

com.oberasoftware.jasdb.entitymapper.AnnotationEntityMapper Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
package com.oberasoftware.jasdb.entitymapper;

import com.oberasoftware.jasdb.api.entitymapper.EntityMapper;
import com.oberasoftware.jasdb.api.entitymapper.EntityMetadata;
import com.oberasoftware.jasdb.api.entitymapper.MapResult;
import com.oberasoftware.jasdb.api.entitymapper.PropertyMetadata;
import com.oberasoftware.jasdb.api.entitymapper.TypeMapper;
import com.oberasoftware.jasdb.api.entitymapper.annotations.Id;
import com.oberasoftware.jasdb.api.entitymapper.annotations.JasDBEntity;
import com.oberasoftware.jasdb.api.entitymapper.annotations.JasDBProperty;
import nl.renarj.core.utilities.StringUtils;
import nl.renarj.jasdb.api.SimpleEntity;
import nl.renarj.jasdb.api.properties.Property;
import nl.renarj.jasdb.core.exceptions.JasDBStorageException;
import nl.renarj.jasdb.core.exceptions.RuntimeJasDBException;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.oberasoftware.core.utils.AnnotationUtils.getAnnotation;
import static com.oberasoftware.core.utils.AnnotationUtils.getOptionalAnnotation;
import static com.oberasoftware.jasdb.entitymapper.types.TypeMapperFactory.getTypeMapper;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * @author Renze de Vries
 */
@Component
public class AnnotationEntityMapper implements EntityMapper {
    private static final Logger LOG = getLogger(AnnotationEntityMapper.class);

    private ConcurrentMap cachedEntityMetadata = new ConcurrentHashMap<>();

    @Override
    public MapResult mapTo(Object mappableObject) throws JasDBStorageException {
        LOG.debug("Mapping entity: {}", mappableObject);

        Class entityClass = mappableObject.getClass();
        EntityMetadata metadata = getEntityMetadata(entityClass);
        Optional keyProperty = metadata.getKeyProperty();
        SimpleEntity entity = new SimpleEntity();

        if(keyProperty.isPresent()) {
            Object keyValue = EntityUtils.getValue(mappableObject, keyProperty.get());
            if(keyValue != null) {
                String id = getTypeMapper(String.class).mapToRawType(keyValue);

                if (EntityUtils.toValidUUID(id) != null) {
                    LOG.debug("Setting entity id to: {}", id);
                    entity.setInternalId(id);
                } else {
                    LOG.warn("Entity marked with an @Id {} field, but invalid UUID, ignoring", id);
                }
            } else {
                LOG.debug("No UUID specified in field marked with @Id, generating during insert");
            }
        }

        metadata.getProperties().forEach((k, v) -> {
            try {
                Property property = EntityUtils.map(mappableObject, v);
                if(property != null) {
                    entity.addProperty(property);
                } else if(!v.isNullable()) {
                    throw new RuntimeJasDBException("Unable to map property: " + v + " value is null");
                }
            } catch (JasDBStorageException e) {
                throw new RuntimeJasDBException("Unable to map property: " + v, e);
            }
        });

        return new MapResultImpl(metadata, entity, mappableObject, metadata.getBagName());
    }

    @Override
    public Object updateId(String id, Object mappableObject) throws JasDBStorageException {
        LOG.debug("Setting ID: {} on mappable object: {}", id, mappableObject);
        Class entityClass = mappableObject.getClass();
        EntityMetadata metadata = getEntityMetadata(entityClass);

        Optional optionalKeyProperty = metadata.getKeyProperty();
        if(optionalKeyProperty.isPresent()) {
            PropertyMetadata keyProperty = optionalKeyProperty.get();
            setValue(mappableObject, id, keyProperty);
        }

        return mappableObject;
    }

    @Override
    public  T mapFrom(Class targetType, SimpleEntity entity) throws JasDBStorageException {
        try {
            if(entity != null) {
                Object instance = targetType.newInstance();
                EntityMetadata metadata = getEntityMetadata(targetType);

                metadata.getProperties().forEach((k, v) -> {
                    if(v.isKey()) {
                        setValue(instance, entity.getInternalId(), v);
                    } else {
                        Property property = entity.getProperty(k);
                        if (property != null) {
                            Object value = v.getTypeMapper().mapFromProperty(property);
                            setValue(instance, value, v);
                        } else if (!v.isNullable()) {
                            throw new RuntimeJasDBException("Unable to map property: " + v + " value was null");
                        } else {
                            Object emptyValue = v.getTypeMapper().mapToEmptyValue();
                            setValue(instance, emptyValue, v);
                        }
                    }
                });

                return targetType.cast(instance);
            } else {
                return null;
            }
        } catch(IllegalAccessException | InstantiationException e) {
            throw new JasDBStorageException("Unable to create instance of entity, missing constructor or inaccessible: " + targetType, e);
        }
    }

    private void setValue(Object instance, Object value, PropertyMetadata v) {
        try {
            v.getWriteMethod().invoke(instance, value);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeJasDBException("Unable to set field value on entity: " + instance.getClass(), e);
        }
    }

    @Override
    public EntityMetadata getEntityMetadata(Class entityClass) throws JasDBStorageException {
        String entityClassName = entityClass.getName();

        if(!cachedEntityMetadata.containsKey(entityClassName)) {
            LOG.debug("Entity: {} metadata not cached, loading now", entityClassName);
            cachedEntityMetadata.putIfAbsent(entityClassName, loadEntityMetadata(entityClass));
        }
        return cachedEntityMetadata.get(entityClassName);
    }

    private EntityMetadata loadEntityMetadata(Class entityClass) throws JasDBStorageException {
            JasDBEntity annotationEntity = getAnnotation(entityClass, JasDBEntity.class);
            String bagName = annotationEntity.bagName();

            Map propertyMetadatas = loadProperties(entityClass);
            Optional keyProperty = propertyMetadatas.values().stream().filter(PropertyMetadata::isKey).findFirst();
            LOG.debug("Found key property: {}", keyProperty);

            return new EntityMetadataImpl(entityClass, bagName, keyProperty, propertyMetadatas);
    }

    private Map loadProperties(Class entityClass) throws JasDBStorageException {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(entityClass);
            Map propertyMetadatas = new HashMap<>();

            for(PropertyDescriptor p : beanInfo.getPropertyDescriptors()) {
                PropertyMetadata propertyMetadata = loadProperty(p);
                if(propertyMetadata != null) {
                    propertyMetadatas.put(propertyMetadata.getPropertyName(), propertyMetadata);
                }
            }

            LOG.debug("Mapped: {} properties to entity: {}", propertyMetadatas.size(), entityClass);

            return propertyMetadatas;
        } catch(IntrospectionException e) {
            throw new JasDBStorageException("Unable to map entity: " + entityClass);
        }
    }

    private PropertyMetadata loadProperty(PropertyDescriptor propertyDescriptor) throws JasDBStorageException {
        Method readMethod = propertyDescriptor.getReadMethod();
        Method writeMethod = propertyDescriptor.getWriteMethod();

        if(readMethod != null && writeMethod != null) {
            Optional readAnnotation = getOptionalAnnotation(readMethod, JasDBProperty.class);
            Optional writeAnnotation = getOptionalAnnotation(writeMethod, JasDBProperty.class);
            Optional idAnnotation = getOptionalAnnotation(readMethod, Id.class);

            if (readAnnotation.isPresent() || writeAnnotation.isPresent()) {
                TypeMapper typeMapper = getTypeMapper(readMethod);

                //here a number of override for the property name, first check read method, next write else default to property bean name
                String propertyName = readAnnotation.isPresent() ? readAnnotation.get().name() : "";
                propertyName = StringUtils.stringEmpty(propertyName) ? writeAnnotation.isPresent() ? writeAnnotation.get().name() : "" : propertyName;
                propertyName = StringUtils.stringEmpty(propertyName) ? propertyDescriptor.getName() : propertyName;
                boolean nullable = readAnnotation.isPresent() ? readAnnotation.get().nullable() : writeAnnotation.get().nullable();

                LOG.debug("Found Entity property: {} nullable: {}", propertyDescriptor.getName(), nullable);
                return new PropertyMetadataImpl(typeMapper, readMethod, writeMethod, propertyName, idAnnotation.isPresent(), nullable);
            }
        } else {
            LOG.debug("Read or Write method not defined for property: {}", propertyDescriptor.getName());
        }

        return null;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy