Amira
Amira

Reputation: 3270

@AfterMapping mehod is not called in generated class

I'm trying to customize a mapping use a string to determine an object attribute so I wrote this:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductMapper {

public abstract ProductInput asProductInputFromIdentifier(String identifier);

@AfterMapping
protected void determineIdentifier(String identifier, @MappingTarget ProductInput out) {
    if (StringUtils.contains(identifier, '?')) {
        out.setExternalId(identifier);
    } else {
        out.setInernalId(identifier);
    }
}
}

The generated class doesn't call the determineIdentifier method. I found another solution by using directly the Java expression on the asProductInputFromIdentifier method but I really want to write a clear code using the @AfterMapping.

@Mapping(target = "externalId", expression = "java( org.apache.commons.lang3.StringUtils.contains(identifier, '|') ? identifier : null )")
@Mapping(target = "internalId", expression = "java( !org.apache.commons.lang3.StringUtils.contains(identifier, '|') ? identifier : null )")
public abstract ProductInput asProductDetailInputFromIdentifier(String identifier);

I didn't understand why it doesn't work Is it because I don't have an Object in method parameter?

Upvotes: 6

Views: 10045

Answers (5)

Yassine CHABLI
Yassine CHABLI

Reputation: 3724

In case of having lombok @Builder, the afterMapping will never be called.

The issue is always not resolved.

Check the issue on the official repo here

Upvotes: 9

roshini
roshini

Reputation: 11

As @Filip mentioned, your @Aftermapping method should return the same return type as that of the mapping method.

@AfterMapping
protected ProductInput determineIdentifier(String identifier, @MappingTarget ProductInput out) {
    if (StringUtils.contains(identifier, '?')) {
        out.setExternalId(identifier);
    } else {
        out.setInernalId(identifier);
    }
return out;
}

I was using Lombok @Builder annotation on my returned object. When I removed that annotation, my code started working.

Upvotes: -1

Amira
Amira

Reputation: 3270

To solve the problem, I added the to ProductInput to the abstract method parameters as follow (which is not the requirement). As explains @Rakesh in his answer above Method with @AfterMapping will be executed at the end of map method and it should have same input parameter as map method, but it can interest others in their case:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductMapper {

   public abstract ProductInput asProductInputFromIdentifier(String identifier, ProductInput out);

   @AfterMapping
   protected void determineIdentifier(String identifier, @MappingTarget ProductInput out) {
      if (StringUtils.contains(identifier, '?')) {
          out.setExternalId(identifier);
      } else {
          out.setInernalId(identifier);
       }
    }
}

But In my case I used the builder = @Builder(disableBuilder = true) to turn off "builders" in MapStruct be it can help someone ;) Mapstruct Builder Now the generated MapperImpl calls the annotated @AfterMapping method.

Upvotes: 3

Filip
Filip

Reputation: 21393

MapStruct doesn't know how to map from String to ProductInput in your case I would suggest that you implement the mapping method on your own.

e.g.

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductMapper {

    public ProductInput asProductInputFromIdentifier(String identifier) {
        ProductInput out = new ProductInput();
        if (StringUtils.contains(identifier, '?')) {
            out.setExternalId(identifier);
        } else {
            out.setInernalId(identifier);
        }
        return out;
    }
}

There is no point in using lifecycle callbacks nor special @Mapping annotations to perform a simple mapping like in this example.

Upvotes: 2

Rakesh Chauhan
Rakesh Chauhan

Reputation: 342

Method with @AfterMapping will be executed at the end of map method and it should have same input parameter as map method for example in below sample

@Mapper(
    componentModel = "spring",
    injectionStrategy = InjectionStrategy.CONSTRUCTOR,
    uses = {IdentifierMapper.class})
public interface HistoryMapper {

  HistoryDynamo toHistoryDynamo(History history);

  @AfterMapping
  default void changeReason(History history) {
    System.out.println(history.getReason());
  }
}

Refer github working sample for the same https://github.com/rakesh-singh-samples/map-struct-samples/tree/stack-question-60523230/src/sample/mapstruct/mapper

Upvotes: 6

Related Questions