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

我的梦境 提交于 2021-02-07 18:11:23

问题


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?


回答1:


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.




回答2:


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.




回答3:


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



来源:https://stackoverflow.com/questions/5041832/dozer-bidirectional-mapping-string-string-with-custom-comverter-impossible

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!