py.una.pol.karaku.dao.util.MainInstanceHelper.java Source code

Java tutorial

Introduction

Here is the source code for py.una.pol.karaku.dao.util.MainInstanceHelper.java

Source

/*-
 * Copyright (c)
 *
 *       2012-2014, Facultad Politcnica, Universidad Nacional de Asuncin.
 *       2012-2014, Facultad de Ciencias Mdicas, Universidad Nacional de Asuncin.
 *       2012-2013, Centro Nacional de Computacin, Universidad Nacional de Asuncin.
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
package py.una.pol.karaku.dao.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javax.annotation.Nonnull;
import javax.persistence.FetchType;
import javax.persistence.NonUniqueResultException;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinType;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import py.una.pol.karaku.dao.annotations.MainInstance;
import py.una.pol.karaku.dao.restrictions.Where;
import py.una.pol.karaku.exception.KarakuException;
import py.una.pol.karaku.log.Log;
import py.una.pol.karaku.util.ListHelper;

/**
 * 
 * Clase que se encarga de crear los proxies para manejar las anotaciones
 * MainInstance, adems en caso de que el {@link FetchType} sea
 * {@link FetchType#EAGER} modifica la consulta para que los resultados sena
 * Trados y luego los parsea para agregar al objeto.
 * 
 * @author Arturo Volpe Torres
 * @since 1.0
 * @version 1.0 Feb 13, 2013
 * 
 */
@Component
@SuppressWarnings("unchecked")
public class MainInstanceHelper {

    @Log
    private Logger log;

    static Object fetchAttribute(final Session session, final String hql, final MainInstance principal,
            final Object parent) {

        if (!session.isOpen()) {
            throw new org.hibernate.LazyInitializationException(
                    "Session is closed, failed to load Main instance " + principal.path());
        }
        Query query = session.createQuery(hql);
        query.setMaxResults(2);
        query.setParameter("value", principal.value());
        query.setParameter("mainEntity", parent);
        List<Object> list = query.list();
        if (list == null || list.isEmpty()) {
            return null;
        }
        if (list.size() > 1) {
            throw new NonUniqueResultException("Attribute " + principal.attribute() + " has more than 1 principal");
        }
        return list.get(0);
    }

    /**
     * Genera un HQL para recuperar el atributo principal de una entidad, el hql
     * es de la forma: <br>
     * {@code "select p from ENTITY o join o.ATRIBUTO p where p.PATH like PRINCIPAL"}
     * <br>
     * <i>Notese que no se limita la cantidad de resultados, esto es para
     * realizar otros controles, como que no se lanzen excepciones cuando hay
     * mas de un atributo principal</i>
     * 
     * @param entity
     *            Entidad raiz
     * @param principal
     *            Anotacion para obtener parametros
     * @return hql preparado para traer el elemento
     */
    static String generateHQL(final Object entity, final MainInstance principal) {

        String root = entity.getClass().getSimpleName();
        StringBuilder sb = new StringBuilder("select p ");
        sb.append("from ").append(root).append(" o ");
        sb.append("join o.").append(principal.attribute()).append(" p ");
        sb.append("where p.").append(principal.path()).append(" like :value");
        sb.append("and o like :mainEntity");
        return sb.toString();
    }

    /**
     * Mtodo principal que maneja la creacin de instancias principales, se
     * encarga de ejecutar todas las consultas del dao, adems crea Proxies
     * Automticos cuando es detectada una anotacin {@link MainInstance} que
     * tenga como {@link FetchType}, {@link FetchType#LAZY} para que se carge
     * dinmicamente mas tarde, el proxy funciona mientras la session este
     * Abierta.
     * <p>
     * Cuando el {@link Where} tiene la propiedad {@link Where#isDistinct()},
     * depende exclusivamente de la capacidad del mtodo {@link T#hashCode()}
     * para realizar su propsito de retornar elementos no duplicados.
     * </p>
     * 
     * 
     * @param session
     *            Session, a la cual se ata el proxy, mientras este viva el
     *            proxy funciona
     * @param criteria
     *            Criteria para filtrar los resultados de la consulta
     * @param clase
     *            Clase de lo esperado por la consulta
     * @return Lista de elementos ya configurada.
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws Exception
     */
    @Nonnull
    public <T> List<T> configureAndReturnList(@Nonnull final Session session, final Criteria criteria,
            final Class<T> clase, final Map<String, String> alias, final Where<T> where)
            throws IllegalAccessException {

        List<Field> fields = MainInstanceFieldHelper.getMainInstanceFields(clase);
        List<T> aRet;

        if (where != null && where.isDistinct()) {
            criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        }

        if (ListHelper.hasElements(fields)) {
            aRet = applyMainInstance(criteria, alias, where, fields);
        } else {
            aRet = criteria.list();
        }

        if (aRet == null) {
            return new ArrayList<T>(0);
        }

        try {
            helpList(aRet, session);
        } catch (Exception e) {
            log.error("Imposible crear proxies para principales", e);
        }
        return aRet;
    }

