AbstractFilterDao.java

package fr.paris.lutece.plugins.notificationstore.business;
/*
 * Copyright (c) 2002-2025, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.util.sql.DAOUtil;

public abstract class AbstractFilterDao<T>
{

    // Maps containing names and types of each databases column associated to a business class attribute
    protected HashMap<String, String> _mapSql;

    // Prefix
    private final static String PREFIX_GET = "get";
    private final static String PREFIX_IS = "is";

    // Constants SQL
    private final static String SQL_WHERE = " WHERE 1 ";
    private final static String SQL_ORDER_BY = " ORDER BY ";
    private final static String SQL_EQUAL = " = ? ";
    private final static String SQL_LIKE = " LIKE ? ";
    private final static String SQL_AND = " AND ";

    // types only allowed for research
    protected final static String TYPE_DATE = "Date";
    protected final static String TYPE_STRING = "String";
    protected final static String TYPE_BOOLEAN = "boolean";
    protected final static String TYPE_INT = "int";

    // List of constraints
    private final static List<String> _listPrefixToRemove = Arrays.asList( PREFIX_GET, PREFIX_IS );
    protected final static List<String> _listTypeAllowedForSearch = Arrays.asList( TYPE_DATE, TYPE_STRING, TYPE_BOOLEAN, TYPE_INT );

    private final Class<T> _clazz;
    protected Plugin _plugin;

    @SuppressWarnings( "unchecked" )
    protected AbstractFilterDao( )
    {
        Type superClass = getClass( ).getGenericSuperclass( );
        if ( superClass instanceof ParameterizedType )
        {
            Type actualType = ( (ParameterizedType) superClass ).getActualTypeArguments( ) [0];
            this._clazz = (Class<T>) actualType;
        }
        else
        {
            throw new IllegalArgumentException( "Pas de type générique trouvé !" );
        }

        initMapSql( _clazz ); // Maps with name and type of each databases column associated to the business class attributes
    }

    /**
     * Preparation of filterStatement
     * 
     * @param mapFilterCriteria
     *            contains searchbar names/values inputs
     * @param strColumnToOrder
     *            contains the column name to use for orderBy statement in case of sorting request (must be null)
     * @param strSortMode
     *            contains the sortMode in case of sorting request : ASC or DESC (must be null)
     * @return a string with the WHERE part and the ORDER BY part of the sql statement
     */

    protected String prepareSelectStatement( String SQL_QUERY_SELECTALL_ID, Map<String, String> mapFilterCriteria, String strColumnToOrder, String strSortMode )
    {

        StringBuilder builder = new StringBuilder( );

        builder.append( SQL_QUERY_SELECTALL_ID );
        builder.append( addWhereClauses( mapFilterCriteria ) );
        builder.append( addOrderByClause( strColumnToOrder, strSortMode ) );

        return builder.toString( );

    }

    /**
     * add Where clause to the filterStatement
     * 
     * @param mapFilterCriteria
     *            contains name and value of each where clause
     * @return the where part of the filterStatement
     */

    protected String addWhereClauses( Map<String, String> mapFilterCriteria )
    {

        StringBuilder WhereClauses = new StringBuilder( );

        if ( !mapFilterIsEmpty( mapFilterCriteria ) )
        {

            WhereClauses.append( SQL_WHERE );

            for ( Map.Entry<String, String> filter : mapFilterCriteria.entrySet( ) )
            {

                // Check if a value was passed for the search
                if ( StringUtils.isNotBlank( filter.getValue( ) ) )
                {

                    // Check if the criteria name match with a BDD column name and if the type of this column is allowed for a search
                    if ( _mapSql.containsKey( filter.getKey( ) ) && _listTypeAllowedForSearch.contains( _mapSql.get( filter.getKey( ) ) ) )
                    {

                        WhereClauses.append( SQL_AND );
                        WhereClauses.append( filter.getKey( ) );
                        WhereClauses.append( addWhereClauseOperator( filter.getKey( ) ) );
                    }
                }
            }

        }

        return WhereClauses.toString( );
    };

    /**
     * add OrderBy columns to the filterStatement
     * 
     * @param strColumnToOrder
     *            contains the column name to use for orderBy statement in case of sorting request (must be null)
     * @param strSortMode
     *            contains the sortMode in case of sorting request : ASC or DESC (must be null)
     * @return the orderBy part of the filterStatement
     */
    protected String addOrderByClause( String strColumnToOrder, String strSortMode )
    {
        if ( StringUtils.isNotBlank( strColumnToOrder ) && _mapSql.containsKey( strColumnToOrder ) )
        {

            StringBuilder orderByClauses = new StringBuilder( );

            orderByClauses.append( SQL_ORDER_BY );
            orderByClauses.append( strColumnToOrder );
            orderByClauses.append( strSortMode );

            return orderByClauses.toString( );
        }

        return "";
    }

    /**
     * Check if _mapFilter is empty
     * 
     * @return boolean
     */
    private boolean mapFilterIsEmpty( Map<String, String> mapFilterCriteria )
    {

        for ( Map.Entry<String, String> entry : mapFilterCriteria.entrySet( ) )
        {
            if ( StringUtils.isNotBlank( entry.getValue( ) ) )
            {
                return false;
            }
        }

        return true;
    }

    /**
     * add where clause operator to the filterStatement
     * 
     * @param strWhereClauseColumn
     *            contains one of the column names to use for where clause part
     * @return operator to use for the clause passed in argument
     */

    private String addWhereClauseOperator( String strWhereClauseColumn )
    {

        if ( _mapSql.containsKey( strWhereClauseColumn ) )
        {

            switch( _mapSql.get( strWhereClauseColumn ) )
            {
                case TYPE_DATE:
                    return SQL_EQUAL;
                case TYPE_STRING:
                    return SQL_LIKE;
                case TYPE_BOOLEAN:
                    return SQL_EQUAL;
                case TYPE_INT:
                    return SQL_EQUAL;
                default:
                    return SQL_LIKE; // Other types will be managed as strings
            }
        }

        return SQL_LIKE; // Other types will be managed as strings
    }

    /**
     * Return name of column in sql database format from getter name of business class exemples : getImageUrl -> image_url , getCost -> cost
     * 
     * @return the name of column in sql database
     */
    private String getFormatedColumnName( String strAttributeName, String strPrefixToCut )
    {

        // Remove prefix (get or is) and lowercase the first character
        String strRemovePrefix = StringUtils.uncapitalize( strAttributeName.substring( strPrefixToCut.length( ) ) ).toString( );

        StringBuilder builder = new StringBuilder( );

        // Change uppercase character to lowercase and add an underscore in front of it. exemple : dateStart -> date_start
        for ( char c : strRemovePrefix.toCharArray( ) )
        {

            if ( Character.isUpperCase( c ) )
            {
                builder.append( '_' );
                builder.append( Character.toLowerCase( c ) );
            }
            else
            {
                builder.append( c );
            }
        }

        return builder.toString( );
    }

    /**
     * Initialization of mapSql. mapSql Containing names and types of each databases column associated to a business class attribute.
     */
    protected void initMapSql( Class<?> businessClass )
    {

        _mapSql = new HashMap<>( );

        for ( Method method : businessClass.getDeclaredMethods( ) )
        {

            for ( String prefix : _listPrefixToRemove )
            {
                // Use only getter and is function of business class to infer database name of each attributes
                if ( method.getName( ).startsWith( prefix ) )
                {
                    _mapSql.put( getFormatedColumnName( method.getName( ), prefix ), method.getReturnType( ).getSimpleName( ) );
                }
            }
        }
    }

    /**
     * searchItemsIdList
     * 
     * @param mapFilterCriteria
     * @param strColumnToOrder
     * @param strSortMode
     * @param plugin
     * @return
     */
    public List<Integer> searchItemsIdList( String strSqlSelectAllIds, Map<String, String> mapFilterCriteria, String strColumnToOrder, String strSortMode )
    {

        List<Integer> itemList = new ArrayList<>( );

        String strSelectStatement = prepareSelectStatement( strSqlSelectAllIds, mapFilterCriteria, strColumnToOrder, strSortMode );

        try ( DAOUtil daoUtil = new DAOUtil( strSelectStatement, _plugin ) )
        {
            int nIndex = 1;
            for ( Map.Entry<String, String> filter : mapFilterCriteria.entrySet( ) )
            {
                if ( StringUtils.isNotBlank( filter.getValue( ) ) && _mapSql.containsKey( filter.getKey( ) ) )
                {
                    daoUtil.setString( nIndex++, filter.getValue( ) );
                }
            }

            daoUtil.executeQuery( );

            while ( daoUtil.next( ) )
            {
                itemList.add( daoUtil.getInt( 1 ) );
            }

            return itemList;
        }
    }

}