WorkflowProvider.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.workflow.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;

import fr.paris.lutece.api.user.User;
import fr.paris.lutece.plugins.workflow.service.prerequisite.IManualActionPrerequisiteService;
import fr.paris.lutece.plugins.workflow.utils.WorkflowUtils;
import fr.paris.lutece.plugins.workflowcore.business.action.Action;
import fr.paris.lutece.plugins.workflowcore.business.prerequisite.IPrerequisiteConfig;
import fr.paris.lutece.plugins.workflowcore.business.prerequisite.Prerequisite;
import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceHistory;
import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceWorkflow;
import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceWorkflowFilter;
import fr.paris.lutece.plugins.workflowcore.business.state.State;
import fr.paris.lutece.plugins.workflowcore.business.state.StateFilter;
import fr.paris.lutece.plugins.workflowcore.business.workflow.Workflow;
import fr.paris.lutece.plugins.workflowcore.business.workflow.WorkflowFilter;
import fr.paris.lutece.plugins.workflowcore.service.action.IActionService;
import fr.paris.lutece.plugins.workflowcore.service.prerequisite.IAutomaticActionPrerequisiteService;
import fr.paris.lutece.plugins.workflowcore.service.prerequisite.IPrerequisiteManagementService;
import fr.paris.lutece.plugins.workflowcore.service.resource.IResourceHistoryService;
import fr.paris.lutece.plugins.workflowcore.service.resource.IResourceWorkflowService;
import fr.paris.lutece.plugins.workflowcore.service.state.IStateService;
import fr.paris.lutece.plugins.workflowcore.service.task.ITask;
import fr.paris.lutece.plugins.workflowcore.service.task.ITaskService;
import fr.paris.lutece.plugins.workflowcore.service.workflow.IWorkflowService;
import fr.paris.lutece.plugins.workflowcore.web.task.ITaskComponentManager;
import fr.paris.lutece.portal.business.user.AdminUserHome;
import fr.paris.lutece.portal.service.admin.AdminUserService;
import fr.paris.lutece.portal.service.plugin.PluginService;
import fr.paris.lutece.portal.service.rbac.RBACService;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.workflow.IWorkflowProvider;
import fr.paris.lutece.portal.service.workgroup.AdminWorkgroupService;
import fr.paris.lutece.util.ReferenceList;
import fr.paris.lutece.util.html.HtmlTemplate;

/**
 *
 * WorkflowProvider
 *
 */
public class WorkflowProvider implements IWorkflowProvider
{
    // MARKS
    private static final String MARK_RESOURCE_HISTORY = "resource_history";
    private static final String MARK_TASK_INFORMATION_LIST = "task_information_list";
    private static final String MARK_USER_HISTORY = "user_history";
    private static final String MARK_HISTORY_INFORMATION_LIST = "history_information_list";
    private static final String MARK_TASK_FORM_ENTRY_LIST = "task_form_entry_list";
    private static final String MARK_ADMIN_AVATAR = "adminAvatar";

    // TEMPLATES
    private static final String TEMPLATE_RESOURCE_HISTORY = "admin/plugins/workflow/resource_history.html";
    private static final String TEMPLATE_TASKS_FORM = "admin/plugins/workflow/tasks_form.html";

    // SERVICES
    @Inject
    private IActionService _actionService;
    @Inject
    private IResourceWorkflowService _resourceWorkflowService;
    @Inject
    private IResourceHistoryService _resourceHistoryService;
    @Inject
    private IStateService _stateService;
    @Inject
    private ITaskService _taskService;
    @Inject
    private IWorkflowService _workflowService;
    @Inject
    private ITaskComponentManager _taskComponentManager;
    @Inject
    private IPrerequisiteManagementService _prerequisiteManagementService;

    /**
     * {@inheritDoc}
     */
    // @Override don't declare as Override to be compatible with older Lutece Core version
    public Collection<Action> getActions( int nIdResource, String strResourceType, Collection<Action> listActions, User user )
    {
        listActions = listActions.stream( ).filter( a -> canActionBeProcessed( user, nIdResource, strResourceType, a.getId( ) ) )
                .collect( Collectors.toList( ) );
        return RBACService.getAuthorizedCollection( listActions, ActionResourceIdService.PERMISSION_VIEW, user );
    }

