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

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?

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

Related

Deserialize mutable (primitive/object) JSON property to object with Jackson

Given a JSON object having a mutable property (e.g. label) which can be either primitive value (e.g. string) or an object. A hypothetical use-case could be a wrapper for pluralized translation of a label:
{
"label": "User name"
}
or
{
"label": {
"one": "A label",
"other": "The labels"
}
}
The goal is to bring Jackson deserialization always return a fixed structure on the Java-side. Thus, if a primitive value is given it is always translated to a certain property (e.g. other) of the target POJO, i.e.:
public class Translations {
#JsonDeserialize(using = PluralizedTranslationDeserializer.class)
public PluralizedTranslation label;
}
public class PluralizedTranslation {
public String one;
public String other; // used as default fields for primitive value
}
Currently the issue is solved by using a custom JsonDeserializer which checks whether the property is primitive or not:
public class PluralizedTranslationDeserializer extends JsonDeserializer {
#Override
public PluralizedTranslation deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
PluralizedTranslation translation;
if (node.isTextual()) {
translation = new PluralizedTranslation();
translation.other = node.asText();
} else {
translation = oc.treeToValue(node, PluralizedTranslation.class);
}
return translation;
}
}
Is there a more elegant approach for handling mutable JSON properties without having a decoder which operates on node level?
You could make the label setter more generic and add some logic handling the two cases.
public class Translations {
// Fields omitted.
#JsonProperty("label")
public void setLabel(Object o) {
if (o instanceof String) {
// Handle the first case
} else if (o instanceof Map) {
// Handle the second case
} else {
throw new RuntimeException("Unsupported");
}
}
}
Alternative solution, which places the factory method inside the PluralizedTranslation class, leaving the Translations class unaffected:
public class PluralizedTranslation {
public String one;
public String other; // used as default fields for primitive value
#JsonCreator
private PluralizedTranslation(Object obj) {
if (obj instanceof Map) {
Map map = (Map) obj;
one = (String) map.get("one");
other = (String) map.get("other");
} else if (obj instanceof String) {
other = (String) obj;
} else {
throw new RuntimeException("Unsupported");
}
}
}
Note that the constructor can be marked as private to prevent unintended usage.

ModelMapper handling java 8 Optional<MyObjectDto> fields to Optional<MyObject>

I've been using modelmapper and java 8 Optionals all around the application which was working fine because they were primitive types; until I changed one of my model objects' field to Optional type. Then all hell broke loose. Turns out many libraries cannot handle generics very well.
Here is the structure
public class MyObjectDto
{
private Optional<MySubObjectDto> mySubObject;
}
public MyObject
{
privae Optional<MySubjObject> mySubObject;
}
When I attempt to map MyObjectDto to MyObject, modelmapper calls
public void setMySubObject(Optional<MySubObject> mySubObject){
this.mySubObject = mySubObject;
}
with Optional<MySubObjectDto>, which I don't understand how that's even possible (there is no inheritance between them). Of course that crashes fast. For now I've changed my setters to accept Dto type just to survive the day but that's not going to work on the long run. Is there a better way to get around this, or shall I create an issue?
So I digged into the modelmapper code and have done this looking at some generic implementations:
modelMapper.createTypeMap(Optional.class, Optional.class).setConverter(new OptionalConverter());
public class OptionalConverter implements ConditionalConverter<Optional, Optional> {
public MatchResult match(Class<?> sourceType, Class<?> destinationType) {
if (Optional.class.isAssignableFrom(destinationType)) {
return MatchResult.FULL;
} else {
return MatchResult.NONE;
}
}
private Class<?> getElementType(MappingContext<Optional, Optional> context) {
Mapping mapping = context.getMapping();
if (mapping instanceof PropertyMapping) {
PropertyInfo destInfo = ((PropertyMapping) mapping).getLastDestinationProperty();
Class<?> elementType = TypeResolver.resolveArgument(destInfo.getGenericType(),
destInfo.getInitialType());
return elementType == TypeResolver.Unknown.class ? Object.class : elementType;
} else if (context.getGenericDestinationType() instanceof ParameterizedType) {
return Types.rawTypeFor(((ParameterizedType) context.getGenericDestinationType()).getActualTypeArguments()[0]);
}
return Object.class;
}
public Optional<?> convert(MappingContext<Optional, Optional> context) {
Class<?> optionalType = getElementType(context);
Optional source = context.getSource();
Object dest = null;
if (source != null && source.isPresent()) {
MappingContext<?, ?> optionalContext = context.create(source.get(), optionalType);
dest = context.getMappingEngine().map(optionalContext);
}
return Optional.ofNullable(dest);
}
}

