py.una.pol.karaku.dao.search.SearchHelper.java Source code

Java tutorial

Introduction

Here is the source code for py.una.pol.karaku.dao.search.SearchHelper.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.search;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import py.una.pol.karaku.dao.restrictions.Where;
import py.una.pol.karaku.dao.where.Clause;
import py.una.pol.karaku.dao.where.Clauses;
import py.una.pol.karaku.dao.where.DateClauses;
import py.una.pol.karaku.dao.where.MatchMode;
import py.una.pol.karaku.exception.KarakuRuntimeException;
import py.una.pol.karaku.exception.NotDisplayNameException;
import py.una.pol.karaku.log.Log;

/**
 * Componente que sirve de ayuda al realizar bsquedas dinmicas sobre objetos.
 * Esta clase es til cuando la utilizamos en ambientes genricos, donde la
 * informacin de los objetos, atributos y sus relaciones no esta bien definida.
 * <p>
 * Utiliza reflexin y una nomenclatura de atributos separados por
 * <code>.</code> (similar al ELExpression) para navegar a travs de relaciones
 * y identificar los atributos especficos.
 * </p>
 * <p>
 * Utiliza la informacin del atributo para determinar cual es el mejor filtro a
 * aplicar en las distintas situaciones, los tipos soportados actualmente son:
 * <ul>
 * <li> {@link String}: utiliza una bsqueda por <i>like</i></li>
 * <li> {@link Number}: utiliza una bsqueda por <i>like</i>, todas las clases
 * que heredan de number se buscan bajo los mismos criterios.</li>
 * <li> {@link Date}: utiliza una bsqueda por rango, donde la primera fecha, es
 * la fecha pasada en formato
 * {@link py.una.pol.karaku.util.FormatProvider#DATE_FORMAT} o
 * {@link py.una.pol.karaku.util.FormatProvider#DATETIME_SHORT_FORMAT}, utilizando
 * el componente auxiliar {@link DateClauses}.</li>
 * <li>Si se detecta una {@link Collection} en el path a un atributo, se utiliza
 * un {@link py.una.pol.karaku.dao.where.ILike} como tcnica de mejor esfuerzo, no
 * se garantiza que el tipo de bsqueda es el correcto. Si la {@link Collection}
 * que se encuentra es una interfaz, entones <b>SI</b> se puede obtener su tipo
 * genrico.</li>
 * </ul>
 * </p>
 * 
 * @author Arturo Volpe
 * @since 1.0
 * @version 1.0 Sep 19, 2013
 * @see #getClause(Class, String, String)
 */
@Component
public class SearchHelper {

    @Log
    private Logger log;

    @Autowired
    private DateClauses dc;

    /**
     * Mtodo auxiliar a {@link #getClause(Class, String, String)} que crea un
     * nuevo {@link Where} y lo retorna con la {@link Clause} agregada.
     * 
     * @param root
     *            raz de la bsqueda el atributo, no debe ser <code>null</code>
     * @param property
     *            cadena separada por <code>.</code> (puntos) que sirve para
     *            recorrer a travs del objeto y obtener la propiedad deseada.
     * @param value
     *            valor con el cual se desea comparar, dependiendo de la
     *            propiedad, cada valor es tratado por separado.
     * @return {@link Where} nuevo, con la clusula correspondiente agregada.
     * @see #getClause(Class, String, String)
     */
    public <T> Where<T> buildWhere(@NotNull Class<T> root, @NotNull String property, String value) {

        return new Where<T>().addClause(this.getClause(root, property, value));
    }

