org.jdto.impl.SimpleBinderDelegate.java Source code

Java tutorial

Introduction

Here is the source code for org.jdto.impl.SimpleBinderDelegate.java

Source

/*
 *    Copyright 2012 Juan Alberto Lpez Cavallotti
 *
 * 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 org.jdto.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.jdto.BeanModifier;
import org.jdto.Binding;
import org.jdto.MultiPropertyValueMerger;
import org.jdto.PropertyValueMergerInstanceManager;
import org.jdto.SinglePropertyValueMerger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class responsible for binding the DTOs for real.
 *
 * @author Juan Alberto Lopez Cavallotti
 */
class SimpleBinderDelegate implements Serializable {

    private static final long serialVersionUID = 1L;
    private final DTOBinderBean binderBean;
    private AbstractBeanInspector inspector;
    private BeanModifier modifier;
    private PropertyValueMergerInstanceManager mergerManager;
    private static final Logger logger = LoggerFactory.getLogger(SimpleBinderDelegate.class);

    private ObjectLifecycleManager lifecycleManager;

    SimpleBinderDelegate(DTOBinderBean binderBean) {
        this.binderBean = binderBean;
        this.lifecycleManager = new ObjectLifecycleManager(binderBean);
    }

    <T> T bindFromBusinessObject(HashMap<Class, BeanMetadata> metadataMap, Class<T> dtoClass,
            Object... businessObjects) {

        //log what I'm doing
        if (logger.isDebugEnabled()) {
            logger.debug("Populating dto of type: " + dtoClass.getName());
            logger.debug("\t from source objects: " + Arrays.toString(businessObjects));
        }

        //first of all, we try to find the metadata for the DTO to build, if not
        //then we build it.
        BeanMetadata metadata = findBeanMetadata(metadataMap, dtoClass);

        DTOCacheKey cacheKey = new DTOCacheKey(metadata, businessObjects);

        //check if the object to return is cached, this enhances performance and 
        //helps to mitigate the DTOCascade cycle problem.

        T ret = (T) binderBean.bindingContext.get().get(cacheKey);

        if (ret != null) {
            return ret;
        }

        //the immutable constructor args list
        ArrayList immutableConstructorArgs = null;

        //build the appropiate object.
        if (metadata.isImmutableBean()) {
            immutableConstructorArgs = new ArrayList();
        } else {
            ret = BeanClassUtils.createInstance(dtoClass);
            lifecycleManager.notify(LifecyclePhase.BEFORE_PROPERTIES_SET, ret, metadata, businessObjects);
            binderBean.bindingContext.get().put(cacheKey, ret);
        }

        HashMap<String, FieldMetadata> propertyMappings = metadata.getFieldMetadata();

        //the map of source beans by name
        HashMap<String, Object> sourceBeans = new HashMap<String, Object>();

        if (metadata.isImmutableBean()) {
            List<FieldMetadata> args = metadata.getConstructorArgs();
            for (FieldMetadata fieldMetadata : args) {
                Object targetValue = buildTargetValue(metadata, fieldMetadata, ret, sourceBeans, businessObjects);

                //if the source and target types are not compatible, then apply the compatibility logic
                targetValue = ValueConversionHelper.applyCompatibilityLogic(fieldMetadata.getTargetType(),
                        targetValue);

                //if the object is immutable, then we need to store the value.
                immutableConstructorArgs.add(targetValue);
            }
            ret = BeanClassUtils.createInstance(dtoClass, metadata.getImmutableConstructor(),
                    immutableConstructorArgs);
            binderBean.bindingContext.get().put(cacheKey, ret);
        } else {
            //iterate through the properties and read the values from the business objects.
            for (String targetProperty : propertyMappings.keySet()) {
                //get the configuration for the DTO objects.
                FieldMetadata fieldMetadata = propertyMappings.get(targetProperty);
                Object targetValue = buildTargetValue(metadata, fieldMetadata, ret, sourceBeans, businessObjects);
                modifier.writePropertyValue(targetProperty, targetValue, ret);
            }
        }

        lifecycleManager.notify(LifecyclePhase.AFTER_PROFPERTIES_SET, ret, metadata, businessObjects);

        return ret;
    }

