es.logongas.ix3.web.json.impl.JsonReaderImplEntityJackson.java Source code

Java tutorial

Introduction

Here is the source code for es.logongas.ix3.web.json.impl.JsonReaderImplEntityJackson.java

Source

/*
 * Copyright 2013 Lorenzo Gonzlez.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package es.logongas.ix3.web.json.impl;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.databind.module.SimpleModule;
import es.logongas.ix3.core.BusinessException;
import es.logongas.ix3.dao.DAOFactory;
import es.logongas.ix3.dao.DataSession;
import es.logongas.ix3.dao.GenericDAO;
import es.logongas.ix3.dao.metadata.CollectionType;
import es.logongas.ix3.dao.metadata.MetaData;
import es.logongas.ix3.dao.metadata.MetaDataFactory;
import es.logongas.ix3.service.CRUDService;
import es.logongas.ix3.service.CRUDServiceFactory;
import es.logongas.ix3.web.json.JsonReader;
import es.logongas.ix3.web.json.beanmapper.BeanMapper;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Se usa para una entidad de negocio y usa Jackson
 *
 * @author Lorenzo Gonzlez
 */
public class JsonReaderImplEntityJackson implements JsonReader {

    private final Class clazz;
    private final ObjectMapper objectMapper;
    @Autowired
    private CRUDServiceFactory crudServiceFactory;
    @Autowired
    private MetaDataFactory metaDataFactory;

    public JsonReaderImplEntityJackson(Class clazz) {
        this.clazz = clazz;
        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        SimpleModule module = new SimpleModule();
        module.addDeserializer(java.util.Date.class, new DateDeserializer());
        objectMapper.registerModule(module);
    }

    @Override
    public Object fromJson(String json, DataSession dataSession) {
        return fromJson(json, null, dataSession);
    }

