Riscontri.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.protocol.engine.driver;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.openspcoop2.protocol.engine.Configurazione;
import org.openspcoop2.protocol.engine.constants.Costanti;
import org.openspcoop2.protocol.sdk.ProtocolException;
import org.openspcoop2.protocol.sdk.constants.ProfiloDiCollaborazione;
import org.openspcoop2.protocol.sdk.state.IState;
import org.openspcoop2.protocol.sdk.state.StateMessage;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.jdbc.JDBCUtilities;
import org.openspcoop2.utils.sql.ISQLQueryObject;
import org.openspcoop2.utils.sql.SQLObjectFactory;
import org.slf4j.Logger;
/**
* Sono inclusi i metodi per la gestione dei Riscontri.
* La gestione dei riscontri puo' essere classificati nella seguente maniera :
* <ul>
* <li> Riscontri da ricevere, dove un mittente ha inviato una busta e sta' attendendo un ACK!
* <li> Riscontri da inviare (modalita' NON PIGGYBACKING), quando una porta di dominio riceve una busta
* con profilo 'ConfermaRicezione'==true, deve generare un riscontro apposito.
* <li> Riscontri da inviare (modalita' PIGGYBACKING), quando una porta di dominio riceve una busta
* con profilo 'ConfermaRicezione'==true, deve salvarsi le informazioni per generare successivi riscontri.
* </ul>
* Tutti i metodi hanno bisogno di una connessione ad un DB, precedentemente impostata
* e passata attraverso l'apposito metodo.
*
*
* @author Poli Andrea (apoli@link.it)
* @author Tronci Fabio (tronci@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class Riscontri {
/** Logger utilizzato per debug. */
private Logger log = null;
/** Se IState e' un'istanza di StatefulMessage possiede una Connessione SQL in autoCommit mode su cui effettuare query
* Altrimenti, e' un'istanza di StatelessMessage e nn necessita di connessioni */
private IState state;
/* ******** C O S T R U T T O R E ******** */
/**
* Costruttore.
*
* @param state Oggetto che rappresenta lo stato di una busta
*
*/
public Riscontri(IState state){
this(state,Configurazione.getLibraryLog());
}
/**
* Costruttore.
*
* @param state Oggetto che rappresenta lo stato di una busta
*
*/
public Riscontri(IState state ,Logger alog){
this.state = state;
if(alog!=null){
this.log = alog;
}else{
this.log = LoggerWrapperFactory.getLogger(Riscontri.class.getName());
}
}
public void updateState(IState state) {
this.state = state;
}
/* ******** R I S C O N T R I D A R I C E V E R E ******** */
/**
* Aggiunge un riscontro da ricevere nella tabella per la gestione della fase di ricezione riscontro.
*
* @param id identificativo della busta.
* @param timestamp data di invio della busta.
*
*/
public void registraRiscontroDaRicevere(String id , Date timestamp)throws ProtocolException{
StateMessage stateMSG = (StateMessage)this.state;
Connection connectionDB = stateMSG.getConnectionDB();
PreparedStatement pstmt = null;
try{
java.sql.Timestamp oraInvio = new java.sql.Timestamp(timestamp.getTime());
StringBuilder query = new StringBuilder();
query.append("INSERT INTO ");
query.append(Costanti.RISCONTRI_DA_RICEVERE);
query.append(" VALUES ( ? , ? )");
pstmt = connectionDB.prepareStatement(query.toString());
pstmt.setString(1,id);
pstmt.setTimestamp(2,oraInvio);
// Add PreparedStatement (LASCIARE UPDATE per ordine esecuzione)
stateMSG.getPreparedStatement().put("UPDATE saveRiscontroDaRicevere_"+id,pstmt);
// Registrazione nella tabella History
History historyBuste = new History(this.state);
historyBuste.registraBustaInviata(id);
} catch(Exception e) {
String errorMsg = "RISCONTRI, Errore di registrazione "+id+": "+e.getMessage();
this.log.error(errorMsg,e);
try{
if( pstmt != null )
pstmt.close();
} catch(Exception er) {
// Eccezione SQL.
}
throw new ProtocolException(errorMsg,e);
}
}
/**
* In caso esista nella tabella dei riscontri da ricevere, un riscontro scaduto,
* si occupa di ritornare un array di riscontro da reinviare, aggiornando le loro data di registrazione.
* Il controllo non e' serializzato, quindi possono essere ritornate anche busta gia' riscontrate, in seguito al controllo.
*
* @param timeout Minuti dopo il quale una data risulta scaduta.
* @return un List di {@link org.openspcoop2.protocol.sdk.Busta} contenente le informazioni necessarie per il re-invio delle buste,
* se esistono riscontro scaduti.
*/
public List<BustaNonRiscontrata> getBustePerUlterioreInoltro(long timeout, int limit, int offset, boolean logQuery)throws ProtocolException{
StateMessage stateMSG = (StateMessage)this.state;
Connection connectionDB = stateMSG.getConnectionDB();
PreparedStatement pstmt = null;
ResultSet rs = null;
java.util.List<String> IDBuste = new java.util.ArrayList<>();
String queryString = null;
try{
long nowTime = DateManager.getTimeMillis() - (timeout * 60 * 1000);
java.sql.Timestamp scadenzaRiscontro = new java.sql.Timestamp(nowTime);
if(Configurazione.getSqlQueryObjectType()==null){
StringBuilder query = new StringBuilder();
query.append("SELECT ID_MESSAGGIO FROM ");
query.append(Costanti.RISCONTRI_DA_RICEVERE);
query.append(" WHERE DATA_INVIO < ? ");
queryString = query.toString();
}else{
ISQLQueryObject sqlQueryObject = SQLObjectFactory.createSQLQueryObject(Configurazione.getSqlQueryObjectType());
sqlQueryObject.addSelectField("ID_MESSAGGIO");
sqlQueryObject.addSelectField("DATA_INVIO");
sqlQueryObject.addFromTable(Costanti.RISCONTRI_DA_RICEVERE);
sqlQueryObject.addWhereCondition("DATA_INVIO < ?"); // order by e' obbligatorio essendoci l'offset
sqlQueryObject.setANDLogicOperator(true);
sqlQueryObject.addOrderBy("DATA_INVIO");
sqlQueryObject.setSortType(true);
sqlQueryObject.setLimit(limit);
sqlQueryObject.setOffset(offset);
queryString = sqlQueryObject.createSQLQuery();
}
//System.out.println("QUERY RISCONTRI IS: ["+queryString+"] 1["+scadenzaRiscontro+"]");
pstmt = connectionDB.prepareStatement(queryString);
pstmt.setTimestamp(1,scadenzaRiscontro);
// Esecuzione comando SQL
long startDateSQLCommand = DateManager.getTimeMillis();
if(logQuery)
this.log.debug("[QUERY] (Riscontri) ["+queryString+"] 1["+scadenzaRiscontro+"]...");
rs = pstmt.executeQuery();
long endDateSQLCommand = DateManager.getTimeMillis();
long secondSQLCommand = (endDateSQLCommand - startDateSQLCommand) / 1000;
if(logQuery)
this.log.debug("[QUERY] (Riscontri) ["+queryString+"] 1["+scadenzaRiscontro+"] effettuata in "+secondSQLCommand+" secondi");
if(rs == null) {
pstmt.close();
throw new ProtocolException("RS NULL?");
}
int countLimit = 0;
int countOffset = 0;
while(rs.next()){
if(Configurazione.getSqlQueryObjectType()==null){
// OFFSET APPLICATIVO
if( countOffset>=offset ){
String id = rs.getString("ID_MESSAGGIO");
IDBuste.add(id);
// LIMIT Applicativo
countLimit++;
if(countLimit==limit)
break;
}
else{
countOffset++;
}
}else{
String id = rs.getString("ID_MESSAGGIO");
IDBuste.add(id);
}
}
rs.close();
pstmt.close();
} catch(Exception e) {
try{
if( rs != null )
rs.close();
} catch(Exception er) {
// close
}
try{
if( pstmt != null )
pstmt.close();
} catch(Exception er) {
// close
}
String errorMsg = "[Riscontri.getBustePerUlterioreInoltro] errore, queryString["+queryString+"]: "+e.getMessage();
this.log.error(errorMsg,e);
throw new ProtocolException(errorMsg,e);
}
List<BustaNonRiscontrata> listaBustaNonRiscontrata = new ArrayList<BustaNonRiscontrata>();
for (int i = 0; i < IDBuste.size(); i++) {
BustaNonRiscontrata bustaNonRiscontrata = new BustaNonRiscontrata();
bustaNonRiscontrata.setIdentificativo(IDBuste.get(i));
bustaNonRiscontrata.setProfiloCollaborazione(ProfiloDiCollaborazione.ONEWAY);
listaBustaNonRiscontrata.add(bustaNonRiscontrata);
}
return listaBustaNonRiscontrata;
}
/**
* Valida un riscontro ricevuto, identificato dall'identificativo della busta.
* In caso di validazione del riscontro, l'entry nella tabella di gestione dei riscontri da ricevere,
* con chiave di accesso uguale al parametro <var>idRiscontro</var> viene cancellata.
*
* @param idRiscontro identificativo del riscontro da validare.
* @deprecated utilizzare la versione non serializable
*/
@Deprecated
public void validazioneRiscontroRicevuto_serializable(String idRiscontro) throws ProtocolException{
validazioneRiscontroRicevuto_serializable(idRiscontro,60l,100);
}
/**
* Valida un riscontro ricevuto, identificato dall'identificativo della busta.
* In caso di validazione del riscontro, l'entry nella tabella di gestione dei riscontri da ricevere,
* con chiave di accesso uguale al parametro <var>idRiscontro</var> viene cancellata.
*
* @param idRiscontro identificativo del riscontro da validare.
* @param attesaAttiva AttesaAttiva per la gestione del livello di serializable
* @param checkInterval Intervallo di check per la gestione del livello di serializable
* @deprecated utilizzare la versione non serializable
*/
@Deprecated
public void validazioneRiscontroRicevuto_serializable(String idRiscontro,long attesaAttiva,int checkInterval) throws ProtocolException{
StateMessage stateMSG = (StateMessage)this.state;
Connection connectionDB = stateMSG.getConnectionDB();
/*
Viene realizzato con livello di isolamento SERIALIZABLE, per essere sicuri
che esecuzioni parallele non leggano dati inconsistenti.
Con il livello SERIALIZABLE, se ritorna una eccezione, deve essere riprovato
*/
// setAutoCommit e livello Isolamento
int oldTransactionIsolation = -1;
try{
oldTransactionIsolation =connectionDB.getTransactionIsolation();
connectionDB.setAutoCommit(false);
JDBCUtilities.setTransactionIsolationSerializable(Configurazione.getSqlQueryObjectType(), connectionDB);
} catch(Exception er) {
String errorMsg = "RISCONTRI, Errore durante la validazioneRiscontroRicevuto(setIsolation): "+er.getMessage();
this.log.error(errorMsg,er);
throw new ProtocolException(errorMsg,er);
}
boolean deleteRiscontroOK = false;
long scadenzaWhile = DateManager.getTimeMillis() + attesaAttiva;
while(deleteRiscontroOK==false && DateManager.getTimeMillis() < scadenzaWhile){
PreparedStatement pstmtDelete = null;
try{
// Eliminazione dalla tabella Riscontri
StringBuilder query = new StringBuilder();
query.delete(0,query.capacity());
query.append("DELETE FROM ");
query.append(Costanti.RISCONTRI_DA_RICEVERE);
query.append(" WHERE ID_MESSAGGIO = ?");
pstmtDelete = connectionDB.prepareStatement(query.toString());
pstmtDelete.setString(1,idRiscontro);
pstmtDelete.execute();
pstmtDelete.close();
// Eliminazione dalla tabella History
History historyBuste = new History(this.state,this.log);
historyBuste.eliminaBustaInviata(idRiscontro);
stateMSG.executePreparedStatement();
// Chiusura Transazione
connectionDB.commit();
// ID Costruito
deleteRiscontroOK = true;
} catch(Exception e) {
try{
if( pstmtDelete != null )
pstmtDelete.close();
} catch(Exception er) {
// close
}
try{
connectionDB.rollback();
} catch(Exception er) {
// ignore
}
}
if(deleteRiscontroOK == false){
// Per aiutare ad evitare conflitti
try{
Utilities.sleep(ProtocolRandomUtilities.getRandom().nextInt(checkInterval)); // random da 0ms a checkIntervalms
}catch(Exception eRandom){
// ignore
}
}
}
// Ripristino Transazione
try{
connectionDB.setTransactionIsolation(oldTransactionIsolation);
connectionDB.setAutoCommit(true);
} catch(Exception er) {
String errorMsg = "RISCONTRI, Errore durante la validazioneRiscontroRicevuto(ripristinoIsolation): "+er.getMessage();
this.log.error(errorMsg,er);
throw new ProtocolException(errorMsg,er);
}
}
/**
* Valida un riscontro ricevuto, identificato dall'identificativo della busta.
* In caso di validazione del riscontro, l'entry nella tabella di gestione dei riscontri da ricevere,
* con chiave di accesso uguale al parametro <var>idRiscontro</var> viene cancellata.
*
* @param idRiscontro identificativo del riscontro da validare.
*/
public void validazioneRiscontroRicevuto(String idRiscontro) throws ProtocolException{
StateMessage stateMSG = (StateMessage)this.state;
Connection connectionDB = stateMSG.getConnectionDB();
PreparedStatement pstmtDelete = null;
try{
// Eliminazione dalla tabella Riscontri
StringBuilder query = new StringBuilder();
query.delete(0,query.capacity());
query.append("DELETE FROM ");
query.append(Costanti.RISCONTRI_DA_RICEVERE);
query.append(" WHERE ID_MESSAGGIO = ?");
pstmtDelete = connectionDB.prepareStatement(query.toString());
pstmtDelete.setString(1,idRiscontro);
// Add PreparedStatement (LASCIARE UPDATE per ordine esecuzione)
stateMSG.getPreparedStatement().put("UPDATE validazioneRiscontroRicevuto_"+idRiscontro,pstmtDelete);
// Eliminazione dalla tabella History
History historyBuste = new History(stateMSG,this.log);
historyBuste.eliminaBustaInviataPerRiscontri(idRiscontro);
} catch(Exception e) {
try{
if( pstmtDelete != null )
pstmtDelete.close();
} catch(Exception er) {
// close
}
String errorMsg = "RISCONTRI, Errore durante la validazioneRiscontroRicevuto: "+e.getMessage();
this.log.error(errorMsg,e);
throw new ProtocolException(errorMsg,e);
}
}
}