    /**
     * Retorna una clusula para realizar una bsqueda por el atributo pasado.
     * <p>
     * Notar que la intencin de este mtodo es ser utilizados en mbitos
     * dinmicos, donde la informacin no se dispone, por ejemplo al momento de
     * crear filtros automticos.
     * </p>
     * <p>
     * Ver la descripcin de esta clase ( {@link SearchHelper} )
     * </p>
     * 
     * <h3>Ejemplos</h3> Estos ejemplos utilizan las entidades de Test, y son
     * extraidos de los Test, para verlos en funcionamiento, ir a los Test.
     * 
     * <pre>
     * getClause(TestEntity.class, &quot;testChild.description&quot;, &quot;COSTO&quot;)
     * </pre>
     * 
     * Es lo mismo que utilizar:
     * 
     * <pre>
     * Clauses.ilike("testChild.description", "COSTO", {@link MatchMode#CONTAIN})
     * </pre>
     * 
     * Cambiando solamente el path, el {@link Clause} generado cambia
     * drsticamente:
     * 
     * <pre>
     * getClause(TestEntity.class, &quot;testChild.fecha&quot;, &quot;13-11-2013&quot;)
     * </pre>
     * 
     * Es lo mismo que utilizar:
     * 
     * <pre>
     * dateClauses.between("testChild.fecha", "13-11-2013", "13-11-2013", <code>true</code>);
     * </pre>
     * 
     * @param root
     *            raz de la bsqueda el atributo, no debe ser <code>null</code>
     * @param property
     *            cadena separada por <code>.</code> (punto) que sirve para
     *            recorrer a travs del objeto y obtener la propiedad deseada.
     * @param value
     *            valor con el cual se desea comparar, dependiendo de la
     *            propiedad, cada valor es tratado por separado.
     * @return {@link Clause} correspondiente a la propiedad.
     */
    public <T> Clause getClause(@Nonnull Class<T> root, @Nonnull String property, String value) {

        if ("".equals(property)) {
            throw new IllegalArgumentException("Property can't be empty");
        }

        FieldInfo fi = getFieldInfo(root, property);
        if (fi.isCollection()) {
            // Si es una coleccin, se utiliza una tcnica de mejor
            // esfuerzo.
            return Clauses.iLike(property, value, MatchMode.CONTAIN);
        }
        return this.getFilter(fi, property, value);
    }

    /**
     * Retorna la informacin relacionada a un Field definido por una propiedad
     * (con un formato separado por puntos).
     * <p>
     * Por ejemplo, si invocamos de la siguiente manera:
     * 
     * <pre>
     *    class Pais {
     *       ...
     *       Set{@literal <}Departamento> departamentos;
     * 
     *       ...
     *    }
     * 
     *    class Departamento {
     *       ...
     *       Set{@literal <}Ciudad> ciudades;
     * 
     *       Set etnias;
     * 
     *       Pais pais;
     *       ...
     *    }
     * 
     *    class Ciudad {
     *       ...
     * 
     *       Departamento departamento;
     *       ...
     *    }
     * 
     * 1. Y invocamos de la siguiente manera:
     * 
     *    fi = getFieldInfo(Ciudad.class, "departamento.pais");
     *    fi.getField() ==> Pais.class
     *    fi.isCollection() == > <code>false</code>
     * 
     * 2. El siguiente ejemplo, es cuando se encuentra una {@link Collection}
     * 
     *    fi = getFieldInfo(Ciudad.class, "departamento.etnias");
     *    fi.getField() ==> Etnia.class
     *    fi.isCollection() == > <code>true</code>
     * </pre>
     * 
     * <p>
     * TODO ver para meter en una clase de utilidad
     * </p>
     * 
     * @param root
     *            {@link Class} que sirve de base para buscar la propiedad
     * @param property
     *            cadena separada por <code>.</code> (puntos) que sirve para
     *            recorrer a travs del objeto y obtener la propiedad deseada.
     * @return {@link FieldInfo} con la informacin posible que se ha
     *         conseguido.
     * @throw {@link KarakuRuntimeException} si no encuentra el field o no es
     *        accesible.
     */
    public static FieldInfo getFieldInfo(@NotNull Class<?> root, @NotNull String property) {

        try {
            String[] splited = property.split(Pattern.quote("."));
            String cProperty;
            Class<?> cClass = root;
            Field toRet = null;
            boolean collectionFound = false;
            for (String element : splited) {
                cProperty = element;
                toRet = ReflectionUtils.findField(cClass, cProperty);
                if (toRet == null) {
                    throw new KarakuRuntimeException(
                            "Field: " + cProperty + " not found. (Full path: " + property + ")");
                }
                cClass = toRet.getType();
                // Si tenemos una lista, buscar el tipo de la lista.
                if (Collection.class.isAssignableFrom(cClass)) {
                    cClass = GenericCollectionTypeResolver.getCollectionFieldType(toRet);
                }
                // TODO add expansion if found a @Display in the last field
                // Ejemplo: pais.departamento, puede seguir siendo explotado
                // si departamento tiene un DisplayName
            }
            return new FieldInfo(toRet, collectionFound);
        } catch (SecurityException e) {
            throw new KarakuRuntimeException(
                    "Field not accessible: " + property + " in class " + root.getSimpleName(), e);
        }
    }