Dozer mapping/updating a collection

I am trying to map a A-DTO object to an A-DO object, each having a collection (a List) of T-DTOs, and T-DOs, respectively. I am trying to do it in the context of a REST API. It's a separate question whether it's a right approach - the problem I'm solving is a case of update. Basically, if one of the T-DTOs inside the A-DTO changes, I want that change to be mapped into the corresponding T-DO inside the A-DO.
I found relationship-type="non-cumulative" in Dozer documentation, so that the object inside the collection is updated, if present. But I end up with Dozer inserting a new T-DO into the A-DO's collection!
NOTE: I did implement equals! it is based on the primary key only for now.
Any ideas?
PS: and, if you think this is a bad idea to handle updates to a one-to-many dependent entity, feel free to point that out.. I'm not 100% sure I like that approach, but my REST foo is not very strong.
UPDATE
equals implementation:
#Override
public boolean equals(Object obj) {
if (obj instanceof MyDOClass) {
MyDOClass other = (MyDOClass) obj;
return other.getId().equals(this.getId());
}
return false;
}
I just had the same problem and I solved it:
Dozer uses contains to determine if a member is inside a collection.
You should implement hashCode so that "contains" will work appropriately.
You can see this in the following documentation page:
http://dozer.sourceforge.net/documentation/collectionandarraymapping.html
Under: "Cumulative vs. Non-Cumulative List Mapping (bi-directional)"
Good luck!
Ended up doing a custom mapping.
I did endup doing my own AbstractConverter please find it below:
It has some constraints which are suitable for me (possibly not for you).
will update based on "sameId" implementation
will remove orphans (element from destination not in the source).
Only works on List (enough for my needs).
While the converter will manage the decision to update the mapping of objects are delegated back to Dozer so you don't need to implement the mapping of the elements in your list
Sample use
public class MyConverter extends AbstractListConverter<ClassX,ClassY>{
public MyConverter(){ super(ClassX.class, ClassY.class);}
#Override
protected boolean sameId(ClassX o1, ClassY o2) {
return // your custom comparison here... true means the o2 and o1 can update each other.
}
}
Declaration in mapper.xml
<mapping>
<class-a>x.y.z.AClass</class-a>
<class-b>a.b.c.AnotherClass</class-b>
<field custom-converter="g.e.MyConverter">
<a>ListField</a>
<b>OtherListField</b>
</field>
</mapping>
public abstract class AbstractListConverter<A, B> implements MapperAware, CustomConverter {
private Mapper mapper;
private Class<A> prototypeA;
private Class<B> prototypeB;
#Override
public void setMapper(Mapper mapper) {
this.mapper = mapper;
}
AbstractListConverter(Class<A> prototypeA, Class<B> prototypeB) {
this.prototypeA = prototypeA;
this.prototypeB = prototypeB;
}
#Override
public Object convert(Object destination, Object source, Class<?> destinationClass, Class<?> sourceClass) {
if (destinationClass == null || sourceClass == null || source == null) {
return null;
}
if (List.class.isAssignableFrom(sourceClass) && List.class.isAssignableFrom(destinationClass)) {
if (destination == null || ((List) destination).size() == 0) {
return produceNewList((List) source, destinationClass);
}
return mergeList((List) source, (List) destination, destinationClass);
}
throw new Error("This specific mapper is only to be used when both source and destination are of type java.util.List");
}
private boolean same(Object o1, Object o2) {
if (prototypeA.isAssignableFrom(o1.getClass()) && prototypeB.isAssignableFrom(o2.getClass())) {
return sameId((A) o1, (B) o2);
}
if (prototypeB.isAssignableFrom(o1.getClass()) && prototypeA.isAssignableFrom(o2.getClass())) {
return sameId((A) o2, (B) o1);
}
return false;
}
abstract protected boolean sameId(A o, B t);
private List mergeList(List source, List destination, Class<?> destinationClass) {
return (List)
source.stream().map(from -> {
Optional to = destination.stream().filter(search -> same(from, search)).findFirst();
if (to.isPresent()) {
Object ret = to.get();
mapper.map(from, ret);
return ret;
} else {
return create(from);
}
}
).collect(Collectors.toList());
}
private List produceNewList(List source, Class<?> destinationClass) {
if (source.size() == 0) return source;
return (List) source.stream().map(o -> create(o)).collect(Collectors.toList());
}
private Object create(Object o) {
if (prototypeA.isAssignableFrom(o.getClass())) {
return mapper.map(o, prototypeB);
}
if (prototypeB.isAssignableFrom(o.getClass())) {
return mapper.map(o, prototypeA);
}
return null;
}
}

