Donation

If you found the contents in this blog useful, then please make a donation to keep this blog running. You can make donations via Skrill with email address shazin.sadakath@gmail.com

Friday, June 13, 2014

Custom Dozer Mapper Factory Bean

As part of my work I had to learn and make use of Dozer mapper. A Java Bean to Java Bean Mapper which enables Converting from one type of Java Bean (Ex:- Spring hateoas Resource) to another Java Bean (Ex:- Entity) easy and less error prone. You can learn more about this library from Dozer page.

The tricky part doing the job was that the requirement was that every JPA Entity had a corresponding Mutable and Non Mutable interface. Mutable interface extends from the Non Mutable interface. So the mapping needed to be done by using the Mutable interface to Spring hateoas Resource.

<?xml version="1.0" encoding="utf-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping>
        <class-a>com.shazin.example.domain.MutableItem</class-a>
        <class-b>com.shazin.example.web.resource.ItemEditResource</class-b>
        <field>
            <a>..</a>
            <b>..</b>
        </field>
        ..
    </mapping>
</mappings>

Furthermore when mapping the two back using the DozerBeanMapper API inside of the Service, the following needed to be done, completely avoiding the Entity class because at the service level only interfaces must be used not entities.

mapper.map(sourceItemResource, MutableItem.class);

This posed an issue because map method expected a second argument of implementation class of the destination bean.

The solution was writing an Custom implementation of org.dozer.BeanFactory which will provide the correct Implementation class based on the interface. So wrote the following AbstractMapperBeanFactory class.

import java.util.HashMap;
import java.util.Map;

import org.dozer.BeanFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractMapperBeanFactory implements BeanFactory {
    private static final Logger logger = LoggerFactory
            .getLogger(AbstractMapperBeanFactory.class);

    protected AbstractMapperBeanFactory() {
        register(sourceToDestinationMap);
    }

    private final Map<String, Class<?>> sourceToDestinationMap = new HashMap<>();

    protected abstract void register(
            Map<String, Class<?>> sourceToDestinationMap);

    @Override
    public final Object createBean(Object source, Class<?> sourceClass,
            String targetBeanId) {
        Class<?> destinationClass = null;
        Object result = null;
        try {
            Class<?> targetBeanClass = Thread.currentThread()
                    .getContextClassLoader().loadClass(targetBeanId);
            if (targetBeanClass.isInterface()) {
                destinationClass = sourceToDestinationMap.get(targetBeanId);
            } else {
                destinationClass = targetBeanClass;
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Source Object : " + source);
                logger.debug("Source Class : " + sourceClass);
                logger.debug("Target Bean Id : " + targetBeanId);
                logger.debug("Destination Class : " + destinationClass);
                logger.debug("Target Bean Class : " + targetBeanClass);
            }

            if (destinationClass == null) {
                logger.warn(String.format(
                        "No matching destination class found for class %s",
                        targetBeanId));
            } else {
                result = destinationClass.newInstance();
            }

        } catch (ClassNotFoundException | InstantiationException
                | IllegalAccessException e) {
            logger.error(String.format(
                    "Error while creating target bean for class %s",
                    targetBeanId), e);
        }
        return result;
    }
}


And for each Module we could register Interfaces and corresponding implementations by Sub classing the above abstract class and implementing the register method as following.

public class CustomMappingBeanFactory extends AbstractMapperBeanFactory {

    protected void register(Map<String, Class<?>> sourceToDestinationMap) {
        sourceToDestinationMap.put(MutableItem.class.getName(), ItemEntity.class);
        ..
    }

}

And finally making use of the CustomMappingBeanFactory in the mapping configuration file as below.

<?xml version="1.0" encoding="utf-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping bean-factory="CustomMappingBeanFactory">
        <class-a>com.shazin.example.domain.MutableItem</class-a>
        <class-b>com.shazin.example.web.resource.ItemEditResource</class-b>
        <field>
            <a>..</a>
            <b>..</b>
        </field>
        ..
    </mapping>
</mappings>

No comments: