Tuesday, July 12, 2011

Howto map UUID's using the Dozer library

In our new server module we are using an automatic object mapping library called
Dozer. When trying to map an object with a java.util.UUID field i kept receiving the following exception:

Jul 12, 2011 5:18:01 PM org.dozer.MappingProcessor mapField
SEVERE: Field mapping error -->
  MapId: null
  Type: null
  Source parent class: path.to.some.class.TreeNode
  Source field name: parentUuid
  Source field type: class java.util.UUID
  Source field value: 7ed46d06-e715-448b-ae69-54123d4e2c82
  Dest parent class: path.to.some.class.TreeNode
  Dest field name: parentUuid
  Dest field type: java.util.UUID
org.dozer.MappingException: java.lang.NoSuchMethodException: java.util.UUID.<init>()
 at org.dozer.util.MappingUtils.throwMappingException(MappingUtils.java:82)
 at org.dozer.factory.ConstructionStrategies$ByConstructor.newInstance(ConstructionStrategies.java:261)
 at org.dozer.factory.ConstructionStrategies$ByConstructor.create(ConstructionStrategies.java:245)
 at org.dozer.factory.DestBeanCreator.create(DestBeanCreator.java:65)
 at org.dozer.MappingProcessor.mapCustomObject(MappingProcessor.java:477)
 at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:434)
 at org.dozer.MappingProcessor.mapFromFieldMap(MappingProcessor.java:330)
 at org.dozer.MappingProcessor.mapField(MappingProcessor.java:276)
 at org.dozer.MappingProcessor.map(MappingProcessor.java:245)
 at org.dozer.MappingProcessor.mapCustomObject(MappingProcessor.java:483)
 at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:434)
 at org.dozer.MappingProcessor.addToSet(MappingProcessor.java:703)
 at org.dozer.MappingProcessor.mapCollection(MappingProcessor.java:545)
 at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:422)
 at org.dozer.MappingProcessor.mapFromFieldMap(MappingProcessor.java:330)
 at org.dozer.MappingProcessor.mapField(MappingProcessor.java:276)
 at org.dozer.MappingProcessor.map(MappingProcessor.java:245)
 at org.dozer.MappingProcessor.map(MappingProcessor.java:187)
 at org.dozer.MappingProcessor.map(MappingProcessor.java:133)
 at org.dozer.MappingProcessor.map(MappingProcessor.java:128)
 at org.dozer.DozerBeanMapper.map(DozerBeanMapper.java:118)
 // a bunch of my code
Caused by: java.lang.NoSuchMethodException: java.util.UUID.<init>()
 at java.lang.Class.getConstructor0(Class.java:2723)
 at java.lang.Class.getDeclaredConstructor(Class.java:2002)
 at org.dozer.factory.ConstructionStrategies$ByConstructor.newInstance(ConstructionStrategies.java:257)
 ... 34 more

My object that i'm trying to map is part of a tree structure, each node has a parent that he cares about, if the parent is null then that object is a root in the tree (there can be any number of roots). My tree node looks something like:

public class TreeNode {

   private final UUID id; //the id of this node
   private String name;
   private UUID parentId; //id of the parent, or null if there is no parent

   public TreeNode() {
       this(UUID.randomUUID(), "");
   }

   public TreeNode(UUID id, String name) { //used if we dont have a parent
       this(UUID.randomUUID(), "", null);
   }

   public TreeNode(UUID id, String name, UUID parentId) {
       this.id = id;
       this.name = name;
       this.parentId = parentId;
   }

   public UUID getId() {
       return id;
   }
   
   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public UUID getParentId() {
       return parentId;
   }

   public void setParentId(UUID parentId) {
       this.parentId = parentId;
   }
}

Now when the DozerBeanMapper.map() function tries to map an object it tries to perform a "deep" copy, below is a rough algorithm outline of how it works.
public void map(Source to Destination) {
    for each field in Source {
        if Source.field is a primitive {
            Destination.field = Source.field;
        } else if Source.field is null {
            Destination.field = new field(); //does this using reflection (Class.newInstance())
            map(Source.field to Destination.field);
        } else {
            map(Source.field to Destination.field);
        }
    }
}


Now, at the bottom of my exception you can see the line "Caused by: java.lang.NoSuchMethodException: java.util.UUID.()" which is java's way of saying "This class doesn't a default constructor" which in java.util.UUID's case is completely true. However, java.util.UUID does have a non-default constructor with the signiture:

