FormsResponseUtils.java

package fr.paris.lutece.plugins.forms.util;

import java.sql.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import fr.paris.lutece.plugins.forms.business.Control;
import fr.paris.lutece.plugins.forms.business.ControlHome;
import fr.paris.lutece.plugins.forms.business.ControlType;
import fr.paris.lutece.plugins.forms.business.Form;
import fr.paris.lutece.plugins.forms.business.FormHome;
import fr.paris.lutece.plugins.forms.business.FormQuestionResponse;
import fr.paris.lutece.plugins.forms.business.FormResponse;
import fr.paris.lutece.plugins.forms.business.FormResponseHome;
import fr.paris.lutece.plugins.forms.business.Question;
import fr.paris.lutece.plugins.forms.business.QuestionHome;
import fr.paris.lutece.plugins.forms.business.Step;
import fr.paris.lutece.plugins.forms.business.StepHome;
import fr.paris.lutece.plugins.forms.business.Transition;
import fr.paris.lutece.plugins.forms.business.TransitionHome;
import fr.paris.lutece.plugins.forms.exception.QuestionValidationException;
import fr.paris.lutece.plugins.forms.service.EntryServiceManager;
import fr.paris.lutece.plugins.forms.validation.IValidator;
import fr.paris.lutece.plugins.forms.web.FormResponseData;
import fr.paris.lutece.plugins.forms.web.FormResponseManager;
import fr.paris.lutece.plugins.forms.web.FormResponseXPage;
import fr.paris.lutece.plugins.forms.web.StepDisplayTree;
import fr.paris.lutece.plugins.forms.web.entrytype.DisplayType;
import fr.paris.lutece.plugins.forms.web.entrytype.IEntryDataService;
import fr.paris.lutece.plugins.genericattributes.business.GenericAttributeError;
import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
import fr.paris.lutece.plugins.workflowcore.business.state.State;
import fr.paris.lutece.plugins.workflowcore.service.state.IStateService;
import fr.paris.lutece.plugins.workflowcore.service.state.StateService;
import fr.paris.lutece.portal.business.file.FileHome;
import fr.paris.lutece.portal.business.physicalfile.PhysicalFileHome;
import fr.paris.lutece.portal.service.admin.AdminUserService;
import fr.paris.lutece.portal.service.message.SiteMessageException;
import fr.paris.lutece.portal.service.security.LuteceUser;
import fr.paris.lutece.portal.service.security.SecurityService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppPathService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.util.mvc.utils.MVCUtils;
import fr.paris.lutece.portal.web.l10n.LocaleService;
import fr.paris.lutece.util.url.UrlItem;

public class FormsResponseUtils 
{
    private static ConcurrentMap<Integer, Object> _lockFormId = new ConcurrentHashMap<>( );
    private static Map<Integer, Integer> _responsePerFormMap = new HashMap<>( );

	private FormsResponseUtils( )
	{
		
	}
	
	 /**
     * Check that a given user is allowed to access for manage the formResponse
     * 
     * @param formResponse
     *           the formsResponse
     * @param user
     *            the user trying to access the resource
     * @return true if the user can access for manage the given resource, false otherwise
     */
    public static boolean isAuthorized( FormResponse formResponse, LuteceUser user )
    {
        Form form= FormHome.findByPrimaryKey( formResponse.getFormId( ));
        return isAuthorized(  formResponse,  user, form );
    }
    /**
     * Check that a given user is allowed to access for manage the formResponse
     * 
     * @param formResponse
     *           the formsResponse
     * @param user
     *            the user trying to access the resource
     * @param form
     * 				the form
     * @return true if the user can access for manage the given resource, false otherwise
     */
    public static boolean isAuthorized( FormResponse formResponse, LuteceUser user, Form form )
    {
        boolean userOwnsReponse = user != null && formResponse.getGuid( ) != null && user.getName( ).equals( formResponse.getGuid( ) );

        if( user != null && form != null && form.isAccessToResponsesByRole( ) && formResponse.getRole() != null  ) 
        {           
            	return isUserHasRole( user, formResponse.getRole( ) ) && formResponse.getFormId() == form.getId( );
        }
        
        return userOwnsReponse && ( form != null && !form.isAccessToResponsesByRole( )) && formResponse.getFormId() == form.getId( ); 
    }
    
