AbstractJspBean.java

/*
 * Copyright (c) 2002-2018, Mairie de 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
 */
package fr.paris.lutece.plugins.stock.modules.billetterie.web;

import fr.paris.lutece.plugins.stock.business.BeanFilter;
import fr.paris.lutece.plugins.stock.commons.ResultList;
import fr.paris.lutece.plugins.stock.commons.dao.PaginationPropertiesAdapterDataTable;
import fr.paris.lutece.plugins.stock.commons.exception.BusinessException;
import fr.paris.lutece.plugins.stock.commons.exception.FunctionnalException;
import fr.paris.lutece.plugins.stock.commons.exception.ValidationException;
import fr.paris.lutece.plugins.stock.modules.tickets.utils.constants.TicketsConstants;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.web.admin.PluginAdminPageJspBean;
import fr.paris.lutece.util.beanvalidation.BeanValidationUtil;
import fr.paris.lutece.util.datatable.DataTableManager;
import fr.paris.lutece.util.datatable.DataTablePaginationProperties;
import fr.paris.lutece.util.html.HtmlTemplate;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Path;

/**
 * Abstract class for jsp bean
 *
 * @author abataille
 */
public class AbstractJspBean extends PluginAdminPageJspBean
{
    public static final int N_DEFAULT_ITEMS_PER_PAGE = AppPropertiesService.getPropertyInt( TicketsConstants.PROPERTY_DEFAULT_ITEM_PER_PAGE, 50 );
    public static final String PROPERTY_DEFAULT_PAGINATION = "stock-billetterie.nbitemsperpage";
    protected static final String ERROR_MESSAGE_KEY = "module.stock.billetterie.validation.error";
    protected static final String ERROR_TEMPLATE = "admin/plugins/stock/modules/billetterie/error.html";
    protected static final String FIELD_MESSAGE_PREFIX = "module.stock.billetterie.field.";
    protected static final String MARK_FILTER = "filter";
    protected static final String MARK_DATA_TABLE_MANAGER = "dataTableManager";
    protected static final String MARK_MESSAGE_LIST = "messageList";
    protected static final String MARK_NB_ITEMS_PER_PAGE = "nb_items_per_page";
    protected static final String MARK_PAGINATOR = "paginator";
    protected static final String MARK_PLUGIN_NAME = "plugin_name";
    protected static final String MARK_ASC_SORT = "asc_sort";
    protected static final String PARAMETER_TRUE = "true";
    protected static final String PARAMETER_FIND_BY_FILTER_NAME_METHOD = "findByFilter";
    private static final long serialVersionUID = 5259767254583048437L;
    private static final Logger LOGGER = Logger.getLogger( AbstractJspBean.class );
    protected int _nItemsPerPage;

    /**
     *
     * Get the datatable save in the session
     *
     * @param request
     *            the http request
     * @param key
     *            the key for the data table
     * @return the DataTableManager keep in session
     */
    @SuppressWarnings( "unchecked" )
    protected <T> DataTableManager<T> loadDataTableFromSession( HttpServletRequest request, String key )
    {
        DataTableManager<T> dataTable = null;

        Object object = request.getSession( ).getAttribute( StringUtils.isNotBlank( key ) ? key : MARK_DATA_TABLE_MANAGER );

        if ( ( object != null ) && object instanceof DataTableManager<?> )
        {
            try
            {
                dataTable = (DataTableManager<T>) object;
            }
            catch( Exception e )
            {
                LOGGER.error( "Error during cast :" + e );
            }
        }

        return dataTable;
    }

    /**
     * Save a Data table manager into the http session with key. Can save various data table.
     *
     * @param request
     *            the http request with the user session
     * @param dataTable
     *            the datatable
     * @param key
     *            the datatable key
     * @param <T>
     *            Type of data
     */
    protected <T> void saveDataTableInSession( HttpServletRequest request, DataTableManager<T> dataTable, String key )
    {
        request.getSession( ).setAttribute( StringUtils.isNotBlank( key ) ? key : MARK_DATA_TABLE_MANAGER, dataTable );
    }

    /**
     * Validate a bean using jsr 303 specs.
     *
     * @param <T>
     *            the bean type
     * @param bean
     *            to validate
     * @throws ValidationException
     *             exception containing informations about errors and the bean
     */
    protected <T> void validateBilletterie( T bean ) throws ValidationException
    {
        Set<ConstraintViolation<T>> constraintViolations = BeanValidationUtil.validate( bean );

        if ( constraintViolations.size( ) > 0 )
        {
            ValidationException ve = new ValidationException( bean );

            for ( ConstraintViolation<T> constraintViolation : constraintViolations )
            {
                ve.addConstraintViolation( constraintViolation );
            }

            throw ve;
        }
    }