How can I tell Dozer to bypass mapping null or empty string values per field using the programming api?

The faq explains how to do this in XML. This shows how to do it per class using the API. The problem is you have to set it on on the class. What if I only want it on one field? Is this possible?
You don't need converter. Dozer allows to do it easily:
import static org.dozer.loader.api.TypeMappingOptions.mapNull;
import static org.dozer.loader.api.TypeMappingOptions.mapEmptyString;
...
public Mapper getMapper() {
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.addMapping(beanMappingBuilder());
return mapper;
}
private BeanMappingBuilder beanMappingBuilder() {
return new BeanMappingBuilder() {
#Override
protected void configure() {
mapping(ClassA.class, ClassB.class, mapNull(false), mapEmptyString(false));
}
}
}
I was able to accomplish what I needed by creating my own custom converters for this. Here's the code:
import org.apache.commons.lang.StringUtils;
import org.dozer.DozerConverter;
public class IfNotBlankConverter extends DozerConverter<String, String> {
public IfNotBlankConverter() {
super(String.class, String.class);
}
private String getObject(String source, String destination) {
if (StringUtils.isNotBlank(source)) {
return source;
} else {
return destination;
}
}
#Override
public String convertTo(String source, String destination) {
return getObject(source, destination);
}
#Override
public String convertFrom(String source, String destination) {
return getObject(source, destination);
}
}
Second one:
import org.dozer.DozerConverter;
public class IfNotNullConverter extends DozerConverter<Object, Object> {
public IfNotNullConverter() {
super(Object.class, Object.class);
}
#Override
public Object convertTo(Object source, Object destination) {
return getObject(source, destination);
}
#Override
public Object convertFrom(Object source, Object destination) {
return getObject(source, destination);
}
private Object getObject(Object source, Object destination) {
if (source != null) {
return source;
} else {
return destination;
}
}
}
you can use the following to bypass null value in xml configuration
<mapping map-null="false">
<class-a>org.dozer.vo.AnotherTestObject</class-a>
<class-b>org.dozer.vo.AnotherTestObjectPrime</class-b>
<field>
<a>field4</a>
<b>to.one</b>
</field>
</mapping>
For the sake of future readers: the solution with the custom converter did not work for me. It seems that the converters are just being ignored during the mapping.
However, defining custom field mapper did work quite well: (written in java 8)
dozerBeanMapper.setCustomFieldMapper((source, destination, sourceFieldValue, classMap, fieldMapping) ->
sourceFieldValue == null);
This mapper returns a boolean indicating weather the mapping was done for that field. So, returning true if the object is null, will notify Dozer that the mapping was finished. Sending false will continue the default mapping. (See MappingProcessor.java - mapField method)

Suggestions on extending fit.RowFixture and fit.TypeAdapter so that I can bind/invoke on a class that keeps attrs in a map

