javagenericsmapstructobjectfactory

How to make sure that MapStruct uses method annotated with @ObjectFactory


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?


Solution

  • 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.