    /**
     * Return localized message
     *
     * @param key
     *            i18n key
     * @return localized message
     */
    protected String getMessage( String key )
    {
        return I18nService.getLocalizedString( key, this.getLocale( ) );
    }

    /**
     * Return localized message with args
     *
     * @param key
     *            i18n key
     * @param args
     *            args
     * @return localized message
     */
    protected String getMessage( String key, String... args )
    {
        return I18nService.getLocalizedString( key, args, this.getLocale( ) );
    }

    /**
     * Get validation error from session and remove from it
     *
     * @param request
     *            http request
     * @return validation exception
     */
    protected FunctionnalException getErrorOnce( HttpServletRequest request )
    {
        FunctionnalException fe = (FunctionnalException) request.getSession( ).getAttribute( TicketsConstants.PARAMETER_ERROR );

        if ( fe != null )
        {
            request.getSession( ).removeAttribute( TicketsConstants.PARAMETER_ERROR );
        }

        return fe;
    }

    /**
     * Return html code for error message
     *
     * @param e
     *            functionnal exception
     * @return html
     */
    protected String getHtmlError( FunctionnalException e )
    {
        Map<String, Object> model = new HashMap<String, Object>( );
        List<String> messageList = new ArrayList<String>( );

        try
        {
            throw e;
        }

        // Validation error
        catch( ValidationException ve )
        {
            String typeName = ve.getBean( ).getClass( ).getSimpleName( );

            // Add a validation error message using value, field name and
            // provided
            // message
            for ( ConstraintViolation<?> constraintViolation : ve.getConstraintViolationList( ) )
            {
                String fieldName = getMessage( FIELD_MESSAGE_PREFIX + typeName + "." + correctPath( constraintViolation.getPropertyPath( ) ) );
                messageList.add( getMessage( ERROR_MESSAGE_KEY, String.valueOf( constraintViolation.getInvalidValue( ) ), fieldName,
                        constraintViolation.getMessage( ) ) );
            }
        }

        // Business error
        catch( BusinessException be )
        {
            messageList.add( getMessage( be.getCode( ), be.getArguments( ) ) );
        }

        model.put( MARK_MESSAGE_LIST, messageList );

        HtmlTemplate template = AppTemplateService.getTemplate( ERROR_TEMPLATE, getLocale( ), model );

        return template.getHtml( );
    }

    /**
     * Ensure path given which use indexing value like [%d] is cleaning
     *
     * @param propertyPath
     *            the property path to clean
     * @return the right i18n key
     */
    private String correctPath( Path propertyPath )
    {
        String path = propertyPath.toString( );

        Pattern pattern = Pattern.compile( "\\[\\d*\\]" );
        Matcher matcher = pattern.matcher( path );

        if ( matcher.find( ) )
        {
            path = path.substring( 0, matcher.start( ) ) + path.substring( matcher.start( ) + matcher.group( ).length( ), path.length( ) );
        }

        return path;
    }

    /**
     * Manage functionnal exception. Save it into session
     *
     * @param request
     *            the request
     * @param e
     *            the e
     * @param targetUrl
     *            the target url
     * @return the string
     */
    protected String manageFunctionnalException( HttpServletRequest request, FunctionnalException e, String targetUrl )
    {
        request.getSession( ).setAttribute( TicketsConstants.PARAMETER_ERROR, e );

        return targetUrl;
    }

    /**
     * Get the correct filter to use with data table manager
     *
     * @param request
     *            the http request
     * @param filter
     *            the bean filter get with request
     * @param markFilter
     *            the key of the filter
     * @param dataTable
     *            the datatable to use
     * @param <T>
     *            the bean filter type
     * @return the filter to use
     */
    protected <T> T getFilterToUse( HttpServletRequest request, T filter, String markFilter, DataTableManager<?> dataTable )
    {
        T filterFromSession = null;

        if ( request.getParameter( MARK_PLUGIN_NAME ) != null )
        {
            request.getSession( ).setAttribute( markFilter, null );
        }
        else
        {
            filterFromSession = ( request.getParameter( MARK_PLUGIN_NAME ) != null ) ? null : (T) request.getSession( ).getAttribute( markFilter );
        }

        // 1) est-ce qu'une recherche vient d'être faite ? 2) est-ce qu'un
        // filtre existe en session ? 3) est-ce que le filtre en session est
        // d'un type héritant du fitre fournit en parametre ?
        T filterToUse = ( ( request.getParameter( TicketsConstants.MARK_FILTER ) != null ) || ( filterFromSession == null ) || !filterFromSession.getClass( )
                .isAssignableFrom( filter.getClass( ) ) ) ? dataTable.getAndUpdateFilter( request, filter ) : filterFromSession;

        return filterToUse;
    }

