Im working on a Springboot App exposing some endpoints that do not return the original Entities but they return their DTO. To map all the entities i am working using "org.modelmapper" version: 0.7.4 and project lombok to avoid implement getters, setters and some another utilities.
Everything worked perfectly until I had to map a list of subclasses on a entity. The source entity is like this (i removed some properties and hibernate annotations because they are not necessary in the example):
package com.pfi.repository.entity.sport;
import com.pfi.repository.entity.address.Address;
import lombok.*;
import java.util.List;
@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class SportPlace {
private Long id;
private List<AbstractSportField> sportFields;
}
The destination DTO is:
package dto.sport;
import lombok.*;
import java.util.List;
@Getter
@NoArgsConstructor
@Setter
public class SportPlaceDTO {
private Long id;
private List<AbstractSportFieldDTO> sportFields;
}
Both AbstractSportFieldDTO and AbstractSportField are abstract classes that with two possible subclasses:
package dto.sport;
import dto.reserve.AbstractReserveDTO;
import java.util.List;
public abstract class AbstractSportFieldDTO {
protected Long id;
protected List<AbstractReserveDTO> reserves;
public AbstractSportFieldDTO() {
}
public AbstractSportFieldDTO(Long id, List<AbstractReserveDTO> reserves) {
this.id = id;
this.reserves = reserves;
}
public Long getId() {
return id;
}
public AbstractSportFieldDTO setId(Long id) {
this.id = id;
return this;
}
public List<AbstractReserveDTO> getReserves() {
return reserves;
}
public AbstractSportFieldDTO setReserves(List<AbstractReserveDTO> reserves) {
this.reserves = reserves;
return this;
}
}
The first DTO subclass is:
package dto.sport;
import dto.reserve.AbstractReserveDTO;
import java.util.List;
public class ComboSportFieldDTO extends AbstractSportFieldDTO {
private List<SportFieldDTO> sportFields;
public ComboSportFieldDTO(List<SportFieldDTO> sportFields) {
this.sportFields = sportFields;
}
public ComboSportFieldDTO(Long id, List<AbstractReserveDTO> reserves, List<SportFieldDTO> sportFields) {
super(id, reserves);
this.sportFields = sportFields;
}
public ComboSportFieldDTO() {
super();
}
public List<SportFieldDTO> getSportFields() {
return sportFields;
}
public void setSportFields(List<SportFieldDTO> sportFields) {
this.sportFields = sportFields;
}
}
The second DTO subclass is:
package dto.sport;
import dto.reserve.AbstractReserveDTO;
import java.util.List;
public class SportFieldDTO extends AbstractSportFieldDTO {
private Boolean joineable;
public SportFieldDTO(Long id, List<AbstractReserveDTO> reserves, Boolean joineable) {
super(id, reserves);
this.joineable = joineable;
}
public SportFieldDTO() {
super();
}
public Boolean getJoineable() {
return joineable;
}
public void setJoineable(Boolean joineable) {
this.joineable = joineable;
}
}
Then the Entities are:
package com.pfi.repository.entity.sport;
import com.pfi.repository.entity.reserve.AbstractReserve;
import java.util.List;
public abstract class AbstractSportField {
protected Long id;
protected List<AbstractReserve> reserves;
public AbstractSportField(Long id, List<AbstractReserve> reserves) {
this.id = id;
this.reserves = reserves;
}
public AbstractSportField() {
}
public Long getId() {
return id;
}
public AbstractSportField setId(Long id) {
this.id = id;
return this;
}
public List<AbstractReserve> getReserves() {
return reserves;
}
public AbstractSportField setReserves(List<AbstractReserve> reserves) {
this.reserves = reserves;
return this;
}
}
The first subclass is:
package com.pfi.repository.entity.sport;
import com.pfi.repository.entity.reserve.AbstractReserve;
import java.util.List;
public class ComboSportField extends AbstractSportField {
private List<SportField> sportFields;
public ComboSportField(Long id, List<AbstractReserve> reserves, List<SportField> sportFields) {
super(id, reserves);
this.sportFields = sportFields;
}
public ComboSportField(){
super();
}
public List<SportField> getSportFields() {
return sportFields;
}
public ComboSportField setSportFields(List<SportField> sportFields) {
this.sportFields = sportFields;
return this;
}
}
And the last subclass is:
package com.pfi.repository.entity.sport;
import com.pfi.repository.entity.reserve.AbstractReserve;
import javax.persistence.Entity;
import java.util.List;
public class SportField extends AbstractSportField {
private Boolean joineable;
public SportField(Long id, List<AbstractReserve> reserves, Boolean joineable) {
super(id, reserves);
this.joineable = joineable;
}
public SportField(){
super();
}
public Boolean getJoineable() {
return joineable;
}
public SportField setJoineable(Boolean joineable) {
this.joineable = joineable;
return this;
}
}
When i try to map an instance of SportPlace to SportPlaceDTO like this:
modelMapper.map(sportPlace, SportPlaceDTO.class)
That de sportPlace contains the given 6 Sportfields that are instances of subclasses. The model mapper throws (on the 6 elements of the list):
Failed to instantiate instance of destination dto.sport.AbstractSportFieldDTO. Ensure that dto.sport.AbstractSportFieldDTO has a non-private no-argument constructor.
Reading about how can i map subclasses on modelmapper i found setting TypeMap, that can set a specific mapping on a class like this:
modelMapper.createTypeMap(ComboSportField.class, ComboSportFieldDTO.class);
For that reason i created a builder to config the model mapper like this
package com.pfi.repository.builder;
import com.pfi.repository.entity.*;;
import dto.reserve.*;
import dto.sport.*;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.stereotype.Service;
@Service
public class MapperBuilder {
public ModelMapper buildModelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
mapEntitiesToDTO(modelMapper);
mapDTOToEntities(modelMapper);
return modelMapper;
}
private void mapEntitiesToDTO(final ModelMapper modelMapper){
modelMapper.createTypeMap(ComboSportField.class, ComboSportFieldDTO.class);
modelMapper.createTypeMap(SportField.class, SportFieldDTO.class);
modelMapper.createTypeMap(Reserve.class, ReserveDTO.class);
modelMapper.createTypeMap(AppReserve.class, AppReserveDTO.class);
}
private void mapDTOToEntities(final ModelMapper modelMapper){
modelMapper.createTypeMap(ComboSportFieldDTO.class, ComboSportField.class);
modelMapper.createTypeMap(SportFieldDTO.class, SportField.class);
modelMapper.createTypeMap(ReserveDTO.class, Reserve.class);
modelMapper.createTypeMap(AppReserveDTO.class, AppReserve.class);
}
}
And i use the builder when i create the controller like this:
@Autowired
protected MapperBuilder mapperBuilder;
BaseController(){
this.getLogger();
}
@PostConstruct
public void buildMapper(){
modelMapper = mapperBuilder.buildModelMapper();
}
I don't know what else I can do to be able map the subclasses.
Does anyone know how to solve this issue?
Thanks!