oglimmer/lunchy

View on GitHub
src/main/java/de/oglimmer/lunchy/beanMapping/BeanMappingProvider.java

Summary

Maintainability
A
0 mins
Test Coverage
package de.oglimmer.lunchy.beanMapping;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import lombok.SneakyThrows;

import org.apache.commons.lang3.StringUtils;
import org.dozer.DozerBeanMapper;
import org.dozer.loader.api.BeanMappingBuilder;
import org.dozer.loader.api.FieldDefinition;
import org.dozer.loader.api.TypeDefinition;
import org.dozer.loader.api.TypeMappingBuilder;
import org.jooq.Record;
import org.reflections.Reflections;

import de.oglimmer.lunchy.database.Dao;
import de.oglimmer.lunchy.database.DaoFactory;

public enum BeanMappingProvider {
    INSTANCE;

    private DozerBeanMapper dbm = new DozerBeanMapper();
    private KeyResolver keyResolver = new KeyResolver();

    private BeanMappingProvider() {
        dbm.addMapping(new AutoBeanMappingBuilder());
    }

    public void map(Object source, Object destination) {
        dbm.map(source, destination);
        keyResolver.resolve(source, destination);
    }

    public <T> T map(Object source, Class<T> destinationClass) {
        return createObjectMapping(source, destinationClass);
    }

    public <T> List<T> mapList(Collection<?> col, Class<T> clazz) {
        return col.stream().map(rec -> createObjectMapping(rec, clazz)).collect(Collectors.toList());
    }

    public <T> List<T> mapListCustomDto(Collection<? extends Record> col, Class<T> clazz) {
        return col.stream().map(rec -> new DozerAdapter(rec)).map(rec -> createObjectMapping(rec, clazz))
                .collect(Collectors.toList());
    }

    private <T> T createObjectMapping(Object sourceObject, Class<T> destinationClass) {
        T resultObject = dbm.map(sourceObject, destinationClass);
        keyResolver.resolve(sourceObject, resultObject);
        return resultObject;
    }

}

/**
 * Creates Dozer mappings for all target classes annotated with @RestDto. It is assumed that the source class is of type
 * DozerAdapter. As this is a generic data container it creates a field mapping for each attribute in the target class.
 */
class AutoBeanMappingBuilder extends BeanMappingBuilder {
    @Override
    protected void configure() {
        Reflections ref = new Reflections("de.oglimmer");
        Set<Class<?>> allRestDtoClasses = ref.getTypesAnnotatedWith(RestDto.class);
        allRestDtoClasses.forEach(clazz -> handleRestDtoClass(clazz));
    }

    private void handleRestDtoClass(Class<?> clazz) {
        TypeDefinition from = new TypeDefinition(DozerAdapter.class);
        TypeDefinition to = new TypeDefinition(clazz);
        TypeMappingBuilder tmb = mapping(from, to);
        ClassFields.getAllDeep(clazz).forEach(field -> handleRestDtoField(tmb, field));
    }

    private void handleRestDtoField(TypeMappingBuilder tmb, Field field) {
        if (!field.isSynthetic()) {
            ForeignKey foreignKey = field.getAnnotation(ForeignKey.class);
            if (foreignKey == null) {
                createField(tmb, field);
            }
        }
    }

    private void createField(TypeMappingBuilder typeMappingBuilder, Field field) {
        // fieldA (the from) is of type DozerAdapter
        FieldDefinition fieldA = new FieldDefinition("this");
        fieldA.mapMethods("getValue", null);
        fieldA.mapKey(field.getName());
        // fieldB (the to) is a standard java bean
        FieldDefinition fieldB = new FieldDefinition(field.getName());
        typeMappingBuilder.fields(fieldA, fieldB);
    }

};

/**
 * Fields on a target DTO annotated with ForeignKey, needs to get their value from another Record
 */
class KeyResolver {

    /**
     * @param loadedRecord
     *            either of type Record or DozerAdapter
     * @param targetDto
     */
    public void resolve(Object loadedRecord, Object targetDto) {
        Set<Field> allFields = ClassFields.getAllDeep(targetDto.getClass());
        allFields.forEach(fieldOnTargetDto -> resolveField(loadedRecord, targetDto, fieldOnTargetDto));
    }

    @SneakyThrows(value = { NoSuchMethodException.class, InvocationTargetException.class, IllegalAccessException.class })
    private void resolveField(Object loadedRecord, Object targetDto, Field fieldOnTargetDto) {
        ForeignKey foreignKey = fieldOnTargetDto.getAnnotation(ForeignKey.class);
        if (foreignKey != null) {
            setFieldWithForeignKeyAnnotation(loadedRecord, targetDto, fieldOnTargetDto, foreignKey);
        }
    }

    private void setFieldWithForeignKeyAnnotation(Object loadedRecord, Object targetDto, Field targetField, ForeignKey foreignKey)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Dao<?> dao = DaoFactory.INSTANCE.getDao(foreignKey.dao());
        Integer entityId = getIntValueGeneric(loadedRecord, foreignKey.refColumnName());
        Integer fkCommunity = getIntValueGeneric(loadedRecord, "fkCommunity");
        Record referencedRecord = dao.getById(entityId, fkCommunity);
        setValueOnTargetDto(targetDto, targetField, foreignKey, referencedRecord);
    }

    private void setValueOnTargetDto(Object targetDto, Field targetField, ForeignKey foreignKey, Record referencedRecord)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        targetField.setAccessible(true);
        targetField.set(targetDto, callGetter(referencedRecord, foreignKey.refColumnLabel()));
    }

    private Integer getIntValueGeneric(Object object, String recordKey) throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, SecurityException {
        if (object instanceof DozerAdapter) {
            return getIntValueGeneric((DozerAdapter) object, recordKey);
        } else if (object instanceof Record) {
            return getIntValueGeneric((Record) object, recordKey);
        } else {
            throw new RuntimeException("Unsupported object of type " + object.getClass().getName());
        }
    }

    private Integer getIntValueGeneric(DozerAdapter object, String recordKey) throws IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        Method method = object.getClass().getDeclaredMethod("getValue", String.class);
        return (Integer) method.invoke(object, recordKey);
    }

    private Integer getIntValueGeneric(Record object, String recordKey) throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, SecurityException {
        Method method = object.getClass().getDeclaredMethod("get" + StringUtils.capitalize(recordKey));
        return (Integer) method.invoke(object);
    }

    private Object callGetter(Object record, String getterMethodName) throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, SecurityException {
        return record.getClass().getMethod("get" + StringUtils.capitalize(getterMethodName)).invoke(record);
    }

}

class ClassFields {

    static Set<Field> getAllDeep(Class<?> type) {
        Set<Field> fields = new HashSet<>();
        getClassHierachy(type).forEach(clazz -> fields.addAll(Arrays.asList(clazz.getDeclaredFields())));
        return fields;
    }

    static private List<Class<?>> getClassHierachy(Class<?> type) {
        List<Class<?>> listOfAllClasses = new ArrayList<>();
        for (Class<?> c = type; c != null; c = c.getSuperclass()) {
            listOfAllClasses.add(c);
        }
        return listOfAllClasses;
    }
}