    /**
     * @param criteria
     * @param alias
     * @param where
     * @param fields
     * @return
     * @throws IllegalAccessException
     */
    @SuppressWarnings({ "rawtypes" })
    private <T> List<T> applyMainInstance(final Criteria criteria, final Map<String, String> alias,
            final Where<T> where, List<Field> fields) throws IllegalAccessException {

        List<T> aRet;
        Collection<T> toBuild;
        if (where != null && where.isDistinct()) {
            toBuild = new LinkedHashSet<T>();
        } else {
            toBuild = new ArrayList<T>();
        }

        configureCriteria(criteria, fields, alias);
        List list = criteria.list();
        Iterator i = list.iterator();
        while (i.hasNext()) {
            Map m = (Map) i.next();
            T entity = (T) m.get(Criteria.ROOT_ALIAS);
            toBuild.add(entity);
            for (Field f : fields) {
                MainInstance mi = f.getAnnotation(MainInstance.class);
                Object o = m.get(generateAlias(mi));
                f.setAccessible(true);
                f.set(entity, o);
                f.setAccessible(false);
            }
        }
        aRet = new ArrayList<T>(toBuild);
        return aRet;
    }

    /**
     * 
     * @param criteria
     * @param fields
     *            lista de fields que tienen la anotacin {@link MainInstance},
     *            solo atiende a los fields que son eager
     * @return mapa de alias
     */

    private Map<String, String> configureCriteria(final Criteria criteria, final List<Field> fields,
            final Map<String, String> alias) {

        for (Field f : fields) {

            MainInstance mi = f.getAnnotation(MainInstance.class);
            if (mi.fetch().equals(FetchType.LAZY)) {
                continue;
            }
            String newAlias = alias.get(mi.attribute());
            if (newAlias == null) {
                newAlias = generateAlias(mi);

                criteria.createCriteria(mi.attribute(), newAlias, JoinType.LEFT_OUTER_JOIN).add(
                        Restrictions.or(Restrictions.like(mi.path(), mi.value()), Restrictions.isNull(mi.path())));
                alias.put(f.getName(), newAlias);
            } else {
                criteria.add(Restrictions.or(Restrictions.like(newAlias + "." + mi.path(), mi.value()),
                        Restrictions.isNull(newAlias + "." + mi.path())));
            }

        }
        criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
        return alias;
    }

    private String generateAlias(final MainInstance mi) {

        return mi.attribute() + "_";
    }

    /**
     * Dado <b>un</b> objeto, agrega el proxy que tiene sentido mientras dure la
     * session
     * 
     * @param entity
     *            entidad a aplicar los proxies
     * @param session
     *            Session hibernate durante la cual tendr sentido el proxy
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     */
    public void help(final Object entity, final Session session) throws KarakuException {

        if (entity == null) {
            return;
        }

        try {
            for (Field f : MainInstanceFieldHelper.getMainInstanceFields(entity.getClass())) {
                MainInstance principal = f.getAnnotation(MainInstance.class);
                f.setAccessible(true);
                ReflectionUtils.setField(f, entity, newInstance(entity, session, principal, f.getType()));
            }
        } catch (Exception exception) {
            throw new KarakuException(exception);
        }
    }

    /**
     * Mtodo que agrega los proxy's para el {@link FetchType#LAZY}, para
     * agregar otro tipo de fetch utilice
     * {@link MainInstanceHelper#configureAndReturnList(Session, Criteria, Class)}
     * 
     * @param entities
     * @param session
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws Exception
     */
    public void helpList(@Nonnull final List<?> entities, @Nonnull final Session session) throws KarakuException {

        if (!ListHelper.hasElements(entities)) {
            return;
        }
        try {
            Class<?> clazz = entities.get(0).getClass();
            for (Field f : MainInstanceFieldHelper.getMainInstanceFields(clazz)) {
                MainInstance principal = f.getAnnotation(MainInstance.class);
                f.setAccessible(true);
                for (Object object : entities) {
                    if (principal.fetch() == FetchType.LAZY) {
                        f.set(object, newInstance(object, session, principal, f.getType()));
                    }
                }
            }
        } catch (Exception exception) {
            throw new KarakuException(exception);
        }
    }

    private <T> T newInstance(final Object entity, final Session em, final MainInstance principal,
            final Class<T> fieldType) throws NoSuchMethodException, InstantiationException, IllegalAccessException,
            InvocationTargetException {

        ProxyFactory factory = new ProxyFactory();
        factory.setSuperclass(fieldType);
        MethodHandler handler = new MainInstanceMethodHandler<T>(em, principal, entity, fieldType);

        return (T) factory.create(new Class<?>[0], new Object[0], handler);
    }
}