Question

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;
}
Was it helpful?

Solution

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!

OTHER TIPS

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;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top