Search code examples
javamappingdozer

Dozer bidirectional mapping (String, String) with custom comverter impossible?


I have a Dozer mapping with a custom converter:

<mapping>
    <class-a>com.xyz.Customer</class-a>
    <class-b>com.xyz.CustomerDAO</class-b>
    <field custom-converter="com.xyz.DozerEmptyString2NullConverter">
        <a>customerName</a>
        <b>customerName</b>
    </field>
</mapping>

And the converter:

public class DozerEmptyString2NullConverter extends DozerConverter<String, String> {

    public DozerEmptyString2NullConverter() {
        super(String.class, String.class);
    }

    public String convertFrom(String source, String destination) {
        String ret = null;
        if (source != null) {
            if (!source.equals(""))
            {
                ret = StringFormatter.wildcard(source);
            } 
        }
        return ret;
    }

    public String convertTo(String source, String destination) {
        return source;
    }
}

When I call the mapper in one direction (Customer -> CustomerDAO) the method 'convertTo' is called.

Since Dozer is able to handle bi-directional mapping, I expect that, as soon as I call the mapper in the opposite direction, the method 'convertFrom' will be called.

But the method convertTo is never called.

I suspect that the problem is, that both types are Strings - but how can I make this work?

As a workaround I created two one-way-mapping, is this the standard solution, or is the behavior a bug?


Solution

  • Yes, the problem is that your source and destination classes are the same. Here is the dozer source for DozerConverter:

      public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass) {
        Class<?> wrappedDestinationClass = ClassUtils.primitiveToWrapper(destinationClass);
        Class<?> wrappedSourceClass = ClassUtils.primitiveToWrapper(sourceClass);
    
        if (prototypeA.equals(wrappedDestinationClass)) {
          return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
        } else if (prototypeB.equals(wrappedDestinationClass)) {
          return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
        } else if (prototypeA.equals(wrappedSourceClass)) {
          return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
        } else if (prototypeB.equals(wrappedSourceClass)) {
          return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
        } else if (prototypeA.isAssignableFrom(wrappedDestinationClass)) {
          return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
        } else if (prototypeB.isAssignableFrom(wrappedDestinationClass)) {
          return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
        } else if (prototypeA.isAssignableFrom(wrappedSourceClass)) {
          return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
        } else if (prototypeB.isAssignableFrom(wrappedSourceClass)) {
          return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
        } else {
          throw new MappingException("Destination Type (" + wrappedDestinationClass.getName()
              + ") is not accepted by this Custom Converter (" 
              + this.getClass().getName() + ")!");
        }
    
      }
    

    Instead of using the convertFrom and convertTo methods (which are part of the new API), do it the original way in you have to implement CustomConverter.convert as shown in the tutorial.