UUID(long mostSigBits, long leastSigBits) 

So, how do you tell Dozer to use this non-default constructor only for java.util.UUID's? Simple, you need to create an org.dozer.BeanFactory and tell Dozer to use this BeanFactory to create new destination objects instead of calling the default constructor. There are two ways to do this: XML and via the API, i will cover both.

Bean Factory
First off we need to create our BeanFactory that will be used to create our new UUID objects:
package path.to.my.bean.factory;

import java.util.UUID;
import org.dozer.BeanFactory;

public class UuidBeanFactory implements BeanFactory {

    public Object createBean(Object sourceBean, Class<?> destinationType, String mapId) {
        if (sourceBean == null) {
            return null;
        }
        UUID source = (UUID) sourceBean;
        UUID destination = new UUID(source.getMostSignificantBits(), source.getLeastSignificantBits());
        return destination;
    }
}

What this BeanFactory does under the hood is to call the non-default constructor on java.util.UUID using the information from our already constructed UUID in order to copy the fields. Since there are only two fields that matter on a UUID, mostSigBits and leastSigBits we can simply pass those values into the non-default constructor and BAM we have our copied UUID object.

Now i'll show you how to tell Dozer to use this BeanFactory when creating new instances of UUID.

XML
We need to create an XML file which contains our Dozer mapping configuration, in my case i called it "mappings.xml".
<?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 type="one-way">
        <class-a>java.util.UUID</class-a>
        <class-b bean-factory="path.to.my.bean.factory.UuidBeanFactory">java.util.UUID</class-b>
    </mapping> 
</mappings>

This file is telling Dozer that when it sees the source as java.util.UUID and the destination as a java.util.UUID to use the path.to.my.bean.factory.UuidBeanFactory to create the destination object.

Now this XML file needs to be put somewhere on the classpath, i put mine in my src/ folder and by default when i build everything in src/ gets copied into build/classes which is automatically put on my classpath. If you don't have that luxury you can specify the classpath on the command line when running java by passing in the -cp option, see this link for a slew of ways to set your classpath .

When Dozer loads we now need to tell it to load this custom mappings file, we do this manually when creating the new instance of the DozerBeanMapper by calling setMappingFiles() and giving it our "mappings.xml" file name:
import java.util.UUID;
import org.dozer.DozerBeanMapper;

public class MapUtils {
    
    /* DozerMapperBean is thread safe so no locking */
    private static final DozerBeanMapper 
    
    static {
        MAPPER = new DozerBeanMapper();
        MAPPER.setMappingFiles(Collections.singletonList("mappings.xml"));
    }
    
    private MapUtils() {
    }
    
    public static <T> void map(T source, T destination) {
        MAPPER.map(source, destination);
    }
}

Now you should have a fully functioning UUID mapper!

API
Alternatively we can use the API to specify that Dozer should use path.to.my.bean.factory.UuidBeanFactory to create the destination UUID objects...
import java.util.UUID;
import org.dozer.DozerBeanMapper;
import org.dozer.loader.api.BeanMappingBuilder;
import org.dozer.loader.api.TypeMappingOptions;
import path.to.my.bean.factory.UuidBeanFactory;

public class MapUtils {
    
    /* DozerMapperBean is thread safe so no locking */
    private static final DozerBeanMapper MAPPER;
    
    static {
        MAPPER = new DozerBeanMapper();
        BeanMappingBuilder builder = new BeanMappingBuilder() {
            protected void configure() {
                mapping(UUID.class, UUID.class, TypeMappingOptions.oneWay(), TypeMappingOptions.beanFactory(UuidBeanFactory.class.getName()));
            }
        };
        MAPPER.addMapping(builder);
    }
    
    private MapUtils() {
    }
    
    public static <T> void map(T source, T destination) {
        MAPPER.map(source, destination);
    }
}

Hope this helps someone else!

4 comments:

  1. Just what I was looking for! Thanks so much.

    ReplyDelete
  2. Thank you very much! You saved my day...

    I just have one suggestion, irrelevant maybe: "MAPPER" is not a constant value hence this variable name should be "mapper" although this is static.

    ReplyDelete
  3. UUID's are immutable. I don't think you actually have to create a new instance of the UUID in the bean factory. Just returning the source is fine.

    ReplyDelete
  4. Thanks very much for this article! It saved me a lot of time.

    ReplyDelete