    /**
     * {@inheritDoc}
     */
    // @Override don't declare as Override to be compatible with older Lutece Core version
    public Collection<Action> getAuthorizedActions( Collection<Action> listActions, User user )
    {
        return RBACService.getAuthorizedCollection( listActions, ActionResourceIdService.PERMISSION_VIEW, user );
    }

    /**
     * {@inheritDoc}
     */
    // @Override don't declare as Override to be compatible with older Lutece Core version
    public Map<Integer, List<Action>> getActions( String strResourceType, Map<Integer, List<Action>> mapActions, User user )
    {
        for ( Entry<Integer, List<Action>> entry : mapActions.entrySet( ) )
        {
            List<Action> listActions = entry.getValue( );
            listActions = listActions.stream( ).filter( a -> canActionBeProcessed( user, entry.getKey( ), strResourceType, a.getId( ) ) )
                    .collect( Collectors.toList( ) );
            listActions = (List<Action>) RBACService.getAuthorizedCollection( listActions, ActionResourceIdService.PERMISSION_VIEW, user );
            mapActions.put( entry.getKey( ), listActions );
        }

        return mapActions;
    }

    private boolean canActionBeProcessed( User user, int nIdResource, String strResourceType, int nIdAction )
    {
        for ( Prerequisite prerequisite : _prerequisiteManagementService.getListPrerequisite( nIdAction ) )
        {
            IAutomaticActionPrerequisiteService prerequisiteService = _prerequisiteManagementService
                    .getPrerequisiteService( prerequisite.getPrerequisiteType( ) );

            IPrerequisiteConfig config = _prerequisiteManagementService.getPrerequisiteConfiguration( prerequisite.getIdPrerequisite( ), prerequisiteService );
            boolean canBePerformed = false;
            if ( prerequisiteService instanceof IManualActionPrerequisiteService )
            {
                canBePerformed = ( (IManualActionPrerequisiteService) prerequisiteService ).canManualActionBePerformed( user, nIdResource, strResourceType,
                        config, nIdAction );
            }
            else
            {
                canBePerformed = prerequisiteService.canActionBePerformed( nIdResource, strResourceType, config, nIdAction );
            }

            if ( !canBePerformed )
            {
                return false;
            }
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<State> getAllStateByWorkflow( Collection<State> listStates, User user )
    {
        return RBACService.getAuthorizedCollection( listStates, StateResourceIdService.PERMISSION_VIEW, user );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getAuthorizedResourceList( String strResourceType, int nIdWorkflow, int nIdWorkflowState, Integer nExternalParentId, User user )
    {
        if ( nIdWorkflowState < 1 )
        {
            return this.getAuthorizedResourceList( strResourceType, nIdWorkflow, null, nExternalParentId, user );
        }

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

        State state = _stateService.findByPrimaryKey( nIdWorkflowState );

        ResourceWorkflowFilter resourceWorkflowFilter = new ResourceWorkflowFilter( );

        if ( user != null )
        {
            if ( RBACService.isAuthorized( state, StateResourceIdService.PERMISSION_VIEW, user ) )
            {
                if ( Boolean.TRUE.equals( state.isRequiredWorkgroupAssigned( ) ) )
                {

                    ReferenceList refWorkgroupKey = getUserWorkgroups( user );
                    resourceWorkflowFilter.setWorkgroupKeyList( refWorkgroupKey.toMap( ) );
                }

                resourceWorkflowFilter.setIdState( state.getId( ) );
                resourceWorkflowFilter.setIdWorkflow( nIdWorkflow );
                resourceWorkflowFilter.setResourceType( strResourceType );
                resourceWorkflowFilter.setExternalParentId( nExternalParentId );
                resourceIdList = _resourceWorkflowService.getListResourceIdWorkflowByFilter( resourceWorkflowFilter );
            }
        }
        else
        // WARNING : if content "user!=null" because for the batch the user is null, for the other case the user is not null
        {
            resourceWorkflowFilter.setIdState( state.getId( ) );
            resourceWorkflowFilter.setIdWorkflow( nIdWorkflow );
            resourceWorkflowFilter.setResourceType( strResourceType );
            resourceWorkflowFilter.setExternalParentId( nExternalParentId );
            resourceIdList = _resourceWorkflowService.getListResourceIdWorkflowByFilter( resourceWorkflowFilter );
        }

        return resourceIdList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Integer> getAuthorizedResourceList( String strResourceType, int nIdWorkflow, List<Integer> lListIdWorkflowState, Integer nExternalParentId,
            User user )
    {
        List<Integer> lListAutorizedIdSate = new ArrayList<>( );

        StateFilter stateFilter = new StateFilter( );
        stateFilter.setIdWorkflow( nIdWorkflow );

        Collection<State> listState = _stateService.getListStateByFilter( stateFilter );

        for ( State state : listState )
        {
            Integer nIdState = state.getId( );
            if ( lListIdWorkflowState == null || lListIdWorkflowState.contains( nIdState ) )
            {
                if ( user != null )
                {
                    if ( RBACService.isAuthorized( state, StateResourceIdService.PERMISSION_VIEW, user ) )
                    {
                        lListAutorizedIdSate.add( nIdState );
                    }
                }
                else
                // WARNING : if content "user!=null" because for the batch the user is null, for the other case the user is not null
                {
                    lListAutorizedIdSate.add( nIdState );
                }
            }
        }

        ResourceWorkflowFilter resourceWorkflowFilter = new ResourceWorkflowFilter( );
        resourceWorkflowFilter.setIdState( ResourceWorkflowFilter.ALL_INT );
        resourceWorkflowFilter.setIdWorkflow( nIdWorkflow );
        resourceWorkflowFilter.setResourceType( strResourceType );
        resourceWorkflowFilter.setExternalParentId( nExternalParentId );

        if ( user != null )
        {
            ReferenceList refWorkgroupKey = getUserWorkgroups( user );
            resourceWorkflowFilter.setWorkgroupKeyList( refWorkgroupKey.toMap( ) );
        }

        return _resourceWorkflowService.getListResourceIdWorkflowByFilter( resourceWorkflowFilter, lListAutorizedIdSate );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDisplayDocumentHistory( int nIdResource, String strResourceType, int nIdWorkflow, HttpServletRequest request, Locale locale, User user )
    {
        return getDisplayDocumentHistory( nIdResource, strResourceType, nIdWorkflow, request, locale, null, TEMPLATE_RESOURCE_HISTORY, user );
    }

    /**
     * Implements IWorkflowProvider of Lutece Core version 5.1
     * 
     * @param nIdResource
     *            The resource
     * @param strResourceType
     *            The resource type
     * @param nIdWorkflow
     *            the workflow id
     * @param request
     *            The request
     * @param locale
     *            The locale
     * @param model
     *            The model to add to the default model
     * @param strTemplate
     *            The template
     * @return The HTML code to display
     */

    // @Override don't declare as Override to be compatible with older Lutece Core version
    public String getDisplayDocumentHistory( int nIdResource, String strResourceType, int nIdWorkflow, HttpServletRequest request, Locale locale,
            Map<String, Object> model, String strTemplate, User user )
    {
        Map<String, Object> defaultModel = getDefaultModelDocumentHistory( nIdResource, strResourceType, nIdWorkflow, request, locale );

        if ( model != null )
        {
            defaultModel.putAll( model );
        }

        HtmlTemplate templateList = AppTemplateService.getTemplate( strTemplate, locale, defaultModel );

        return templateList.getHtml( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDisplayTasksForm( int nIdResource, String strResourceType, int nIdAction, HttpServletRequest request, Locale locale, User user )
    {
        List<ITask> listTasks = _taskService.getListTaskByIdAction( nIdAction, locale );
        List<String> listFormEntry = new ArrayList<>( );
        String strFormEntry;

        for ( ITask task : listTasks )
        {
            strFormEntry = _taskComponentManager.getDisplayTaskForm( nIdResource, strResourceType, request, locale, task );

            if ( strFormEntry != null )
            {
                listFormEntry.add( strFormEntry );
            }
        }

        Map<String, Object> model = new HashMap<>( );

        model.put( MARK_TASK_FORM_ENTRY_LIST, listFormEntry );

        HtmlTemplate templateList = AppTemplateService.getTemplate( TEMPLATE_TASKS_FORM, locale, model );

        return templateList.getHtml( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ReferenceList getWorkflowsEnabled( User user, Locale locale )
    {
        return WorkflowUtils.getRefList( getWorkflowsEnabled( user ), true, locale );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getUserAccessCode( HttpServletRequest request, User user )
    {
        String strAccessCode = null;
        if ( user == null )
        { /// get user in the httpservletRequest
            user = getUserInRequest( request );
        }

        if ( user != null )
        {
            strAccessCode = user.getAccessCode( );
        }
        return strAccessCode;
    }

    // CHECK

    /**
     * {@inheritDoc}
     */
    // @Override don't declare as Override to be compatible with older Lutece Core version
    public boolean canProcessAction( int nIdResource, String strResourceType, int nIdAction, HttpServletRequest request, User user )
    {
        if ( user == null )
        { // get user in the httpservletRequest
            user = getUserInRequest( request );
        }

        if ( user != null )
        {
            Action action = _actionService.findByPrimaryKey( nIdAction );
            return canActionBeProcessed( user, nIdResource, strResourceType, nIdAction )
                    && RBACService.isAuthorized( action, ActionResourceIdService.PERMISSION_VIEW, user );
        }

        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAuthorized( int nIdResource, String strResourceType, int nIdWorkflow, User user )
    {
        boolean bReturn = false;
        State resourceState = null;
        ResourceWorkflow resourceWorkflow = _resourceWorkflowService.findByPrimaryKey( nIdResource, strResourceType, nIdWorkflow );

        if ( resourceWorkflow != null )
        {
            resourceState = _stateService.findByPrimaryKey( resourceWorkflow.getState( ).getId( ) );
        }
        else
        {
            // Get initial state
            StateFilter filter = new StateFilter( );
            filter.setIsInitialState( StateFilter.FILTER_TRUE );
            filter.setIdWorkflow( nIdWorkflow );

            List<State> listState = _stateService.getListStateByFilter( filter );

            if ( CollectionUtils.isNotEmpty( listState ) )
            {
                resourceState = listState.get( 0 );
            }
        }

        if ( resourceState == null || !RBACService.isAuthorized( resourceState, StateResourceIdService.PERMISSION_VIEW, user ) )
        {
            return bReturn;
        }

        if ( Boolean.TRUE.equals( resourceState.isRequiredWorkgroupAssigned( ) ) && ( resourceWorkflow != null ) )
        {

            for ( String strWorkgroup : resourceWorkflow.getWorkgroups( ) )
            {
                if ( isUserInWorkgroup( user, strWorkgroup )
                        || RBACService.isAuthorized( resourceState, StateResourceIdService.PERMISSION_VIEW_ALL_WORKGROUP, user ) )
                {
                    bReturn = true;

                    break;
                }
            }
        }
        else
        {
            bReturn = true;
        }

        return bReturn;
    }

    // DO

    /**
     * {@inheritDoc}
     */
    @Override
    public String doValidateTasksForm( int nIdResource, String strResourceType, int nIdAction, HttpServletRequest request, Locale locale, User user )
    {
        List<ITask> listTasks = _taskService.getListTaskByIdAction( nIdAction, locale );
        String strError = null;

        for ( ITask task : listTasks )
        {
            strError = _taskComponentManager.doValidateTask( nIdResource, strResourceType, request, locale, task );

            if ( strError != null )
            {
                return strError;
            }
        }

        return null;
    }

    // PRIVATE METHODS

    /**
     * Return a collection witch contains a list enabled workflow
     * 
     * @param user
     *            the User
     * @return a collection witch contains a list enabled workflow
     */
    private Collection<Workflow> getWorkflowsEnabled( User user )
    {
        WorkflowFilter filter = new WorkflowFilter( );
        filter.setIsEnabled( WorkflowFilter.FILTER_TRUE );

        List<Workflow> listWorkflow = _workflowService.getListWorkflowsByFilter( filter );

        return AdminWorkgroupService.getAuthorizedCollection( listWorkflow, user );
    }

    /**
     * returns the default model to build history performed on a resource.
     *
     * @param nIdResource
     *            the resource id
     * @param strResourceType
     *            the resource type
     * @param nIdWorkflow
     *            the workflow id
     * @param request
     *            the request
     * @param locale
     *            the locale
     * @return the default model
     */
    private Map<String, Object> getDefaultModelDocumentHistory( int nIdResource, String strResourceType, int nIdWorkflow, HttpServletRequest request,
            Locale locale )
    {
        List<ResourceHistory> listResourceHistory = _resourceHistoryService.getAllHistoryByResource( nIdResource, strResourceType, nIdWorkflow );
        List<ITask> listActionTasks;
        List<String> listTaskInformation;
        Map<String, Object> model = new HashMap<>( );
        Map<String, Object> resourceHistoryTaskInformation;
        List<Map<String, Object>> listResourceHistoryTaskInformation = new ArrayList<>( );
        String strTaskinformation;

        for ( ResourceHistory resourceHistory : listResourceHistory )
        {
            resourceHistoryTaskInformation = new HashMap<>( );
            resourceHistoryTaskInformation.put( MARK_RESOURCE_HISTORY, resourceHistory );

            if ( resourceHistory.getUserAccessCode( ) != null )
            {
                if ( resourceHistory.getResourceUserHistory( ) != null )
                {
                    resourceHistoryTaskInformation.put( MARK_USER_HISTORY, resourceHistory.getResourceUserHistory( ) );
                }
                else
                {
                    resourceHistoryTaskInformation.put( MARK_USER_HISTORY, getUserByAccessCode( resourceHistory.getUserAccessCode( ) ) );
                }
            }

            listTaskInformation = new ArrayList<>( );
            listActionTasks = _taskService.getListTaskByIdAction( resourceHistory.getAction( ).getId( ), locale );

            for ( ITask task : listActionTasks )
            {
                strTaskinformation = _taskComponentManager.getDisplayTaskInformation( resourceHistory.getId( ), request, locale, task );

                if ( strTaskinformation != null )
                {
                    listTaskInformation.add( strTaskinformation );
                }
            }

            resourceHistoryTaskInformation.put( MARK_TASK_INFORMATION_LIST, listTaskInformation );

            listResourceHistoryTaskInformation.add( resourceHistoryTaskInformation );
        }

        model.put( MARK_HISTORY_INFORMATION_LIST, listResourceHistoryTaskInformation );
        model.put( MARK_ADMIN_AVATAR, PluginService.isPluginEnable( "adminavatar" ) );

        return model;
    }

    /**
     * Method used when the user is not provided.
     * 
     * @param request
     *            the httpServletRequest
     * @return the user in the request
     */
    private User getUserInRequest( HttpServletRequest request )
    {
        return request != null ? AdminUserService.getAdminUser( request ) : null;
    }

    /**
     * Return a ReferenceList witch contains the user workgoups
     * 
     * @param user
     *            the user
     * @return a ReferenceList witch contains the user workgoups
     */

    private ReferenceList getUserWorkgroups( User user )
    {

        ReferenceList refListWorkgroup = new ReferenceList( );
        if ( user.getUserWorkgroups( ) != null )
        {
            user.getUserWorkgroups( ).forEach( x -> refListWorkgroup.addItem( x, x ) );
        }
        return refListWorkgroup;

    }

    /**
     * Return true if the user is in the workgoup
     * 
     * @param user
     *            the user
     * @param strWorkgroup
     *            the workgroup
     * @return true if the user is in the workgroup
     */
    private boolean isUserInWorkgroup( User user, String strWorkgroup )
    {
        if ( user.getUserWorkgroups( ) != null )
        {
            return user.getUserWorkgroups( ).stream( ).anyMatch( x -> x.equals( strWorkgroup ) );
        }
        return false;
    }

    // TODO provide UserInfo depending the User type who made the action
    /**
     * get a User by Access Code
     * 
     * @param strAccessCode
     *            the strAccessCode
     * @return a user by access code
     */
    private User getUserByAccessCode( String strAccessCode )
    {
        return AdminUserHome.findUserByLogin( strAccessCode );
    }

}