    private <T> Object buildTargetValue(BeanMetadata metadata, FieldMetadata fieldMetadata, T ret,
            HashMap<String, Object> sourceBeans, Object... businessObjects) {
        //create a buffer for the source values.
        List<Object> sourceValues = new ArrayList<Object>();

        //get a list of the properties to read for this DTO

        List<String> sourceProperties = fieldMetadata.getSourceFields();

        //field index to resolve collisions between property names
        int sourcePropertyIndex = 0;
        //read all the values.
        for (String sourceProperty : sourceProperties) {

            populateSourceBeans(sourceBeans, metadata, fieldMetadata, businessObjects);

            Object finalValue = applyMergeToSingleField(sourceBeans, sourceProperty, fieldMetadata,
                    sourcePropertyIndex);
            sourceValues.add(finalValue);
            sourcePropertyIndex++;
        }

        //merge the values into one
        Class mergerClass = fieldMetadata.getPropertyValueMerger();

        Object targetValue = null;

        if (fieldMetadata.isCascadePresent()) {
            targetValue = applyCascadeLogic(sourceValues, fieldMetadata);
        } else {
            MultiPropertyValueMerger merger = (MultiPropertyValueMerger) mergerManager
                    .getPropertyValueMerger(mergerClass); //todo get the merger from the context.
            targetValue = merger.mergeObjects(sourceValues, fieldMetadata.getMergerParameter());
        }

        //return the value
        return targetValue;
    }

    /**
     * Create the appropiate metadata if it doesn't exist.
     *
     * @param <T>
     * @param metadataMap
     * @param dtoClass
     * @return
     */
    private <T> BeanMetadata findBeanMetadata(HashMap<Class, BeanMetadata> metadataMap, Class<T> dtoClass) {

        BeanMetadata ret = metadataMap.get(dtoClass);

        if (ret == null) {
            ret = inspector.inspectBean(dtoClass);
        }

        metadataMap.put(dtoClass, ret);

        return ret;
    }

    /**
     * Apply the cascade logic for generating the target value.
     *
     * @param sourceValues
     * @param fieldMetadata
     * @return
     */
    private Object applyCascadeLogic(List<Object> sourceValues, FieldMetadata fieldMetadata) {

        Object[] values = sourceValues.toArray();

        Object ret = null;
        List[] listValues = null;

        if (fieldMetadata.getCascadeTargetClass() == null
                || fieldMetadata.getCascadeTargetClass() == Object.class) {
            throw new IllegalStateException("Could not infer correctly the type of cascaded dto");
        }

        switch (fieldMetadata.getCascadeType()) {
        case SINGLE:
            ret = binderBean.bindFromBusinessObject(fieldMetadata.getCascadeTargetClass(), values);
            break;
        case COLLECTION:
            //pretty dangerous cast I say :)
            listValues = new List[values.length];

            for (int i = 0; i < values.length; i++) {
                listValues[i] = (List) convertValueToList(values[i]);
            }

            ret = binderBean.bindFromBusinessObjectList(fieldMetadata.getCascadeTargetClass(), listValues);
            break;
        case ARRAY:
            //pretty dangerous cast I say :)
            listValues = new List[values.length];

            for (int i = 0; i < values.length; i++) {
                listValues[i] = (List) convertValueToList(values[i]);
            }

            List retList = binderBean.bindFromBusinessObjectList(fieldMetadata.getCascadeTargetClass(), listValues);
            ret = retList.toArray();
            break;
        default:
            break;
        }

        return ret;
    }

    private List convertValueToList(Object value) {

        if (value == null) {
            return null;
        }

        if (value instanceof List) {
            return (List) value;
        }

        if (value instanceof Collection) {
            return new ArrayList((Collection) value);
        }

        if (value.getClass().isArray()) {
            return Arrays.asList((Object[]) value);
        }

        return null;
    }

    //***************** REVERSE PROCESS ************************//
    /**
     * This is the modest reverse process. Not that useful but stay tuned :)
     *
     * @param <T>
     * @param metadataMap
     * @param entityClass
     * @param dto
     * @return
     */
    <T> T extractFromDto(HashMap metadataMap, Class<T> entityClass, Object dto) {
        return extractFromDto2BussinesObject(metadataMap, entityClass, dto, null);
    }

