AbstractFormJsonService.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.service.json;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.transaction.annotation.Transactional;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

import fr.paris.lutece.plugins.forms.business.CompositeDisplayType;
import fr.paris.lutece.plugins.forms.business.Control;
import fr.paris.lutece.plugins.forms.business.ControlMapping;
import fr.paris.lutece.plugins.forms.business.ControlType;
import fr.paris.lutece.plugins.forms.business.Form;
import fr.paris.lutece.plugins.forms.business.FormDisplay;
import fr.paris.lutece.plugins.forms.business.Group;
import fr.paris.lutece.plugins.forms.business.Question;
import fr.paris.lutece.plugins.forms.business.Step;
import fr.paris.lutece.plugins.forms.business.Transition;
import fr.paris.lutece.plugins.forms.business.export.FormExportConfig;
import fr.paris.lutece.plugins.forms.service.IFormDatabaseService;
import fr.paris.lutece.plugins.forms.util.FormsConstants;
import fr.paris.lutece.plugins.genericattributes.business.Entry;
import fr.paris.lutece.plugins.genericattributes.business.Field;
import fr.paris.lutece.plugins.referencelist.business.ReferenceItemHome;
import fr.paris.lutece.portal.service.i18n.I18nService;

public abstract class AbstractFormJsonService
{
    private static final String PROPERTY_COPY_STEP_TITLE = "forms.copyStep.title";

    protected final IFormDatabaseService _formDatabaseService;
    protected ObjectMapper _objectMapper;

