2

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?

Adnan Sharif
  • 919
  • 7
  • 17
user2087103
  • 141
  • 2
  • 13
  • Do you have to do the transformation for all (hundreds) of Entities or only for some? – RealSkeptic Mar 26 '19 at 18:03
  • Only for some. May be all of them in the future, but not thinking about that at the moment. – user2087103 Mar 26 '19 at 18:08
  • Bean Introspection ? – Victor Gubin Mar 26 '19 at 18:09
  • Could you provide an example of that, Victor? I'm not quite sure of understanding you, but if I have... how would i know which K extending GenericDTO applies to each T extending AbstractEntity? – user2087103 Mar 26 '19 at 18:12
  • Well, how about defining that method that you wanted - but not as abstract, but with a default implementation (maybe throwing an exception, or returning an empty `Optional`), and overriding it only in the entities you need? – RealSkeptic Mar 26 '19 at 18:29

1 Answers1

1

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]
Victor Gubin
  • 2,782
  • 10
  • 24
  • Well this seems exactly what i was looking for. Let me try it first, but thank you so much for your effort! – user2087103 Mar 27 '19 at 08:54
  • 1
    I think I will finally opt between ModelMapper and MapStruct, but many thanks for the guidance, I wasn't aware all these frameworks did exist at all! – user2087103 Mar 27 '19 at 09:45