Model mapper - how to map incompatible types

7.6k views Asked by At

I have entities User and Event which have many to many relationship to each other. If I try to return User object from Controller to REST API, it results in cyclic dependency.

I decided to use DTOs where I would use instead of List<Event> just List<Integer> that would represent ids of event objects.

But if I try to create custom PropertyMap, it does not work. Do you recommend different approach, how to do that?

Event entity

@Entity
@Table(name = "events")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Event extends AbstractEntity {

@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "event_participants",
        joinColumns = @JoinColumn(name = "event_id"),
        inverseJoinColumns = @JoinColumn(name = "user_id"))
private List<User> eventParticipants;

User entity

@Entity
@Table(name = "users")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class User extends AbstractEntity {

@ManyToMany(mappedBy = "eventParticipants")
private List<Event> participatingEvents;

UserController

@RestController
@RequestMapping("/users")
public class UserController extends AbstractController {

@Autowired
private UserService userService;

@Autowired
private ModelMapper modelMapper;

@RequestMapping(method = RequestMethod.GET, value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public UserDTO find(@PathVariable("id") Integer id) {
    final User user = userService.find(id);
    if (user == null) {
        //TODO throw some exception
        return null;
    }
    UserDTO userDTO = userToDTO(user);
    return userDTO;
}

private UserDTO userToDTO(User user) {
    modelMapper.addMappings(new UserMap());
    return modelMapper.map(user, UserDTO.class);
}

And finally, my PropertyMap class

public class UserMap extends PropertyMap<User, UserDTO> {

    @Override
    protected void configure() {
        map().setFirstName(source.getFirstName());

        map().setLastName(source.getLastName());

        map().setEmail(source.getEmail());

        List<Integer> friendsDTO = new ArrayList<>();
        for(User u : source.getFriends()){
            friendsDTO.add(u.getId());
        }
        map().setFriends(friendsDTO);

        List<Integer> participatingEventsDTO = new ArrayList<>();
        for(Event e : source.getParticipatingEvents()){
            participatingEventsDTO.add(e.getId());
        }
        map().setParticipatingEvents(participatingEventsDTO);

        List<Integer> ownedEventsDTO = new ArrayList<>();
        for(Event e : source.getOwnedEvents()){
            ownedEventsDTO.add(e.getId());
        }
        map().setOwnedEvents(ownedEventsDTO);

        List<Integer> commentsDTO = new ArrayList<>();
        for(Comment c : source.getComments()){
            commentsDTO.add(c.getId());
        }
        map().setComments(commentsDTO);

        List<Integer> thingsDTO = new ArrayList<>();
        for(ThingToTake t : source.getThingsToTakeList()){
            thingsDTO.add(t.getId());
        }
        map().setThingsToTakeList(thingsDTO);
    }

}

This is an exception I get if I try to GET using rest:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

2) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

3) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

4) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

5) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

5 errors
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

root cause

org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

2) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

3) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

4) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

5) Invalid source method java.util.List.add(). Ensure that method has zero parameters and does not return void.

5 errors
    org.modelmapper.internal.Errors.throwConfigurationExceptionIfErrorsExist(Errors.java:241)
    org.modelmapper.internal.ExplicitMappingBuilder.visitPropertyMap(ExplicitMappingBuilder.java:229)
    org.modelmapper.PropertyMap.configure(PropertyMap.java:380)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    java.lang.reflect.Method.invoke(Method.java:483)
    org.modelmapper.internal.ExplicitMappingBuilder.build(ExplicitMappingBuilder.java:195)
    org.modelmapper.internal.TypeMapImpl.addMappings(TypeMapImpl.java:72)
    org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:101)
    org.modelmapper.ModelMapper.addMappings(ModelMapper.java:93)
    com.ear.tripplan.rest.UserController.userToDTO(UserController.java:50)
    com.ear.tripplan.rest.UserController.find(UserController.java:39)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    java.lang.reflect.Method.invoke(Method.java:483)
    org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
    org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
1

There are 1 answers

0
roki On

I had the same problem and I did it by wrapping id in another object

public class UserDTO {

     List<EventDTO> participatingEvents
     /// getter setter
}

public class EventDTO {
    private Long id;
    // getter setter
}

and you dont need any propertyMap or converter

UserDTO userDTO = modelmapper.map(user, UserDTO.class);

hope it helps

UPDATED

You can do it with Converters as well

public class UserMap extends PropertyMap<User, UserDTO> {

@Override
protected void configure() {
    Converter<List<User>, List<Long>> converter = new AbstractConverter<List<User>, List<Long>>() {
        List<Long> result = new ArrayList<>();
        @Override
        protected List<Long> convert(List<User> source) {
            source.forEach(user -> result.add(user.getId()));
            return result;
        }
    };
    using(converter).map(source.getUsers(), destination.getUsers());
}

}