Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MapStruct inheritance, more than one configuration prototype is application

Tags:

All my entities extends a base entity that has a Customer createdBy and String createdById field, and some more that work the same. I want to exclude the full Customer object when data is transferred around.

Some others entities extends another entity that itself extends the base entity, hence the following mappers :

@MapperConfig(componentModel = "spring", mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG)
public interface AuditingEntityMapper<Dto, Entity> {

    @Mapping(target = "createdBy", source = "createdById")
    @Mapping(target = "lastModifiedBy", source = "lastModifiedById")
    Dto toDto(Entity entity);

    @Mapping(target = "createdBy", ignore = true)
    @Mapping(target = "lastModifiedBy", ignore = true)
    Entity toEntity(Dto dto);
}


@MapperConfig(componentModel = "spring", mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG)
public interface ManageableEntityMapper<ManageableDtoType extends ManageableDTO, ManageableType extends Manageable> {

    ManageableDtoType toDto(ManageableType manageable);

    @Mapping(target = "project", ignore = true)
    ManageableType toEntity(ManageableDtoType dto);
}

Then I exploit those two mappers :

@Mapper(config = AuditingEntityMapper.class)
public interface ManageableStatusMapper {

    @Mapping(target = "project", ignore = true)
    @Mapping(target = "organization", ignore = true)
    ManageableStatus toEntity(ManageableStatusDTO manageableStatusDTO);

    default ManageableStatus fromId(String id) {
        if (id == null) {
            return null;
        }
        ManageableStatus manageableStatus = new ManageableStatus();
        manageableStatus.setId(id);
        return manageableStatus;
    }
}

// Returns this error and therefore others about mapping problems :
// More than one configuration prototype method is applicable. Use @InheritConfiguration to select one of them explicitly: java.lang.Object toDto(java.lang.Object entity), java.lang.Object toEntity(java.lang.Object dto).


@Mapper(config = ManageableEntityMapper.class)
public interface TaskMapper {

    @Mapping(target = "timeEntries", ignore = true)
    @Mapping(target = "assignments", ignore = true)
    Task toEntity(TaskDTO taskDTO);

    default Task fromId(String id) {
        if (id == null) {
            return null;
        }
        Task task = new Task();
        task.setId(id);
        return task;
    }
}
// Returns this error and one more of the same type :
// Can't map property "java.lang.String createdBy" to "com.acme.domain.Customer createdBy". Consider to declare/implement a mapping method: "com.acme.domain.Customer map(java.lang.String value)".

If my business mapper doesn't need extra mapping it works fine. Also, MapStruct seems to try to generate an implementation of both @MapperConfig annotated classes, which generates a lot of errors, but it's probably connected to all those classes connected to them.

How am I supposed to implement this behavior ? The doc about inheritance and/or shared configuration is very confusing to me.

like image 563
JesusTheHun Avatar asked Sep 09 '19 19:09

JesusTheHun


People also ask

What is MapStruct used for?

MapStruct is a code generator tool that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The generated mapping code uses plain method invocations and thus is fast, type-safe, and easy to understand.

How do you ignore property in MapStruct?

To do this, we use the MapStruct unmappedTargetPolicy to provide our desired behavior when there is no source field for the mapping: ERROR: any unmapped target property will fail the build – this can help us avoid accidentally unmapped fields. WARN: (default) warning messages during the build. IGNORE: no output or ...

Does MapStruct use reflection?

During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar.

How do I map a MapStruct list?

Using Mapstruct we can map list in similar fashion as we map primitives. To get a list of objects, we should provide a mapper method which can map an object.


1 Answers

This only works when ManageableStatus extends Entity and ManageableStatusDTO extends Dto.

The error message More than one configuration prototype method is applicable. Use @InheritConfiguration (where is it in on your example) means that you have to be explicit and possibly indicate the name of the method because more methods qualify.

so:

@Mapper(config = AuditingEntityMapper.class)
public interface ManageableStatusMapper {

    @InheritConfiguration // use this one to inherit stuf from the config
    @Mapping(target = "project", ignore = true)
    @Mapping(target = "organization", ignore = true)
    ManageableStatus toEntity(ManageableStatusDTO manageableStatusDTO);

    default ManageableStatus fromId(String id) {
        if (id == null) {
            return null;
        }
        ManageableStatus manageableStatus = new ManageableStatus();
        manageableStatus.setId(id);
        return manageableStatus;
    }
}

or.. if MapStruct has a conflict

@Mapper(config = AuditingEntityMapper.class)
public interface ManageableStatusMapper {

    @InheritConfiguration( name= "toDto" ) // really point to AuditingEntityMapper#toDto
    @Mapping(target = "project", ignore = true)
    @Mapping(target = "organization", ignore = true)
    ManageableStatus toEntity(ManageableStatusDTO manageableStatusDTO);

    default ManageableStatus fromId(String id) {
        if (id == null) {
            return null;
        }
        ManageableStatus manageableStatus = new ManageableStatus();
        manageableStatus.setId(id);
        return manageableStatus;
    }
}

or do it lazy

@Mapper(config = AuditingEntityMapper.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG )
public interface ManageableStatusMapper {

    // no explicit @InheritConfiguration required, because of AUTO_INHERIT_ALL_FROM_CONFIG
    @Mapping(target = "project", ignore = true)
    @Mapping(target = "organization", ignore = true)
    ManageableStatus toEntity(ManageableStatusDTO manageableStatusDTO);

    default ManageableStatus fromId(String id) {
        if (id == null) {
            return null;
        }
        ManageableStatus manageableStatus = new ManageableStatus();
        manageableStatus.setId(id);
        return manageableStatus;
    }
}

By the way: there's also @InheritInverseConfigiration

like image 116
Sjaak Avatar answered Sep 22 '22 17:09

Sjaak