org.debux.webmotion.jpa.GenericDAO.java Source code

Java tutorial

Introduction

Here is the source code for org.debux.webmotion.jpa.GenericDAO.java

Source

/*
 * #%L
 * WebMotion extra jpa
 * 
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 - 2015 Debux
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */
package org.debux.webmotion.jpa;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.persistence.EntityManager;
import javax.persistence.MapKey;
import javax.persistence.Parameter;
import javax.persistence.Query;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang3.ArrayUtils;
import org.debux.webmotion.server.WebMotionException;

/**
 * The class is a generic DAO. It is used to CRUD operations on specific class.
 * The DAO use JPA specification. In oder to create a generic DAO on entity, you
 * must extends @see IdentifiableEntity.
 * 
 * @author julien
 */
public class GenericDAO {

    /** Utility on bean */
    protected BeanUtilsBean beanUtil;

    /** Utility to convert value */
    protected ConvertUtilsBean convertUtils;

    /** Utility on property */
    protected PropertyUtilsBean propertyUtils;

    /** Current entity manager */
    protected EntityManager manager;

    /** Current entity class do crud */
    protected Class<? extends IdentifiableEntity> entityClass;

    /**
     * Constructor with direct entity class.
     * 
     * @param manager entity manager
     * @param entityClass entity class
     */
    public GenericDAO(EntityManager manager, Class<? extends IdentifiableEntity> entityClass) {
        this.manager = manager;
        this.entityClass = entityClass;

        this.beanUtil = BeanUtilsBean.getInstance();
        this.convertUtils = beanUtil.getConvertUtils();
        this.propertyUtils = beanUtil.getPropertyUtils();
    }

    /**
     * Constructor with entity class as string.
     * 
     * @param manager entity manager
     * @param entityName entity class as string
     */
    public GenericDAO(EntityManager manager, String entityName) {
        this.manager = manager;
        try {
            this.entityClass = (Class<? extends IdentifiableEntity>) Class.forName(entityName);
        } catch (ClassNotFoundException cnfe) {
            throw new WebMotionException("Invalid class name", cnfe);
        }

        this.beanUtil = BeanUtilsBean.getInstance();
        this.convertUtils = beanUtil.getConvertUtils();
        this.propertyUtils = beanUtil.getPropertyUtils();
    }

    /**
     * Helper to create dynamic parameter for the GenericDAO. Thus you can pass 
     * parameters easily from the http request or from code.<br/>
     * 
     * By default the parameters come from the http request, which are 
     * represent with a Map. The key represents the name of parameter and the 
     * object array represent the values.
     */
    public static class Parameters {

        /** All values */
        protected Map<String, Object[]> parameters;

        /**
         * Constructor by default
         */
        public Parameters() {
            this(new HashMap<String, Object[]>());
        }

        /**
         * Constructor with the parameters.
         * 
         * @param parameters parameters
         */
        public Parameters(Map<String, Object[]> parameters) {
            this.parameters = parameters;
        }

        /**
         * @return empty Parameters object
         */
        public static Parameters create() {
            return new Parameters();
        }

        /**
         * @return Parameters object the parameters
         */
        public static Parameters create(Map values) {
            return new Parameters(values);
        }

        /**
         * Add all parameters.
         * 
         * @param parameters parameters to add
         * @return Parameters object
         */
        public Parameters addAll(Map parameters) {
            this.parameters.putAll(parameters);
            return this;
        }

        /**
         * Add one parameter with all values.
         * 
         * @param name parameter name
         * @param values parameter values
         * @return Parameters object
         */
        public Parameters add(String name, Object[] values) {
            this.parameters.put(name, values);
            return this;
        }

        /**
         * Add one parameter with null value.
         * 
         * @param name parameter name
         * @return Parameters object
         */
        public Parameters add(String name) {
            this.parameters.put(name, new Object[] {});
            return this;
        }

        /**
         * Add one parameter to complete with a value.
         * 
         * @param name parameter name
         * @param value parameter value
         * @return Parameters object
         */
        public Parameters add(String name, Object value) {
            Object[] values = this.parameters.get(name);
            if (values == null) {
                values = new Object[] { value };

            } else {
                values = ArrayUtils.add(values, value);
            }

            this.parameters.put(name, values);
            return this;
        }

        /**
         * @return all parameters
         */
        public Map<String, Object[]> getParameters() {
            return parameters;
        }

        /**
         * @param name parameter name
         * @return parameter values
         */
        public Object[] get(String name) {
            return parameters.get(name);
        }
    }

    /**
     * Create an entity with parameters.
     * 
     * @param parameters parameters
     * @return entity created.
     */
    public IdentifiableEntity create(Parameters parameters) {
        IdentifiableEntity entity = extract(parameters);
        manager.persist(entity);
        return entity;
    }

    /**
     * Update an entity with parameters, that is identified by this id.
     * 
     * @param id identifier
     * @param parameters parameters
     * @return entity updated
     */
    public IdentifiableEntity update(String id, Parameters parameters) {
        IdentifiableEntity entity = manager.find(entityClass, id);
        if (entity != null) {
            entity = extract(entity, parameters);
        }
        return entity;
    }

    /**
     * Delete an entity, that is identified by this id.
     * 
     * @param id identifier
     * @return true if is deleted else false if the entity was not found.
     */
    public boolean delete(String id) {
        IdentifiableEntity entity = manager.find(entityClass, id);
        if (entity != null) {
            manager.remove(entity);
            return true;
        }
        return false;
    }