TLDR: I'd like to know how to extend fit.TypeAdaptor so that I can invoke a method that expects parameters as default implementation of TypeAdaptor invokes the binded (bound ?) method by reflection and assumes it's a no-param method...
Longer version -
I'm using fit to build a test harness for my system (a service that returns a sorted list of custom objects). In order to verify the system, I thought I'd use fit.RowFixture to assert attributes of the list items.
Since RowFixture expects the data to be either a public attribute or a public method, I thought of using a wrapper over my custom object (say InstanceWrapper) - I also tried to implement the suggestion given in this previous thread about formatting data in RowFixture.
The trouble is that my custom object has around 41 attributes and I'd like to provide testers with the option of choosing which attributes they want to verify in this RowFixture. Plus, unless I dynamically add fields/methods to my InstanceWrapper class, how will RowFixture invoke either of my getters since both expect the attribute name to be passed as a param (code copied below) ?
I extended RowFixture to bind on my method but I'm not sure how to extend TypeAdaptor so that it invokes with the attr name..
Any suggestions ?
public class InstanceWrapper {
private Instance instance;
private Map<String, Object> attrs;
public int index;
public InstanceWrapper() {
super();
}
public InstanceWrapper(Instance instance) {
this.instance = instance;
init(); // initialise map
}
private void init() {
attrs = new HashMap<String, Object>();
String attrName;
for (AttrDef attrDef : instance.getModelDef().getAttrDefs()) {
attrName = attrDef.getAttrName();
attrs.put(attrName, instance.getChildScalar(attrName));
}
}
public String getAttribute(String attr) {
return attrs.get(attr).toString();
}
public String description(String attribute) {
return instance.getChildScalar(attribute).toString();
}
}
public class MyDisplayRules extends fit.RowFixture {
#Override
public Object[] query() {
List<Instance> list = PHEFixture.hierarchyList;
return convertInstances(list);
}
private Object[] convertInstances(List<Instance> instances) {
Object[] objects = new Object[instances.size()];
InstanceWrapper wrapper;
int index = 0;
for (Instance instance : instances) {
wrapper = new InstanceWrapper(instance);
wrapper.index = index;
objects[index++] = wrapper;
}
return objects;
}
#Override
public Class getTargetClass() {
return InstanceWrapper.class;
}
#Override
public Object parse(String s, Class type) throws Exception {
return super.parse(s, type);
}
#Override
protected void bind(Parse heads) {
columnBindings = new TypeAdapter[heads.size()];
for (int i = 0; heads != null; i++, heads = heads.more) {
String name = heads.text();
String suffix = "()";
try {
if (name.equals("")) {
columnBindings[i] = null;
} else if (name.endsWith(suffix)) {
columnBindings[i] = bindMethod("description", name.substring(0, name.length()
- suffix.length()));
} else {
columnBindings[i] = bindField(name);
}
} catch (Exception e) {
exception(heads, e);
}
}
}
protected TypeAdapter bindMethod(String name, String attribute) throws Exception {
Class partypes[] = new Class[1];
partypes[0] = String.class;
return PHETypeAdaptor.on(this, getTargetClass().getMethod("getAttribute", partypes), attribute);
}
}
For what it's worth, here's how I eventually worked around the problem:
I created a custom TypeAdapter (extending TypeAdapter) with the additional public attribute (String) attrName. Also:
#Override
public Object invoke() throws IllegalAccessException, InvocationTargetException {
if ("getAttribute".equals(method.getName())) {
Object params[] = { attrName };
return method.invoke(target, params);
} else {
return super.invoke();
}
}
Then I extended fit.RowFixture and made the following overrides:
public getTargetClass() - to return my class reference
protected TypeAdapter bindField(String name) throws Exception - this is a protected method in ColumnFixture which I modified so that it would use my class's getter method:
#Override
protected TypeAdapter bindField(String name) throws Exception {
String fieldName = camel(name);
// for all attributes, use method getAttribute(String)
Class methodParams[] = new Class[1];
methodParams[0] = String.class;
TypeAdapter a = TypeAdapter.on(this, getTargetClass().getMethod("getAttribute", methodParams));
PHETypeAdapter pheAdapter = new PHETypeAdapter(fieldName);
pheAdapter.target = a.target;
pheAdapter.fixture = a.fixture;
pheAdapter.field = a.field;
pheAdapter.method = a.method;
pheAdapter.type = a.type;
return pheAdapter;
}
I know this is not a neat solution, but it was the best I could come up with. Maybe I'll get some better solutions here :-)

Categories

Resources