FormResponseManager.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;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import fr.paris.lutece.plugins.forms.business.*;
import fr.paris.lutece.portal.service.util.AppLogService;
import org.apache.commons.collections.CollectionUtils;

import fr.paris.lutece.plugins.forms.service.EntryServiceManager;
import fr.paris.lutece.plugins.forms.util.FormsConstants;
import fr.paris.lutece.plugins.forms.validation.IValidator;
import fr.paris.lutece.plugins.genericattributes.business.GenericAttributeError;

/**
 *
 * Class for breadcrumb management and responses history
 *
 */
public class FormResponseManager
{
    private final List<Step> _listValidatedStep;
    private final FormResponse _formResponse;
    private boolean _bIsResponseLoadedFromBackup = false;
    private boolean _isBackupResponseAlreadyInitiated = false;
    /**
     * Constructor
     *
     * @param form
     *            the form
     */
    public FormResponseManager( Form form )
    {
        _listValidatedStep = new ArrayList<>( );
        _formResponse = new FormResponse( );
        _formResponse.setFormId( form.getId( ) );
        _formResponse.setSteps( new ArrayList<>( ) );
    }

    /**
     * Constructor
     *
     * @param formResponse
     *            the form response
     */
    public FormResponseManager( FormResponse formResponse )
    {
        _listValidatedStep = new ArrayList<>( );
        _formResponse = formResponse;

        initValidatedStep( );
    }

    /**
     * Initializes the validated steps
     */
    private void initValidatedStep( )
    {
        for ( FormResponseStep formResponseStep : _formResponse.getSteps( ) )
        {
            int nStepOrder = formResponseStep.getOrder( );

            if ( nStepOrder != FormsConstants.ORDER_NOT_SET )
            {
                _listValidatedStep.add( nStepOrder, formResponseStep.getStep( ) );
            }
            for ( FormQuestionResponse formQuestionResponse : formResponseStep.getQuestions( ) )
            {
                if ( CollectionUtils.isNotEmpty( formQuestionResponse.getEntryResponse( ) ) )
                {
                    formQuestionResponse.getQuestion( ).setIsVisible( true );
                }
            }
        }
    }

    /**
     * Gives the current step
     *
     * @return the current step
     */
    public Step getCurrentStep( )
    {
        Step step = null;

        if ( !_listValidatedStep.isEmpty( ) )
        {
            step = _listValidatedStep.get( _listValidatedStep.size( ) - 1 );
        }

        return step;
    }

    /**
     * Gives the form response
     *
     * @return the form response
     */
    public FormResponse getFormResponse( )
    {
        initStepsOrder( );

        return _formResponse;
    }

    /**
     * Return the form Response update date
     *
     * @return
     */
    public Timestamp getFormResponseUpdateDate()
    {
        FormResponse formResponse = getFormResponse();
        Timestamp updateDate = formResponse.getUpdate();
        if (updateDate == null)
        {
            FormResponse formResponseFromDB = FormResponseHome.findUncompleteByPrimaryKey(formResponse.getId());
            updateDate = formResponseFromDB != null ? formResponseFromDB.getUpdate() : null;
        }
        return updateDate;
    }

    public void setFormResponseUpdateDate(Timestamp updateDate)
    {
        FormResponse formResponse = getFormResponse();
        formResponse.setUpdate(updateDate);
    }
    /**
     * Give a boolean indicating that indicates if view (getViewStep) has been initialized from backup
     * So with _isBackupResponseAlreadyInitiated and _bIsResponseLoadedFromBackup we can deduce if it's the first time the getViewStep is loaded with the backup response
     *
     * @return a boolean indicating that indicates if view has been initialized from backup
     */
    public Boolean getIsBackupResponseAlreadyInitiated() {
        return _isBackupResponseAlreadyInitiated;
    }
    public void setBackupResponseAlreadyInitiated(Boolean isBackupResponseAlreadyInitiated) {
        _isBackupResponseAlreadyInitiated = isBackupResponseAlreadyInitiated;
    }
    /**
     * Gives a boolean indicating if the response is loaded from backup
     *
     * @return a boolean indicating if the response is loaded from backup
     */
    public Boolean getIsResponseLoadedFromBackup () {
        return _bIsResponseLoadedFromBackup;
    }
    public void setIsResponseLoadedFromBackup (Boolean bIsResponseLoadedFromBackup) {
        _bIsResponseLoadedFromBackup = bIsResponseLoadedFromBackup;
    }

    /**
     * Initializes the steps order
     */
    private void initStepsOrder( )
    {
        for ( FormResponseStep formResponseStep : _formResponse.getSteps( ) )
        {
            formResponseStep.setOrder( FormsConstants.ORDER_NOT_SET );
        }

        for ( int i = 0; i < _listValidatedStep.size( ); i++ )
        {
            Step step = _listValidatedStep.get( i );

            for ( FormResponseStep formResponseStep : _formResponse.getSteps( ) )
            {
                if ( formResponseStep.getStep( ).getId( ) == step.getId( ) )
                {
                    formResponseStep.setOrder( i );
                    break;
                }
            }
        }
    }

