DataTableManager.java
/*
* Copyright (c) 2002-2022, 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
*/
package fr.paris.lutece.util.datatable;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.StringUtils;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.web.constants.Parameters;
import fr.paris.lutece.portal.web.l10n.LocaleService;
import fr.paris.lutece.portal.web.util.LocalizedDelegatePaginator;
import fr.paris.lutece.portal.web.util.LocalizedPaginator;
import fr.paris.lutece.util.ReferenceList;
import fr.paris.lutece.util.UniqueIDGenerator;
import fr.paris.lutece.util.html.AbstractPaginator;
import fr.paris.lutece.util.html.IPaginator;
import fr.paris.lutece.util.sort.AttributeComparator;
import fr.paris.lutece.util.url.UrlItem;
/**
* Class to manage data tables with freemarker macros
*
* @param <T>
* Type of data to display
*/
public class DataTableManager<T> implements Serializable
{
/**
* Serial version UID
*/
private static final long serialVersionUID = -3906455886374172029L;
private static final String CONSTANT_GET = "get";
private static final String CONSTANT_IS = "is";
private static final String CONSTANT_DATA_TABLE_MANAGER_ID_PREFIX = "dataTableManager";
private String _strSortUrl;
private List<DataTableColumn> _listColumn = new ArrayList<>( );
private FilterPanel _filterPanel;
private IPaginator<T> _paginator;
private String _strCurrentPageIndex = StringUtils.EMPTY;
private int _nItemsPerPage;
private int _nDefautlItemsPerPage;
private boolean _bEnablePaginator;
private Locale _locale;
private String _strSortedAttributeName;
private boolean _bIsAscSort;
private String _strUid;
/**
* Private constructor
*/
protected DataTableManager( )
{
generateDataTableId( );
}
/**
* Constructor of the DataTableManager class
*
* @param strSortUrl
* URL used by the paginator to sort data
* @param strFilterUrl
* URL used to filter data
* @param nDefautlItemsPerPage
* Default number of items to display per page
* @param bEnablePaginator
* True to enable pagination, false to disable it
*/
public DataTableManager( String strSortUrl, String strFilterUrl, int nDefautlItemsPerPage, boolean bEnablePaginator )
{
_filterPanel = new FilterPanel( strFilterUrl );
_nDefautlItemsPerPage = nDefautlItemsPerPage;
_nItemsPerPage = _nDefautlItemsPerPage;
_strCurrentPageIndex = "1";
_bEnablePaginator = bEnablePaginator;
generateDataTableId( );
setSortUrl( strSortUrl );
}
/**
* Add a column to this DataTableManager
*
* @param strColumnTitle
* I18n key of the title of the column
* @param strObjectName
* Name of the property of objects that should be displayed in this column.<br>
* For example, if a class "Data" contains a property named "title", then the value of the parameter <i>strObjectName</i> should be "title".
* @param bSortable
* True if the column is sortable, false otherwise
*/
public void addColumn( String strColumnTitle, String strObjectName, boolean bSortable )
{
_listColumn.add( new DataTableColumn( strColumnTitle, strObjectName, bSortable, DataTableColumnType.STRING ) );
}
/**
* Add a label column to this DataTableManager. Values of cells of this column will be interpreted as i18n keys.
*
* @param strColumnTitle
* I18n key of the title of the column
* @param strObjectName
* Name of the property of objects that should be displayed in this column. This properties must be i18n keys.<br>
* For example, if a class "Data" contains a property named "title", then the value of the parameter <i>strObjectName</i> should be "title".
* @param bSortable
* True if the column is sortable, false otherwise
*/
public void addLabelColumn( String strColumnTitle, String strObjectName, boolean bSortable )
{
_listColumn.add( new DataTableColumn( strColumnTitle, strObjectName, bSortable, DataTableColumnType.LABEL ) );
}
/**
* Add an column to this DataTableManager that will display actions on items. Actions are usually parameterized links. A DataTableManager can only have 1
* action column. The content of the action column must be generated by a macro.this macro must have one parameter named "item", and its name must be given
* to the macro <i>@tableData</i>.
*
* @param strColumnTitle
* I18n key of the title of the column
*/
public void addActionColumn( String strColumnTitle )
{
_listColumn.add( new DataTableColumn( strColumnTitle, null, false, DataTableColumnType.ACTION ) );
}
/**
* Add a column to this DataTableManager
*
* @param strColumnTitle
* I18n key of the title of the column
* @param strObjectName
* Name of the property of objects that should be displayed in this column.<br>
* For example, if a class "Data" contains a property named "title", then the value of the parameter <i>strObjectName</i> should be "title".
* @param strLabelTrue
* I18n key of the label to display when the value is true
* @param strLabelFalse
* I18n key of the label to display when the value is false
*/
public void addBooleanColumn( String strColumnTitle, String strObjectName, String strLabelTrue, String strLabelFalse )
{
_listColumn.add( new DataTableColumn( strColumnTitle, strObjectName, false, DataTableColumnType.BOOLEAN, strLabelTrue, strLabelFalse ) );
}
/**
* Add a free column to this DataTableManager. The content of this column must be generated by a macro. The macro must have one parameter named "item".
*
* @param strColumnTitle
* I18n key of the title of the column
* @param strFreemarkerMacroName
* Name of the freemarker macro that will display the content of the column.<br>
* The macro must have a single parameter named <i>item</i> of type T that will contain the object associated with a row of the table.
*/
public void addFreeColumn( String strColumnTitle, String strFreemarkerMacroName )
{
_listColumn.add( new DataTableColumn( strColumnTitle, strFreemarkerMacroName, false, DataTableColumnType.ACTION ) );
}
/**
* Add an email column to this DataTableManager. Displayed cell will be a "mailto:" link.
*
* @param strColumnTitle
* I18n key of the title of the column
* @param strObjectName
* Name of the property of objects that should be displayed in this column.<br>
* For example, if a class "Data" contains a property named "title", then the value of the parameter <i>strObjectName</i> should be "title".
* @param bSortable
* True if the column is sortable, false otherwise
*/
public void addEmailColumn( String strColumnTitle, String strObjectName, boolean bSortable )
{
_listColumn.add( new DataTableColumn( strColumnTitle, strObjectName, bSortable, DataTableColumnType.EMAIL ) );
}
/**
* Add a filter to the filter panel of this DataTableManager
*
* @param filterType
* data type of the filter. For drop down list, use {@link DataTableManager#addDropDownListFilter(String, String, ReferenceList)
* addDropDownListFilter} instead
* @param strParameterName
* Name of the parameter of the object to filter.<br>
* For example, if this filter should be applied on the parameter "title" of a class named "Data", then the value of the parameter
* <i>strParameterName</i> should be "title".
* @param strFilterLabel
* Label describing the filter
*/
public void addFilter( DataTableFilterType filterType, String strParameterName, String strFilterLabel )
{
_filterPanel.addFilter( filterType, strParameterName, strFilterLabel );
}
/**
* Add a drop down list filter to the filter panel of this DataTableManager
*
* @param strParameterName
* Name of the parameter of the object to filter.<br>
* For example, if this filter should be applied on the parameter "title" of a class named "Data", then the value of the parameter
* <i>strParameterName</i> should be "title".
* @param strFilterLabel
* Label describing the filter
* @param refList
* Reference list containing data of the drop down list
*/
public void addDropDownListFilter( String strParameterName, String strFilterLabel, ReferenceList refList )
{
_filterPanel.addDropDownListFilter( strParameterName, strFilterLabel, refList );
}
/**
* Apply filters on an objects list, sort it and update pagination values.
*
* @param request
* The request
* @param items
* Collection of objects to filter, sort and paginate
*/
public void filterSortAndPaginate( HttpServletRequest request, List<T> items )
{
List<T> filteredSortedPaginatedItems = new ArrayList<>( items );
boolean bSubmitedDataTable = hasDataTableFormBeenSubmited( request );
// FILTER
Collection<DataTableFilter> listFilters = _filterPanel.getListFilter( );
boolean bUpdateFilter = false;
boolean bResetFilter = false;
// We check if filters must be updated or cleared
if ( bSubmitedDataTable )
{
bResetFilter = StringUtils.equals( request.getParameter( FilterPanel.PARAM_FILTER_PANEL_PREFIX + FilterPanel.PARAM_RESET_FILTERS ),
Boolean.TRUE.toString( ) );
bUpdateFilter = true;
if ( !bResetFilter )
{
bUpdateFilter = StringUtils.equals( request.getParameter( FilterPanel.PARAM_FILTER_PANEL_PREFIX + FilterPanel.PARAM_UPDATE_FILTERS ),
Boolean.TRUE.toString( ) );
}
}
for ( DataTableFilter filter : listFilters )
{
String strFilterValue;
if ( bSubmitedDataTable && bUpdateFilter )
{
// We update or clear filters
strFilterValue = request.getParameter( FilterPanel.PARAM_FILTER_PANEL_PREFIX + filter.getParameterName( ) );
if ( !bResetFilter && ( filter.getFilterType( ) == DataTableFilterType.BOOLEAN ) && ( strFilterValue == null ) )
{
strFilterValue = Boolean.FALSE.toString( );
}
filter.setValue( strFilterValue );
}
else
{
strFilterValue = filter.getValue( );
}
if ( StringUtils.isNotBlank( strFilterValue ) )
{
List<T> bufferList = new ArrayList<>( );
for ( T item : filteredSortedPaginatedItems )
{
Method method = getMethod( item, filter.getParameterName( ), CONSTANT_GET );
if ( ( method == null ) && ( filter.getFilterType( ) == DataTableFilterType.BOOLEAN ) )
{
method = getMethod( item, filter.getParameterName( ), CONSTANT_IS );
}
if ( method != null )
{
try
{
Object value = method.invoke( item );
if ( ( value != null ) && strFilterValue.equals( value.toString( ) ) )
{
bufferList.add( item );
}
}
catch( Exception e )
{
AppLogService.error( e.getMessage( ), e );
}
}
}
filteredSortedPaginatedItems.retainAll( bufferList );
}
}
// SORT
if ( bSubmitedDataTable )
{
// We update the sort parameters
String strSortedAttributeName = request.getParameter( Parameters.SORTED_ATTRIBUTE_NAME );
if ( strSortedAttributeName != null )
{
// We update sort properties
_strSortedAttributeName = strSortedAttributeName;
_bIsAscSort = Boolean.parseBoolean( request.getParameter( Parameters.SORTED_ASC ) );
}
}
// We sort the items
if ( _strSortedAttributeName != null )
{
Collections.sort( filteredSortedPaginatedItems, new AttributeComparator( _strSortedAttributeName, _bIsAscSort ) );
}
// PAGINATION
if ( bSubmitedDataTable )
{
// We update the pagination properties
if ( _bEnablePaginator )
{
int nOldItemsPerPage = _nItemsPerPage;
_nItemsPerPage = AbstractPaginator.getItemsPerPage( request, AbstractPaginator.PARAMETER_ITEMS_PER_PAGE, _nItemsPerPage,
_nDefautlItemsPerPage );
// If the number of items per page has changed, we switch to the first page
if ( _nItemsPerPage != nOldItemsPerPage )
{
_strCurrentPageIndex = Integer.toString( 1 );
}
else
{
_strCurrentPageIndex = AbstractPaginator.getPageIndex( request, AbstractPaginator.PARAMETER_PAGE_INDEX, _strCurrentPageIndex );
}
}
else
{
_strCurrentPageIndex = Integer.toString( 1 );
_nItemsPerPage = filteredSortedPaginatedItems.size( );
}
}
if ( request != null )
{
// We paginate create the new paginator
_paginator = new LocalizedPaginator<>( filteredSortedPaginatedItems, _nItemsPerPage, getSortUrl( ), AbstractPaginator.PARAMETER_PAGE_INDEX,
_strCurrentPageIndex, request.getLocale( ) );
}
}
/**
* Get the filter panel of the DataTableManager
*
* @return The filter panel of the DataTableManager
*/
public FilterPanel getFilterPanel( )
{
return _filterPanel;
}
/**
* Set the filter panel of the DataTableManager
*
* @param filterPanel
* Filter panel
*/
public void setFilterPanel( FilterPanel filterPanel )
{
_filterPanel = filterPanel;
}
/**
* Get the list of columns of this DataTableManager
*
* @return The list of columns of this DataTableManager
*/
public List<DataTableColumn> getListColumn( )
{
return _listColumn;
}
/**
* Set the list of columns of this DataTableManager
*
* @param listColumn
* The list of columns of this DataTableManager
*/
public void setListColumn( List<DataTableColumn> listColumn )
{
_listColumn = listColumn;
}
/**
* Get the sort url of this DataTableManager
*
* @return The sort url of this DataTableManager
*/
public String getSortUrl( )
{
return _strSortUrl;
}
/**
* Set the sort url of this DataTableManager
*
* @param strSortUrl
* The sort url of this DataTableManager
*/
public void setSortUrl( String strSortUrl )
{
_strSortUrl = strSortUrl;
if ( ( _strSortUrl != null ) && StringUtils.isNotEmpty( _strSortUrl ) && !StringUtils.contains( _strSortUrl, getId( ) ) )
{
// We add to the sort URL the unique parameter of this data table manager
UrlItem urlItem = new UrlItem( _strSortUrl );
urlItem.addParameter( getId( ), getId( ) );
_strSortUrl = urlItem.getUrl( );
}
}
/**
* Get the filtered, sorted and paginated items collection of this DataTableManager
*
* @return The filtered, sorted and paginated items collection of this DataTableManager
*/
public List<T> getItems( )
{
return _paginator.getPageItems( );
}
/**
* Set the items to display. The list of items must be fintered, sorted and paginated. Methods
* {@link DataTableManager#getAndUpdatePaginator(HttpServletRequest ) getAndUpdatePaginator}, {@link DataTableManager#getAndUpdateSort(HttpServletRequest )
* getAndUpdateSort} and {@link DataTableManager#getAndUpdateFilter(HttpServletRequest, Object) getAndUpdateFilter} must have been called before the
* generation of the list of items.
*
* @param items
* The filtered sorted and paginated list of items to display
* @param nTotalItemsNumber
* The total number of items
*/
public void setItems( List<T> items, int nTotalItemsNumber )
{
_paginator = new LocalizedDelegatePaginator<>( items, _nItemsPerPage, getSortUrl( ), AbstractPaginator.PARAMETER_PAGE_INDEX, _strCurrentPageIndex,
nTotalItemsNumber, _locale );
}
/**
* Clear the items stored by this DataTableManager so that the garbage collector can free the memory they use.
*/
public void clearItems( )
{
_paginator = null;
_locale = null;
}
/**
* Internal method. Get the paginator.
*
* Do not use this method, use {@link DataTableManager#getAndUpdatePaginator(HttpServletRequest ) getAndUpdatePaginator} instead to get up to date values !
*
* @return The paginator
*/
public IPaginator<T> getPaginator( )
{
return _paginator;
}
/**
* Get the enable paginator boolean
*
* @return True if pagination is active, false otherwise
*/
public boolean getEnablePaginator( )
{
return _bEnablePaginator;
}
/**
* Get the locale
*
* @return The locale
*/
public Locale getLocale( )
{
return _locale;
}
/**
* Set the locale
*
* @param locale
* The locale
*/
public void setLocale( Locale locale )
{
_locale = locale;
}
/**
* Get the unique id of this data table manager
*
* @return The unique id of this data table manager
*/
public String getId( )
{
return _strUid;
}
/**
* Get the paginator updated with values in the request
*
* @param request
* The request
* @return The paginator up to date
*/
public DataTablePaginationProperties getAndUpdatePaginator( HttpServletRequest request )
{
DataTablePaginationProperties paginationProperties = null;
if ( _bEnablePaginator )
{
paginationProperties = new DataTablePaginationProperties( );
if ( hasDataTableFormBeenSubmited( request ) )
{
_strCurrentPageIndex = AbstractPaginator.getPageIndex( request, AbstractPaginator.PARAMETER_PAGE_INDEX, _strCurrentPageIndex );
_nItemsPerPage = AbstractPaginator.getItemsPerPage( request, AbstractPaginator.PARAMETER_ITEMS_PER_PAGE, _nItemsPerPage,
_nDefautlItemsPerPage );
}
paginationProperties.setItemsPerPage( _nItemsPerPage );
int nCurrentPageIndex = 1;
if ( !StringUtils.isEmpty( _strCurrentPageIndex ) )
{
nCurrentPageIndex = Integer.parseInt( _strCurrentPageIndex );
}
paginationProperties.setCurrentPageIndex( nCurrentPageIndex );
}
_locale = ( request != null ) ? request.getLocale( ) : LocaleService.getDefault( );
return paginationProperties;
}
/**
* Get sort properties updated with values in the request
*
* @param request
* The request
* @return The sort properties up to date
*/
public DataTableSort getAndUpdateSort( HttpServletRequest request )
{
if ( hasDataTableFormBeenSubmited( request ) )
{
String strSortedAttributeName = request.getParameter( Parameters.SORTED_ATTRIBUTE_NAME );
if ( strSortedAttributeName != null )
{
// We update sort properties
_strSortedAttributeName = strSortedAttributeName;
_bIsAscSort = Boolean.parseBoolean( request.getParameter( Parameters.SORTED_ASC ) );
}
}
return new DataTableSort( _strSortedAttributeName, _bIsAscSort );
}
/**
* Get filter properties updated with values in the request
*
* @param request
* The request
* @param <K>
* Type of the filter to use. This type must have accessors for every declared filter.
* @param filterObject
* Filter to apply.
* @return The filter properties up to date
*/
public <K> K getAndUpdateFilter( HttpServletRequest request, K filterObject )
{
List<DataTableFilter> listFilters = _filterPanel.getListFilter( );
boolean bSubmitedDataTable = hasDataTableFormBeenSubmited( request );
boolean bUpdateFilter = false;
Map<String, Object> mapFilter = new HashMap<>( );
if ( bSubmitedDataTable )
{
StringUtils.equals( request.getParameter( FilterPanel.PARAM_FILTER_PANEL_PREFIX + FilterPanel.PARAM_RESET_FILTERS ), Boolean.TRUE.toString( ) );
bUpdateFilter = StringUtils.equals( request.getParameter( FilterPanel.PARAM_FILTER_PANEL_PREFIX + FilterPanel.PARAM_UPDATE_FILTERS ),
Boolean.TRUE.toString( ) );
}
for ( DataTableFilter filter : listFilters )
{
if ( bSubmitedDataTable )
{
String strFilterValue = request.getParameter( FilterPanel.PARAM_FILTER_PANEL_PREFIX + filter.getParameterName( ) );
if ( bUpdateFilter )
{
filter.setValue( strFilterValue );
}
}
if ( StringUtils.isNotBlank( filter.getValue( ) ) )
{
mapFilter.put( filter.getParameterName( ), filter.getValue( ) );
}
}
try
{
BeanUtilsBean.getInstance( ).populate( filterObject, mapFilter );
}
catch( InvocationTargetException | IllegalAccessException e )
{
AppLogService.error( e.getMessage( ), e );
return null;
}
return filterObject;
}
/**
* Internal method. Get the prefix of html attributes used by filters
*
* @return The prefix of html attributes used by filters
*/
public String getFilterPanelPrefix( )
{
return FilterPanel.PARAM_FILTER_PANEL_PREFIX;
}
/**
* Return the getter method of the object obj for the attribute <i>strAttributName</i>
*
* @param obj
* the object
* @param strAttributName
* The name of the attribute to get the getter
* @param strMethodPrefix
* Prefix of the name of the method
* @return method Method of the object obj for the attribute <i>strAttributName</i>
*/
private Method getMethod( Object obj, String strAttributName, String strMethodPrefix )
{
Method method = null;
String strFirstLetter = strAttributName.substring( 0, 1 ).toUpperCase( );
String strMethodName = strMethodPrefix + strFirstLetter + strAttributName.substring( 1, strAttributName.length( ) );
try
{
method = obj.getClass( ).getMethod( strMethodName );
}
catch( Exception e )
{
AppLogService.debug( e.getMessage( ), e );
}
return method;
}
/**
* Generates the unique identifier of this data table manager
*/
private void generateDataTableId( )
{
this._strUid = CONSTANT_DATA_TABLE_MANAGER_ID_PREFIX + UniqueIDGenerator.getNewId( );
}
/**
* Check if a request contain data of this data table manager
*
* @param request
* The request
* @return True if a form of this data table manager has been submited, false otherwise
*/
private boolean hasDataTableFormBeenSubmited( HttpServletRequest request )
{
return request != null && StringUtils.equals( request.getParameter( getId( ) ), getId( ) );
}
}