TicketPurgeAnonymisationDaemon.java

/*
 * Copyright (c) 2002-2025, 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.modules.ticketing.service.daemon;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.StringJoiner;

import fr.paris.lutece.plugins.ticketing.business.category.TicketCategory;
import fr.paris.lutece.plugins.ticketing.business.category.TicketCategoryHome;
import fr.paris.lutece.plugins.ticketing.business.search.IndexerActionHome;
import fr.paris.lutece.plugins.ticketing.business.search.TicketIndexer;
import fr.paris.lutece.plugins.ticketing.business.search.TicketIndexerException;
import fr.paris.lutece.plugins.ticketing.business.ticket.TicketHome;
import fr.paris.lutece.plugins.ticketing.business.ticketpj.TicketPjHome;
import fr.paris.lutece.plugins.ticketing.service.category.TicketCategoryService;
import fr.paris.lutece.plugins.ticketing.service.util.PluginConfigurationService;
import fr.paris.lutece.plugins.workflow.modules.ticketing.business.resourcehistory.IResourceHistoryDAO;
import fr.paris.lutece.plugins.workflow.modules.ticketing.service.WorkflowTicketingPlugin;
import fr.paris.lutece.plugins.workflow.modules.ticketing.service.purgeanonymisation.PurgeAnonymisationService;
import fr.paris.lutece.portal.service.daemon.Daemon;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.util.sql.TransactionManager;

/**
 * The purge daemon for tickets to be anonymized.
 */
public class TicketPurgeAnonymisationDaemon extends Daemon
{
    private static final int DELAI_ANONYMISATION = PluginConfigurationService.getInt( PluginConfigurationService.PROPERTY_ANONYMISATION_DELAI, 1096 );
    private static final int MAX_ANONYMISATION_PAR_DOMAINE = PluginConfigurationService
            .getInt( PluginConfigurationService.PROPERTY_ANONYMISATION_TICKET_MAX_DOMAINE, 200 );


    private static IResourceHistoryDAO daoResourceHist = SpringContextService.getBean( IResourceHistoryDAO.BEAN_SERVICE );
    // Services
    private PurgeAnonymisationService  purgeAnonymisationService     = SpringContextService.getBean( PurgeAnonymisationService.BEAN_SERVICE );;

    private static Plugin plugin = WorkflowTicketingPlugin.getPlugin( );

