AbstractStatistiche.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 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.monitor.engine.statistic;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openspcoop2.core.commons.dao.DAOFactoryProperties;
import org.openspcoop2.core.constants.CostantiDB;
import org.openspcoop2.core.constants.TipoPdD;
import org.openspcoop2.core.id.IDServizio;
import org.openspcoop2.core.registry.driver.IDServizioFactory;
import org.openspcoop2.core.statistiche.Statistica;
import org.openspcoop2.core.statistiche.StatisticaContenuti;
import org.openspcoop2.core.statistiche.constants.TipoIntervalloStatistico;
import org.openspcoop2.core.statistiche.dao.IServiceManager;
import org.openspcoop2.core.statistiche.dao.IStatisticaInfoService;
import org.openspcoop2.core.statistiche.dao.IStatisticaInfoServiceSearch;
import org.openspcoop2.core.statistiche.model.StatisticaModel;
import org.openspcoop2.core.transazioni.constants.PddRuolo;
import org.openspcoop2.core.transazioni.dao.ITransazioneServiceSearch;
import org.openspcoop2.generic_project.beans.ConstantField;
import org.openspcoop2.generic_project.beans.FunctionField;
import org.openspcoop2.generic_project.beans.Union;
import org.openspcoop2.generic_project.beans.UnionExpression;
import org.openspcoop2.generic_project.dao.IDBServiceUtilities;
import org.openspcoop2.generic_project.dao.IServiceSearchWithoutId;
import org.openspcoop2.generic_project.dao.IServiceWithoutId;
import org.openspcoop2.generic_project.exception.ExpressionException;
import org.openspcoop2.generic_project.exception.ExpressionNotImplementedException;
import org.openspcoop2.generic_project.exception.MultipleResultException;
import org.openspcoop2.generic_project.exception.NotFoundException;
import org.openspcoop2.generic_project.exception.NotImplementedException;
import org.openspcoop2.generic_project.exception.ServiceException;
import org.openspcoop2.generic_project.expression.IExpression;
import org.openspcoop2.generic_project.expression.Index;
import org.openspcoop2.generic_project.expression.impl.sql.ISQLFieldConverter;
import org.openspcoop2.monitor.engine.condition.EsitoUtils;
import org.openspcoop2.monitor.engine.condition.FilterImpl;
import org.openspcoop2.monitor.engine.config.BasicServiceLibrary;
import org.openspcoop2.monitor.engine.config.BasicServiceLibraryReader;
import org.openspcoop2.monitor.engine.config.StatisticsServiceLibrary;
import org.openspcoop2.monitor.engine.config.StatisticsServiceLibraryReader;
import org.openspcoop2.monitor.engine.config.TransactionServiceLibrary;
import org.openspcoop2.monitor.engine.config.TransactionServiceLibraryReader;
import org.openspcoop2.monitor.engine.config.statistiche.ConfigurazioneStatistica;
import org.openspcoop2.monitor.engine.config.statistiche.dao.IConfigurazioneStatisticaService;
import org.openspcoop2.monitor.engine.constants.Costanti;
import org.openspcoop2.monitor.engine.dynamic.DynamicFactory;
import org.openspcoop2.monitor.engine.dynamic.IDynamicLoader;
import org.openspcoop2.monitor.sdk.constants.StatisticType;
import org.openspcoop2.monitor.sdk.exceptions.StatisticException;
import org.openspcoop2.monitor.sdk.plugins.IStatisticProcessing;
import org.openspcoop2.monitor.sdk.statistic.StatisticResourceFilter;
import org.openspcoop2.protocol.engine.ProtocolFactoryManager;
import org.openspcoop2.protocol.sdk.constants.EsitoTransazioneName;
import org.openspcoop2.protocol.utils.EsitiProperties;
import org.openspcoop2.utils.BooleanNullable;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.TipiDatabase;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.sql.Case;
import org.openspcoop2.utils.sql.CastColumnType;
import org.openspcoop2.utils.sql.ISQLQueryObject;
import org.openspcoop2.utils.sql.SQLObjectFactory;
import org.slf4j.Logger;