    protected AbstractFormJsonService( IFormDatabaseService formDatabaseService )
    {
        _formDatabaseService = formDatabaseService;
        SimpleModule timestampModule = new SimpleModule( "TimestampModule" );
        timestampModule.addSerializer( Timestamp.class, new TimestampSerializer( ) );
        timestampModule.addDeserializer( Timestamp.class, new TimestampDeserializer( ) );

        _objectMapper = new ObjectMapper( ).configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false ).registerModule( timestampModule );
    }

    /**
     * Export the step as a Json Object.
     * 
     * @return
     * @throws JsonProcessingException
     */
    public String jsonExportStep( int idForm, int idStep ) throws JsonProcessingException
    {
        StepJsonData jsonData = jsonExportStepAsData( idForm, idStep );
        return _objectMapper.writeValueAsString( jsonData );
    }

    /**
     * Export the step as a StepJsonData Object.
     * 
     * @return
     * @throws JsonProcessingException
     */
    public StepJsonData jsonExportStepAsData( int idForm, int idStep ) throws JsonProcessingException
    {
        StepJsonData jsonData = new StepJsonData( );

        jsonData.setStep( _formDatabaseService.findStepByPrimaryKey( idStep ) );
        jsonData.setGroupList( _formDatabaseService.getGroupsListByIdStepList( Collections.singletonList( idStep ) ) );

        List<Control> controlList = new ArrayList<>( );
        List<Question> questionList = _formDatabaseService.getQuestionsListByStep( idStep );
        for ( Question question : questionList )
        {
            for ( Field field : question.getEntry( ).getFields( ) )
            {
                Integer idItem = _formDatabaseService.findIdReferenceItemByIdField( field.getIdField( ) );
                if ( idItem > 0 )
                {
                    field.setLinkedItem( ReferenceItemHome.findByPrimaryKey( idItem ) );
                }
            }
            controlList.addAll( _formDatabaseService.getControlByQuestion( question.getId( ) ) );
        }
        List<ControlMapping> controlMappingList = new ArrayList<>( );
        for ( Control control : controlList )
        {
            controlMappingList.addAll( _formDatabaseService.getControlMappingListByIdControl( control.getId( ) ) );
        }
        jsonData.setQuestionList( questionList );
        jsonData.setControlList( controlList );
        jsonData.setControlMappingList( controlMappingList );

        List<FormDisplay> formDisplayList = getAllFormDisplays( idForm, idStep );
        jsonData.setFormDisplayList( formDisplayList.stream( ).filter( fd -> fd.getStepId( ) == idStep ).collect( Collectors.toList( ) ) );

        return jsonData;
    }

    /**
     * Import the step from a Json Object.
     * 
     * @return
     */
    @Transactional( FormsConstants.BEAN_TRANSACTION_MANAGER )
    public void jsonImportStep( int idForm, String json, Locale locale ) throws JsonProcessingException
    {
        StepJsonData jsonData = _objectMapper.readValue( json, StepJsonData.class );

        jsonImportStep( idForm, jsonData, locale );
    }

    @Transactional( FormsConstants.BEAN_TRANSACTION_MANAGER )
    public void jsonImportStep( int idForm, StepJsonData jsonData, Locale locale )
    {
        Step step = jsonData.getStep( );
        List<Group> groupList = jsonData.getGroupList( );
        List<Question> questionList = jsonData.getQuestionList( );
        List<Control> controlList = jsonData.getControlList( );
        List<ControlMapping> controlMappingList = jsonData.getControlMappingList( );
        List<FormDisplay> formDisplayList = jsonData.getFormDisplayList( );

        Object [ ] tabSTepTitleCopy = {
                step.getTitle( ),
        };
        String strTitleCopyStep = I18nService.getLocalizedString( PROPERTY_COPY_STEP_TITLE, tabSTepTitleCopy, locale );

        if ( strTitleCopyStep != null )
        {
            step.setTitle( strTitleCopyStep );
        }

        importSteps( idForm, Collections.singletonList( step ), groupList, null, questionList, formDisplayList );
        importQuestions( idForm, questionList, controlList, controlMappingList, formDisplayList, null );
        importGroups( groupList, formDisplayList );
        importFormDisplay( idForm, formDisplayList, controlList );
        importControls( controlList, controlMappingList );
    }

    protected void importSteps( int newIdForm, List<Step> stepList, List<Group> groupList, List<Transition> transitionList, List<Question> questionList,
            List<FormDisplay> formDisplayList )
    {
        Map<Integer, Integer> mapIdSteps = new HashMap<>( );
        for ( Step step : stepList )
        {
            int oldStepId = step.getId( );
            step.setIdForm( newIdForm );

            _formDatabaseService.createStep( step );
            int newStepId = step.getId( );

            mapIdSteps.put( oldStepId, newStepId );
        }

        if ( CollectionUtils.isNotEmpty( transitionList ) )
        {
            updateTransitionWithNewStep( transitionList, mapIdSteps );
        }
        updateGroupWithNewStep( groupList, mapIdSteps );
        updateQuestionWithNewStep( questionList, mapIdSteps );
        updateFormDisplayWithNewStep( formDisplayList, mapIdSteps );
    }

    protected void importQuestions( int newIdForm, List<Question> questionList, List<Control> controlList, List<ControlMapping> controlMappingList,
            List<FormDisplay> formDisplayList, List<FormExportConfig> formExportConfigList )
    {
        Map<Integer, Integer> mapIdQuestions = new HashMap<>( );
        for ( Question question : questionList )
        {
            int oldIdQuestion = question.getId( );

            Entry entry = question.getEntry( );
            entry.setIdResource( newIdForm );
            entry.setResourceType( Form.RESOURCE_TYPE );
            _formDatabaseService.createEntry( entry );
            question.setIdEntry( entry.getIdEntry( ) );

            List<Field> fieldList = entry.getFields( );
            for ( Field field : fieldList )
            {
                field.setParentEntry( entry );
                _formDatabaseService.createField( field );
            }

            _formDatabaseService.createQuestion( question );

            int newIdQuestion = question.getId( );

            mapIdQuestions.put( oldIdQuestion, newIdQuestion );
        }
        updateControlWithNewQuestion( controlMappingList, controlList, mapIdQuestions );
        updateFormDisplayWithNewQuestion( formDisplayList, mapIdQuestions );
        if ( CollectionUtils.isNotEmpty( formExportConfigList ) )
        {
            updateExportConfigWithNewQuestion( formExportConfigList, mapIdQuestions );
        }
    }

    protected void importGroups( List<Group> groupList, List<FormDisplay> formDisplayList )
    {
        Map<Integer, Integer> mapIdGroups = new HashMap<>( );
        for ( Group group : groupList )
        {
            int oldId = group.getId( );

            _formDatabaseService.createGroup( group );

            int newId = group.getId( );

            mapIdGroups.put( oldId, newId );
        }
        updateFormDisplayWithNewGroup( formDisplayList, mapIdGroups );
    }

    protected void importFormDisplay( int newIdForm, List<FormDisplay> formDisplayList, List<Control> controlList )
    {
        Map<Integer, Integer> mapIdFormDisplay = new HashMap<>( );
        for ( FormDisplay formDisplay : formDisplayList )
        {
            int oldId = formDisplay.getId( );
            formDisplay.setFormId( newIdForm );
            _formDatabaseService.createFormDisplay( formDisplay );

            int newId = formDisplay.getId( );

            mapIdFormDisplay.put( oldId, newId );
        }
        for ( FormDisplay formDisplay : formDisplayList )
        {
            if ( formDisplay.getParentId( ) > 0 )
            {
                formDisplay.setParentId( mapIdFormDisplay.get( formDisplay.getParentId( ) ) );
            }
            // Update id parent and display order
            _formDatabaseService.updateFormDisplay( formDisplay );
        }
        updateControlWithFormDisplay( controlList, mapIdFormDisplay );
    }

    protected void importControls( List<Control> controlList, List<ControlMapping> controlMappingList )
    {
        Map<Integer, Integer> mapIdControls = new HashMap<>( );
        for ( Control control : controlList )
        {
            int oldId = control.getId( );
            _formDatabaseService.createControl( control );
            int newId = control.getId( );

            mapIdControls.put( oldId, newId );
        }

        for ( ControlMapping controlMapping : controlMappingList )
        {
            _formDatabaseService.createMappingControl( mapIdControls.get( controlMapping.getIdControl( ) ), controlMapping.getIdQuestion( ),
                    controlMapping.getValue( ) );
        }
    }

    private void updateGroupWithNewStep( List<Group> groupList, Map<Integer, Integer> mapIdSteps )
    {
        for ( Group group : groupList )
        {
            group.setIdStep( mapIdSteps.get( group.getIdStep( ) ) );
        }
    }

    private void updateQuestionWithNewStep( List<Question> questionList, Map<Integer, Integer> mapIdSteps )
    {
        for ( Question question : questionList )
        {
            question.setIdStep( mapIdSteps.get( question.getIdStep( ) ) );
        }
    }

    private void updateFormDisplayWithNewStep( List<FormDisplay> formDisplayList, Map<Integer, Integer> mapIdSteps )
    {
        for ( FormDisplay formDisplay : formDisplayList )
        {
            formDisplay.setStepId( mapIdSteps.get( formDisplay.getStepId( ) ) );
        }
    }

    private void updateExportConfigWithNewQuestion( List<FormExportConfig> formExportConfigList, Map<Integer, Integer> mapIdQuestions )
    {
        for ( FormExportConfig config : formExportConfigList )
        {
            int idQuestion = NumberUtils.toInt( config.getField( ), -1 );
            if ( idQuestion != -1 )
            {
                config.setField( String.valueOf( mapIdQuestions.get( idQuestion ) ) );
            }
        }
    }

    private void updateControlWithNewQuestion( List<ControlMapping> controlMappingList, List<Control> controlList, Map<Integer, Integer> mapIdQuestions )
    {
        for ( Control control : controlList )
        {
            Set<Integer> newSetId = new HashSet<>( );
            Set<Integer> oldSetId = control.getListIdQuestion( );

            for ( Integer oldId : oldSetId )
            {
                newSetId.add( mapIdQuestions.get( oldId ) );
            }
            control.setListIdQuestion( newSetId );

            if ( ControlType.VALIDATION.getLabel( ).equals( control.getControlType( ) ) )
            {
                control.setIdControlTarget( mapIdQuestions.get( control.getIdControlTarget( ) ) );
            }
        }
        for ( ControlMapping controlMapping : controlMappingList )
        {
            controlMapping.setIdQuestion( mapIdQuestions.get( controlMapping.getIdQuestion( ) ) );
        }
    }

    private void updateFormDisplayWithNewQuestion( List<FormDisplay> formDisplayList, Map<Integer, Integer> mapIdQuestions )
    {
        for ( FormDisplay formDisplay : formDisplayList )
        {
            if ( CompositeDisplayType.QUESTION.getLabel( ).equals( formDisplay.getCompositeType( ) ) )
            {
                formDisplay.setCompositeId( mapIdQuestions.get( formDisplay.getCompositeId( ) ) );
            }
        }
    }

    private void updateFormDisplayWithNewGroup( List<FormDisplay> formDisplayList, Map<Integer, Integer> mapIdGroup )
    {
        for ( FormDisplay formDisplay : formDisplayList )
        {
            if ( CompositeDisplayType.GROUP.getLabel( ).equals( formDisplay.getCompositeType( ) ) )
            {
                formDisplay.setCompositeId( mapIdGroup.get( formDisplay.getCompositeId( ) ) );
            }
        }
    }

    private void updateControlWithFormDisplay( List<Control> controlList, Map<Integer, Integer> mapIdFormDisplay )
    {
        for ( Control control : controlList )
        {
            if ( ControlType.CONDITIONAL.getLabel( ).equals( control.getControlType( ) ) && mapIdFormDisplay.containsKey( control.getIdControlTarget( ) ) )
            {
                control.setIdControlTarget( mapIdFormDisplay.get( control.getIdControlTarget( ) ) );
            }
        }
    }

    private void updateTransitionWithNewStep( List<Transition> transitionList, Map<Integer, Integer> mapIdSteps )
    {
        for ( Transition transition : transitionList )
        {
            transition.setFromStep( mapIdSteps.get( transition.getFromStep( ) ) );
            transition.setNextStep( mapIdSteps.get( transition.getNextStep( ) ) );
        }
    }

    protected abstract List<FormDisplay> getAllFormDisplays( int idForm, int idStep );
}