I want to standardize my mapping processes to follow a particular scheme. Therefore I introduced a few generic interfaces that helps to reduce effort when writing new mappers for new types. Here is the interface for types:
import lombok.NonNull;
public interface MappableCyclic<IN extends MappableCyclic<IN>>
{
void beforeMapping(@NonNull IN in, @NonNull ReferenceCycleTracking context);
void afterMapping(@NonNull IN in, @NonNull ReferenceCycleTracking context);
}
ReferenceCycleTracking
is a typical implementation for a mapstruct context to avoid infinite recursion when mapping objects with cyclic dependencies. In addition to that there is a generic super interface for mappers:
public interface MappableCyclicMapper<IN extends MappableCyclic<IN, OUT>, OUT extends MappableCyclic<OUT, IN>>
{
@NonNull OUT map(@NonNull IN in, @NonNull @Context ReferenceCycleTracking context);
@BeforeMapping default void beforeMapping(
@NonNull IN in,
@NonNull @MappingTarget OUT out,
@NonNull @Context ReferenceCycleTracking context)
{
out.beforeMapping(out, in, context);
}
@AfterMapping default void afterMapping(
@NonNull IN in,
@NonNull @MappingTarget OUT out,
@NonNull @Context ReferenceCycleTracking context)
{
out.afterMapping(out, in, context);
}
@NonNull Class<OUT> outType();
@NonNull OUT create(IN in);
/**
* object factory should be called by mapstruct during generated {@link #map(MappableCyclic, ReferenceCycleTracking)}
* implementation
*/
@ObjectFactory default @NonNull OUT lookupOrCreate(@NonNull IN in, @NonNull ReferenceCycleTracking context)
{
OUT out = context.get(in, outType());
if (out == null)
{
out = create(in);
context.put(in, out);
}
return out;
}
}
And finally here is the mapper interface for two of my mappable types:
@Mapper interface Map_TaskGroup_EntityDTO_EntityJPA extends MappableCyclicMapper<TaskGroupEntityDTO, TaskGroupEntityJPA>
{
Map_TaskGroup_EntityDTO_EntityJPA INSTANCE = Mappers.getMapper(Map_TaskGroup_EntityDTO_EntityJPA.class);
@NonNull TaskGroupEntityJPA map(@NonNull TaskGroupEntityDTO input, @NonNull @Context ReferenceCycleTracking context);
@Override default @NonNull Class<TaskGroupEntityJPA> outType() { return TaskGroupEntityJPA.class; }
@Override default @NonNull TaskGroupEntityJPA create(TaskGroupEntityDTO in) { return new TaskGroupEntityJPA(in.name()); }
@ObjectFactory
@Override default @NonNull TaskGroupEntityJPA lookupOrCreate(
@NonNull TaskGroupEntityDTO taskGroupEntityDTO, @NonNull ReferenceCycleTracking context)
{
return MappableCyclicMapper.super.lookupOrCreate(taskGroupEntityDTO, context);
}
}
I expected that mapstruct would use the @ObjectFactory
method provided in the super interface but it doesn´t, so I tried to help mapstruct with an additional object factory method in the sub interface above. However the implementation for that interface generated by mapstruct ignores my object factory methods:
@Override
public TaskGroupEntityJPA map(TaskGroupEntityDTO input, ReferenceCycleTracking context) {
TaskGroupEntityJPA target = context.get( input, TaskGroupEntityJPA.class );
if ( target != null ) {
return target;
}
if ( input == null ) {
return null;
}
String name = null;
TaskGroupEntityJPA taskGroupEntityJPA = new TaskGroupEntityJPA( name );
context.put( input, taskGroupEntityJPA );
beforeMapping( input, taskGroupEntityJPA, context );
afterMapping( input, taskGroupEntityJPA, context );
return taskGroupEntityJPA;
}
How can I make sure that my object factory methods are not ignored any longer?
The reason why the object factory method is not taken into account is not because it is a default
method in an interface. Rather, the ReferenceCycleTracking
parameter is missing the annotation @Context
in my example.