Dani
Dani

Reputation: 341

Extend Existing Mapstruct Interface To Add Additional Mapping Methods

I have a Mapstruct mapper which has been already defined in a common library which is currently used by my application. I have a new mapper method I want to add to this mapper, and it is specific to my application so I do not want to modify the original mapper to add this functionality.

I have tried to extend the interface, but I keep running into issues. Currently with the code below, it will compile, but it throws runtime exceptions as Mapstruct is not generating the MyMapperExtendedImpl class.

Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.whatever.package.name.MyMapperExtended

I do have a warning from Sonar that when I try to access the mapper by calling Mappers.getMapper(MyMapperExtended.class) that it does not have the @Mapper annotation. I then changed MyMapperExtended.class by removing the @MapperConfig annotation and replacing it with the same @Mapper(imports = { SomeUtilityClass.class }) from the parent mapper. The code no longer compiles and I have a series of errors saying that is unable to find certain variables. These variables appear to be the function parameters as the names match exactly.

How can I extend my existing MyMapper interface correctly so I can add additional mapping methods to it?

@MapperConfig(uses = MyMapper.class)
public interface MyMapperExtended extends MyMapper {

    default List<ChildClass> someObjectListToChildClassList(List<SomeObject> someList) {
        //Some special logic here and looping which makes it so cannot use the @Mapping annotations
        
        //Call the mapper for the parent so that the base properties are mapped 
        ParentClass mappedParentClass = this.someObjectListToParentClassList(someList);
        
        //Uses a copy constructor to copy over the mapped base properties
        ChildClass myChildClass = new ChildClass(mappedParentClass);
        myChildClass.setExtraProperty("whatever value");

        return myChildClass;
    }
}

Mapper from shared common library

@Mapper(imports = { SomeUtilityClass.class })
public interface MyMapper {
    @Mapping(target = "id", source = "someId")
    @Mapping(target = "name", source = "name")
    @Mapping(target = "someFieldWeIgnoreWhileMapping", ignore = true)
    ParentClass someObjectListToParentClass(SomeObject someObject)

    List<ParentClass> someObjectListToParentClassList(List<SomeObject> someList)
}

Classes ParentClass & ChildClass being mapped too (simplified so not showing getters/setters for member variables)

public class ParentClass {
    UUID id; 
    String name;
    String someFieldWeIgnoreWhileMapping;

    public ParentClass() {}
}

public class ChildClass {
    String extraProperty; 

    public ChildClass(ParentClass parent) {
        super();
        this.setId(parent.getId());
        this.setName(parent.getName());
    }
}

SomeObject class which is being used as the source object for mapping

public class SomeObject {
    UUID someId; 
    String name; 
    String someFieldWeIgnore; 

    public SomeObject () {}
}

Upvotes: 2

Views: 3627

Answers (1)

Dani
Dani

Reputation: 341

After working through several different issues. I finally got it "working" where it would generate the code, but it will not work for my use case. I excluded the use of "expression" from the code I posted above because I did not at the time think it was relevant but it is causing the final issue.

@Mapping(target = "id", source = "someId")
@Mapping(target = "name", source = "name")
@Mapping(target = "someFieldWeIgnoreWhileMapping", ignore = true)
@Mapping(expression = "java(SomeUtilityClass.doSomethingHelpful(someObject.getSpecialField()))", target = "specialField")
ParentClass someObjectListToParentClass(SomeObject someObject);

The generated code from Mapstruct looks something like this.

public ParentClass someObjectListToParentClass(SomeObject arg0) {
    if (arg0 == null) {
        return null;
    } else { 
        ParentClass parentClass = new ParentClass();
        parentClass.setId(arg0.getId()); 
        parentClass.setName(arg0.getName());

        //The problem. 
        //It puts the variable name as "someObject" because that's what the expression is hardcoded as in the Interface, but the varible is actually arg0 here 
        parentClass.setSpecialField(SomeUtilityClass.doSomethingHelpful(someObject.getSpecialField()));
    }
}

For anyone who does not have a scenario like mine, looks like just adding @Mapper should work (including ALL of the imports if you have any). I have decided to just make a separate mapper instead of inheriting at all from the original one.

Upvotes: 1

Related Questions