I'm developing a service oriented platform for retrieving, creating and updating entities from DB. The point here is that every single java entity extends AbstractEntity
, so for example, I have,
MyCar extends AbstractEntity implements Serializable
This AbstractEntity
has common fields (such as id or audit ones). So I have already developed a GenericReadService
that, receiving a classname and a parameterMap, can read any entity and creates a EntityActionResult<T>
including a List<T extends AbstractEntity>
.
My problem comes when trying to transform that T
type into something like <K extends GenericDTO>
, as the client asking doesn't know AbstractEntity
(obvious) but only GenericDTO
. Doing this for safety and modularization.
So, now, I'm stuck on transforming the ListResponse<T>
to a ReturnList<K extends GenericDTO>
, as I don't find the way for knowing which K
class should apply for each T
.
This is what I actually have written:
private GenericEntityActionResult transform (EntityActionResult result) {
AsnGenericEntityActionResult r = new AsnGenericEntityActionResult();
if (result.getCode() == EntityActionResult.Code.ENTITY || result.getCode() == EntityActionResult.Code.ENTITY_LIST ) {
r.setCode(AsnGenericEntityActionResult.Code.OK);
List <? extends GenericDTO> list = new ArrayList<>();
if (result.getEntity() != null) {
//transform this entity into DTO
//<b>SOMETHING LIKE list.add(result.getEntity());</b>
} else if (result.getList() != null && !result.getList().isEmpty()) {
for (AbstractEntity a:result.getList()) {
//transform this entity into DTO
//<b>SOMETHING LIKE list.add(result.getEntity());</b>
//list.add(result.getEntity());
}
}
r.setList(list);
}
return r;
I´m obviously stuck on the SOMETHING LIKE, but can't find a proper way of doing so.
I thought about creating a abstract <T extends GenericDTO> transformToDTO()
method on AbstractEntity
, but can't do it since there are lots (and i mean hundreds) of Entities extending AbstractEntity
and this client-service approach is a new development for some Entities, not the whole system.
Any clever ideas?
You can try to use the Java Introspection API or some more robust library on top of this API like apache commons beanutils or even more powerful bean mapping library like Dozer or something newer see
Following example demonstrating the basic technique, only raw introspection and reflection with two compatible POJO beans.
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
class Tranformation {
public static void main(String[] args) {
MyCar car = new MyCar();
car.setId("id01");
car.setName("Komodo");
car.setMadeIn("Jakarta");
CarDTO dto = toDto(CarDTO.class, car);
System.out.println(dto);
}
public <E extends AbstractEntity, D extends GenericDTO> D toDto(Class<D> dtoClass, E entity) {
if (null == entity) {
throw new NullPointerException("Entity can not be null");
}
try {
final D ret = dtoClass.newInstance();
BeanInfo dtoBeanInfo = Introspector.getBeanInfo(dtoClass);
final Map<String, PropertyDescriptor> mapping = Arrays.stream(dtoBeanInfo.getPropertyDescriptors())
.collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));
final BeanInfo entityBeanInfo = Introspector.getBeanInfo(entity.getClass());
Arrays.stream(entityBeanInfo.getPropertyDescriptors()).forEach(src -> {
if (!"class".equals(src.getName())) {
PropertyDescriptor dst = mapping.get(src.getName());
if (null != dst) {
try {
dst.getWriteMethod().invoke(ret, src.getReadMethod().invoke(entity, null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
});
return ret;
} catch (InstantiationException | IntrospectionException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
public static class GenericDTO {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class CarDTO extends GenericDTO {
private String madeIn;
public String getMadeIn() {
return madeIn;
}
public void setMadeIn(String madeIn) {
this.madeIn = madeIn;
}
@Override
public String toString() {
return String.format("CarDTO [id=%s, name=%s, madeIn=%s]", getId(), getName(), madeIn);
}
}
public static class AbstractEntity implements Serializable {
private static final long serialVersionUID = 70377433289079231L;
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class MyCar extends AbstractEntity {
private static final long serialVersionUID = 8702011501211036271L;
private String madeIn;
public String getMadeIn() {
return madeIn;
}
public void setMadeIn(String madeIn) {
this.madeIn = madeIn;
}
}
}
Outputs:
CarDTO [id=id01, name=Komodo, madeIn=Jakarta]