    /**
     * Adds the specified step
     *
     * @param step
     *            the step to add
     */
    public void add( Step step )
    {
        if ( isStepValidated( step ) )
        {
            AppLogService.error("The step is already validated !" );
        } else {

            _listValidatedStep.add(step);

            FormResponseStep formResponseStep = findFormResponseStepFor(step);

            if (formResponseStep == null) {
                _formResponse.getSteps().add(createFormResponseStepFrom(step));
            }
        }
    }

    /**
     * Tests if the specified step is validated or not
     *
     * @param step
     *            the step
     * @return {@code true} if the step is validated, {@code false} otherwise
     */
    private boolean isStepValidated( Step step )
    {
        return _listValidatedStep.stream( ).anyMatch( stepValidate -> stepValidate.getId( ) == step.getId( ) );
    }

    /**
     * Creates a form response step from the specified step
     *
     * @param step
     *            the step
     * @return the created form response step
     */
    private FormResponseStep createFormResponseStepFrom( Step step )
    {
        FormResponseStep formResponseStep = new FormResponseStep( );
        formResponseStep.setFormResponseId( _formResponse.getId( ) );
        formResponseStep.setQuestions( new ArrayList<>( ) );
        formResponseStep.setStep( step );

        return formResponseStep;
    }

    /**
     * Goes to the step of the specified index
     *
     * @param nStepIndex
     *            the step index
     * @return the Step
     */
    public Step goTo( int nStepIndex )
    {
        if ( nStepIndex + 1 < _listValidatedStep.size( ) )
        {
            _listValidatedStep.subList( nStepIndex + 1, _listValidatedStep.size( ) ).clear( );
        }

        return getCurrentStep( );
    }

    /**
     * Finds the responses for the specified step
     *
     * @param step
     *            the step
     * @return the found responses
     */
    public List<FormQuestionResponse> findResponsesFor( Step step )
    {
        List<FormQuestionResponse> listFormQuestionResponse = new ArrayList<>( );

        if ( isStepValidated( step ) )
        {
            FormResponseStep formResponseStep = findFormResponseStepFor( step );
            if ( formResponseStep != null )
            {
                listFormQuestionResponse = formResponseStep.getQuestions( );
            }
        }

        return listFormQuestionResponse;
    }

    /**
     * Finds all responses
     *
     * @return the found responses
     */
    public List<FormQuestionResponse> findAllResponses( )
    {
        return _formResponse.getSteps( ).stream( ).flatMap( step -> step.getQuestions( ).stream( ) ).collect( Collectors.toList( ) );
    }

    /**
     * Finds the form response step for the specified step
     *
     * @param step
     *            the step
     * @return the found form response step
     */
    private FormResponseStep findFormResponseStepFor( Step step )
    {
        FormResponseStep formResponseStepResult = null;

        for ( FormResponseStep formResponseStep : _formResponse.getSteps( ) )
        {
            if ( formResponseStep.getStep( ).getId( ) == step.getId( ) )
            {
                formResponseStepResult = formResponseStep;
                break;
            }
        }

        return formResponseStepResult;
    }

    /**
     * Adds the specified responses
     *
     * @param listFormQuestionResponse
     *            the responses to add
     */
    public void addResponses( List<FormQuestionResponse> listFormQuestionResponse )
    {
        FormResponseStep formResponseStep = findFormResponseStepFor( getCurrentStep( ) );

        if ( formResponseStep != null )
        {
            formResponseStep.setQuestions( listFormQuestionResponse );
        }
    }

    /**
     * Pops the last step
     *
     * @return the last step, or {@code null} if there no step to pop
     */
    public Step popStep( )
    {
        Step step = getCurrentStep( );

        if ( !_listValidatedStep.isEmpty( ) )
        {
            _listValidatedStep.remove( _listValidatedStep.size( ) - 1 );
        }

        return step;

    }

    /**
     * Gives the validated steps
     *
     * @return the validated steps
     */
    public List<Step> getValidatedSteps( )
    {
        List<Step> listStep = new ArrayList<>( _listValidatedStep.size( ) );

        for ( Step step : _listValidatedStep )
        {
            listStep.add( step );
        }

        return listStep;
    }

    /**
     *
     * @return the form validation result
     */
    public boolean validateFormResponses( )
    {
        for ( Step step : _listValidatedStep )
        {
            List<FormQuestionResponse> listFormQuestionResponse = findResponsesFor( step );

            for ( FormQuestionResponse formQuestionResponse : listFormQuestionResponse )
            {
                if ( formQuestionResponse.getQuestion( ).isVisible( ) )
                {
                    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( formQuestionResponse, control ) )
                        {
                            GenericAttributeError error = new GenericAttributeError( );

                            error.setIsDisplayableError( true );
                            error.setErrorMessage( control.getErrorMessage( ) );

                            formQuestionResponse.setError( error );

                            goTo( _listValidatedStep.indexOf( step ) );

                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }

}