    <T> T extractFromDto2BussinesObject(HashMap metadataMap, Class<T> entityClass, Object dto, Object entity) {

        if (dto == null) {
            return null; //this satisfies f(null) = null
        }

        BeanMetadata metadata = findBeanMetadata(metadataMap, dto.getClass());

        T ret = null;
        if (null == entity) {
            ret = BeanClassUtils.createInstance(entityClass);
        } else {
            ret = (T) entity;
        }
        HashMap<String, FieldMetadata> mappings = metadata.getFieldMetadata();

        for (String source : mappings.keySet()) {

            FieldMetadata fieldMetadata = mappings.get(source);

            //as documented we do not support dissasembling compound properties.
            if (fieldMetadata.getSourceFields().size() != 1) {
                continue;
            }

            //we currently dont support field uncascading
            //but we might on the future.
            if (fieldMetadata.isCascadePresent()) {
                continue;
            }

            //this is the only possibility left.
            String target = fieldMetadata.getSourceFields().get(0);

            //if the source was the root object, then it's safer to ignore it.
            if (StringUtils.equals(Binding.ROOT_OBJECT, target)) {
                continue;
            }

            //read the source value
            Object value = modifier.readPropertyValue(source, dto);

            //try to restore the value
            value = applyRestoreToSingleField(value, fieldMetadata, 0);

            //valor ya existente en la entidad, no cambiamos el campo id            
            if (source.toUpperCase().equals("id".toUpperCase())) {
                continue;
            }

            //and set
            modifier.writePropertyValue(target, value, ret);
        }

        return ret;
    }

    private Object applyMergeToSingleField(HashMap<String, Object> sourceBeans, String sourceProperty,
            FieldMetadata fieldMetadata, int fieldIndex) {

        //read the source value
        Object sourceValue = readSourceValue(sourceBeans, sourceProperty, fieldMetadata, fieldIndex);

        //read the merger information
        Class mergerClass = fieldMetadata.getSourceMergers()[fieldIndex];
        String[] mergerExtraParam = fieldMetadata.getSourceMergersParams()[fieldIndex];

        SinglePropertyValueMerger merger = (SinglePropertyValueMerger) mergerManager
                .getPropertyValueMerger(mergerClass); //todo get the merger from the context
        return merger.mergeObjects(sourceValue, mergerExtraParam);
    }

    private Object applyRestoreToSingleField(Object originalValue, FieldMetadata fieldMetadata,
            int sourceValueIndex) {
        Class mergerClass = fieldMetadata.getSourceMergers()[sourceValueIndex];
        String[] mergerExtraParam = fieldMetadata.getSourceMergersParams()[sourceValueIndex];

        SinglePropertyValueMerger merger = (SinglePropertyValueMerger) mergerManager
                .getPropertyValueMerger(mergerClass);

        if (merger.isRestoreSupported(mergerExtraParam)) {
            return merger.restoreObject(originalValue, mergerExtraParam);
        }

        return originalValue;
    }

    private Object readSourceValue(HashMap<String, Object> sourceBeans, String sourceProperty,
            FieldMetadata fieldMetadata, int sourceIndex) {

        String sourceBean = fieldMetadata.getSourceBeans()[sourceIndex];

        Object bo = sourceBeans.get(sourceBean);

        if (bo == null) {
            throw new IllegalStateException("could not find source bean with name: " + sourceBean);
        }

        //if it is the root object, then just return it.
        if (StringUtils.equals(sourceProperty, Binding.ROOT_OBJECT)) {
            return bo;
        }

        Object sourceValue = modifier.readPropertyValue(sourceProperty, bo);

        return sourceValue;
    }

    private void populateSourceBeans(HashMap<String, Object> sourceBeans, BeanMetadata metadata,
            FieldMetadata fieldMetadata, Object[] bos) {

        //clear the mappings
        sourceBeans.clear();

        //populate, by default the mapping on the field or else, the one on the ben.
        String[] names = (ArrayUtils.isEmpty(fieldMetadata.getSourceBeanNames())) ? metadata.getDefaultBeanNames()
                : fieldMetadata.getSourceBeanNames();

        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            Object bean = bos[i];
            sourceBeans.put(name, bean);
        }

        //add the default bean name.
        sourceBeans.put("", bos[0]);
    }

    //GETTERS AND SETTERS
    public AbstractBeanInspector getInspector() {
        return inspector;
    }

    public void setInspector(AbstractBeanInspector inspector) {
        this.inspector = inspector;
    }

    public BeanModifier getModifier() {
        return modifier;
    }

    public void setModifier(BeanModifier modifier) {
        this.modifier = modifier;
    }

    public PropertyValueMergerInstanceManager getMergerManager() {
        return mergerManager;
    }

    public void setMergerManager(PropertyValueMergerInstanceManager mergerManager) {
        this.mergerManager = mergerManager;
    }
}