Wednesday, November 3, 2010

Dozer and enums

While trying to map a persistent heavyweight class to a cleaner (and much smaller) POJO I also wanted to substitute some String parameters of the persistent class to Enums. The tool of choice is Dozer, of which I already wrote.

As I had the possibility to create the POJO from scratch I choose only the properties I needed and used the same names of the persistent class, thus having no need to write a custom mapping at all.

To check that everything was working fine I wrote a unit test - what else would you expect? :-) - as simple as that:
@Test
public void testPojoToPersistenceEntity() {
System.out.println("testPojoToPersistenceEntity");
MyPojo pojo = new MyPojo(
propOne,
propTwo,
propThree,
propFour);
MyPersistentClass persistent = mapper.map(pojo, MyPersistentClass.class);
assertNotNull(persistent);
assertEquals(propOne, persistent.getPropOne());
assertEquals(propTwoTxt, persistent.getPropTwo());
assertEquals(propThreeTxt, persistent.getPropThree());
assertEquals(propFour, persistent.getPropFour());
}
I run the test and got a green bar for free. You may notice that in the comparison there are a couple of different variables: these are the names of the Enums, and I was quite pleased with the fact that Dozer had silently mapped them just as I expected.

Happy as a clam I wrote another test for the other way round:
@Test
public void testPersistenceEntityToExistingPojo() {
System.out.println("testPersistenceEntityToExistingPojo");
MyPersistentClass persistent = new MyPersistentClass(
propOne,
propTwoTxt,
propThreeTxt,
propFour);
persistent.setPropFive(propFive);
persistent.setPropSix(propSix);
MyPojo pojo = new MyPojo(
propOne,
propTwo,
propThree,
propFour);
assertEquals(0d, pojo.getPropFive(), 0.01d);
assertEquals(0d, pojo.getPropSix(), 0.01d);
mapper.map(persistent, pojo);
assertEquals(propFive, pojo.getPropFive(), 0.01d);
assertEquals(propSix, pojo.getPropSix(), 0.01d);
}
Still smiling I hit the CTRL+F6 combination and... WTF? red bar?
org.dozer.MappingException: Illegal object type for the method 'setPropTwo'.
Expected types:
my.package.MyEnum
Actual types:
java.lang.String
Hmmm... weird... but not too much after all, I had been too optimistic.

After a little investigation, and taken for granted that I didn't want to write a custom mapper for each and every enumeration I could need, given the current conditions of use of the POJO I tried to map a one-way relationship. The problem is that when you mark a field as one-way Dozer only maps from <class-a> to <class-b>, while I needed exactly the opposite behaviour. Changing the order of the classes was not an option as we use the convention of mapping all the domain classes as <class-a> so I had to revert to the <field-exclude> syntax:
<mapping>
<class-a>my.model.package.MyPersistentClass</class-a>
<class-b>my.package.MyPojo</class-b>
<field-exclude type="one-way">
<a>propTwo</a>
<b>propTwo</b>
</field-exclude>
<field-exclude type="one-way">
<a>propThree</a>
<b>propThree</b>
</field-exclude>
</mapping>
Not exactly what I had in mind, because it doesn't let me create a new instance of a POJO, but for now it will suffice.

Any hints?

1 comment:

tazzer said...

Hi, I read your post about enums and had the same problem. I fixed it by creating an interface for my enums and created a custom mapper for the interface type. This way all enums having this interface are automatically mapped:




com.tnt.osc.batch.enquiry.service.iface.IListEnum
java.lang.String



And my customEnumConvertor:
public class CustomEnumConvertor implements CustomConverter {

// convert EnquiryType to String
public Object convert(Object destination, Object source, Class destClass, Class sourceClass) {
if (source == null) {
return null;
}

if ( (source instanceof IListEnum)) {
if(destClass.equals( String.class ) ) {
return (String)((IListEnum)source).toDb();

} else if(destClass.equals( BigInteger.class ) ) {
return (BigInteger)( new BigInteger( ((IListEnum)source).toDb() ) );

} else {
throw new MappingException("Not supported destination type used in CustomEnumConvertor. Arguments passed in were:"
+ destination + " and " + source);
}

} else if ( sourceClass.equals(String.class) || sourceClass.equals(BigInteger.class) ) {
if(destination==null) {
// get the enum of the given type and use the first enum value
// the fromDbVal() methos will return the default db value is no match can be done
Object[] decClass = destClass.getEnumConstants();
IListEnum newDestination = (IListEnum) decClass[0];
return (destClass.cast( newDestination.fromDbVal("" + source) ));

} else {
// we have an object
return (destClass.cast( ((IListEnum)destination).fromDbVal("" + source) ));
}

} else {
throw new MappingException("Converter CustomEnumConvertor used incorrectly. Arguments passed in were:"
+ destination + " and " + source);
}
}

}

hope it help ;)