RedisTTLConfig.java

/*
 * GovWay - A customizable API Gateway
 * https://govway.org
 *
 * Copyright (c) 2005-2025 Link.it srl (https://link.it).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.pdd.core.controllo_traffico.policy.driver.redisson.counters;

import java.io.Serializable;

import org.openspcoop2.core.controllo_traffico.beans.ActivePolicy;
import org.openspcoop2.core.controllo_traffico.ConfigurazionePolicy;
import org.openspcoop2.core.controllo_traffico.constants.TipoControlloPeriodo;
import org.openspcoop2.core.controllo_traffico.constants.TipoPeriodoRealtime;
import org.openspcoop2.core.controllo_traffico.constants.TipoPeriodoStatistico;
import org.openspcoop2.pdd.config.OpenSPCoop2Properties;

/**
 * RedisTTLConfig - Configurazione TTL per contatori Redis nel rate limiting
 *
 * Questa classe gestisce il calcolo del TTL (Time To Live) per i contatori Redis
 * usati nel rate limiting distribuito. Il TTL permette la pulizia automatica
 * dei contatori relativi a clientId non più attivi.
 *
 * Il comportamento del renewOnWrite dipende dal tipo di policy:
 * - Policy con intervallo temporale (es. 100 req/minuto): ad ogni finestra temporale
 *   vengono creati nuovi contatori, quindi non serve rinnovare il TTL sui vecchi
 * - Policy senza intervallo (es. richieste simultanee) o con TTL troncato al massimo:
 *   il rinnovo del TTL garantisce che i contatori rimangano attivi finché il client è attivo
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class RedisTTLConfig implements Serializable {

	private static final long serialVersionUID = 1L;

	// Valori di default
	public static final long DEFAULT_TTL_SECONDS = 300; // 5 minuti
	public static final long MIN_TTL_SECONDS = 60; // 1 minuto minimo
	public static final long MAX_TTL_SECONDS = 604800; // 7 giorni massimo
	public static final int DEFAULT_INTERVAL_MULTIPLIER = 2; // TTL = intervallo × 2

	private boolean enabled;
	private long ttlSeconds;
	private boolean renewTTLOnWrite;

	/**
	 * Costruttore di default - legge configurazione da properties
	 * Usa renewOnWrite per policy senza intervallo (default: true)
	 */
	public RedisTTLConfig() {
		OpenSPCoop2Properties props = OpenSPCoop2Properties.getInstance();
		this.enabled = props.isControlloTrafficoGestorePolicyInMemoryRedisTTLEnabled();
		this.ttlSeconds = props.getControlloTrafficoGestorePolicyInMemoryRedisTTLDefaultSeconds();
		// Senza policy, usiamo il default per policy senza intervallo
		this.renewTTLOnWrite = props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval();
	}

	/**
	 * Costruttore con valori espliciti
	 */
	public RedisTTLConfig(boolean enabled, long ttlSeconds, boolean renewTTLOnWrite) {
		this.enabled = enabled;
		this.ttlSeconds = ttlSeconds;
		this.renewTTLOnWrite = renewTTLOnWrite;
	}

	/**
	 * Costruttore basato su ActivePolicy - calcola TTL e renewOnWrite automaticamente
	 */
	public RedisTTLConfig(ActivePolicy activePolicy) {
		OpenSPCoop2Properties props = OpenSPCoop2Properties.getInstance();
		this.enabled = props.isControlloTrafficoGestorePolicyInMemoryRedisTTLEnabled();

		if (this.enabled && activePolicy != null) {
			TTLCalculationResult result = calculateTTLFromPolicy(activePolicy, props);
			this.ttlSeconds = result.ttlSeconds;
			this.renewTTLOnWrite = result.renewOnWrite;
		} else {
			this.ttlSeconds = props.getControlloTrafficoGestorePolicyInMemoryRedisTTLDefaultSeconds();
			// Senza policy valida, usiamo il default per policy senza intervallo
			this.renewTTLOnWrite = props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval();
		}
	}

	/**
	 * Risultato del calcolo TTL che include anche il renewOnWrite appropriato
	 */
	private static class TTLCalculationResult {
		long ttlSeconds;
		boolean renewOnWrite;

		TTLCalculationResult(long ttlSeconds, boolean renewOnWrite) {
			this.ttlSeconds = ttlSeconds;
			this.renewOnWrite = renewOnWrite;
		}
	}

	/**
	 * Calcola il TTL e il renewOnWrite basandosi sulla configurazione della policy di rate limiting
	 */
	private TTLCalculationResult calculateTTLFromPolicy(ActivePolicy activePolicy, OpenSPCoop2Properties props) {
		if (activePolicy == null || activePolicy.getConfigurazionePolicy() == null) {
			// Nessuna policy: usa default per policy senza intervallo
			return new TTLCalculationResult(
				props.getControlloTrafficoGestorePolicyInMemoryRedisTTLDefaultSeconds(),
				props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval()
			);
		}

		ConfigurazionePolicy config = activePolicy.getConfigurazionePolicy();

		// Policy per richieste simultanee: non ha intervallo temporale
		// Il TTL è fisso e il renewOnWrite serve per mantenere attivi i contatori dei client attivi
		if (config.isSimultanee()) {
			return new TTLCalculationResult(
				props.getControlloTrafficoGestorePolicyInMemoryRedisTTLDefaultSeconds(),
				props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval()
			);
		}

		// Calcola intervallo della policy in secondi
		long intervalloSeconds = calculateIntervalSeconds(config);

		if (intervalloSeconds <= 0) {
			// Intervallo non determinabile: usa default per policy senza intervallo
			return new TTLCalculationResult(
				props.getControlloTrafficoGestorePolicyInMemoryRedisTTLDefaultSeconds(),
				props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval()
			);
		}

		// TTL = intervallo × moltiplicatore
		int multiplier = props.getControlloTrafficoGestorePolicyInMemoryRedisTTLIntervalMultiplier();
		long ttlCalcolato = intervalloSeconds * multiplier;

		// Recupera limiti min/max
		long minTTL = props.getControlloTrafficoGestorePolicyInMemoryRedisTTLMinSeconds();
		long maxTTL = props.getControlloTrafficoGestorePolicyInMemoryRedisTTLMaxSeconds();

		// Applica limite minimo
		long ttlFinale = Math.max(ttlCalcolato, minTTL);

		// Verifica se il TTL viene troncato al massimo
		boolean ttlTroncato = ttlFinale > maxTTL;
		if (ttlTroncato) {
			ttlFinale = maxTTL;
		}

		// Determina il renewOnWrite in base al tipo di policy:
		// - Se TTL troncato: il TTL potrebbe essere minore dell'intervallo, serve renewOnWrite
		// - Se TTL non troncato: il TTL è sufficiente, non serve renewOnWrite
		boolean renewOnWrite;
		if (ttlTroncato) {
			// TTL troncato al massimo: potrebbe scadere durante l'intervallo attivo,
			// quindi serve rinnovare il TTL ad ogni scrittura per mantenere attivi i contatori
			renewOnWrite = props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval();
		} else {
			// TTL calcolato normalmente dall'intervallo: i contatori delle finestre precedenti
			// non ricevono più scritture, quindi rinnovare il TTL sarebbe inutile
			renewOnWrite = props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteIntervalBased();
		}

		return new TTLCalculationResult(ttlFinale, renewOnWrite);
	}

	/**
	 * Calcola l'intervallo della policy in secondi
	 */
	private long calculateIntervalSeconds(ConfigurazionePolicy config) {
		Integer intervallo = config.getIntervalloOsservazione();
		if (intervallo == null || intervallo <= 0) {
			return 0;
		}

		long intervalloSeconds = 0;

		if (TipoControlloPeriodo.REALTIME.equals(config.getModalitaControllo())) {
			intervalloSeconds = calculateRealtimeIntervalSeconds(config, intervallo);
		} else if (TipoControlloPeriodo.STATISTIC.equals(config.getModalitaControllo())) {
			intervalloSeconds = calculateStatisticIntervalSeconds(config, intervallo);
		}

		return intervalloSeconds;
	}

	private long calculateRealtimeIntervalSeconds(ConfigurazionePolicy config, int intervallo) {
		TipoPeriodoRealtime tipo = config.getTipoIntervalloOsservazioneRealtime();
		if (tipo == null) {
			return 0;
		}

		switch (tipo) {
			case SECONDI:
				return intervallo;
			case MINUTI:
				return intervallo * 60L;
			case ORARIO:
				return intervallo * 3600L;
			case GIORNALIERO:
				return intervallo * 86400L;
			default:
				return 0;
		}
	}

	private long calculateStatisticIntervalSeconds(ConfigurazionePolicy config, int intervallo) {
		TipoPeriodoStatistico tipo = config.getTipoIntervalloOsservazioneStatistico();
		if (tipo == null) {
			return 0;
		}

		switch (tipo) {
			case ORARIO:
				return intervallo * 3600L;
			case GIORNALIERO:
				return intervallo * 86400L;
			case SETTIMANALE:
				return intervallo * 604800L;
			case MENSILE:
				return intervallo * 2592000L; // 30 giorni
			default:
				return 0;
		}
	}

	// Getters e Setters

	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public long getTtlSeconds() {
		return this.ttlSeconds;
	}

	public void setTtlSeconds(long ttlSeconds) {
		this.ttlSeconds = ttlSeconds;
	}

	public boolean isRenewTTLOnWrite() {
		return this.renewTTLOnWrite;
	}

	public void setRenewTTLOnWrite(boolean renewTTLOnWrite) {
		this.renewTTLOnWrite = renewTTLOnWrite;
	}

	/**
	 * Crea una configurazione con TTL disabilitato
	 */
	public static RedisTTLConfig disabled() {
		return new RedisTTLConfig(false, -1, false);
	}

	/**
	 * Crea una configurazione con TTL specifico
	 */
	public static RedisTTLConfig withTTL(long ttlSeconds) {
		return new RedisTTLConfig(true, ttlSeconds, true);
	}

	/**
	 * Crea una configurazione basata su ActivePolicy
	 */
	public static RedisTTLConfig fromPolicy(ActivePolicy activePolicy) {
		return new RedisTTLConfig(activePolicy);
	}

	/**
	 * Crea una configurazione per contatori senza intervallo temporale (es. activeRequestCounter).
	 * Questi contatori devono avere renewTTLOnWrite=true per rimanere attivi finché il client fa richieste.
	 */
	public static RedisTTLConfig forCountersWithoutInterval() {
		OpenSPCoop2Properties props = OpenSPCoop2Properties.getInstance();
		return new RedisTTLConfig(
			props.isControlloTrafficoGestorePolicyInMemoryRedisTTLEnabled(),
			props.getControlloTrafficoGestorePolicyInMemoryRedisTTLDefaultSeconds(),
			props.isControlloTrafficoGestorePolicyInMemoryRedisTTLRenewOnWriteWithoutInterval() // true di default
		);
	}

	@Override
	public String toString() {
		return "RedisTTLConfig [enabled=" + this.enabled + ", ttlSeconds=" + this.ttlSeconds +
			   ", renewTTLOnWrite=" + this.renewTTLOnWrite + "]";
	}
}