开发者

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

开发者 https://www.devze.com 2023-02-11 16:38 出处:网络
I have a Dozer mapping with a custom converter: <mapping> <class-a>com.xyz.Customer</class-a>

I have a Dozer mapping with a custom converter:

<mapping>
    <class-a>com.xyz.Customer</class-a>
    <class-b>com.xyz.CustomerDAO</class-b>
 开发者_如何学JAVA   <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?


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.


I had the same problem and currently (as of Dozer 5.5.x) there's no simple way, but there is complex one.

Note, that it relies on having no security manager enabled in JVM, or else you will need to add few permissions in the security rules. That's because this solution uses reflection to access private fields of Dozer classes.

You need to extend 2 classes: DozerBeanMapper and MappingProcessor. You will also need enum for direction and interface to get direction from above classes.

The enum:

public enum Direction {
    TO,
    FROM;
}

The interface:

public interface DirectionAware {
    Direction getDirection();
}

The class extending DozerBeanMapper:

public class DirectionAwareDozerBeanMapper extends DozerBeanMapper implements DirectionAware {
    private Direction direction;

    public DirectionAwareDozerBeanMapper(Direction direction) {
        super();
        this.direction = direction;
    }

    public DirectionAwareDozerBeanMapper(Direction direction, List<String> mappingFiles) {
        super(mappingFiles);
        this.direction = direction;
    }

    @Override
    protected Mapper getMappingProcessor() {
        try {
            Method m = DozerBeanMapper.class.getDeclaredMethod("initMappings");
            m.setAccessible(true);
            m.invoke(this);
        } catch (NoSuchMethodException|SecurityException|IllegalAccessException|IllegalArgumentException|InvocationTargetException e) {
            // Handle the exception as you want
        }

        ClassMappings arg1 = (ClassMappings)getField("customMappings");
        Configuration arg2 = (Configuration)getFieldValue("globalConfiguration");
        CacheManager arg3 = (CacheManager)getField("cacheManager");
        StatisticsManager arg4 = (StatisticsManager)getField("statsMgr");
        List<CustomConverter> arg5 = (List<CustomConverter>)getField("customConverters");
        DozerEventManager arg6 = (DozerEventManager)getField("eventManager");
        Map<String, CustomConverter> arg7 = (Map<String, CustomConverter>)getField("customConvertersWithId");

        Mapper mapper = new DirectionAwareMappingProcessor(arg1, arg2, arg3, arg4, arg5,
                                arg6, getCustomFieldMapper(), arg7, direction);

        return mapper;
    }

    private Object getField(String fieldName) {
        try {
            Field field = DozerBeanMapper.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(this);
        } catch (NoSuchFieldException|SecurityException|IllegalArgumentException|IllegalAccessException e) {
            // Handle the exception as you want
        }
        return null;
    }

    public Direction getDirection() {
        return direction;
    }
}

The class extending MappingProcessor:

public class DirectionAwareMappingProcessor extends MappingProcessor implements DirectionAware {
    private Direction direction;

    protected DirectionAwareMappingProcessor(ClassMappings arg1, Configuration arg2, CacheManager arg3, StatisticsManager arg4, List<CustomConverter> arg5, DozerEventManager arg6, CustomFieldMapper arg7, Map<String, CustomConverter> arg8, Direction direction) {
        super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
        this.direction = direction;
    }

    public Direction getDirection() {
        return direction;
    }
}

Now, the usage.

1) Everytime you want to map the same primitive type (for example String-String), use DozerConverter with that type for both arguments as a custom converter in your dozer mappings file. The implementation of such converter should extend: DozerConverter<String,String> and implement MapperAware interface. This is important that you have MapperAware available, becuase having the mapper you will be able to cast it to DirectionAware and then get the direction.

For example:

public class MyMapper extends DozerConverter<String, String> implements MapperAware {
    private DirectionAware dirAware;

    public MyMapper(Class<String> cls) {
        super(cls, cls);
    }

    @Override
    public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<String> destinationClass, Class<String> sourceClass) {
        if (dirAware.getDirection() == Direction.FROM) {
            // TODO convert sourceFieldValue for "FROM" direction and return it
        } else {
            // TODO convert sourceFieldValue for "TO" direction and return it
        }
    }

    @Override
    public void setMapper(Mapper mapper) {
        dirAware = (DirectionAware)mapper;
    }
}

2) You need to create 2 global Dozer mapper objects, one per mapping direction. They should be configured with the same mapping files, but with different direction argument. For example:

DirectionAwareDozerBeanMapper mapperFrom = DirectionAwareDozerBeanMapper(mappingFiles, Direction.FROM);
DirectionAwareDozerBeanMapper mapperTo = DirectionAwareDozerBeanMapper(mappingFiles, Direction.TO);

Of course you will need use proper mapper (from/to) to provide information to custom mappers on which direction you're mapping.


I got into same kind of issue after couple of years and somehow DozerConverter API which is a new API, still does not work properly as bi-direction !!

So, rather than getting into all these complex solutions advised here, I also created 2 one-way mapping to get over this issue(with ) . And then my conversions started working . I am using DozerConverter api like below :

public class MapToStringConverter extends DozerConverter

0

精彩评论

暂无评论...
验证码 换一张
取 消