    /**
     * Find entity by the id.
     * 
     * @param id identifier
     * @return 
     */
    public IdentifiableEntity find(String id) {
        IdentifiableEntity entity = manager.find(entityClass, id);
        return entity;
    }

    /**
     * Execute a query named as read in entity.
     * 
     * @param name query name
     * @param parameters parameters
     * @return outturn
     */
    public List query(String name, Parameters parameters) {
        Query query = manager.createNamedQuery(name);
        extract(query, parameters);
        List list = query.getResultList();
        return list;
    }

    /**
     * Execute a query named as write in entity.
     * 
     * @param name query name
     * @param parameters parameters
     * @return the number of entities updated or deleted 
     */
    public int exec(String name, Parameters parameters) {
        Query query = manager.createNamedQuery(name);
        extract(query, parameters);
        int executeUpdate = query.executeUpdate();
        return executeUpdate;
    }

    /**
     * Set parameter in the query.
     * 
     * @param query query
     * @param parameters parameters
     */
    protected void extract(Query query, Parameters parameters) {
        Set<Parameter<?>> queryParameters = query.getParameters();
        for (Parameter<?> parameter : queryParameters) {
            String parameterName = parameter.getName();
            Object[] values = parameters.get(parameterName);

            List<Object> converted = Arrays.asList(values);
            query.setParameter(parameterName, converted);
        }
    }

    /**
     * Create a new entity with parameters.
     * 
     * @param parameters parameters
     * @return entity with parameters
     */
    protected IdentifiableEntity extract(Parameters parameters) {
        try {
            IdentifiableEntity entity = entityClass.newInstance();
            return extract(entity, parameters);

        } catch (IllegalAccessException iae) {
            throw new WebMotionException("Error during create instance", iae);
        } catch (InstantiationException ie) {
            throw new WebMotionException("Not default constructor", ie);
        }
    }

    /**
     * Complete entity with parameters. Try to convert parameter, if the type not 
     * match. To create an association on entity, you must pass only identifier.
     * 
     * @param entity entity
     * @param parameters parameters
     * @return entity completed
     */
    protected IdentifiableEntity extract(IdentifiableEntity entity, Parameters parameters) {
        try {
            Field[] fields = entityClass.getDeclaredFields();
            for (Field field : fields) {

                String name = field.getName();
                Class<?> type = field.getType();
                Object[] values = parameters.get(name);

                // The identifier can't be set
                if (!IdentifiableEntity.ATTRIBUTE_NAME_ID.equals(name) && values != null) {

                    if (values.length == 0) {
                        // Null value
                        beanUtil.setProperty(entity, name, null);

                    } else if (type.isAnnotationPresent(javax.persistence.Entity.class)) {
                        // Association
                        List<Object> references = new ArrayList<Object>(values.length);
                        for (Object value : values) {
                            Object reference = manager.find(type, value);
                            if (reference != null) {
                                references.add(reference);
                            }
                        }

                        Object converted = null;
                        if (List.class.isAssignableFrom(type)) {
                            converted = references;

                        } else if (Set.class.isAssignableFrom(type)) {
                            converted = new HashSet<Object>(references);

                        } else if (SortedSet.class.isAssignableFrom(type)) {
                            converted = new TreeSet<Object>(references);

                        } else if (type.isArray()) {
                            converted = references.toArray();

                        } else if (Map.class.isAssignableFrom(type)) {
                            String keyName = IdentifiableEntity.ATTRIBUTE_NAME_ID;
                            MapKey annotation = type.getAnnotation(MapKey.class);
                            if (annotation != null) {
                                String annotationName = annotation.name();
                                if (annotationName != null && !annotationName.isEmpty()) {
                                    keyName = annotationName;
                                }
                            }

                            Map<Object, Object> map = new HashMap<Object, Object>();
                            for (Object object : references) {
                                Object key = propertyUtils.getProperty(object, keyName);
                                map.put(key, object);
                            }
                            converted = map;

                        } else if (!references.isEmpty()) {
                            converted = references.get(0);
                        }

                        beanUtil.setProperty(entity, name, converted);

                    } else {
                        // Basic object
                        if (Collection.class.isAssignableFrom(type)) {
                            Class convertType = String.class;
                            Type genericType = field.getGenericType();
                            if (genericType != null && genericType instanceof ParameterizedType) {
                                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                                convertType = (Class) parameterizedType.getActualTypeArguments()[0];
                            }

                            Collection<Object> collection = null;
                            if (Set.class.isAssignableFrom(type)) {
                                collection = new HashSet<Object>();
                            } else if (SortedSet.class.isAssignableFrom(type)) {
                                collection = new TreeSet();
                            } else {
                                collection = new ArrayList<Object>();
                            }

                            for (Object object : values) {
                                Object convertedObject = convertUtils.convert(object, convertType);
                                collection.add(convertedObject);
                            }

                            beanUtil.setProperty(entity, name, collection);

                        } else if (Map.class.isAssignableFrom(type)) {
                            throw new UnsupportedOperationException(
                                    "Map is not supported, you must create a specific entity.");

                        } else {
                            Object converted = convertUtils.convert(values, type);
                            beanUtil.setProperty(entity, name, converted);
                        }
                    }
                }
            }

            return entity;

        } catch (IllegalAccessException iae) {
            throw new WebMotionException("Error during create instance", iae);
        } catch (InvocationTargetException ite) {
            throw new WebMotionException("Error during set field on instance", ite);
        } catch (NoSuchMethodException nsme) {
            throw new WebMotionException("Error during set field on instance", nsme);
        }
    }

}