Borgy Manotoy
Borgy Manotoy

Reputation: 2038

Mapstruct mapping with condition and nullValuePropertyMappingStrategy

I apologize if the title is not clear, let me make it clear by giving sample codes:

UpdateProfileDto

public class UpdateProfileDto {

    @NotEmpty
    private String firstName;

    @NotEmpty
    private String lastName;

    @Size(max = 20)
    private String currentPassword;

    @Size(max = 20)
    private String newPassword;

    @Size(max = 20)
    private String confirmNewPassword;

    // getters and setters
}

EncodedMapping

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EncodedMapping {
}

PasswordEncoderMapper

public class PasswordEncoderMapper {
    protected final PasswordEncoder passwordEncoder;

    public PasswordEncoderMapper(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @EncodedMapping
    public String encode(String value) {
        return passwordEncoder.encode(value);
    }
}

UserMapper

@Mapper(config = MapperConfig.class, componentModel = "spring", uses = PasswordEncoderMapper.class)
public interface UserMapper {

    @Mappings({
            @Mapping(target = "firstName", source = "firstName"),
            @Mapping(target = "lastName", source = "lastName"),
            @Mapping(target = "fullName", expression = "java(user.getFirstName() + \" \" + user.getLastName())"),
            @Mapping(target = "password",
                    source = "newPassword",
                    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
                    qualifiedBy = EncodedMapping.class)
    })
    void updateUserFromDto(UpdateUserProfileDto updateUserProfileDto, @MappingTarget User user);
}

Generated UserMapperImpl

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-03-11T13:51:34+0800",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_231 (Oracle Corporation)"
)
@Component
public class UserMapperImpl implements UserMapper {

    @Autowired
    private PasswordEncoderMapper passwordEncoderMapper;

    @Override
    public void updateUserFromDto(UpdateUserProfileDto updateUserProfileDto, User user) {
        if ( updateUserProfileDto == null ) {
            return;
        }

        if ( updateUserProfileDto.getFirstName() != null ) {
            user.setFirstName( updateUserProfileDto.getFirstName() );
        }
        else {
            user.setFirstName( null );
        }
        if ( updateUserProfileDto.getLastName() != null ) {
            user.setLastName( updateUserProfileDto.getLastName() );
        }
        else {
            user.setLastName( null );
        }
        if ( updateUserProfileDto.getNewPassword() != null ) {
            user.setPassword( passwordEncoderMapper.encode( updateUserProfileDto.getNewPassword() ) );
        }

        user.setFullName( user.getFirstName() + " " + user.getLastName() );
    }
}

From the generated UserMapperImpl, I would like to check not only if newPassword has value... but to check currentPassword and newPassword have values and proceed with user.setPassword().

I mean something like this:

...
if ( updateUserProfileDto.getCurrentPassword() != null && updateUserProfileDto.getNewPassword() != null ) {
    user.setPassword( passwordEncoderMapper.encode( updateUserProfileDto.getNewPassword() ) );
}
...

Problem

How could I change my mapper interface UserMapper so that i will check both currentPassword and newPassword before it will set the target user.password and will still use PasswordEncoderMapper.encode(password)?

If I try to use expression instead of source and check both currentPassword and newPassword if both have values and then set user.password to newPassword. Otherwise, it will not do anything to user.passwordusing NullValuePropertyMappingStrategy... but it seems it is not allowede to mix expression and NullValuePropertyMappingStrategy.

Thanks!

Upvotes: 5

Views: 9166

Answers (1)

Nikolai  Shevchenko
Nikolai Shevchenko

Reputation: 7521

I would start with following approach

@Mapper(config = MapperConfig.class, componentModel = "spring")
public abstract class UserMapper { // using class instead of interface to be able to inject beans

    @Autowired
    private PasswordEncoderMapper passwordEncoderMapper;

    @Mappings({
            // your non-password mappings
    })
    void updateUserFromDto(UpdateUserProfileDto updateUserProfileDto, @MappingTarget User user);


    @AfterMapping
    void setPassword(UpdateUserProfileDto updateUserProfileDto, @MappingTarget User user) {
        if (updateUserProfileDto.getCurrentPassword() != null && updateUserProfileDto.getNewPassword() != null) {
            user.setPassword(passwordEncoderMapper.encode( updateUserProfileDto.getNewPassword()));
        }
    }
}

Upvotes: 5

Related Questions