    /**
     * Retorna el filtro simple.
     * 
     * @param where
     *            {@link Where}, no puede ser <code>null</code>
     * @param fi
     *            propiedad de la que se saca la meta-informacin
     * @param property
     *            path al field
     * @param value
     *            valor con el que se compara
     * @return {@link Where} con la opcin agregada
     */
    private <T> Clause getFilter(FieldInfo fi, String property, String value) {

        if (fi.collection) {
            return this.handleCollection(property, value);
        }
        try {
            if (String.class.isAssignableFrom(fi.field.getType())) {
                return this.handleString(property, value);
            }
            if (Number.class.isAssignableFrom(fi.field.getType())) {
                return this.handleNumber(property, value);
            }
            if (Date.class.isAssignableFrom(fi.field.getType())) {
                return this.handleDate(property, value);
            }
            throw new NotDisplayNameException();
        } catch (Exception e) {
            this.log.error("Error building clause for: {}", e, property);
        }
        return null;
    }

    /**
     * @param property
     * @param value
     * @return
     */
    private <T> Clause handleNumber(String property, String value) {

        return Clauses.numberLike(property, value);
    }

    /**
     * @param property
     * @param value
     * @return
     */
    private <T> Clause handleString(String property, String value) {

        return Clauses.iLike(property, value);
    }

    private <T> Clause handleCollection(String property, String value) {

        return Clauses.iLike(property, value);
    }

    /**
     * @param property
     * @param value
     * @return
     */
    private <T> Clause handleDate(String property, String value) {

        return this.dc.between(property, value, value, true);
    }

    /**
     * Clase auxiliar para {@link #getField()} que se utiliza para obtener
     * informacin valiosa a partir de un {@link Field}.
     * 
     * @author Arturo Volpe
     * @since 1.0
     * @version 1.0 Sep 26, 2013
     * 
     */
    public static class FieldInfo {

        private Field field;

        private boolean collection;

        /**
         * Crea un nuevo field, que no es una coleccin con el {@link Field}
         * dado.
         * 
         * @param f
         *            {@link Field} del cual se presenta la informacin
         */
        public FieldInfo(Field f) {

            this(f, false);
        }

        /**
         * Crea un nuevo field, que es o no una coleccin (depende de
         * {@link #isCollection()} con el {@link Field} dado.
         * 
         * @param f
         *            {@link Field} del cual se presenta la informacin
         * @param isCollection
         *            define si el mismo es una coleccin o no.
         */
        public FieldInfo(Field f, boolean isCollection) {

            this.field = f;
            this.collection = isCollection;
        }

        /**
         * Retorna el {@link Field} Asociado a esta informacin. En el caso de
         * que {@link #isCollection()} sea <code>true</code>, este mtodo
         * retorna el primer {@link Field} que es asignable desde una
         * {@link Collection}.
         * 
         * @return field asociado.
         */
        public Field getField() {

            return this.field;
        }

        /**
         * Verifica si la informacin se asocia a una {@link Collection}.
         * 
         * @return isCollection <code>false</code> si no es una coleccin y
         *         <code>true</code> en caso contrario.
         */
        public boolean isCollection() {

            return this.collection;
        }
    }
}