I have several DTOResponse class, each one of them extending MiniDTOResponse.
@Data
@NoArgsConstructor
public class MiniDTOResponse extends EmptyDTOResponse {
private boolean exists;
}
@Data
public class ServicesDTOResponse extends MiniDTOResponse {
private boolean light;
private ToiletDTOResponse toilets;
private int barCounterNumber;
}
@Data
public class ToiletDTOResponse extends MiniDTOResponse {
private int numMaleRestrooms;
private int numFemaleRestrooms;
}
@Data
public class VIPDTOResponse extends MiniDTOResponse {
private int numVIPSeats;
private boolean isAccesible;
}
and so on. There are also bigger DTOs, with Ids and dates of deletion, such as
@Data
@EqualsAndHashCode(callSuper = true)
public class BasicDTOResponse extends BasicDTO {
private Date dateDeletion;
private String userDeletion;
}
@Data
public class AttendanceDTOResponse extends BasicDTOResponse {
private int numStanding;
private int numSeats;
private ServicesDTOResponse services;
private VIPDTOResponse vip;
}
and different Mapstruct mappers for each DTO.
My goal is to return something like this, assuming there are services and toilets but no vip
{
"id": "39F47F8B83F5653CE0631F1EA8C0D44F",
"dateDeletion": null,
"userDeletion": null,
"numStanding": 30000,
"numSeats": 20000,
"services": {
"exists": true,
"light": false,
"toilets": {
"exists": true,
"numMaleRestrooms" : 5,
"numFemaleRestrooms" : 6
},
"barCounterNumber": 6,
},
"vip": {
"exists": false,
"numVIPSeats": 0,
"isAccesible": false,
}
}
so i created this
@Mapper(componentModel = "spring")
public interface GenericMapper {
default boolean exists(String id) {
return StringUtils.isNotBlank(id);
}
default boolean active(ZonedDateTime dateDeletion) {
return dateDeletion == null;
}
default boolean computeExists(BaseEntity entity) {
return entity != null && exists(entity.getId()) && active(entity.getFechaBaja());
}
@AfterMapping
default <K extends BaseEntity, T extends MiniDTOResponse> void setExists(@MappingTarget T dto, K entity) {
boolean exists = computeExists(entity);
dto.setExists(hay);
}
@ObjectFactory
default <K extends BaseEntity, T extends MiniDTOResponse> T createDto(K source, @TargetType Class<T> dtoClass) {
try {
T dto = dtoClass.getDeclaredConstructor().newInstance();
setExists(dto, source);
return dto;
} catch (Exception e) {
throw new RuntimeException("Couldn't create DTO", e);
}
}
default <T extends MiniDTOResponse> T createDTOwithExitsFalse(Class<T> dtoClass) {
try {
T dto = dtoClass.getDeclaredConstructor().newInstance();
dto.setExists(false);
return dto;
} catch (Exception e) {
throw new RuntimeException("Couldn't create DTO", e);
}
}
}
public interface AttendanceMapper extends GenericMapper {
@Mapping(target = "numStanding", source = "standing")
@Mapping(target = "numSeats", source = "seats")
@Mapping(target = "services", source = "installations", defaultExpression = "java(createDTOwithExitsFalse(ServicesDTOResponse.class))")
@Mapping(target = "vip", source= "vipFacilities", defaultExpression = "java(createDTOwithExitsFalse(VIPDTOResponse.class))")
AttendanceDTOResponse toDto (Attendance source);
}
and works like a charm without having to write lots of defaults on every Mapper.
My problem is that its not working with Toilets.
If I declare that AttendanceMapper uses ToiletsMapper (what is expected) and both of them (ToiletsMapper and AttendanceMapper) extends GenericMapper, i get a
Ambiguous factory methods found for creating ServicesDTOResponse: T createDto(K source, @TargetType Class<T> dtoClass), T ToiletMapper.createDto(K source, @TargetType Class<T> dtoClass). See https://mapstruct.org/faq/#ambiguous for more info.
as both of them are extending the GenericMapper.
What can I do? I'd love to do it in ageneric way, as there are like 40 DTORespones. Maybe there is a cleaner way of resolving the exists and the default of every attribute without using expressions, I'm willing to know how to do it!
Instead of extending from other mapper import them.
@Mapper( uses = { GenericMapper.class } )
public interface AttendanceMapper {
}