    @Override
    public Object fromJson(String json, BeanMapper beanMapper, DataSession dataSession) {
        try {
            if (beanMapper == null) {
                beanMapper = new BeanMapper(clazz);
            }

            Object jsonObj = objectMapper.readValue(json, clazz);

            MetaData metaData = metaDataFactory.getMetaData(clazz);

            Object entity = readEntity(jsonObj, metaData, "", beanMapper, dataSession);

            return entity;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

    }

    /**
     * Crea una entidad completa en base a la clave primaria y a los datos que vienen desde JSON
     *
     * @param jsonObj Los datos JSON
     * @param metaData Los metadatos de la entidad a tranformar
     * @return El Objeto Entidad
     */
    private Object readEntity(Object jsonObj, MetaData metaData, String path, BeanMapper beanMapper,
            DataSession dataSession) {
        try {

            Object entity;

            CRUDService crudService = crudServiceFactory.getService(metaData.getType());

            if (emptyKey(getValueFromBean(jsonObj, metaData.getPrimaryKeyPropertyName())) == false) {
                //Si hay clave primaria es que hay que leerla entidad pq ya existe
                Serializable primaryKey = (Serializable) getValueFromBean(jsonObj,
                        metaData.getPrimaryKeyPropertyName());
                entity = crudService.read(dataSession, primaryKey);
                if (entity == null) {
                    throw new RuntimeException(
                            "No existe la entidad " + metaData.getType() + " con PK:" + primaryKey);
                }
            } else {
                //No hay clave primaria , as que creamos una nueva fila

                entity = crudService.create(dataSession, null);
            }

            populateEntity(entity, jsonObj, metaData, path, beanMapper, dataSession);

            return entity;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Lee una entidad de la base de datos en funcin de su clave primaria o de alguna de sus claves naturales
     *
     * @param propertyValue
     * @param propertyMetaData
     * @return
     */
    private Object readForeingEntity(Object propertyValue, MetaData propertyMetaData, DataSession dataSession) {
        try {
            if (propertyValue == null) {
                return null;
            }

            //Usamos un Set para guardar todas las entidades
            //Al ser un Set si son la misma se quedar solo una.
            Set entities = new HashSet();

            CRUDService crudService = crudServiceFactory.getService(propertyMetaData.getType());

            //Leer la entidad en funcin de su clave primaria
            String primaryKeyPropertyName = propertyMetaData.getPrimaryKeyPropertyName();
            Object primaryKey = getValueFromBean(propertyValue, primaryKeyPropertyName);
            if (primaryKey != null) {
                Object entity = crudService.read(dataSession, (Serializable) primaryKey);
                if (entity != null) {
                    entities.add(entity);
                }
            }

            //Leer la entidad en funcin de cada una de sus claves primarias
            for (String naturalKeyPropertyName : propertyMetaData.getNaturalKeyPropertiesName()) {
                Object naturalKey = getValueFromBean(propertyValue, naturalKeyPropertyName);
                if (naturalKey != null) {
                    Object entity = crudService.readByNaturalKey(dataSession, (Serializable) naturalKey);
                    if (entity != null) {
                        entities.add(entity);
                    }
                }
            }

            //Si hay ms de un elemento es que hay conflictos entre
            //la clave primaria y las claves naturales pq identifican entidades distintas
            if (entities.size() > 1) {
                StringBuilder sb = new StringBuilder();
                sb.append(propertyMetaData.getPrimaryKeyPropertyName());
                sb.append(":");
                sb.append(getValueFromBean(propertyValue, propertyMetaData.getPrimaryKeyPropertyName()));
                sb.append(",");

                for (String naturalKeyPropertyName : propertyMetaData.getNaturalKeyPropertiesName()) {
                    sb.append(naturalKeyPropertyName);
                    sb.append(":");
                    sb.append(getValueFromBean(propertyValue, naturalKeyPropertyName));
                    sb.append(",");
                }

                throw new RuntimeException(
                        "El objeto JSON tiene clave primarias y claves naturales que referencian distintos objetos:"
                                + sb);
            }

            if (entities.size() == 1) {
                //Retornamos el nica elemento que se ha leido de la base de datos
                return entities.iterator().next();
            } else {
                //Si no hay nada retornamos null
                return null;
            }

        } catch (BusinessException be) {
            throw new RuntimeException(be);
        }
    }

    private void populateEntity(Object entity, Object jsonObj, MetaData metaData, String path,
            BeanMapper beanMapper, DataSession dataSession) throws BusinessException {

        if (jsonObj == null) {
            return;
        }

        if (metaData == null) {
            throw new IllegalArgumentException("El argumento 'metaData' no puede ser null");
        }

        for (String propertyName : metaData.getPropertiesMetaData().keySet()) {
            MetaData propertyMetaData = metaData.getPropertiesMetaData().get(propertyName);

            String absolutePropertyName;
            if ((path == null) || (path.trim().length() == 0)) {
                absolutePropertyName = propertyName;
            } else {
                absolutePropertyName = path + "." + propertyName;
            }

            if (beanMapper.isDeleteInProperty(absolutePropertyName) == true) {
                //No se puede generar esa propiedad desde el "exterior"
                continue;
            }

            if (propertyMetaData.isCollection() == false) {
                switch (propertyMetaData.getMetaType()) {
                case Scalar: {
                    Object rawValue = getValueFromBean(jsonObj, propertyName);
                    setValueToBean(entity, rawValue, propertyName);
                    break;
                }
                case Entity: {
                    //Debemos leer la referencia de la base de datos
                    Object rawValue = getValueFromBean(jsonObj, propertyName);

                    Object value = readForeingEntity(rawValue, propertyMetaData, dataSession);
                    setValueToBean(entity, value, propertyName);
                    break;
                }
                case Component: {
                    //Es un componente, as que hacemos la llamada recursiva
                    Object rawValue = getValueFromBean(jsonObj, propertyName);
                    Object component = getValueFromBean(entity, propertyName);
                    if (component == null) {
                        try {
                            component = propertyMetaData.getType().newInstance();
                            setValueToBean(entity, component, propertyName);
                        } catch (InstantiationException ex) {
                            throw new RuntimeException(ex);
                        } catch (IllegalAccessException ex) {
                            throw new RuntimeException(ex);
                        }
                    }
                    populateEntity(component, rawValue, propertyMetaData, absolutePropertyName, beanMapper,
                            dataSession);
                    break;
                }
                default:
                    throw new RuntimeException("El MetaTypo es desconocido:" + propertyMetaData.getMetaType());
                }
            } else {
                switch (propertyMetaData.getCollectionType()) {
                case List:
                case Set: {
                    if (beanMapper.isExpandInProperty(absolutePropertyName)) {
                        Collection rawCollection = (Collection) getValueFromBean(jsonObj, propertyName);
                        Collection currentCollection = (Collection) getValueFromBean(entity, propertyName);

                        //Borramos todos los elementos para aadir despues los que vienen desde JSON
                        if (currentCollection != null) {
                            currentCollection.clear();
                        } else {
                            //Si no hay coleccion hay que crearla aqui
                            if (propertyMetaData.getCollectionType() == CollectionType.List) {
                                currentCollection = new ArrayList();
                            } else if (propertyMetaData.getCollectionType() == CollectionType.Set) {
                                currentCollection = new HashSet();
                            } else {
                                throw new RuntimeException(
                                        "El tipo coneccion no es vlida:" + propertyMetaData.getCollectionType());
                            }
                            setValueToBean(entity, currentCollection, propertyName);
                        }
                        //Aadimos los elementos que vienen desde JSON
                        if (rawCollection != null) {
                            for (Object rawValue : rawCollection) {
                                Object value = readForeingEntity(rawValue, propertyMetaData, dataSession);
                                currentCollection.add(value);
                            }
                        }
                    } else {
                        //NO cargamos la coleccion desde el JSON
                    }
                    break;
                }
                case Map: {
                    if (beanMapper.isExpandInProperty(absolutePropertyName)) { //TODO:No cargamos nunca las coleccione pq aun no sabemos si hay que hacerlo o no
                        Map rawMap = (Map) getValueFromBean(jsonObj, propertyName);
                        Map currentMap = (Map) getValueFromBean(entity, propertyName);

                        //Borramos todos los elementos para aadir despues los que vienen desde JSON
                        if (currentMap != null) {
                            currentMap.clear();
                        } else {
                            //Si no hay coleccion hay que crearla qui
                            currentMap = new HashMap();
                            setValueToBean(entity, currentMap, propertyName);
                        }

                        //Aadimos los elementos que vienen desde JSON
                        if (rawMap != null) {
                            for (Object key : rawMap.keySet()) {
                                Object rawValue = rawMap.get(key);
                                Object value = readEntity(rawValue, propertyMetaData, absolutePropertyName,
                                        beanMapper, dataSession);
                                currentMap.put(key, value);
                            }
                        }
                    } else {
                        //NO cargamos la coleccion desde el JSON
                    }
                    break;
                }
                default:
                    throw new RuntimeException(
                            "El CollectionType es desconocido:" + propertyMetaData.getCollectionType());
                }
            }

        }
    }

    private boolean emptyKey(Object primaryKey) {
        if (primaryKey == null) {
            return true;
        }
        if (primaryKey instanceof Number) {
            Number number = (Number) primaryKey;
            if (number.longValue() == 0) {
                return true;
            }
        }

        if (primaryKey instanceof String) {
            String s = (String) primaryKey;
            if (s.trim().equals("")) {
                return true;
            }
        }

        return false;
    }

    /**
     * Obtiene el valor de la propiedad de un Bean
     *
     * @param obj El objeto Bean
     * @param propertyName El nombre de la propiedad
     * @return El valor de la propiedad
     */
    private Object getValueFromBean(Object obj, String propertyName) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

            Method readMethod = null;
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                if (propertyDescriptor.getName().equals(propertyName)) {
                    readMethod = propertyDescriptor.getReadMethod();
                }
            }

            if (readMethod == null) {
                throw new RuntimeException(
                        "No existe la propiedad:" + propertyName + " en " + obj.getClass().getName());
            }

            return readMethod.invoke(obj);

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Establece el valor de la propiedad de un Bean
     *
     * @param obj El objeto Bean
     * @param value El valor de la propiedad
     * @param propertyName El nombre de la propiedad
     */
    private void setValueToBean(Object obj, Object value, String propertyName) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

            Method writeMethod = null;
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                if (propertyDescriptor.getName().equals(propertyName)) {
                    writeMethod = propertyDescriptor.getWriteMethod();
                }
            }

            if (writeMethod == null) {
                throw new RuntimeException(
                        "No existe la propiedad:" + propertyName + " en " + obj.getClass().getName());
            }

            writeMethod.invoke(obj, value);

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}