How to use decorated method in Mapstruct collection mapper?
Asked Answered
H

2

8

I am using MapStruct to map from a JPA entity to a POJO DTO, in a Spring app with dependency injection.

I have added some additional processing of the DTO to a method in a decorator as specified in the doc.

It works fine for mapping a single entity. But I also have a mapping for a collection (set) of these entities and the method is called automatically when a collection of those entities is found in a relationship.

However the generated collection mapping method does not use the decorated method to map each entity, is just uses the "vanilla" generated method on the delegate. Here is the code of the generated method :

@Override
public Set<DimensionItemTreeDTO> missionSetToTreeDtoSet(Set<Mission> set)  {
    return delegate.missionSetToTreeDtoSet( set );
}

The delegate method itself is not aware of the decorator and calls the individual item mapping method on itself :

@Override
public Set<DimensionItemTreeDTO> missionSetToTreeDtoSet(Set<Mission> set) {
    if ( set == null ) {
        return null;
    }

    Set<DimensionItemTreeDTO> set__ = new HashSet<DimensionItemTreeDTO>();
    for ( Mission mission : set ) {
        set__.add( missionToTreeDto( mission ) ); //here the decorator is not called !
    }

    return set__;
}

...and the decorated method is never called for the items inside the collection.

Is there a way I can make Mapstruct use the decorator method in the collection mappings, short of writing the collection method manually in my decorator (which works but is verbose and defeats the purpose of having MapStruct in the first place which is not to have to write this kind of code) ?

Hotblooded answered 16/5, 2016 at 11:4 Comment(0)
H
12

I found the solution to my problem : actually my use case was better suited for the MapStruct @AfterMapping methods, I used it and it is now working fine for all cases :

@Mapper
public abstract class ConstraintsPostProcessor {

    @Inject
    private UserService userService; // can use normal Spring DI here

    @AfterMapping
    public void setConstraintsOnMissionTreeDTO(Mission mission, @MappingTarget MissionDTO dto){ // do not forget the @MappingTarget annotation or it will not work
        dto.setUser(userService.getCurrentUser()); // can do any additional logic here, using services etc.
    }
}

And in the main mapper :

@Mapper(uses = {ConstraintsPostProcessor.class}) // just add the previous class here in the uses attribute
public interface DimensionMapper {
    ...
}
Hotblooded answered 16/5, 2016 at 15:23 Comment(0)
T
1

You should create another decorator for sub object. Suppose you have Book class which has one Author as sub object.

public class Book {

private int id;

private Author author;

getters and setters
}

public class BookDto {

private int bookId;

private AuthorDto author;

getters and setters
}

public class Author {

private int id;

private String name;

getters and setters

}

public class AuthorDto {

private int authorId;

private String name;

getters and setters
}

And we create BookMapper and BookMapperDecorator

@Mapper(componentModel = "spring", uses = AuthorMapper.class)
@DecoratedWith(BookMapperDecorator.class)
public interface BookMapper {

@Mapping(target = "bookId", ignore = true)
BookDto toDto(Book book);
}

public abstract class BookMapperDecorator implements BookMapper{

@Autowired
@Qualifier("delegate")
private BookMapper delegate;

@Override
public BookDto toDto(Book book) {
    BookDto bookDto = delegate.toDto(book);
    bookDto.setBookId(book.getId());
    return bookDto;
}
}

And also AuthorMapper and AuthorMapperDecorator

@Mapper(componentModel = "spring")
@DecoratedWith(AuthorMapperDecorator.class)
public interface AuthorMapper {

@Mapping(target = "authorId", source = "id")
@Mapping(target = "name", ignore = true)
AuthorDto toDto(Author author);
}

public abstract class AuthorMapperDecorator implements AuthorMapper{

@Autowired
@Qualifier("delegate")
private AuthorMapper delegate;

@Override
public AuthorDto toDto(Author author) {
    AuthorDto authorDto = delegate.toDto(author);
    authorDto.setName(author.getName().toUpperCase());
    return authorDto;
}
}

The important parts here are uses = AuthorMapper.class (which says that one mapper uses another one and you don't have to include Author parsing method in BookMapper)

componentModel = "spring" (Which is responsible for bean injection and understands the @Qualifier("delegate") )

You can also ignore one field in mapper and set it in decorator as we did with bookId

Tafilelt answered 5/6, 2022 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.