How to transform entities extending a generic class to another entity extending another generic class

user2087103 :

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?

Victor Gubin :

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]

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=158577&siteId=1