/**
 * AbstractStatistiche
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public abstract class AbstractStatistiche {

	protected Logger logger = LoggerWrapperFactory.getLogger(AbstractStatistiche.class);
	private void logDebug(String msg) {
		if(this.logger!=null) {
			this.logger.debug(msg);
		}
	}
	private void logDebug(String msg, Throwable e) {
		if(this.logger!=null) {
			this.logger.debug(msg,e);
		}
	}
	private void logError(String msg, Throwable e) {
		if(this.logger!=null) {
			this.logger.error(msg,e);
		}
	}
	protected boolean debug = false;
	protected boolean useUnionForLatency = true;
	protected boolean generazioneStatisticheCustom = false;
	protected boolean analisiTransazioniCustom = false;
	protected IServiceManager statisticheSM = null;
	protected IServiceWithoutId<?> statisticaServiceDAO = null;
	protected IServiceSearchWithoutId<?> statisticaServiceSearchDAO = null;
	protected StatisticaModel model = null;
	private IStatisticaInfoServiceSearch statisticaInfoSearchDAO = null;
	private IStatisticaInfoService statisticaInfoDAO = null;
	private org.openspcoop2.core.transazioni.dao.IServiceManager transazioniSM = null;
	private ITransazioneServiceSearch transazioneSearchDAO = null;
	private org.openspcoop2.monitor.engine.config.statistiche.dao.IServiceManager pluginsStatisticheSM = null;
	private org.openspcoop2.core.plugins.dao.IServiceManager pluginsBaseSM = null;
	private org.openspcoop2.core.commons.search.dao.IServiceManager utilsSM = null;
	private org.openspcoop2.monitor.engine.config.transazioni.dao.IServiceManager pluginsTransazioniSM;
	@SuppressWarnings("unused")
	private IConfigurazioneStatisticaService confStatisticaDAO =null;
	private TipiDatabase databaseType;
	private StatisticsForceIndexConfig forceIndexConfig;

	private static final String SUFFIX_NON_INIZIALIZZATO = "] non inizializzato";

	private boolean initialized = false;
	
	AbstractStatistiche(){
		
	}
	protected AbstractStatistiche(Logger logger,boolean debug,boolean useUnionForLatency, 
			boolean generazioneStatisticheCustom, boolean analisiTransazioniCustom,
			StatisticsForceIndexConfig forceIndexConfig,
			org.openspcoop2.core.statistiche.dao.IServiceManager statisticheSM,
			org.openspcoop2.core.transazioni.dao.IServiceManager transazioniSM,
			org.openspcoop2.monitor.engine.config.statistiche.dao.IServiceManager pluginsStatisticheSM,
			org.openspcoop2.core.plugins.dao.IServiceManager pluginsBaseSM,
			org.openspcoop2.core.commons.search.dao.IServiceManager utilsSM,
			org.openspcoop2.monitor.engine.config.transazioni.dao.IServiceManager pluginsTransazioniSM){
		if(logger!=null){
			this.logger = logger;
		}
		this.debug = debug;
		this.useUnionForLatency = useUnionForLatency;
		this.generazioneStatisticheCustom = generazioneStatisticheCustom;
		this.analisiTransazioniCustom = analisiTransazioniCustom;
		
		try {
			if(statisticheSM==null){
				throw new ServiceException("StatisticheServiceManager ["+org.openspcoop2.core.statistiche.dao.IServiceManager.class.getName()+SUFFIX_NON_INIZIALIZZATO);
			}
			if(transazioniSM==null){
				throw new ServiceException("TransazioniServiceManager ["+org.openspcoop2.core.transazioni.dao.IServiceManager.class.getName()+SUFFIX_NON_INIZIALIZZATO);
			}
			
			this.statisticheSM = statisticheSM;
			this.statisticaInfoSearchDAO = this.statisticheSM.getStatisticaInfoServiceSearch();
			this.statisticaInfoDAO = this.statisticheSM.getStatisticaInfoService();

			this.databaseType = DAOFactoryProperties.getInstance(this.logger).getTipoDatabaseEnum(org.openspcoop2.core.statistiche.utils.ProjectInfo.getInstance());
			
			this.transazioniSM = transazioniSM;
			this.transazioneSearchDAO = this.transazioniSM.getTransazioneServiceSearch();

			if(this.generazioneStatisticheCustom){
				
				if(pluginsStatisticheSM==null){
					throw new ServiceException("PluginStatisticheServiceManager ["+org.openspcoop2.monitor.engine.config.statistiche.dao.IServiceManager.class.getName()+SUFFIX_NON_INIZIALIZZATO);
				}
				this.pluginsStatisticheSM = pluginsStatisticheSM;
				this.confStatisticaDAO  = this. pluginsStatisticheSM.getConfigurazioneStatisticaService();
				
				if(utilsSM==null){
					throw new ServiceException("UtilsServiceManager ["+org.openspcoop2.core.commons.search.dao.IServiceManager.class.getName()+SUFFIX_NON_INIZIALIZZATO);
				}
				this.utilsSM = utilsSM;
				
				if(pluginsBaseSM==null){
					throw new ServiceException("PluginBaseServiceManager ["+org.openspcoop2.core.plugins.dao.IServiceManager.class.getName()+SUFFIX_NON_INIZIALIZZATO);
				}
				this.pluginsBaseSM = pluginsBaseSM;
				
				if(this.analisiTransazioniCustom){
					if(pluginsTransazioniSM==null){
						throw new ServiceException("PluginTransazioniServiceManager ["+org.openspcoop2.monitor.engine.config.transazioni.dao.IServiceManager.class.getName()+SUFFIX_NON_INIZIALIZZATO);
					}
					this.pluginsTransazioniSM = pluginsTransazioniSM;
				}
			}
			
			this.forceIndexConfig = forceIndexConfig;
			
			this.initialized = true;
			
		} catch (Exception e) {
			this.logError(e.getMessage(),e);
		}
	}

	public boolean isDebug() {
		return this.debug;
	}
	
	protected TipiDatabase getDatabaseType() {
		return this.databaseType;
	}
	
	public void printDate(Date d){
		Calendar c = Calendar.getInstance();
		c.setTime(d);
		printCalendar(c);
	}

	public void printCalendar(Calendar c){
		StringBuilder bf = new StringBuilder();
		bf.append("DATA ["+c.getTime().toString()+"]");
		bf.append(" DATA in millisecondi: "+c.getTime().getTime());
		bf.append(" YEAR:"+c.get(Calendar.YEAR));
		bf.append(" MONTH:"+(c.get(Calendar.MONTH)+1));
		bf.append(" DAY_OF_MONTH:"+c.get(Calendar.DAY_OF_MONTH));
		bf.append(" HOUR_OF_DAY:"+c.get(Calendar.HOUR_OF_DAY));
		bf.append(" MINUTE:"+c.get(Calendar.MINUTE));
		bf.append(" SECOND:"+c.get(Calendar.SECOND));
		bf.append(" MILLISECOND:"+c.get(Calendar.MILLISECOND));
		this.logDebug(bf.toString());
	}

	// METODI ASTRATTI
	public abstract Date truncDate(Date date,boolean print);

	public abstract Date incrementDate(Date date,boolean print);

	public abstract Date decrementDate(Date date,boolean print);

	public Date decrementDate1Millisecond(Date date){
		Calendar cTmp = Calendar.getInstance();
		cTmp.setTime(date);
		cTmp.add(Calendar.MILLISECOND, -1);
		return cTmp.getTime();
	}

	public abstract String getIntervalloStatistica(Date date,boolean print);

	private String getIntervalloStatistica(Date date){
		Date d = this.incrementDate(date, false);
		return this.getIntervalloStatistica(d, false);
	}

	public abstract TipoIntervalloStatistico getTipoStatistiche();

	public abstract void callStatisticPluginMethod(IStatisticProcessing statProcessing, StatisticBean stat) throws StatisticException;

	public abstract String getStatisticPluginMethodName() throws StatisticException;


	public void generaStatistiche( boolean gestioneUltimoIntervallo, long waitMsBeforeNextInterval, boolean waitStatiInConsegna) throws Exception{

		if(!this.initialized){
			throw new ServiceException("Inizializzazione fallita (verificare errori precedenti)");
		}

		try{
			if(this.debug)
				this.logDebug("********************************************* "+this.getTipoStatistiche()+" *******************************************************");

			// Ottengo data in cui e' stato fatto girare l'ultima volta la procedura di popolamento 
			// Tale data viene troncata in funzione della statistica che sto eseguendo 
			// (La prima insert potrebbe contenere informazioni maggiori di quelle che servono: es. in statistiche giornaliere ci potrebbero essere ora,minuti e secondi)
			Date dataUltimaGenerazioneStatistiche = StatisticsInfoUtils.readDataUltimaGenerazioneStatistiche(this.statisticaInfoSearchDAO, this.getTipoStatistiche(), this.logger);
			dataUltimaGenerazioneStatistiche = truncDate(dataUltimaGenerazioneStatistiche, false);
			// Decremento la data di 1 millisecondo in modo da generare poi il SQL nella forma 
			// statistiche per transazioni emessi in data > dataUltimaGenerazioneStatistiche
			dataUltimaGenerazioneStatistiche = this.decrementDate1Millisecond(dataUltimaGenerazioneStatistiche); 

			Date dataAvvioBatch = DateManager.getDate();

			// Now
			// Tale data viene troncata in funzione della statistica che sto eseguendo 
			Date now = new Date();
			now = truncDate(now,false);
			now = this.decrementDate1Millisecond(now);
			Date nowMenoUno = decrementDate(now, false);

			
			// Incremento la data sul database solamente se non sono avvenuti errori durante la generazione, altrimenti dovro' ricalcolarle tutti gli intervalli da quanto e' successo l'errore
			boolean saveDataStatistica = true; 
			
			
			// Genero statistiche per transazioni emessi in data > dataUltimaGenerazioneStatistiche AND data <= now
			while(dataUltimaGenerazioneStatistiche.compareTo(nowMenoUno) <= 0){

				// genera
				boolean generazioneCompletataConSuccesso = generaStatistica(dataUltimaGenerazioneStatistiche, 
						dataAvvioBatch, waitMsBeforeNextInterval, 
						waitStatiInConsegna);
				if(!generazioneCompletataConSuccesso) {
					saveDataStatistica = false;
				}
												
				// increment
				dataUltimaGenerazioneStatistiche = incrementDate(dataUltimaGenerazioneStatistiche, false);
				if(this.debug)
					this.logDebug("-----------------------");

				// Salvo nuova data di ultima generazione statistiche (entro nel prossimo intervallo)
				if(saveDataStatistica) {
					Date next = this.truncDate(this.incrementDate(dataUltimaGenerazioneStatistiche, false),false);
					if(this.debug)
						this.logDebug("Save data ultima generazione statistiche: "+next.toString());
					StatisticsInfoUtils.updateDataUltimaGenerazioneStatistiche(this.statisticaInfoSearchDAO, this.statisticaInfoDAO, 
							this.getTipoStatistiche(), this.logger, next);
				}
				
			}


			// Genero statistiche parziali della data di oggi
			if(gestioneUltimoIntervallo){

				Date dataUltimoIntervallo = dataUltimaGenerazioneStatistiche;

				// genera
				generaStatistica(dataUltimoIntervallo, 
						dataAvvioBatch, -1, 
						false);
				
			}

		}catch(Exception e){
			this.logError(e.getMessage(), e);
			throw e;
		}
	}

	private boolean generaStatistica(Date dataUltimaGenerazioneStatistiche, 
			Date dataAvvioBatch, long waitMsBeforeNextInterval, 
			boolean waitStatiInConsegna) {
		
		// Algoritmo
		// Per la data in fase di aggiornamento:
		// - 1) si elimina eventuali record rimasti "sporchi" (stato_record <1)
		// - 2) i record esistenti nell'intervallo vengono portati al valore stato_record=2
		// - 3) i nuovi record aggiunti vengono creati con stato_record=0
		// - 4) si fa un unico comando di update nell'intervallo con CASE WHEN stato_record==2 THEN -2 WHEN stato_record==0 THEN 1
		// - 5) si eliminano i record nell'intervallo con stato_record=-2
		
		/**if(dataUltimaGenerazioneStatistiche.compareTo(nowMenoUno) == 0){*/
		// L'IF sopra non lo devo fare, se spengo la macchina, e la riaccendo dopo due ore, l'eliminazione delle statistiche gia' generate riguarda nowMenoDue
		// Se ho la stessa data devo eliminare l'intervallo, posso gia' averlo generato
		// Eliminazione (Fase 1)
		if(this.debug)
			this.logDebug("----------- pulizia Fase1 (DataUguale) ------------");
		boolean esito = this.deleteFisicoStatistiche( TipoPdD.DELEGATA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_VALIDO, false);
		if(!esito) {
			return false;
		}
		if(this.debug)
			this.logDebug("------------ pulizia Fase1 (DataUguale) -----------");
		esito = this.deleteFisicoStatistiche( TipoPdD.APPLICATIVA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_VALIDO, false);
		if(!esito) {
			return false;
		}
		//}

		// Aggiornamento (Fase 2)
		if(this.debug)
			this.logDebug("----------- aggiornamento Fase2 (DataUguale) ------------");
		esito = this.updateStatoRecordStatistiche(TipoPdD.DELEGATA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_ANCORA_VALIDO_IN_FASE_DI_AGGIORNAMENTO);
		if(!esito) {
			return false;
		}
		if(this.debug)
			this.logDebug("------------ aggiornamento Fase2 (DataUguale) -----------");
		esito = this.updateStatoRecordStatistiche( TipoPdD.APPLICATIVA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_ANCORA_VALIDO_IN_FASE_DI_AGGIORNAMENTO);
		if(!esito) {
			return false;
		}
		
		// Generazione (Fase 3)
		BooleanNullable delegataStatiInCorso = BooleanNullable.NULL();
		if(this.debug)
			this.logDebug("----------- generazione Fase3 (DataUguale) ------------");
		if(this.useUnionForLatency) {
			esito = this.generaStatisticheUnion( TipoPdD.DELEGATA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, delegataStatiInCorso );
		}
		else {
			esito = this.generaStatistiche( TipoPdD.DELEGATA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, delegataStatiInCorso );
		}
		if(!esito) {
			return false;
		}
		BooleanNullable applicativaStatiInCorso = BooleanNullable.NULL();		
		if(this.debug)
			this.logDebug("------------ generazione Fase3 (DataUguale) -----------");
		if(this.useUnionForLatency) {
			esito = this.generaStatisticheUnion( TipoPdD.APPLICATIVA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, applicativaStatiInCorso );
		}
		else {
			esito = this.generaStatistiche( TipoPdD.APPLICATIVA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, applicativaStatiInCorso );
		}
		if(!esito) {
			return false;
		}

		// Promozione Record (Fase4)
		boolean unicaPromozione = true; // deve essere un unico comando sql
		if(unicaPromozione) {
			if(this.debug)
				this.logDebug("----------- promozione Fase4 (DataUguale) ------------");
			esito = this.updateStatoRecordStatistiche( null, dataUltimaGenerazioneStatistiche, 
					CostantiDB.STATISTICHE_STATO_RECORD_ANCORA_VALIDO_IN_FASE_DI_AGGIORNAMENTO, CostantiDB.STATISTICHE_STATO_RECORD_ELIMINATO,
					CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, CostantiDB.STATISTICHE_STATO_RECORD_VALIDO);
			if(!esito) {
				return false;
			}
		}
		else {
			if(this.debug)
				this.logDebug("----------- promozione Fase4 (DataUguale) ------------");
			esito = this.updateStatoRecordStatistiche( TipoPdD.DELEGATA, dataUltimaGenerazioneStatistiche, 
					CostantiDB.STATISTICHE_STATO_RECORD_ANCORA_VALIDO_IN_FASE_DI_AGGIORNAMENTO, CostantiDB.STATISTICHE_STATO_RECORD_ELIMINATO,
					CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, CostantiDB.STATISTICHE_STATO_RECORD_VALIDO);
			if(!esito) {
				return false;
			}
			if(this.debug)
				this.logDebug("------------ promozione Fase4 (DataUguale) -----------");
			esito = this.updateStatoRecordStatistiche( TipoPdD.APPLICATIVA, dataUltimaGenerazioneStatistiche, 
					CostantiDB.STATISTICHE_STATO_RECORD_ANCORA_VALIDO_IN_FASE_DI_AGGIORNAMENTO, CostantiDB.STATISTICHE_STATO_RECORD_ELIMINATO,
					CostantiDB.STATISTICHE_STATO_RECORD_IN_AGGIORNAMENTO, CostantiDB.STATISTICHE_STATO_RECORD_VALIDO);
			if(!esito) {
				return false;
			}
		}

		// Eliminazione (Fase 5)
		boolean pulituraOk = true;
		if(this.debug)
			this.logDebug("----------- eliminazione Fase5 (DataUguale) ------------");
		esito = this.deleteFisicoStatistiche( TipoPdD.DELEGATA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_ELIMINATO, true);
		if(!esito) {
			pulituraOk=false;
		}
		if(this.debug)
			this.logDebug("------------ eliminazione Fase5 (DataUguale) -----------");
		esito = this.deleteFisicoStatistiche( TipoPdD.APPLICATIVA, dataUltimaGenerazioneStatistiche, CostantiDB.STATISTICHE_STATO_RECORD_ELIMINATO, true);
		if(!esito) {
			pulituraOk=false;
		}
		
		// Verifica data attuale e record incontrati
		/**System.out.println("CHECK ms'"+waitMsBeforeNextInterval+"' stati:"+waitStatiInConsegna+"");*/
		if(waitMsBeforeNextInterval>0) {
			Date dateNext = incrementDate(dataUltimaGenerazioneStatistiche, false);
			Date valid = new Date( dataAvvioBatch.getTime() - waitMsBeforeNextInterval );
			boolean block = !dateNext.before(valid);
			/**System.out.println("CHECK STAT NOW["+org.openspcoop2.utils.date.DateUtils.getSimpleDateFormatMs().format(DateManager.getDate())+"] VALID["+
					org.openspcoop2.utils.date.DateUtils.getSimpleDateFormatMs().format(valid)+"] < ["+
					org.openspcoop2.utils.date.DateUtils.getSimpleDateFormatMs().format(dateNext)+"]: "+block);*/
			if(block) {
				if(this.debug)
					this.logDebug("Tradeoff '"+waitMsBeforeNextInterval+"'; next interval update disabled");
				return false;
			}
		}
		if(waitStatiInConsegna) {
			if(delegataStatiInCorso!=null && delegataStatiInCorso.getValue()!=null && delegataStatiInCorso.getValue().booleanValue()) {
				if(this.debug)
					this.logDebug("Trovate transazioni di fruizioni ancora in consegna; next interval update disabled");
				return false;
			}
			if(applicativaStatiInCorso!=null && applicativaStatiInCorso.getValue()!=null && applicativaStatiInCorso.getValue().booleanValue()) {
				if(this.debug)
					this.logDebug("Trovate transazioni di erogazioni ancora in consegna; next interval update disabled");
				return false;
			}
		}
		
		return pulituraOk;
	}

	
	
	
	// ---- GENERAZIONE STATISTICHE BASE ----
	
	private boolean generaStatisticheUnion(TipoPdD tipoPdD, Date data, int statoRecord, BooleanNullable statiInCorso) {

		boolean generazioneOk = true;
		
		if(this.debug){
			this.logDebug("Generazione statistiche (union) ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") ...");
		}
		try{
			ISQLFieldConverter fieldConverter = ((IDBServiceUtilities<?>)this.transazioneSearchDAO).getFieldConverter(); 
			
			// ** Select field **
			List<FunctionField> selectList = new ArrayList<>();
			StatisticsUtils.addSelectFieldCountTransaction(selectList);
			StatisticsUtils.addSelectFieldSizeTransaction(tipoPdD, selectList);
			
			List<FunctionField> selectListConLatenze = new ArrayList<>();
			selectListConLatenze.addAll(selectList);
			StatisticsUtils.addSelectFunctionFieldLatencyTransaction(tipoPdD, fieldConverter, selectListConLatenze);
			
			List<FunctionField> selectListSenzaLatenze = new ArrayList<>();
			selectListSenzaLatenze.addAll(selectList);
			List<ConstantField> selectListCostantiLatenze = new ArrayList<>();
			StatisticsUtils.addSelectConstantFieldLatencyTransaction(tipoPdD, fieldConverter, selectListCostantiLatenze);

			// ** Where **
			// Creo intervallo
			Date dateNext = incrementDate(data, false);
			
			IExpression exprConLatenze = this.transazioneSearchDAO.newExpression();
			StatisticsUtils.setExpressionNotNullDate(this.transazioneSearchDAO, exprConLatenze, data, dateNext, tipoPdD, null, fieldConverter);
			
			IExpression exprSenzaLatenze = this.transazioneSearchDAO.newExpression();
			StatisticsUtils.setExpressionNullDate(this.transazioneSearchDAO, exprSenzaLatenze, data, dateNext, tipoPdD, null, fieldConverter);

			if(this.debug){
				this.logDebug("Genero statistiche ["+this.getTipoStatistiche()+"] Intervallo date: ["+data.toString()+" - "+dateNext.toString()+"]");
				this.logDebug("Valori query (ms) tr.data_ingresso_richiesta>["+data.getTime()+"] AND tr.data_ingresso_richiesta<=["+dateNext.getTime()+"]");
			}

			if(this.forceIndexConfig!=null){
				if(this.forceIndexConfig.getTransazioniForceIndexGroupByNumeroDimensione()!=null){
					List<Index> listForceIndexes = this.forceIndexConfig.getTransazioniForceIndexGroupByNumeroDimensione();
					if(!listForceIndexes.isEmpty()){
						for (Index index : listForceIndexes) {
							exprConLatenze.addForceIndex(index);
							exprSenzaLatenze.addForceIndex(index);
						}
					}
				}
			}
			
			// ** Union **
			
			UnionExpression latenzeUnionExpr = new UnionExpression(exprConLatenze);
			for (FunctionField functionField : selectListConLatenze) {
				latenzeUnionExpr.addSelectFunctionField(functionField);
			}
			StatisticsUtils.addSelectUnionField(latenzeUnionExpr, fieldConverter);
			
			UnionExpression senzaLatenzeUnionExpr = new UnionExpression(exprSenzaLatenze);
			for (FunctionField functionField : selectListSenzaLatenze) {
				senzaLatenzeUnionExpr.addSelectFunctionField(functionField);
			}
			for (ConstantField constantField : selectListCostantiLatenze) {
				senzaLatenzeUnionExpr.addSelectField(constantField, constantField.getAlias());
			}
			StatisticsUtils.addSelectUnionField(senzaLatenzeUnionExpr, fieldConverter);
			
			Union union = new Union();
			union.setUnionAll(true);
			for (String alias : latenzeUnionExpr.getReturnFieldAliases()) {
				union.addField(alias);
			}
			
			List<Map<String, Object>> list = this.transazioneSearchDAO.union(union, latenzeUnionExpr, senzaLatenzeUnionExpr);
			
			for (Map<String, Object> row : list) {
				
				StatisticBean stat = null;
				try {
				
					// Leggo informazioni di base dalla statistica
					stat = this.readStatisticBean(data, row, fieldConverter, true);
					
					// Aggiungo informazioni sul numero di transazioni e size delle transazioni
					StatisticsUtils.updateStatisticBeanCountTransactionInfo(stat, row);
					StatisticsUtils.updateStatisticBeanSizeTransactionInfo(stat, row);
					StatisticsUtils.updateStatisticsBeanLatencyTransactionInfo(stat, row);
			
					// Check stati in corso
					if(EsitoUtils.isFaseIntermedia(stat.getEsitoContesto())) {
						statiInCorso.setValue(true);
					}
					
					// Inserisco statistica
					insertStatistica(stat, statoRecord);
	
					if(this.generazioneStatisticheCustom){
						createCustomStatistic(stat);
					}
					
				}catch(Throwable eSingoloRecord) {
					String r = "";
					if(stat!=null) {
						try {
							r ="\nRecord: "+stat.toString();
						}catch(Throwable tPrint) {}
					}
					this.logError("Rilevato errore durante la registrazione del singolo record di informazione statistica: "+eSingoloRecord.getMessage()+r,eSingoloRecord);
					generazioneOk = false;
				}
			}


		} catch (ServiceException e){
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (NotImplementedException e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (ExpressionNotImplementedException e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (ExpressionException e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (NotFoundException e) {
			if(this.debug){
				this.logDebug(e.getMessage(),e);
			}
		}catch (Throwable e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		}  

		if(this.debug){
			this.logDebug("Generazione statistiche ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") terminata");
		}
				
		return generazioneOk;
	}
	
	private boolean generaStatistiche(  TipoPdD tipoPdD, Date data, int statoRecord, BooleanNullable statiInCorso) {

		boolean generazioneOk = true;
		
		if(this.debug){
			this.logDebug("Generazione statistiche ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") ...");
		}
		try{
			IExpression expr = this.transazioneSearchDAO.newExpression();

			ISQLFieldConverter fieldConverter = ((IDBServiceUtilities<?>)this.transazioneSearchDAO).getFieldConverter(); 
			
			// ** Select field **
			List<FunctionField> selectList = new ArrayList<>();
			StatisticsUtils.addSelectFieldCountTransaction(selectList);
			StatisticsUtils.addSelectFieldSizeTransaction(tipoPdD, selectList);
			
			// ** Where **
			// Creo intervallo
			Date dateNext = incrementDate(data, false);
			StatisticsUtils.setExpression(this.transazioneSearchDAO, expr, data, dateNext, tipoPdD, false, null, fieldConverter);

			if(this.debug){
				this.logDebug("Genero statistiche ["+this.getTipoStatistiche()+"] Intervallo date: ["+data.toString()+" - "+dateNext.toString()+"]");
				this.logDebug("Valori query (ms) tr.data_ingresso_richiesta>["+data.getTime()+"] AND tr.data_ingresso_richiesta<=["+dateNext.getTime()+"]");
			}

			if(this.forceIndexConfig!=null){
				if(this.forceIndexConfig.getTransazioniForceIndexGroupByNumeroDimensione()!=null){
					List<Index> listForceIndexes = this.forceIndexConfig.getTransazioniForceIndexGroupByNumeroDimensione();
					if(!listForceIndexes.isEmpty()){
						for (Index index : listForceIndexes) {
							expr.addForceIndex(index);
						}
					}
				}
			}
			
			List<Map<String, Object>> list = this.transazioneSearchDAO.groupBy(expr, selectList.toArray(new FunctionField[1]));

			for (Map<String, Object> row : list) {
				
				StatisticBean stat = null;
				try {
				
					// Leggo informazioni di base dalla statistica
					stat = this.readStatisticBean(data, row, null, false);
					
					// Aggiungo informazioni sul numero di transazioni e size delle transazioni
					StatisticsUtils.updateStatisticBeanCountTransactionInfo(stat, row);
					StatisticsUtils.updateStatisticBeanSizeTransactionInfo(stat, row);
	
					// Aggiungo informazioni che richiedono ulteriori condizioni di where (es. date not null) 
					try {
						this.addLatenze(data, dateNext, tipoPdD, stat, fieldConverter);
					}catch(Throwable eSingoloRecordAddLatenze) {
						this.logError("Rilevato errore durante la lettura delle latenze di un singolo record di informazione statistica: "+eSingoloRecordAddLatenze.getMessage()+
								"\nRecord: "+stat.toString(),eSingoloRecordAddLatenze);
						generazioneOk = false;
					}
									
					// Check stati in corso
					if(EsitoUtils.isFaseIntermedia(stat.getEsitoContesto())) {
						statiInCorso.setValue(true);
					}
					
					// Inserisco statistica
					insertStatistica(stat, statoRecord);
	
					if(this.generazioneStatisticheCustom){
						createCustomStatistic(stat);
					}
					
				}catch(Throwable eSingoloRecord) {
					String r = "";
					if(stat!=null) {
						try {
							r ="\nRecord: "+stat.toString();
						}catch(Throwable tPrint) {}
					}
					this.logError("Rilevato errore durante la registrazione del singolo record di informazione statistica: "+eSingoloRecord.getMessage()+r,eSingoloRecord);
					generazioneOk = false;
				}
			}


		} catch (ServiceException e){
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (NotImplementedException e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (ExpressionNotImplementedException e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (ExpressionException e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		} catch (NotFoundException e) {
			if(this.debug){
				this.logDebug(e.getMessage(),e);
			}
		}catch (Throwable e) {
			this.logError(e.getMessage(),e);
			generazioneOk = false;
		}  

		if(this.debug){
			this.logDebug("Generazione statistiche ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") terminata");
		}
				
		return generazioneOk;
	}
	
	private StatisticBean readStatisticBean(Date data,Map<String, Object> row, ISQLFieldConverter fieldConverter, boolean useFieldConverter) throws ExpressionException{
		StatisticBean stat = new StatisticBean();

		stat.setDateIntervalLeft(data);
		stat.setDateIntervalRight(this.incrementDate(data, false));
		Date next = this.truncDate(this.incrementDate(data, false),false);
		stat.setData(next);
		if(this.debug)
			this.logDebug("Salvo statistica con data ["+next+"]");
	
		return StatisticsUtils.readStatisticBean(stat, row, fieldConverter, useFieldConverter);
	}
	
	private void addLatenze(Date data, Date dateNext, TipoPdD tipoPdD, StatisticBean stat, ISQLFieldConverter fieldConverter) throws Exception{
		
		IExpression exprDateNotNull = this.transazioneSearchDAO.newExpression();
		StatisticsUtils.setExpression(this.transazioneSearchDAO, exprDateNotNull, data, dateNext, tipoPdD, true, stat, fieldConverter);
		
		List<FunctionField> selectListDateNotNull = new ArrayList<>();
		
		StatisticsUtils.addSelectFunctionFieldLatencyTransaction(tipoPdD, fieldConverter, selectListDateNotNull);
		
		if(this.debug){
			this.logDebug("Leggo ulteriormente statistiche con campi data not null ["+this.getTipoStatistiche()+"] Intervallo date: ["+data.toString()+" - "+dateNext.toString()+"]");
			this.logDebug("Valori query (ms) tr.data_ingresso_richiesta>["+data.getTime()+"] AND tr.data_ingresso_richiesta<=["+dateNext.getTime()+"]");
		}

		try{
			if(this.forceIndexConfig!=null){
				if(this.forceIndexConfig.getTransazioniForceIndexGroupByLatenze()!=null){
					List<Index> listForceIndexes = this.forceIndexConfig.getTransazioniForceIndexGroupByLatenze();
					if(!listForceIndexes.isEmpty()){
						for (Index index : listForceIndexes) {
							exprDateNotNull.addForceIndex(index);
						}
					}
				}
			}
			
			List<Map<String, Object>> listDateNotNull = this.transazioneSearchDAO.groupBy(exprDateNotNull, selectListDateNotNull.toArray(new FunctionField[1]));
			
			if(listDateNotNull.size()>1){
				throw new MultipleResultException("Attesa un solo gruppo, ritornati "+listDateNotNull.size()+" gruppi");
			}
			Map<String, Object> row = listDateNotNull.get(0);
			
			StatisticsUtils.updateStatisticsBeanLatencyTransactionInfo(stat, row);
			
		}catch(NotFoundException notFound){
			// possono non esistere
			if(this.debug){
				this.logDebug(notFound.getMessage(),notFound);
			}
			stat.setLatenzaPorta(Costanti.INFORMAZIONE_LATENZA_NON_DISPONIBILE);
			stat.setLatenzaServizio(Costanti.INFORMAZIONE_LATENZA_NON_DISPONIBILE);
			stat.setLatenzaTotale(Costanti.INFORMAZIONE_LATENZA_NON_DISPONIBILE);
		}
	}
	
	
	
	
	
	
	// ---- RICERCA STATISTICHE PERSONALIZZATE ----
	
	private void createCustomStatistic(StatisticBean stat)   {

		/**IExpression expr;*/
		try {
/**			expr = this.confStatisticaDAO.newExpression();
//			expr.and();
//
//			expr.equals(ConfigurazioneStatistica.model().ID_CONFIGURAZIONE_SERVIZIO_AZIONE.ID_CONFIGURAZIONE_SERVIZIO.SERVIZIO, stat.getServizio());
//
//			if ("*".equals(stat.getAzione())) {
//				expr.and().equals(ConfigurazioneStatistica.model().ID_CONFIGURAZIONE_SERVIZIO_AZIONE.AZIONE, "*");
//			} else {
//				IExpression orExpr = this.confStatisticaDAO.newExpression();
//				orExpr.or();
//				orExpr.equals(ConfigurazioneStatistica.model().ID_CONFIGURAZIONE_SERVIZIO_AZIONE.AZIONE, "*");
//				orExpr.equals(ConfigurazioneStatistica.model().ID_CONFIGURAZIONE_SERVIZIO_AZIONE.AZIONE, stat.getAzione());
//
//				expr.and(orExpr);
//			}
//			
//			expr.equals(ConfigurazioneStatistica.model().ENABLED, true);
//
//			IPaginatedExpression pagExpr = this.confStatisticaDAO.toPaginatedExpression(expr);
//			List<ConfigurazioneStatistica> list = this.confStatisticaDAO.findAll(pagExpr);*/

			if(stat.getDestinatario()==null || stat.getDestinatario().getTipo()==null || stat.getDestinatario().getNome()==null){
				if(this.debug){
					this.logDebug("Statistiche personalizzate non ricercate: destinatario non presente");
				}
				return;
			}
			if(stat.getTipoServizio()==null || stat.getServizio()==null){
				if(this.debug){
					this.logDebug("Statistiche personalizzate non ricercate: servizio non presente");
				}
				return;
			}
			
			IDServizio idServizio = IDServizioFactory.getInstance().
					getIDServizioFromValues(stat.getTipoServizio(), stat.getServizio(), 
							stat.getDestinatario(), 
							stat.getVersioneServizio());
			if (!("*".equals(stat.getAzione()))) {
				idServizio.setAzione(stat.getAzione());
			}
			
			BasicServiceLibraryReader reader = new BasicServiceLibraryReader(this.pluginsBaseSM, this.utilsSM, this.debug);
			BasicServiceLibrary basicLibrary = null;
			try{
				basicLibrary = reader.read(idServizio, this.logger);
				if(basicLibrary==null){
					throw new NotFoundException("Null instance return");
				}
			}catch(NotFoundException notFound){
				if(this.debug){
					this.logDebug("Statistiche personalizzate non ricercate: nessuna configurazione base presente per l'IDServizio: "+idServizio,notFound);
				}
				return;
			}

			TransactionServiceLibrary transactionLibrary = null;
			if(this.analisiTransazioniCustom){
				TransactionServiceLibraryReader transactionReader = new TransactionServiceLibraryReader(this.pluginsTransazioniSM, this.debug);
				transactionLibrary = transactionReader.readConfigurazioneTransazione(basicLibrary, this.logger);
			}
			
			StatisticsServiceLibraryReader statReader = new StatisticsServiceLibraryReader(this.pluginsStatisticheSM, this.debug);
			StatisticsServiceLibrary statLibrary = null;
			try{
				statLibrary = statReader.readConfigurazioneStatistiche(basicLibrary, transactionLibrary, this.logger);
				if(statLibrary==null){
					throw new NotFoundException("Null instance return");
				}
			}catch(NotFoundException notFound){
				if(this.debug){
					this.logDebug("Statistiche personalizzate non ricercate: nessuna configurazione specifica per le statistiche presente per l'IDServizio: "+idServizio,notFound);
				}
				return;
			}
						
			List<ConfigurazioneStatistica> list = statLibrary.mergeServiceActionSearchLibrary(false,true); // si vuole registrare nei log che un plugin è disabilitato
			
			for (ConfigurazioneStatistica confStat : list) {

				String classNameStatisticaPersonalizzata = confStat.getPlugin().getClassName();
				String labelStatisticaPersonalizzata = confStat.getLabel();
				if(this.debug){
					this.logDebug("*** Inizio Gestione Statistica personalizzata ("+classNameStatisticaPersonalizzata+" ["+labelStatisticaPersonalizzata+"]) ****");
				}
				stat.setIdStatistica(confStat.getIdConfigurazioneStatistica());
				stat.setPluginClassname(classNameStatisticaPersonalizzata);
				IDynamicLoader cStatPersonalizzata = DynamicFactory.getInstance().newDynamicLoader(confStat.getPlugin().getTipoPlugin(), confStat.getPlugin().getTipo(),
						classNameStatisticaPersonalizzata, this.logger);
				IStatisticProcessing statProcessing = (IStatisticProcessing) cStatPersonalizzata.newInstance();
				if(this.isEnabledStatisticTypeCustom(statProcessing)){
					if(this.debug){
						this.logDebug("---- Invocazione ["+cStatPersonalizzata.getClassName()+"."+this.getStatisticPluginMethodName()+"()] ...");
					}
					try {
						this.callStatisticPluginMethod(statProcessing, stat);
						if(this.debug){
							this.logDebug("---- Invocazione ["+cStatPersonalizzata.getClassName()+"."+this.getStatisticPluginMethodName()+"()] terminata");
						}
					} catch (Exception e) {
						this.logError("---- Invocazione ["+cStatPersonalizzata.getClassName()+"."+this.getStatisticPluginMethodName()+"()] terminata con errori: "+e.getMessage(), e);
					} 
				}
				else{
					if(this.debug){
						this.logDebug("Tipo di statistica ["+this.getTipoStatistiche()+"] non abilitata nel plugin");
					}
				}
				if(this.debug){
					this.logDebug("*** Fine Gestione Statistica personalizzata ("+classNameStatisticaPersonalizzata+" ["+labelStatisticaPersonalizzata+"]) ****");
				}
				
			}

		} catch (Exception e) {
			this.logError(e.getMessage(), e);
		} 

	}
	
	private boolean isEnabledStatisticTypeCustom(IStatisticProcessing statProcessing){
		List<StatisticType> listEnabled = statProcessing.getEnabledStatisticType();
		switch (this.getTipoStatistiche()) {
			case STATISTICHE_ORARIE:
				for (StatisticType statisticType : listEnabled) {
					if(StatisticType.ORARIA.equals(statisticType)){
						return true;
					}
				}
				break;
			case STATISTICHE_GIORNALIERE:
				for (StatisticType statisticType : listEnabled) {
					if(StatisticType.GIORNALIERA.equals(statisticType)){
						return true;
					}
				}
				break;
			case STATISTICHE_SETTIMANALI:
				for (StatisticType statisticType : listEnabled) {
					if(StatisticType.SETTIMANALE.equals(statisticType)){
						return true;
					}
				}
				break;
			case STATISTICHE_MENSILI:
				for (StatisticType statisticType : listEnabled) {
					if(StatisticType.MENSILE.equals(statisticType)){
						return true;
					}
				}
				break;
		}
		return false;
	}
	
	
	
	
	// ---- GENERAZIONE STATISTICHE PERSONALIZZATE ----
	protected void generaStatisticaPersonalizzataByStato(StatisticBean stat) {
		this.generaStatisticaPersonalizzata(stat, true, null, null);
	}
	protected void generaStatisticaPersonalizzata(StatisticBean stat,
			RisorsaSemplice risorsaSemplice) {
		this.generaStatisticaPersonalizzata(stat, false, risorsaSemplice, null);
	}
	protected void generaStatisticaPersonalizzata(StatisticBean stat,
			RisorsaAggregata risorsaAggregata) {
		this.generaStatisticaPersonalizzata(stat, false, null, risorsaAggregata);
	}
	private void generaStatisticaPersonalizzata(StatisticBean stat,
			boolean gropuByStato,
			RisorsaSemplice risorsaSemplice, RisorsaAggregata risorsaAggregata) {

		TipoPdD tipoPdD = null;
		String idStatisticaPersonalizzata = null;
		Date dateStatistics = null;
		try{
			tipoPdD = stat.getTipoPorta();
			Date dateLeft = stat.getDateIntervalLeft();
			Date dateRight = stat.getDateIntervalRight();
			dateStatistics = stat.getData();
			idStatisticaPersonalizzata = stat.getIdStatistica();
			
			if(this.debug){
				this.logDebug("Generazione statistica personalizzata ["+idStatisticaPersonalizzata+"] ["+this.getTipoStatistiche()+"] ["+tipoPdD+
						"]("+this.getIntervalloStatistica(dateStatistics,false)+") ...");
			}
			
			ISQLFieldConverter fieldConverter = ((IDBServiceUtilities<?>)this.transazioneSearchDAO).getFieldConverter(); 
			
			Map<String, StatisticaContenuti> statisticheContenuti = new HashMap<String, StatisticaContenuti>();
			
			
			String idRisorsa = null;
			StatisticResourceFilter [] risorseFiltri = null;
			if(risorsaSemplice!=null){
				idRisorsa = risorsaSemplice.getIdRisorsa();
				if(risorsaSemplice.getFiltri()!=null && !risorsaSemplice.getFiltri().isEmpty()){
					risorseFiltri = risorsaSemplice.getFiltri().toArray(new StatisticResourceFilter[1]);
				}
			}
			
			
			
			
			
			// ** Informazioni di Base (numero transazioni e dimensione) **

			IExpression expr = this.transazioneSearchDAO.newExpression();
			
			List<FunctionField> selectList = new ArrayList<>();
			StatisticsUtils.addSelectFieldCountTransaction(selectList);
			StatisticsUtils.addSelectFieldSizeTransaction(tipoPdD, selectList);
			
			// Creo espressione
			List<AliasFilter> aliases = new ArrayList<>();
			if(gropuByStato){
				StatisticsUtils.setExpressionByStato(this.transazioneSearchDAO, expr, dateLeft, dateRight, tipoPdD, false, stat, fieldConverter);
			}else{
				StatisticsUtils.setExpressionStatsPersonalizzate(this.transazioneSearchDAO, expr, dateLeft, dateRight, tipoPdD, false, stat, fieldConverter, 
						aliases, idRisorsa, risorseFiltri);
			}

			// aggiungo eventuale filtro personalizzato
			if(risorsaAggregata!=null && (risorsaAggregata.getFiltro() instanceof FilterImpl)){
				FilterImpl f = (FilterImpl) risorsaAggregata.getFiltro();
				expr.and(f.getExpression());
			}
			
			if(this.debug){
				this.logDebug("Analizzo dati di base statistica personalizzata ["+idStatisticaPersonalizzata+"]  ["+this.getTipoStatistiche()+
						"] Intervallo date: ["+dateLeft.toString()+" - "+dateRight.toString()+"]");
				this.logDebug("Valori query (ms) tr.data_ingresso_richiesta>["+dateLeft.getTime()+"] AND tr.data_ingresso_richiesta<=["+dateRight.getTime()+"]");
				this.logDebug("Expr: "+expr.toString());
			}

			if(this.forceIndexConfig!=null){
				if(this.forceIndexConfig.getTransazioniForceIndexGroupByCustomNumeroDimensione()!=null){
					List<Index> listForceIndexes = this.forceIndexConfig.getTransazioniForceIndexGroupByCustomNumeroDimensione();
					if(!listForceIndexes.isEmpty()){
						for (Index index : listForceIndexes) {
							expr.addForceIndex(index);
						}
					}
				}
			}
			
			List<Map<String, Object>> list = this.transazioneSearchDAO.groupBy(expr, selectList.toArray(new FunctionField[1]));

			for (Map<String, Object> row : list) {
				
				if(list.size()>1 &&
					risorsaAggregata!=null){
					throw new ServiceException("Localizzata più di una entry?? Comportamento non atteso per una risorsa aggregata");
				}
				
				// Update della statistica per quanto concerne i valori
				StatisticaContenuti statisticaContenuti = new  StatisticaContenuti();
				if(gropuByStato){
					StatisticsUtils.fillStatisticsContenutiByStato(idStatisticaPersonalizzata, statisticaContenuti, row);
				}
				else if(risorsaSemplice!=null){
					StatisticsUtils.fillStatisticsContenuti(idStatisticaPersonalizzata, statisticaContenuti, row, aliases, risorsaSemplice);
				}
				else{
					StatisticsUtils.fillStatisticsContenuti(idStatisticaPersonalizzata, statisticaContenuti, risorsaAggregata);
				}
				
				// Aggiungo informazioni sul numero di transazioni e size delle transazioni
				StatisticBean tmp = new StatisticBean();
				tmp.setTipoPorta(stat.getTipoPorta());
				StatisticsUtils.updateStatisticBeanCountTransactionInfo(tmp, row);
				StatisticsUtils.updateStatisticBeanSizeTransactionInfo(tmp, row);
				statisticaContenuti.setNumeroTransazioni((int)tmp.getRichieste());
				statisticaContenuti.setDimensioniBytesBandaComplessiva(tmp.getBytesBandaTotale());
				statisticaContenuti.setDimensioniBytesBandaInterna(tmp.getBytesBandaInterna());
				statisticaContenuti.setDimensioniBytesBandaEsterna(tmp.getBytesBandaEsterna());
				
				// Aggiungo in cache
				String key = StatisticsUtils.buildKey(statisticaContenuti);
				statisticheContenuti.put(key, statisticaContenuti);
				
			}
			
			
			
			
			// ** Latenze **
			try{
				expr = this.transazioneSearchDAO.newExpression();
				
				selectList = new ArrayList<>();
				StatisticsUtils.addSelectFunctionFieldLatencyTransaction(tipoPdD, fieldConverter, selectList);
				
				// Creo espressione
				aliases = new ArrayList<>();
				if(gropuByStato){
					StatisticsUtils.setExpressionByStato(this.transazioneSearchDAO, expr, dateLeft, dateRight, tipoPdD, false, stat, fieldConverter);
				}else{
					StatisticsUtils.setExpressionStatsPersonalizzate(this.transazioneSearchDAO, expr, dateLeft, dateRight, tipoPdD, true, stat, fieldConverter, 
							aliases, idRisorsa, risorseFiltri);
				}

				// aggiungo eventuale filtro personalizzato
				if(risorsaAggregata!=null && (risorsaAggregata.getFiltro() instanceof FilterImpl)){
					FilterImpl f = (FilterImpl) risorsaAggregata.getFiltro();
					expr.and(f.getExpression());
				}
	
				if(this.debug){
					this.logDebug("Analizzo dati sulla latenza per la statistica personalizzata ["+idStatisticaPersonalizzata+"]  ["+
							this.getTipoStatistiche()+"] Intervallo date: ["+dateLeft.toString()+" - "+dateRight.toString()+"]");
					this.logDebug("Valori query (ms) tr.data_ingresso_richiesta>["+dateLeft.getTime()+"] AND tr.data_ingresso_richiesta<=["+dateRight.getTime()+"]");
				}
	
				if(this.forceIndexConfig!=null
					&& this.forceIndexConfig.getTransazioniForceIndexGroupByCustomLatenze()!=null){
					List<Index> listForceIndexes = this.forceIndexConfig.getTransazioniForceIndexGroupByCustomLatenze();
					if(!listForceIndexes.isEmpty()){
						for (Index index : listForceIndexes) {
							expr.addForceIndex(index);
						}
					}
				}
				
				list = this.transazioneSearchDAO.groupBy(expr, selectList.toArray(new FunctionField[1]));
	
				for (Map<String, Object> row : list) {
					
					if(list.size()>1){
						if(risorsaAggregata!=null){
							throw new Exception("Localizzata più di una entry?? Comportamento non atteso per una risorsa aggregata");
						}
					}
					
					// Update della statistica per quanto concerne i valori
					StatisticaContenuti statisticaContenuti = new  StatisticaContenuti();
					if(gropuByStato){
						StatisticsUtils.fillStatisticsContenutiByStato(idStatisticaPersonalizzata, statisticaContenuti, row);
					}
					else if(risorsaSemplice!=null){
						StatisticsUtils.fillStatisticsContenuti(idStatisticaPersonalizzata, statisticaContenuti, row, aliases, risorsaSemplice);
					}
					else{
						StatisticsUtils.fillStatisticsContenuti(idStatisticaPersonalizzata, statisticaContenuti, risorsaAggregata);
					}
					
					// Prelevo dalla cache (Deve esistere per forza)
					String key = StatisticsUtils.buildKey(statisticaContenuti);
					statisticaContenuti = statisticheContenuti.get(key);
					if(statisticaContenuti==null){
						throw new ServiceException("Statistica ["+key+"] non presente in cache??");
					}
					
					// Aggiorno informazioni sulla latenza
					StatisticBean tmp = new StatisticBean();
					StatisticsUtils.updateStatisticsBeanLatencyTransactionInfo(tmp, row);
					statisticaContenuti.setLatenzaPorta(tmp.getLatenzaPorta());
					statisticaContenuti.setLatenzaServizio(tmp.getLatenzaServizio());
					statisticaContenuti.setLatenzaTotale(tmp.getLatenzaTotale());
					
				}
			} catch (NotFoundException e) {
				if(this.debug){
					this.logDebug(e.getMessage(),e);
				}
			}
			
			
			
			
			// ** Aggiorno statistiche **
			if(this.debug){
				this.logDebug("Aggiorno ["+statisticheContenuti.size()+"] risultati statistiche personalizzate");
			}
			if(statisticheContenuti.size()>0){
				this.updateStatistica(stat.getId(), statisticheContenuti.values().toArray(new StatisticaContenuti[1]));
			}


		} catch (ServiceException e){
			this.logError(e.getMessage(),e);
		} catch (NotImplementedException e) {
			this.logError(e.getMessage(),e);
		} catch (ExpressionNotImplementedException e) {
			this.logError(e.getMessage(),e);
		} catch (ExpressionException e) {
			this.logError(e.getMessage(),e);
		} catch (NotFoundException e) {
			if(this.debug){
				this.logDebug(e.getMessage(),e);
			}
		}catch (Exception e) {
			this.logError(e.getMessage(),e);
		}  

		if(this.debug){
			this.logDebug("Generazione statistica personalizzata ["+idStatisticaPersonalizzata+"] ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+
					this.getIntervalloStatistica(dateStatistics,false)+") terminata");
		}
	}


	
	
	
	
	
	
	// ---- CRUD ----


	private boolean updateStatoRecordStatistiche( TipoPdD tipoPdD, Date data, int stato) {

		if(this.debug){
			this.logDebug("Update record statistiche allo stato '"+stato+"' ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") ...");
		}

		try{

			ISQLFieldConverter fieldConverter = ((IDBServiceUtilities<?>)this.statisticaServiceDAO).getFieldConverter();
			TipiDatabase tipoDB = fieldConverter.getDatabaseType();
			ISQLQueryObject sqlQueryObject = SQLObjectFactory.createSQLQueryObject(tipoDB);
			sqlQueryObject.addUpdateTable(fieldConverter.toTable(this.model));
			sqlQueryObject.addUpdateField(fieldConverter.toColumn(this.model.STATO_RECORD, false), "?");
			sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.DATA, false)+"=?");
			sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.TIPO_PORTA, false)+"=?");
			sqlQueryObject.setANDLogicOperator(true);
			
			Date next = this.truncDate(this.incrementDate(data, false),false);
			if(this.debug){
				this.logDebug("Aggiorno statistiche ["+this.getTipoStatistiche()+"]");
				this.logDebug("Valori update stato record = ("+stato+") tr.data_ingresso_richiesta=["+next.getTime()+"]");
			}
			int righeModificate = this.statisticaServiceDAO.nativeUpdate(sqlQueryObject.createSQLUpdate(), 
					stato,
					next, 
					TipoPdD.DELEGATA.equals(tipoPdD)? PddRuolo.DELEGATA.getValue() : PddRuolo.APPLICATIVA.getValue());
			
			if(this.debug){
				this.logDebug("Modificate ["+righeModificate+"] entry");
			}

			return true;

		}catch(Throwable e){
			this.logError(e.getMessage(), e);
			
			return false;
		}
		finally {
			if(this.debug){
				this.logDebug("Update record statistiche allo stato '"+stato+"' ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") terminata");
			}
		}

	}
	
	private boolean updateStatoRecordStatistiche( TipoPdD tipoPdD, Date data, 
			int case1WhenStato, int case1ThenStato,
			int case2WhenStato, int case2ThenStato) {

		if(this.debug){
			this.logDebug("Update record statistiche negli stati con CASE ("+case1WhenStato+"->"+case1ThenStato+") e ("+case2WhenStato+"->"+case2ThenStato+")  ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") ...");
		}

		try{
			ISQLFieldConverter fieldConverter = ((IDBServiceUtilities<?>)this.statisticaServiceDAO).getFieldConverter();
			TipiDatabase tipoDB = fieldConverter.getDatabaseType();
			String nomeColonna = fieldConverter.toColumn(this.model.STATO_RECORD, false);
			
			Case caseSql = new Case(CastColumnType.INT);
			caseSql.addCase(nomeColonna+"=?", "?");
			caseSql.addCase(nomeColonna+"=?", "?");
			
			ISQLQueryObject sqlQueryObject = SQLObjectFactory.createSQLQueryObject(tipoDB);
			sqlQueryObject.addUpdateTable(fieldConverter.toTable(this.model));
			sqlQueryObject.addUpdateField(nomeColonna, caseSql);
			sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.DATA, false)+"=?");
			if(tipoPdD!=null) {
				sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.TIPO_PORTA, false)+"=?");
			}
			sqlQueryObject.setANDLogicOperator(true);
			
			Date next = this.truncDate(this.incrementDate(data, false),false);
			if(this.debug){
				this.logDebug("Aggiorno statistiche ["+this.getTipoStatistiche()+"]");
				this.logDebug("Valori update stato record CASE ("+case1WhenStato+"->"+case1ThenStato+") e ("+case2WhenStato+"->"+case2ThenStato+") tr.data_ingresso_richiesta=["+next.getTime()+"]");
			}
			List<Object> l = new ArrayList<>();
			l.add(case1WhenStato); l.add(case1ThenStato);
			l.add(case2WhenStato); l.add(case2ThenStato);
			l.add(next);
			if(tipoPdD!=null) {
				l.add(TipoPdD.DELEGATA.equals(tipoPdD)? PddRuolo.DELEGATA.getValue() : PddRuolo.APPLICATIVA.getValue());
			}
			int righeModificate = this.statisticaServiceDAO.nativeUpdate(sqlQueryObject.createSQLUpdate(), 
					l.toArray(new Object[1]));
			
			if(this.debug){
				this.logDebug("Modificate ["+righeModificate+"] entry");
			}

			return true;

		}catch(Throwable e){
			this.logError(e.getMessage(), e);
			
			return false;
		}
		finally {
			if(this.debug){
				this.logDebug("Update record statistiche negli stati con CASE ("+case1WhenStato+"->"+case1ThenStato+") e ("+case2WhenStato+"->"+case2ThenStato+") ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") terminata");
			}
		}

	}
	
	private boolean deleteFisicoStatistiche( TipoPdD tipoPdD, Date data , int statoRecord, boolean equalsStatoRecord) {

		if(this.debug){
			this.logDebug("Eliminazione statistiche con stato record '"+statoRecord+"' equals_stato_record:"+equalsStatoRecord+" ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") ...");
		}

		try{

			ISQLFieldConverter fieldConverter = ((IDBServiceUtilities<?>)this.statisticaServiceDAO).getFieldConverter();
			TipiDatabase tipoDB = fieldConverter.getDatabaseType();
			
			ISQLQueryObject sqlQueryObject = SQLObjectFactory.createSQLQueryObject(tipoDB);
			sqlQueryObject.addDeleteTable(fieldConverter.toTable(this.model));
			sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.DATA, false)+"=?");
			if(equalsStatoRecord) {
				sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.STATO_RECORD, false)+"=?");
			}
			else {
				/**sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.STATO_RECORD, false)+"<>?");*/
				sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.STATO_RECORD, false)+"<?"); // non devo eliminare ne quelli nello stato 1 ne quello nello stato 2
			}
			sqlQueryObject.addWhereCondition(fieldConverter.toColumn(this.model.TIPO_PORTA, false)+"=?");
			sqlQueryObject.setANDLogicOperator(true);
			
			Date next = this.truncDate(this.incrementDate(data, false),false);
			if(this.debug){
				this.logDebug("Elimino statistiche ["+this.getTipoStatistiche()+"]");
				this.logDebug("Valori eliminazione (ms) tr.data_ingresso_richiesta=["+next.getTime()+"]");
			}
			
			int righeEliminate = this.statisticaServiceDAO.nativeUpdate(sqlQueryObject.createSQLDelete(), 
					next, 
					statoRecord,
					TipoPdD.DELEGATA.equals(tipoPdD)? PddRuolo.DELEGATA.getValue() : PddRuolo.APPLICATIVA.getValue());
			
			if(this.debug){
				this.logDebug("Eliminate ["+righeEliminate+"] entry");
			}

			return true;
			
		}catch (Throwable e) {
			this.logError(e.getMessage(), e);
			
			return false;
		}
		finally {
			if(this.debug){
				this.logDebug("Eliminazione statistiche con stato record '"+statoRecord+"' equals_stato_record:"+equalsStatoRecord+" ["+this.getTipoStatistiche()+"] ["+tipoPdD+"]("+this.getIntervalloStatistica(data)+") terminata");
			}
		}

	}

	protected abstract Long insertStatistica(Statistica statistica) throws StatisticException;
	protected abstract void updateStatistica(long idStatistica, StatisticaContenuti ... statisticaContenuti) throws StatisticException;


	private void insertStatistica(StatisticBean stat, int statoRecord) throws StatisticException  {

		if(this.debug){
			this.logDebug("Inserimento statistica ["+stat.toString()+"] in corso ...");
		}

		Statistica statisticaBase = new Statistica();

		statisticaBase.setData(stat.getData());
		statisticaBase.setStatoRecord(statoRecord);
		
		statisticaBase.setTipoPorta(org.openspcoop2.core.statistiche.constants.TipoPorta.toEnumConstant(stat.getTipoPorta().getTipo()));
		statisticaBase.setIdPorta(stat.getIdPorta());
		
		statisticaBase.setTipoMittente(stat.getMittente().getTipo());
		statisticaBase.setMittente(stat.getMittente().getNome());
		
		statisticaBase.setTipoDestinatario(stat.getDestinatario().getTipo());
		statisticaBase.setDestinatario(stat.getDestinatario().getNome());
		
		statisticaBase.setTipoServizio(stat.getTipoServizio());
		statisticaBase.setServizio(stat.getServizio());
		statisticaBase.setVersioneServizio(stat.getVersioneServizio());
		
		statisticaBase.setAzione(stat.getAzione());
		
		statisticaBase.setServizioApplicativo(stat.getServizioApplicativo());
		
		statisticaBase.setTrasportoMittente(stat.getTrasportoMittente());
		
		statisticaBase.setTokenIssuer(stat.getTokenIssuer());
		statisticaBase.setTokenClientId(stat.getTokenClientId());
		statisticaBase.setTokenSubject(stat.getTokenSubject());
		statisticaBase.setTokenUsername(stat.getTokenUsername());
		statisticaBase.setTokenMail(stat.getTokenMail());
		
		statisticaBase.setClientAddress(stat.getClientAddress());
		
		statisticaBase.setGruppi(stat.getGruppo());
		
		statisticaBase.setUriApi(stat.getApi());
		
		statisticaBase.setClusterId(stat.getClusterId());
		
		if(stat.getDestinatario()!=null && stat.getDestinatario().getTipo()!=null) {
			EsitiProperties esitiProperties = null;
			int esitoConsegnaMultipla = -1;
			int esitoConsegnaMultiplaInCorso = -1;
			int esitoConsegnaMultiplaFallita = -1;
			int esitoConsegnaMultiplaCompletata = -1;
			try {
				String protocollo = null;
				if(stat.getDestinatario()!=null && 
						stat.getDestinatario().getTipo()!=null && 
						!"".equals(stat.getDestinatario().getTipo()) && 
						!Costanti.INFORMAZIONE_NON_DISPONIBILE.equals(stat.getDestinatario().getTipo())) {
					protocollo = ProtocolFactoryManager.getInstance().getProtocolByOrganizationType(stat.getDestinatario().getTipo());
				}
				else if(stat.getMittente()!=null && 
						stat.getMittente().getTipo()!=null && 
						!"".equals(stat.getMittente().getTipo()) && 
						!Costanti.INFORMAZIONE_NON_DISPONIBILE.equals(stat.getMittente().getTipo())) {
					protocollo = ProtocolFactoryManager.getInstance().getProtocolByOrganizationType(stat.getMittente().getTipo());
				}
				else {
					protocollo = ProtocolFactoryManager.getInstance().getDefaultProtocolFactory().getProtocol();
				}
				esitiProperties = EsitiProperties.getInstanceFromProtocolName(this.logger, protocollo);
				esitoConsegnaMultipla = esitiProperties.convertoToCode(EsitoTransazioneName.CONSEGNA_MULTIPLA);
				esitoConsegnaMultiplaInCorso = esitiProperties.convertoToCode(EsitoTransazioneName.CONSEGNA_MULTIPLA_IN_CORSO);
				esitoConsegnaMultiplaFallita = esitiProperties.convertoToCode(EsitoTransazioneName.CONSEGNA_MULTIPLA_FALLITA);
				esitoConsegnaMultiplaCompletata = esitiProperties.convertoToCode(EsitoTransazioneName.CONSEGNA_MULTIPLA_COMPLETATA);
				if(stat.getEsito().intValue() == esitoConsegnaMultiplaInCorso || 
						stat.getEsito().intValue() == esitoConsegnaMultiplaFallita || 
						stat.getEsito().intValue() == esitoConsegnaMultiplaCompletata) {
					statisticaBase.setEsito(esitoConsegnaMultipla);
				}
				else {
					statisticaBase.setEsito(stat.getEsito());
				}
			}catch(Exception er) {
				throw new StatisticException(er.getMessage(),er);
			}
		}
		else {
			statisticaBase.setEsito(stat.getEsito());
		}
		statisticaBase.setEsitoContesto(stat.getEsitoContesto());
		
		statisticaBase.setNumeroTransazioni((int)stat.getRichieste());
		
		statisticaBase.setDimensioniBytesBandaComplessiva(stat.getBytesBandaTotale());
		statisticaBase.setDimensioniBytesBandaInterna(stat.getBytesBandaInterna());
		statisticaBase.setDimensioniBytesBandaEsterna(stat.getBytesBandaEsterna());
		
		if(stat.getLatenzaServizio()!=Costanti.INFORMAZIONE_LATENZA_NON_DISPONIBILE) {
			statisticaBase.setLatenzaServizio(stat.getLatenzaServizio());
		}
		if(stat.getLatenzaPorta()!=Costanti.INFORMAZIONE_LATENZA_NON_DISPONIBILE) {
			statisticaBase.setLatenzaPorta(stat.getLatenzaPorta());
		}
		if(stat.getLatenzaTotale()!=Costanti.INFORMAZIONE_LATENZA_NON_DISPONIBILE) {
			statisticaBase.setLatenzaTotale(stat.getLatenzaTotale());
		}
		
		Long id = insertStatistica(statisticaBase);
		if(id!=null && id >0){
			stat.setId(id);
		}	
		if(this.debug){
			this.logDebug("Inserimento statistica effettuato con successo");
		}

	}
	
}