FormListTemplateBuilder.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.plugins.forms.web.form.multiview.util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.paris.lutece.plugins.forms.business.form.FormResponseItem;
import fr.paris.lutece.plugins.forms.business.form.column.FormColumnCell;
import fr.paris.lutece.plugins.forms.service.IMultiviewMapProvider;
import fr.paris.lutece.plugins.forms.web.form.column.display.IFormColumnDisplay;
import fr.paris.lutece.plugins.forms.web.form.column.display.impl.FormColumnDisplayEntryCartography;
import fr.paris.lutece.plugins.forms.web.form.column.display.impl.FormColumnDisplayEntryGeolocation;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.plugin.PluginService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.util.AppException;
/**
* Builder class for the template of FormColumnDisplay and FormFilterDisplay objects
*/
public final class FormListTemplateBuilder
{
// Templates
private static final String TEMPLATE_MULTIVIEW_FORM_TABLE = "admin/plugins/forms/multiview/includes/include_manage_multiview_forms_table.html";
private static final String TEMPLATE_MULTIVIEW_GEOJSON_POPUP = "admin/plugins/forms/multiview/includes/include_manage_multiview_geojson_popup.html";
// Multiviewmap Provider bean name
private static final String BEAN_NAME_MULTIVIEWMAP = "forms.multiviewMap";
// Marks
private static final String MARK_FROM_RESPONSE_ITEM_LIST = "from_response_item_list";
private static final String MARK_FORM_RESPONSE_COLUMN_HEADER_TEMPLATE_LIST = "form_response_column_header_template_list";
private static final String MARK_FORM_RESPONSE_LINE_TEMPLATE_LIST = "form_response_line_template_list";
private static final String MARK_FORM_RESPONSE_LINE_TEMPLATE = "form_response_line_template";
private static final String MARK_FORM_RESPONSE_DETAILS_REDIRECT_BASE_URL = "redirect_details_base_url";
private static final String MARK_FROM_RESPONSE_GEOJSON_POINT_LIST = "form_response_geojson_point_list";
private static final String MARK_MULTIVIEWMAP = "multiviewmap";
private static final String GEOJSON_PROPERTIES = "properties";
private static final String GEOJSON_COORDINATES = "coordinates";
private static final String GEOJSON_GEOMETRY = "geometry";
private static final String GEOJSON_GEOMETRY_TYPE_POINT = "Point";
private static final String GEOJSON_TYPE = "type";
private static final String GEOJSON_TYPE_FEATURE = "Feature";
private static final String PROPERTY_POPUP_CONTENT = "popupContent";
// To serialize to geojson
private static ObjectMapper _mapper = new ObjectMapper( );
/**
* Constructor
*/
private FormListTemplateBuilder( )
{
}
/**
* Build the template of the table of all form response
*
* @param listFormColumnDisplay
* The list of all FormColumnDisplay objects to build the global template of the form columns from
* @param listFormResponseItem
* The list of all FormResponseItem used to build the tab with all form responses
* @param locale
* The locale to used for build template
* @param strRedirectionDetailsBaseUrl
* The base url to use for the redirection on the details page
* @param strSortUrl
* The url to use for sort a column
* @return the global template of all FormColumnDisplay objects
*/
public static String buildTableTemplate( List<IFormColumnDisplay> listFormColumnDisplay, List<FormResponseItem> listFormResponseItem, Locale locale,
String strRedirectionDetailsBaseUrl, String strSortUrl, List<Integer> listIdFormResponsePaginated )
{
String strTableTemplate = StringUtils.EMPTY;
List<FormResponseItem> listFormResponseItemPaginated = buildFormResponseItemListToDisplay( listFormResponseItem, listIdFormResponsePaginated );
if ( !CollectionUtils.isEmpty( listFormColumnDisplay ) && !CollectionUtils.isEmpty( listFormResponseItem ) )
{
// Build the list of all from column header template
List<String> listFormColumnHeaderTemplate = buildFormColumnHeaderTemplateList( listFormColumnDisplay, locale, strSortUrl );
// Build the model
Map<String, Object> model = new LinkedHashMap<>( );
// Build the list of paginated FormColumnLineTemplate and optionally the geojson points
// We optimize by building the full lineTemplate list only when a map is used
// (it is used for the popups). If not, we build only the paginated list.
Optional<FormColumnDisplayEntryGeolocation> maybeFirstGeolocColumn = findFirstGeolocColumnDisplay( listFormColumnDisplay );
Optional<FormColumnDisplayEntryCartography> maybeFirstCartoColumn = findFirstCartographyColumnDisplay( listFormColumnDisplay );
Optional<IMultiviewMapProvider> maybeMapProvider = getMapProvider( );
List<FormColumnLineTemplate> listFormColumnLineTemplatePaginated;
if ( ( maybeFirstGeolocColumn.isPresent( ) && maybeMapProvider.isPresent( ) ) || maybeFirstCartoColumn.isPresent( ) )
{
List<FormColumnLineTemplate> listFormColumnLineTemplate = buildFormColumnLineTemplateList( listFormColumnDisplay, listFormResponseItem,
locale );
List<String> listGeoJsonPoints = new ArrayList<>( );
if ( maybeFirstCartoColumn.isPresent( ) )
{
listGeoJsonPoints.addAll( buildGeoJsonList( maybeFirstCartoColumn.get( ), listFormResponseItem, listFormColumnLineTemplate,
strRedirectionDetailsBaseUrl ) );
}
if ( maybeFirstGeolocColumn.isPresent( ) )
{
listGeoJsonPoints.addAll ( buildGeoJsonPointsList( maybeFirstGeolocColumn.get( ), listFormResponseItem, listFormColumnLineTemplate,
strRedirectionDetailsBaseUrl ) );
}
model.put( MARK_FROM_RESPONSE_GEOJSON_POINT_LIST, listGeoJsonPoints );
model.put( MARK_MULTIVIEWMAP, maybeMapProvider.get( ).getMapTemplate( ) );
listFormColumnLineTemplatePaginated = buildFormColumnLineTemplateList( listFormColumnLineTemplate, listIdFormResponsePaginated );
}
else
{
listFormColumnLineTemplatePaginated = buildFormColumnLineTemplateList( listFormColumnDisplay, listFormResponseItemPaginated, locale );
}
model.put( MARK_FORM_RESPONSE_COLUMN_HEADER_TEMPLATE_LIST, listFormColumnHeaderTemplate );
model.put( MARK_FROM_RESPONSE_ITEM_LIST, listFormResponseItemPaginated );
model.put( MARK_FORM_RESPONSE_LINE_TEMPLATE_LIST, listFormColumnLineTemplatePaginated );
model.put( MARK_FORM_RESPONSE_DETAILS_REDIRECT_BASE_URL, strRedirectionDetailsBaseUrl );
strTableTemplate = AppTemplateService.getTemplate( TEMPLATE_MULTIVIEW_FORM_TABLE, locale, model ).getHtml( );
}
return strTableTemplate;
}
private static Optional<IMultiviewMapProvider> getMapProvider( )
{
// getBean( BEAN_NAME ) throws if the bean is not defined, so check before to avoid an exception
// Also check if the plugin of the bean is enabled because the getBean doesn't..
if ( SpringContextService.getContext( ).containsBean( BEAN_NAME_MULTIVIEWMAP ) )
{
String strOriginalBeanName = SpringContextService.getContext( ).getAliases( BEAN_NAME_MULTIVIEWMAP ) [0];
// TODO copypasted from SpringContextService, maybe this need to be public
int nPos = strOriginalBeanName.indexOf( '.' );
String strPrefix = null;
if ( nPos > 0 )
{
strPrefix = strOriginalBeanName.substring( 0, nPos );
}
Plugin plugin = null;
if ( strPrefix != null )
{
plugin = PluginService.getPlugin( strPrefix );
}
if ( plugin == null || plugin.isInstalled( ) ) // A bean without a plugin is always enabled (core beans)
{
return Optional.of( SpringContextService.getBean( BEAN_NAME_MULTIVIEWMAP ) );
}
}
return Optional.empty( );
}
private static List<String> buildGeoJsonPointsList( FormColumnDisplayEntryGeolocation geolocFormColumnDisplay, List<FormResponseItem> listFormResponseItem,
List<FormColumnLineTemplate> listFormColumnLineTemplate, String strRedirectionDetailsBaseUrl )
{
return IntStream
.range( 0, listFormResponseItem.size( ) ).mapToObj( i -> buildResponseItemJson( geolocFormColumnDisplay, listFormResponseItem.get( i ),
listFormColumnLineTemplate.get( i ), strRedirectionDetailsBaseUrl ) )
.filter( Optional::isPresent ).map( Optional::get ).collect( Collectors.toList( ) );
}
private static Optional<String> buildResponseItemJson( FormColumnDisplayEntryGeolocation formColumnDisplayEntryGeolocation,
FormResponseItem formResponseItem, FormColumnLineTemplate formColumnlineTemplate, String strRedirectionDetailsBaseUrl )
{
int nColumnCellPosition = columnDisplayPositionToCellIndex( formColumnDisplayEntryGeolocation.getPosition( ) );
FormColumnCell geolocFormColumnCell = formResponseItem.getFormColumnCellValues( ).get( nColumnCellPosition );
List<Object> listStoredCoordinates = formColumnDisplayEntryGeolocation.buildXYList( geolocFormColumnCell );
List<Object> listValidatedCoordinates = listStoredCoordinates.stream( ).filter( Objects::nonNull ).map( ( Object str ) -> {
try
{
return _mapper.readValue( (String) str, Number.class );
}
catch( IOException e )
{
throw new AppException( "Error reading coordinates for formResponseItem idFormResponse=" + formResponseItem.getIdFormResponse( ) + " : " + str,
e );
}
} ).collect( Collectors.toList( ) );
if ( listValidatedCoordinates.size( ) != 2 )
{
return Optional.empty( );
}
Map<String, Object> root = new HashMap<>( );
Map<String, Object> geometry = new HashMap<>( );
root.put( GEOJSON_TYPE, GEOJSON_TYPE_FEATURE );
root.put( GEOJSON_GEOMETRY, geometry );
geometry.put( GEOJSON_COORDINATES, listValidatedCoordinates );
geometry.put( GEOJSON_TYPE, GEOJSON_GEOMETRY_TYPE_POINT );
Map<String, Object> properties = new HashMap<>( );
properties.put( PROPERTY_POPUP_CONTENT, buildPopupContent( formColumnlineTemplate, strRedirectionDetailsBaseUrl ) );
root.put( GEOJSON_PROPERTIES, properties );
try
{
return Optional.of( _mapper.writeValueAsString( root ) );
}
catch( JsonProcessingException e )
{
throw new AppException( "Error creating json for formResponseItem idFormResponse=" + formResponseItem.getIdFormResponse( ), e );
}
}
private static List<String> buildGeoJsonList( FormColumnDisplayEntryCartography geolocFormColumnDisplay, List<FormResponseItem> listFormResponseItem,
List<FormColumnLineTemplate> listFormColumnLineTemplate, String strRedirectionDetailsBaseUrl )
{
return IntStream
.range( 0, listFormResponseItem.size( ) ).mapToObj( i -> buildResponseItemGeoJson( geolocFormColumnDisplay, listFormResponseItem.get( i ),
listFormColumnLineTemplate.get( i ), strRedirectionDetailsBaseUrl ) )
.filter( Optional::isPresent ).map( Optional::get ).collect( Collectors.toList( ) );
}
private static Optional<String> buildResponseItemGeoJson( FormColumnDisplayEntryCartography formColumnDisplayEntryCartography,
FormResponseItem formResponseItem, FormColumnLineTemplate formColumnlineTemplate, String strRedirectionDetailsBaseUrl )
{
int nColumnCellPosition = columnDisplayPositionToCellIndex( formColumnDisplayEntryCartography.getPosition( ) );
FormColumnCell geolocFormColumnCell = formResponseItem.getFormColumnCellValues( ).get( nColumnCellPosition );
Object strGeoJSONCoordinate = formColumnDisplayEntryCartography.buildGeoJsonString( geolocFormColumnCell );
if ( strGeoJSONCoordinate != null )
{
return Optional.of( String.valueOf( strGeoJSONCoordinate ) );
}
else
{
return Optional.empty( );
}
}
private static String buildPopupContent( FormColumnLineTemplate formColumnlineTemplate, String strRedirectionDetailsBaseUrl )
{
Map<String, Object> model = new HashMap<>( );
model.put( MARK_FORM_RESPONSE_DETAILS_REDIRECT_BASE_URL, strRedirectionDetailsBaseUrl );
model.put( MARK_FORM_RESPONSE_LINE_TEMPLATE, formColumnlineTemplate );
return AppTemplateService.getTemplate( TEMPLATE_MULTIVIEW_GEOJSON_POPUP, null, model ).getHtml( );
}
/**
* Build the list of all form column header template
*
* @param listFormColumnDisplay
* The list of all form column display to retrieve the header template from
* @param locale
* The locale to use for build the template
* @param strSortUrl
* The url to use for sort a column (can be null)
* @return the list of all form column header template
*/
private static List<String> buildFormColumnHeaderTemplateList( List<IFormColumnDisplay> listFormColumnDisplay, Locale locale, String strSortUrl )
{
List<String> listFormColumnHeaderTemplate = new ArrayList<>( );
if ( !CollectionUtils.isEmpty( listFormColumnDisplay ) )
{
for ( IFormColumnDisplay formColumnDisplay : listFormColumnDisplay )
{
String strFormColumnDisplayHeaderTemplate = formColumnDisplay.buildFormColumnHeaderTemplate( strSortUrl, locale );
listFormColumnHeaderTemplate.add( strFormColumnDisplayHeaderTemplate );
}
}
return listFormColumnHeaderTemplate;
}
/**
* Build the list of all FormColumnLineTemplate
*
* @param listFormColumnDisplay
* The list of form column display to use for build the map of line template
* @param listFormResponseItem
* The list of form response item to use for build the map of line template
* @param locale
* The locale to use for build the template
* @return the list of all FormColumnLineTemplate
*/
private static List<FormColumnLineTemplate> buildFormColumnLineTemplateList( List<IFormColumnDisplay> listFormColumnDisplay,
List<FormResponseItem> listFormResponseItem, Locale locale )
{
List<FormColumnLineTemplate> listFormColumnLineTemplate = new ArrayList<>( );
if ( !CollectionUtils.isEmpty( listFormColumnDisplay ) && !CollectionUtils.isEmpty( listFormResponseItem ) )
{
for ( FormResponseItem formResponseItem : listFormResponseItem )
{
int nIdFormResponse = formResponseItem.getIdFormResponse( );
FormColumnLineTemplate formResponseColumnLineTemplate = new FormColumnLineTemplate( nIdFormResponse );
List<FormColumnCell> listFormColumnCell = formResponseItem.getFormColumnCellValues( );
populateLineTemplateFromCellValues( formResponseColumnLineTemplate, listFormColumnCell, listFormColumnDisplay, locale );
listFormColumnLineTemplate.add( formResponseColumnLineTemplate );
}
}
return listFormColumnLineTemplate;
}
/**
* Populate the given FormColumnLineTemplate with the value from the list of FormColumnCell and the display from the given list of IFormColumnDisplay
*
* @param formColumnLineTemplate
* The FormColumnLineTemplate to populate
* @param listFormColumnCell
* The list of FormColumnCell to retrieve the values from
* @param listFormColumnDisplay
* The list of IFormColumnDisplay to use for the column
* @param locale
* The locale to use for build the templates
*/
private static void populateLineTemplateFromCellValues( FormColumnLineTemplate formColumnLineTemplate, List<FormColumnCell> listFormColumnCell,
List<IFormColumnDisplay> listFormColumnDisplay, Locale locale )
{
if ( !CollectionUtils.isEmpty( listFormColumnCell ) )
{
for ( int index = 0; index < listFormColumnCell.size( ); index++ )
{
int nColumnDisplayPosition = cellIndexToColumnDisplayPosition( index );
IFormColumnDisplay formColumnDisplay = findFormColumnDisplayByPosition( nColumnDisplayPosition, listFormColumnDisplay );
if ( formColumnDisplay != null )
{
FormColumnCell formColumnCell = listFormColumnCell.get( index );
String strFormColunmCellTemplate = formColumnDisplay.buildFormColumnCellTemplate( formColumnCell, locale );
formColumnLineTemplate.addFormColumnCellTemplate( strFormColunmCellTemplate );
}
}
}
}
/**
* Convert from columnDisplayPosition to cell index.
*
* The positions start at index 1 and cells at index 0..
*
* @param nCellIndex
* the cell index
* @return the position of the corresponding ColumnDisplay
* @see columnDisplayPositionToCellIndex
*/
private static int cellIndexToColumnDisplayPosition( int nCellIndex )
{
return nCellIndex + 1;
}
/**
* Convert from cell index to columnDisplayPosition.
*
* The positions start at index 1 and cells at index 0..
*
* @param nColumnDisplayPosition
* the column position
* @return the cell index of the corresponding FormColumnCell
* @see cellIndexToColumnDisplayPosition
*/
private static int columnDisplayPositionToCellIndex( int nColumnDisplayPosition )
{
return nColumnDisplayPosition - 1;
}
/**
* Find the FormColumnDisplay in the given list with the specified position or null if not found
*
* @param nFormColumnPosition
* The position of the FormColumnDisplay to retrieve
* @param listFormColumnDisplay
* The list of FormColumnDisplay from where to find the column with the specified position
* @return the FormColumnDisplay with the specified position nor null if not found
*/
private static IFormColumnDisplay findFormColumnDisplayByPosition( int nFormColumnPosition, List<IFormColumnDisplay> listFormColumnDisplay )
{
IFormColumnDisplay formColumnDisplayResult = null;
if ( !CollectionUtils.isEmpty( listFormColumnDisplay ) )
{
for ( IFormColumnDisplay formColumnDisplay : listFormColumnDisplay )
{
if ( formColumnDisplay.getPosition( ) == nFormColumnPosition )
{
formColumnDisplayResult = formColumnDisplay;
break;
}
}
}
return formColumnDisplayResult;
}
/**
* Build the list of FormResponseItem to display for the active FormPanelDisplay based on the number of items of the current paginator
*
* @return list of FormResponseItem to display for the active FormPanelDisplay
*/
private static List<FormResponseItem> buildFormResponseItemListToDisplay( List<FormResponseItem> listFormResponseItem,
List<Integer> listIdFormResponsePaginated )
{
List<FormResponseItem> listFormResponseItemToDisplay = new ArrayList<>( );
if ( !CollectionUtils.isEmpty( listIdFormResponsePaginated ) && !CollectionUtils.isEmpty( listFormResponseItem ) )
{
for ( FormResponseItem formResponseItem : listFormResponseItem )
{
Integer nIdFormResponse = formResponseItem.getIdFormResponse( );
if ( listIdFormResponsePaginated.contains( nIdFormResponse ) )
{
listFormResponseItemToDisplay.add( formResponseItem );
}
}
}
return listFormResponseItemToDisplay;
}
/**
* Build the list of all FormColumnLineTemplate
*
* @return list of FormColumnLineTemplate to display for the active FormPanelDisplay
*/
private static List<FormColumnLineTemplate> buildFormColumnLineTemplateList( List<FormColumnLineTemplate> listFormColumnLineTemplate,
List<Integer> listIdFormResponsePaginated )
{
List<FormColumnLineTemplate> listFormColumnLineTemplateToDisplay = new ArrayList<>( );
if ( !CollectionUtils.isEmpty( listIdFormResponsePaginated ) && !CollectionUtils.isEmpty( listFormColumnLineTemplate ) )
{
for ( FormColumnLineTemplate formColumnLineTemplate : listFormColumnLineTemplate )
{
Integer nIdFormResponse = formColumnLineTemplate.getIdFormResponse( );
if ( listIdFormResponsePaginated.contains( nIdFormResponse ) )
{
listFormColumnLineTemplateToDisplay.add( formColumnLineTemplate );
}
}
}
return listFormColumnLineTemplateToDisplay;
}
/**
* Get the first geoloc column display if it exists.
*
* Note: this could be replaced with a method to choose the geoloccolumn if we wanted. Note: this could also be replaced with something to return geojson
* multipoints.
*
* @param listFormColumnDisplay
* The list of all form column display to retrieve the header template from
* @param strSortUrl
* The url to use for sort a column (can be null)
* @return the list of all form column header template
*/
private static Optional<FormColumnDisplayEntryGeolocation> findFirstGeolocColumnDisplay( List<IFormColumnDisplay> listFormColumnDisplay )
{
return listFormColumnDisplay.stream( ).filter( FormColumnDisplayEntryGeolocation.class::isInstance )
.map( FormColumnDisplayEntryGeolocation.class::cast ).findFirst( );
}
/**
* Get the first carto column display if it exists.
*
* Note: this could be replaced with a method to choose the cartocolumn if we wanted. Note: this could also be replaced with something to return geojson
* multipoints.
*
* @param listFormColumnDisplay
* The list of all form column display to retrieve the header template from
* @param strSortUrl
* The url to use for sort a column (can be null)
* @return the list of all form column header template
*/
private static Optional<FormColumnDisplayEntryCartography> findFirstCartographyColumnDisplay( List<IFormColumnDisplay> listFormColumnDisplay )
{
return listFormColumnDisplay.stream( ).filter( FormColumnDisplayEntryCartography.class::isInstance )
.map( FormColumnDisplayEntryCartography.class::cast ).findFirst( );
}
}