    /**
     * Get the correct data table manager
     *
     * @param request
     *            the http request
     * @param markFilter
     *            the key of the filter
     * @param jspManage
     *            the jsp file to manage the beans
     * @param <T>
     *            the bean filter type
     * @return the DataTableManager
     */
    protected <T> DataTableManager<T> getDataTableToUse( HttpServletRequest request, String markFilter, String jspManage )
    {
        String strDefaultPagination = AppPropertiesService.getProperty( PROPERTY_DEFAULT_PAGINATION );
        Integer nDefaultPagination = null;
        try
        {
            nDefaultPagination = Integer.parseInt( strDefaultPagination );
        }
        catch( NumberFormatException e )
        {
            AppLogService.error( "NumberFormatException ", e );
        }

        DataTableManager<T> dataTableFromSession = loadDataTableFromSession( request, markFilter );
        DataTableManager<T> dataTablePartner = ( dataTableFromSession != null ) ? dataTableFromSession : new DataTableManager<T>( jspManage, StringUtils.EMPTY,
                nDefaultPagination != null ? nDefaultPagination : 10, true );

        return dataTablePartner;
    }

    /**
     *
     * @param request
     *            the http request
     * @param filter
     *            the filter
     * @param keyDataTable
     *            the key to store data table manager
     * @param jspManage
     *            the url to the manage page
     * @param service
     *            the bean service
     * @param findByFilter
     *            the method which give the method to find beans
     * @return the data table to use
     */
    protected <T> DataTableManager<T> getAbstractDataTableManager( HttpServletRequest request, Object filter, String keyDataTable, String jspManage,
            Object service, Method findByFilter, boolean forceNewFilter )
    {
        // si un objet est déjà présent en session, on l'utilise
        DataTableManager<T> dataTableToUse = getDataTableToUse( request, keyDataTable, jspManage );

        // determination de l'utilisation d'un nouveau filtre (recherche) ou de
        // celui présent en session (changement de page)
        Object filterToUse = filter;
        if ( !forceNewFilter )
        {
            filterToUse = getFilterToUse( request, filter, MARK_FILTER, dataTableToUse );
        }
        BeanUtils.copyProperties( filterToUse, filter );

        sortFilter( request, filterToUse );

        // mise à jour de la pagination dans le data table pour l'affichage de
        // la page courante et du nombre d'items
        DataTablePaginationProperties updatePaginator = dataTableToUse.getAndUpdatePaginator( request );

        // obtention manuel des beans à afficher
        PaginationPropertiesAdapterDataTable paginationProperties = new PaginationPropertiesAdapterDataTable( updatePaginator );

        ResultList<T> listAllBean = null;

        try
        {
            listAllBean = (ResultList<T>) findByFilter.invoke( service, filterToUse, paginationProperties );
        }
        catch( Exception e )
        {
            LOGGER.error( "Erreur lors de l'obtention de la liste des beans : " + e );
        }

        request.getSession( ).setAttribute( MARK_FILTER, filterToUse );

        if ( listAllBean != null )
        {
            dataTableToUse.setItems( listAllBean, listAllBean.getTotalResult( ) );
        }

        return dataTableToUse;
    }

    /**
     *
     * @param request
     *            the http request
     * @param filter
     *            the filter
     * @param keyDataTable
     *            the key to store data table manager
     * @param jspManage
     *            the url to the manage page
     * @param service
     *            the bean service
     * @param findByFilter
     *            the method which give the method to find beans
     * @return the data table to use
     */
    protected <T> DataTableManager<T> getAbstractDataTableManager( HttpServletRequest request, Object filter, String keyDataTable, String jspManage,
            Object service, Method findByFilter )
    {
        return getAbstractDataTableManager( request, filter, keyDataTable, jspManage, service, findByFilter, false );
    }

    private void sortFilter( HttpServletRequest request, Object filter )
    {
        String ascSort = request.getParameter( MARK_ASC_SORT );
        String sortedAttribute = request.getParameter( "sorted_attribute_name" );
        List<String> orders = new ArrayList<String>( );

        if ( StringUtils.isNotBlank( ascSort ) && StringUtils.isNotBlank( sortedAttribute ) )
        {
            if ( filter instanceof BeanFilter )
            {
                orders.add( sortedAttribute );
                ( (BeanFilter) filter ).setOrders( orders );

                ( (BeanFilter) filter ).setOrderAsc( PARAMETER_TRUE.equals( ascSort ) );
            }
        }
    }
}