   /**
    * Filter formResponse by access rights for LuteceUser
    * @param formResponseList the list of fromResponse objects
    * @param user the luteceUser
    * @return list of FormResponse objects filtered
    */
    public static List<FormResponse> filterFormResponseForLuteceUser( List<FormResponse> formResponseList, LuteceUser user )
    {

        List<FormResponse> formResponseResult= new ArrayList<>( );
        if( CollectionUtils.isNotEmpty( formResponseList) ) 
        {
        	List<Form> listForm=FormHome.getFormByPrimaryKeyList( formResponseList.stream()
        			.map( FormResponse::getFormId )
        			.collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll));
        	
        	List<Integer>  listIdFormByRole=listForm.stream()
        					 .filter(Form::isAccessToResponsesByRole )
        					 .map(Form::getId )
        					 .collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
        	
        	formResponseList.forEach(( FormResponse frmRsp ) -> {
        		if( isAuthorized( listIdFormByRole,  frmRsp,  user ))
        		{
        			formResponseResult.add( frmRsp );
        		}
        	});
        }
        return formResponseResult;
    }
    /**
     * Filter and build formResponseData by access rights for LuteceUser
     * @param formResponseList the list of fromResponse objects
     * @param user the luteceUser
     * @return list of FormResponseData objects builded
     */
    public static List<FormResponseData> filterFormResponseListForLuteceUser( List<FormResponse> formResponseList, LuteceUser user )
    {
        List<FormResponseData> formResponseResult= new ArrayList<>( );
        if( CollectionUtils.isNotEmpty( formResponseList) ) 
        {
        	List<Form> listForm=FormHome.getFormByPrimaryKeyList( formResponseList.stream()
        			.map( FormResponse::getFormId )
        			.collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll));
        	
        	List<Integer>  listIdFormByRole=listForm.stream()
        					 .filter(Form::isAccessToResponsesByRole )
        					 .map(Form::getId )
        					 .collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
        	
        	formResponseList.forEach(( FormResponse frmRsp ) -> {
        		if( isAuthorized( listIdFormByRole,  frmRsp,  user ))
        		{
        			formResponseResult.add( buildFormResponseData(  frmRsp, listForm.stream().filter(frm->frm.getId()==frmRsp.getFormId()).findAny( )) );
        		}
        	});
        }
        return formResponseResult;
    }
    /**
     * Get FormResponse list By Role And Guid
     * @param user the LuteceUser
     * @return FormResponse objects list
     */
    public static List<FormResponse> getFormResponseListByRoleAndGuid( LuteceUser user )
    {
        List<FormResponse> formResponseListByGuid = FormResponseHome.getFormResponseByGuid( user.getName( ) );
        String[] userRoles=  SecurityService.getInstance().getRolesByUser(user);
        userRoles= (userRoles!= null && userRoles.length != 0) ? userRoles:user.getRoles( ); 
		List<FormResponse> formResponseList= (userRoles==null)? new ArrayList<>( ):FormResponseHome.getFormResponseByRole(Arrays.asList( userRoles ));
		
        formResponseListByGuid.forEach( (FormResponse formResponse) ->{
			if(formResponseList.stream().noneMatch( response -> response.getId() == formResponse.getId() )) 
			{
				formResponseList.add( formResponse );
			}
		});	
		return formResponseList ;
    }
    
    /**
     * Build FormResponseData
     * @param formResponse the from response
     * @param form the form
     * @return the FormResponseData builded
     */
    public static FormResponseData buildFormResponseData( FormResponse formResponse, Optional<Form> form)
    {
    	    IStateService stateService = SpringContextService.getBean( StateService.BEAN_SERVICE );

	        FormResponseData data = new FormResponseData( );
	        data.setIdFormResponse( formResponse.getId( ) );
	        data.setDateUpdate( new Date( formResponse.getUpdate( ).getTime( ) ) );
	        if ( form.isPresent())
	        {
	        	data.setFormTitle( form.get( ).getTitle( ) );
		        
		        if ( stateService != null )
		        {
		            State formResponseState = stateService.findByResource( formResponse.getId( ), FormResponse.RESOURCE_TYPE, form.get().getIdWorkflow( ) );
		            if ( formResponseState != null )
		            {
		                data.setWorkflowState( formResponseState.getName( ) );
		            }
		        }
	        }
	    return data;
    }
    
  /**
   * Build next Step object
   * @param nIdCurrentStep the id current step
   * @param errorMessageList the error message list
   * @param formResponseManager the formResponseManager
   * @return Step object
   */
    public static Step getNextStep( int nIdCurrentStep, List<String> errorMessageList, FormResponseManager formResponseManager )
    {
        List<Transition> listTransition = TransitionHome.getTransitionsListFromStep( nIdCurrentStep );

        for ( Transition transition : listTransition )
        {
            List<Control> listTransitionControl = ControlHome.getControlByControlTargetAndType( transition.getId( ), ControlType.TRANSITION );
            boolean controlsValidated = true;

            if ( listTransitionControl.isEmpty( ) )
            {
                return StepHome.findByPrimaryKey( transition.getNextStep( ) );
            }

            for ( Control transitionControl : listTransitionControl )
            {
                Question targetQuestion = QuestionHome.findByPrimaryKey( transitionControl.getListIdQuestion( ).iterator( ).next( ) );
                Step stepTarget = StepHome.findByPrimaryKey( targetQuestion.getIdStep( ) );
                List<FormQuestionResponse> listQuestionResponse = formResponseManager.findResponsesFor( stepTarget ).stream( )
                        .filter( q -> transitionControl.getListIdQuestion( ).stream( ).anyMatch( t -> t.equals( q.getQuestion( ).getId( ) ) ) )
                        .collect( Collectors.toList( ) );

                IValidator validator = EntryServiceManager.getInstance( ).getValidator( transitionControl.getValidatorName( ) );
                if ( validator != null && !validator.validate( listQuestionResponse, transitionControl ) )
                {
                    controlsValidated = false;
                    errorMessageList.add( transitionControl.getErrorMessage( ) );
                    break;
                }
            }

            if ( controlsValidated )
            {
                return StepHome.findByPrimaryKey( transition.getNextStep( ) );
            }
        }

        return null;
    }   
    
    /**
     * Populate form with Physical File logo and number of response
     * @param form the form object
     */
    public static void populateFormWithLogoAndNumberResponse( Form form )
    {
        if ( form.isCountResponses( ) )
        {
            form.setCurrentNumberResponse( FormHome.getNumberOfResponseForms( form.getId( ) ) );
        }
        if ( form.getLogo( ) != null )
        {
            form.setLogo( FileHome.findByPrimaryKey( form.getLogo( ).getIdFile( ) ) );
            form.getLogo( ).setPhysicalFile( PhysicalFileHome.findByPrimaryKey( form.getLogo( ).getPhysicalFile( ).getIdPhysicalFile( ) ) );
    
        }
    }

    /**
     * @param request
     *            The Http request
     * @param bValidateQuestionStep
     *            valid question ton next step
     * @return 
     * 
     * @throws QuestionValidationException
     *             if there is at least one question not valid
     */
    public static void fillResponseManagerWithResponses( HttpServletRequest request, boolean bValidateQuestionStep, FormResponseManager formResponseManager, List<Question> listQuestionStep, boolean displayInBO ) throws QuestionValidationException
    {
        boolean bValidStep = true;
        List<FormQuestionResponse> listResponsesTemp = new ArrayList<>( );

        String [ ] listConditionalQuestionsValues = request.getParameterValues( FormsConstants.PARAMETER_DISPLAYED_QUESTIONS );

        for ( Question question : listQuestionStep )
        {
            for ( int i = 0; i < listConditionalQuestionsValues.length; i++ )
            {
                String [ ] listQuestionId = listConditionalQuestionsValues [i].split( FormsConstants.SEPARATOR_UNDERSCORE );
                if ( StringUtils.isNotEmpty( listQuestionId [0] ) && Integer.parseInt( listQuestionId [0] ) == question.getId( )
                        && Integer.parseInt( listQuestionId [1] ) == question.getIterationNumber( ) )
                {
                    question.setIsVisible( true );
                    break;
                }
                else
                {
                    question.setIsVisible( false );
                }
            }
            IEntryDataService entryDataService = EntryServiceManager.getInstance( ).getEntryDataService( question.getEntry( ).getEntryType( ) );
            if ( question.getEntry( ).isOnlyDisplayInBack( ) )
            {
            	if ( displayInBO )
            	{
            		question.setIsVisible( true );
            	}
            	else
            	{
            		continue;
            	}
            }
            
            if ( entryDataService == null )
            {
            	continue;
            }

            FormQuestionResponse formQuestionResponse = entryDataService.createResponseFromRequest( question, request,
                    question.isVisible( ) && bValidateQuestionStep );

            if ( formQuestionResponse.hasError( ) )
            {
                bValidStep = false;
            }

            listResponsesTemp.add( formQuestionResponse );
        }
        
        for ( FormQuestionResponse formQuestionResponse : listResponsesTemp )
        {
        	if ( bValidateQuestionStep )
        	{
	        	List<Control> listControl = ControlHome.getControlByQuestionAndType( formQuestionResponse.getQuestion( ).getId( ), ControlType.VALIDATION.getLabel( ) );
	        	
	        	for ( Control control : listControl )
	            {
	                IValidator validator = EntryServiceManager.getInstance( ).getValidator( control.getValidatorName( ) );
	                if ( !validator.validate( listResponsesTemp, control ) )
	                {
	                	GenericAttributeError error = new GenericAttributeError( );
	
	                    error.setIsDisplayableError( true );
	                    error.setErrorMessage( control.getErrorMessage( ) );
	
	                    formQuestionResponse.setError( error );
	
	                    break;
	                }
	            }
        	}
        	
        	if ( formQuestionResponse.hasError( ) )
            {
                bValidStep = false;
            }
        }

        formResponseManager.addResponses( listResponsesTemp );

        if ( !bValidStep )
        {
            throw new QuestionValidationException( );
        }        
    }
    
    public static synchronized Object getLockOnForm( Form form )
    {
        _lockFormId.putIfAbsent( form.getId( ), new Object( ) );
        return _lockFormId.get( form.getId( ) );
    } 
    /**
     * Increase the number of response of the Form
     * 
     * @param form
     */
    public static void increaseNumberResponse( Form form )
    {        
       if ( form.getMaxNumberResponse( ) != 0 )
       {
        	synchronized( FormsResponseUtils.getLockOnForm( form ) )
             {
        		int nNumberReponseForm = _responsePerFormMap.get( form.getId( ) );
        		_responsePerFormMap.put( form.getId( ), nNumberReponseForm + 1 );
              }
        }
        
    }
    
    /**
     * check if form is reached the number max of response
     * 
     * @param form
     *            the form
     * @param request
     *            the request
     * @throws SiteMessageException
     *             the exception
     */
    public static boolean checkNumberMaxResponseForm( Form form )
    {
    	
	    if ( form.getMaxNumberResponse( ) != 0 )
	    {
	      	synchronized( FormsResponseUtils.getLockOnForm( form ) )
	        {
	      		return ( _responsePerFormMap.computeIfAbsent( form.getId( ), FormHome::getNumberOfResponseForms ) < form.getMaxNumberResponse( ) );
	        }
	    }
	    return true;
    }
    
    /**
     * check if user can answer the form again
     * 
     * @param form
     *            the form
     * @param request
     *            the request
     * @throws SiteMessageException
     */
    public static boolean checkIfUserResponseForm( Form form, String strGuid )
    {
        if ( strGuid != null && form.isAuthentificationNeeded( ) && form.isOneResponseByUser( ) )
        {
        		return ( FormHome.getNumberOfResponseFormByUser( form.getId( ), strGuid ) < NumberUtils.INTEGER_ONE );
        }
        return true;
    }
    
    /**
     * Builds the HTML for the specified list of steps
     * 
     * @param request
     *            The request
     * @param listStep
     *            The list of steps
     * @return the list of HTML
     */
    public static List<String> buildStepsHtml( HttpServletRequest request, List<Step> listStep, FormResponseManager formResponseManager, boolean frontOffice )
    {
        List<String> listFormDisplayTrees = new ArrayList<>( );

        for ( Step step : listStep )
        {
            StepDisplayTree stepDisplayTree = new StepDisplayTree( step.getId( ), formResponseManager.getFormResponse( ) );

            listFormDisplayTrees.add( stepDisplayTree.getCompositeHtml( request, formResponseManager.findResponsesFor( step ), frontOffice ? getLocaleFO( request ) : AdminUserService.getLocale( request ),
            		frontOffice ? DisplayType.READONLY_FRONTOFFICE: DisplayType.READONLY_BACKOFFICE ) );
        }

        return listFormDisplayTrees;
    }
    
    public static Locale getLocaleFO( HttpServletRequest request )
    {
        return LocaleService.getContextUserLocale( request );
    }
  
    /**
     * Checks that the current user is associated to a given role
     * 
     * @param user
     *            The user
     * @param strRole
     *            The role name
     * @return Returns true if the user is associated to the role, otherwise false
     */
    private static boolean isUserHasRole( LuteceUser user, String strRole  )
    {
    	return user.getLuteceAuthenticationService( ).isUserInRole( user, null, strRole);
    }
    
    private static boolean isAuthorized( List<Integer> listIdFormByRole, FormResponse formResponse, LuteceUser user )
    {
    	return (listIdFormByRole.contains( formResponse.getFormId( )) && formResponse.getRole() != null && isUserHasRole(user, formResponse.getRole( ) )) 
				|| (!listIdFormByRole.contains( formResponse.getFormId( )) && user.getName().equals(formResponse.getGuid( )));
    }
    /**
     * Build FormsResponse file url. This url is only used for FO
     * @param nIdFormQuestionResponse the forms question response
     * @param nIdFile the file id
     * @return the url builded 
     */
    public static String buildFileUrl(int nIdFormQuestionResponse, int nIdFile) 
    {
    	UrlItem url = new UrlItem( getRootUrl( ) + FormsConstants.FORMS_FILE_URL_FO );
        url.addParameter( FormsConstants.PARAMETER_ID_FORM_QUESTION_REPONSE, nIdFormQuestionResponse );
        url.addParameter( FormsConstants.PARAMETER_ID_FILE, nIdFile );

        return url.getUrl( );
    }
    /**
     * Build FormsResponse FO url.
     * @param nIdFormQuestionResponse the forms question response
     * @param nIdFile the file id
     * @return the url builded 
     */
    public static String buildFormsResponseFOUrl(int nIdFormResponse) 
    {
    	UrlItem url = new UrlItem( getRootUrl( ) + AppPathService.getPortalUrl( ) );
        url.addParameter( MVCUtils.PARAMETER_PAGE, FormResponseXPage.XPAGE_NAME );
        url.addParameter( MVCUtils.PARAMETER_VIEW, FormResponseXPage.VIEW_FORM_RESPONSE );
        url.addParameter( FormsConstants.PARAMETER_ID_RESPONSE, nIdFormResponse );

        return url.getUrl( );
    }
    /**
     * Return the url of the Root of the webapp
     * 
     * @return strBase the webapp url
     */
    public static String getRootUrl( )
    {
        String strBaseUrl = AppPropertiesService.getProperty( FormsConstants.PROPERTY_BASE_URL );

        if ( StringUtils.isBlank( strBaseUrl ) )
        {
            strBaseUrl = AppPropertiesService.getProperty( FormsConstants.PROPERTY_PROD_URL );
        }

        if ( !strBaseUrl.endsWith( "/" ) )
        {
            strBaseUrl = strBaseUrl + "/";
        }
        strBaseUrl = StringUtils.isBlank( strBaseUrl ) ? "" : strBaseUrl;
        return strBaseUrl;
    }
    
}