    /*
     * Constructor
     */
    public TicketPurgeAnonymisationDaemon( )
    {
        super( );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void run( )
    {
        StringJoiner sb = new StringJoiner( "\n\r" );

        sb.add( "Début de l'anonymisation" );
        anonymisationPurge( sb );
        sb.add( "Fin de l'anonymisation" );
        setLastRunLogs( sb.toString( ) );
    }

    /**
     * Purge the ticket to anonymize
     *
     * @param sb
     *            the logs
     */
    public void anonymisationPurge( StringJoiner sb )
    {
        List<Integer> allIdDomaines = TicketCategoryHome.selectIdCategoriesDomainList( );

        for ( Integer idCategory : allIdDomaines )
        {
            TicketCategory categorieDomaine = TicketCategoryService.getInstance( true ).findCategoryById( idCategory );

            if ( null != categorieDomaine )
            {
                anonymizePurgeByDomaine( categorieDomaine, sb );
            }
        }
    }

    /**
     * Purge the ticket to anonymize by domaine
     *
     * @param categorieDomaine
     *            the category domaine
     * @param sb
     *            The logs
     */
    private void anonymizePurgeByDomaine( TicketCategory categorieDomaine, StringJoiner sb )
    {

        List<Integer> allChildrenForADomaine = TicketCategoryService.getInstance( true ).getAllChildren( categorieDomaine );

        int delaiAnonymisation = ( !categorieDomaine.getDelaiAnonymisation( ).trim( ).isEmpty( ) )
                ? Integer.parseInt( categorieDomaine.getDelaiAnonymisation( ).trim( ) )
                        : DELAI_ANONYMISATION;

        if ( !allChildrenForADomaine.isEmpty( ) )
        {
            java.sql.Date date = findDateClotureForAnonymisationDomaine( delaiAnonymisation, categorieDomaine, sb );

            if ( allChildrenForADomaine.isEmpty( ) )
            {
                allChildrenForADomaine.add( categorieDomaine.getId( ) );
            }

            List<Integer> listIdTickets = TicketHome.getForAnonymisationForDomaine( date, allChildrenForADomaine, MAX_ANONYMISATION_PAR_DOMAINE );

            if ( !listIdTickets.isEmpty( ) )
            {
                sb.add( "nombre de tickets à anonymiser : " + listIdTickets.size( ) );
                for ( Integer idTicket : listIdTickets )
                {
                    if ( ( null != idTicket ) && ( idTicket != 0 ) )
                    {
                        try
                        {
                            TransactionManager.beginTransaction( plugin );
                            // suppression des données historique du ticket
                            cleanHistoryData( idTicket );

                            // delete address
                            TicketHome.removeAddress( idTicket );

                            // suppression des pj
                            purgeAnonymisationService.deleteAllAttachment( idTicket );

                            // suppression genatt response
                            removeFromGenattResponseAndPjCentralized( idTicket );

                            // retrait indexation
                            removeIndexingTicketToAnonymize( idTicket, sb );

                            TicketHome.removeJustTicketAndTicketResponse( idTicket );
                        }
                        catch( Exception e )
                        {
                            TransactionManager.rollBack( plugin );
                            sb.add( "Annulation de l'anonymisation pour le domaine" + categorieDomaine.getLabel( ) + "idTicket : " + idTicket );
                            AppLogService.error( e );
                        }
                        TransactionManager.commitTransaction( plugin );
                    }
                }
            }
            else
            {
                sb.add( "aucun ticket à anonymiser " );
            }
        }

    }

    ////// GENERAL

    /**
     * Find the date closed max for a domaine accross a delay
     *
     * @param delaiAnonymisation
     *            the delay for a domaine
     * @param category
     *            the category of the ticket to anonymize
     * @param sb
     *            the logs
     * @return the date for cloture for a domaine
     */
    private java.sql.Date findDateClotureForAnonymisationDomaine( int delaiAnonymisation, TicketCategory category, StringJoiner sb )
    {
        Date date = new Date( );
        SimpleDateFormat sdf = new SimpleDateFormat( "dd/MM/yyyy" );
        Calendar calDate = Calendar.getInstance( );
        calDate.setTime( date );
        calDate.add( Calendar.DAY_OF_YEAR, -delaiAnonymisation );
        date = calDate.getTime( );
        sb.add( "- anonymisation des tickets du domaine " + category.getLabel( ) + " dont la date de clôture est plus ancienne que le " + sdf.format( date ) );
        return new java.sql.Date( date.getTime( ) );
    }

    /**
     * Clean historic data with message exchanges
     *
     * @param ticket
     *            the ticket to anonymize
     */
    private void cleanHistoryData( int idTicket )
    {
        purgeTicketHistoryFromUsager( idTicket );
        purgeTicketHistoryFromAgent( idTicket );
    }

    private void removeFromGenattResponseAndPjCentralized( Integer idTicket )
    {
        List<Integer> idResponseList = TicketPjHome.removeAllIdResponseToDelete( idTicket );
        if ( ( null != idResponseList ) && !idResponseList.isEmpty( ) )
        {
            TicketPjHome.removeGenattResponseByIdResponseList( idResponseList );
        }
        // Retrait des lignes de PJ pour les agents (id_response = 0)
        TicketPjHome.removeByIdTicket( idTicket );
    }

    /**
     * Remove indexation of a Ticket for the anonymisation
     *
     * @param idTicket
     *            the id of the Ticket to index
     */
    protected void removeIndexingTicketToAnonymize( int idTicket, StringJoiner sb )
    {
        try
        {
            TicketIndexer ticketIndexer = new TicketIndexer( );
            ticketIndexer.deleteTicketIndex( idTicket );
        } catch ( TicketIndexerException ticketIndexerException )
        {
            sb.add( "Le ticket id " + idTicket + " est en attente pour retrait indexation" );

            // The indexation of the Ticket fail, we will store the Ticket in the table for the daemon
            IndexerActionHome.removeByIdTicket( idTicket );
        }
    }

    ////// USAGER

    /**
     * Anonymize historic data with message exchanges From Usager
     *
     * @param ticket
     *            the ticket to anonymize
     */
    private void purgeTicketHistoryFromUsager( int idTicket )
    {
        // suppression message externe lié au message
        purgeAnonymisationService.removeMessageExternalUserByIdTicket( idTicket );
    }


    ////// AGENT

    /**
     * Anonymize historic data with message exchanges From Usager
     *
     * @param ticket
     *            the ticket to anonymize
     */
    private void purgeTicketHistoryFromAgent( int idTicket )
    {
        List<Integer> idHistoryList = daoResourceHist.getIdHistoryListByResource( idTicket, plugin );

        if ( ( null != idHistoryList ) && !idHistoryList.isEmpty( ) )
        {
            purgeAnonymisationService.purgeHistoryAnonymisation( idHistoryList, plugin );

            purgeAnonymisationService.purgeHistoryWorkflowTablesAnonymisation( idHistoryList, plugin );
        }
        purgeAnonymisationService.purgeWorkflowResourceAnonymisation( idTicket, plugin );
    }



}