ConnettoreHTTPCOREResponseCallback.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.connettori.httpcore5.nio;

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

import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpResponse;
import org.openspcoop2.core.constants.CostantiConnettori;
import org.openspcoop2.pdd.config.OpenSPCoop2Properties;
import org.openspcoop2.pdd.core.connettori.ConnettoreException;
import org.openspcoop2.pdd.core.connettori.ConnettoreLogger;
import org.openspcoop2.pdd.core.connettori.ConnettoreMsg;
import org.openspcoop2.pdd.core.connettori.ConnettoreUtils;
import org.openspcoop2.pdd.core.transazioni.TransactionContext;
import org.openspcoop2.pdd.mdb.ConsegnaContenutiApplicativi;
import org.openspcoop2.pdd.services.connector.AsyncResponseCallbackClientEvent;
import org.openspcoop2.pdd.services.connector.IAsyncResponseCallback;
import org.openspcoop2.utils.BooleanNullable;
import org.openspcoop2.utils.transport.TransportUtils;
import org.openspcoop2.utils.transport.http.HttpBodyParameters;
import org.openspcoop2.utils.transport.http.HttpConstants;

/**
 * ConnettoreHTTPCOREResponseCallback
 *
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class ConnettoreHTTPCOREResponseCallback implements FutureCallback<ConnettoreHTTPCOREResponse> {
	private ConnettoreHTTPCORE connettore;
	private ConnettoreMsg request;
	private HttpBodyParameters httpBody;
	private boolean connettoreDebug;
	private ConnettoreLogger connettoreLogger;

	private boolean switchThreadContext = false; 
	private String function;
	private BooleanNullable switchThreadLocalContextDoneHolder = BooleanNullable.FALSE();
		
	public ConnettoreHTTPCOREResponseCallback(ConnettoreHTTPCORE connettore, ConnettoreMsg request, HttpBodyParameters httpBody) {
		this.connettore = connettore;
		this.request = request;
		this.httpBody = httpBody;
		
		this.connettoreDebug = this.connettore.isDebug();
		this.connettoreLogger = this.connettore.getLogger();
		
		// L'inizializzazione avviene nel worker thread del webserver o nel worker thread della richiesta a seconda della configurazione (vedi org.openspcoop2.pdd.services.connector.ConnectorApplicativeThreadPool)
		// Il thread context è stato poi spostato sul thread della libreria client nio tramite ConnettoreHTTPCORETransactionThreadContextSwitchEntityProducer
		// Infine se viene usato il thread local per gestire il contesto, gli oggetti devono essere riportati sul thread iin cui viene gestita la risposta
		if(TransactionContext.isUseThreadLocal()) {
			this.switchThreadContext = true;
			this.function = "ResponseCallback-"+this.getClass().getName();
		}
	}

	@Override
	public void completed(final ConnettoreHTTPCOREResponse responseParam) {

		OpenSPCoop2Properties openspcoopProperties = OpenSPCoop2Properties.getInstance();
		
		Map<String, List<String>> connettorePropertiesTrasportoRisposta = this.connettore.getPropertiesTrasportoRisposta(); 
		
		try {
			if(this.switchThreadContext) {
				this.connettore.checkThreadLocalContext(this.function, this.switchThreadLocalContextDoneHolder);
			}
			
			HttpResponse response = responseParam.getHttpResponse();
			HttpEntity httpEntityResponse = responseParam.getEntity();
			
			if(this.connettoreDebug) {
				this.connettoreLogger.debug("NIO - Callback Response started after 'complete' event ...");
			}
			// Analisi MimeType e ContentLocation della risposta
			if(this.connettoreDebug)
				this.connettoreLogger.debug("Analisi risposta...");
			Header [] hdrRisposta = response.getHeaders();
			Map<String, List<String>> mapHeaderHttpResponse = new HashMap<>();
			if(hdrRisposta!=null){
				for (int i = 0; i < hdrRisposta.length; i++) {
					
					String key = null;
					String value = null;
					
					if(hdrRisposta[i].getName()==null){
						// Check per evitare la coppia che ha come chiave null e come valore HTTP OK 200
						if(this.connettoreDebug)
							this.connettoreLogger.debug("HTTP risposta ["+HttpConstants.RETURN_CODE+"] ["+hdrRisposta[i].getValue()+"]...");
						key = HttpConstants.RETURN_CODE;
						value = hdrRisposta[i].getValue();
					}
					else{
						if(this.connettoreDebug)
							this.connettoreLogger.debug("HTTP risposta ["+hdrRisposta[i].getName()+"] ["+hdrRisposta[i].getValue()+"]...");
						key = hdrRisposta[i].getName();
						value = hdrRisposta[i].getValue();
					}
					
					TransportUtils.addHeader(connettorePropertiesTrasportoRisposta, key, value);
					
					List<String> list = null;
					if(mapHeaderHttpResponse.containsKey(key)) {
						list = mapHeaderHttpResponse.get(key);
					}
					if(list==null) {
						list = new ArrayList<>();
						mapHeaderHttpResponse.put(key, list);
					}
					list.add(value);
				}
			}
			
			
			// TipoRisposta
			this.connettore.setTipoRisposta(TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.CONTENT_TYPE));
			
			// ContentLength
			String contentLengthHdr = TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.CONTENT_LENGTH);
			if(contentLengthHdr!=null){
				this.connettore.setContentLength(Long.parseLong(contentLengthHdr));
			}
			else {
				if(httpEntityResponse!=null && httpEntityResponse.getContentLength()>0) {
					this.connettore.setContentLength(httpEntityResponse.getContentLength());
				}
			}
			
			
			// Parametri di imbustamento
			if(this.connettore.isSoap()){
				this.connettore.setImbustamentoConAttachment(false);
				if("true".equals(TransportUtils.getObjectAsString(mapHeaderHttpResponse, openspcoopProperties.getTunnelSOAPKeyWord_headerTrasporto()))){
					this.connettore.setImbustamentoConAttachment(true);
				}
				String mimeTypeAttachment = TransportUtils.getObjectAsString(mapHeaderHttpResponse, openspcoopProperties.getTunnelSOAPKeyWordMimeType_headerTrasporto());
				if(mimeTypeAttachment==null)
					mimeTypeAttachment = HttpConstants.CONTENT_TYPE_OPENSPCOOP2_TUNNEL_SOAP;
				this.connettore.setMimeTypeAttachment(mimeTypeAttachment);
				/**System.out.println("IMB["+imbustamentoConAttachment+"] MIME["+mimeTypeAttachment+"]");*/
			}

			
			// Ricezione Risposta
			if(this.connettoreDebug)
				this.connettoreLogger.debug("Analisi risposta input stream e risultato http...");
			this.connettore.initConfigurationAcceptOnlyReturnCode202or200();

			
			// return code
			this.connettore.setCodiceTrasporto(response.getCode());
			this.connettore.setResultHTTPMessage(response.getReasonPhrase());

			if(this.connettore.getCodiceTrasporto()<300) {
				if(this.connettore.isSoap() && this.connettore.isAcceptOnlyReturnCode202or200() &&
					this.connettore.getCodiceTrasporto()!=200 && this.connettore.getCodiceTrasporto()!=202){
					throw new ConnettoreException("Return code ["+this.connettore.getCodiceTrasporto()+"] non consentito dal WS-I Basic Profile (http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html#HTTP_Success_Status_Codes)");
				}
				if(this.httpBody.isDoInput() && httpEntityResponse!=null){
					this.connettore.setInputStreamResponse(httpEntityResponse!=null ? httpEntityResponse.getContent() : null);
				}
			}else{
				if(this.connettore.getCodiceTrasporto()<400){
					String redirectLocation = TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.REDIRECT_LOCATION);

					// 3XX
					if(this.connettore.isFollowRedirects()){

						if(redirectLocation==null){
							throw new ConnettoreException("Non è stato rilevato l'header HTTP ["+HttpConstants.REDIRECT_LOCATION+"] necessario alla gestione del Redirect (code:"+this.connettore.getCodiceTrasporto()+")"); 
						}

						TransportUtils.removeObject(this.request.getConnectorProperties(), CostantiConnettori.CONNETTORE_LOCATION);
						TransportUtils.removeObject(this.request.getConnectorProperties(), CostantiConnettori.CONNETTORE_HTTP_REDIRECT_NUMBER);
						TransportUtils.removeObject(this.request.getConnectorProperties(), CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE);
						this.request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_LOCATION, redirectLocation);
						this.request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_NUMBER, (this.connettore.getNumberRedirect()+1)+"" );
						if(this.connettore.getRouteRedirect()!=null){
							this.request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE, this.connettore.getRouteRedirect()+" -> "+redirectLocation );
						}else{
							this.request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE, redirectLocation );
						}
						if(this.connettore.getOriginalAbsolutePrefixForRelativeRedirectLocation()==null) {
							this.connettore.setOriginalAbsolutePrefixForRelativeRedirectLocation(this.connettore.url.getProtocol()+"://"+this.connettore.url.getHost()+":"+this.connettore.url.getPort());
						}
						this.connettore.setRedirectLocation(redirectLocation); // per la prossima build()
						if(redirectLocation.startsWith("/")) {
							// relative
							this.connettore.setRedirectLocation(this.connettore.getOriginalAbsolutePrefixForRelativeRedirectLocation() + redirectLocation);
						}

						this.connettoreLogger.warn("(hope:"+(this.connettore.getNumberRedirect()+1)+") Redirect verso ["+redirectLocation+"] ...");

						if(this.connettore.getNumberRedirect()==this.connettore.getMaxNumberRedirects()){
							throw new ConnettoreException(ConnettoreUtils.getPrefixRedirect(this.connettore.getCodiceTrasporto(), redirectLocation)+"non consentita ulteriormente, sono già stati gestiti "+this.connettore.getMaxNumberRedirects()+" redirects: "+this.connettore.getRouteRedirect());
						}

						boolean acceptOnlyReturnCode307 = false;
						if(this.connettore.isSoap()) {
							if(ConsegnaContenutiApplicativi.ID_MODULO.equals(this.connettore.getIdModulo())){
								acceptOnlyReturnCode307 = openspcoopProperties.isAcceptOnlyReturnCode_307_consegnaContenutiApplicativi();
							}
							else{
								// InoltroBuste e InoltroRisposte
								acceptOnlyReturnCode307 = openspcoopProperties.isAcceptOnlyReturnCode_307_inoltroBuste();
							}
						}
						if(acceptOnlyReturnCode307 &&
							this.connettore.getCodiceTrasporto()!=307){
								throw new ConnettoreException("Return code ["+this.connettore.getCodiceTrasporto()+"] (redirect "+HttpConstants.REDIRECT_LOCATION+":"+redirectLocation+") non consentito dal WS-I Basic Profile (http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html#HTTP_Redirect_Status_Codes)");
							
						}
						
						if(this.connettore.getPropertiesTrasportoRisposta() != null) {
							this.connettore.getPropertiesTrasportoRisposta().clear();
						}
						
						IAsyncResponseCallback callback = this.request.getAsyncResponseCallback();
						this.request.setAsyncResponseCallback(new ConnettoreHTTPCORERedirectCallback(this, callback));
						this.connettore.disconnect();
						this.connettore.send(this.request);
						return;

					}else{
						if(this.connettore.isSoap()) {
							throw new ConnettoreException(ConnettoreUtils.getPrefixRedirect(this.connettore.getCodiceTrasporto(), redirectLocation)+"non attiva");
						}
						else {
							this.connettoreLogger.debug(ConnettoreUtils.getPrefixRedirect(this.connettore.getCodiceTrasporto(), redirectLocation)+"non attiva");
							
							if(this.connettore.internalGetLocationValue()!=null && redirectLocation!=null){
								this.connettore.setLocation(this.connettore.internalGetLocationValue()+" [redirect-location: "+redirectLocation+"]");
					    	}
							
							/**if(this.httpBody.isDoInput()){*/
							if(httpEntityResponse!=null) {
								this.connettore.setInputStreamResponse(httpEntityResponse.getContent());
							}
						}
					}
				}
				else {
					if(httpEntityResponse!=null) {
						this.connettore.setInputStreamResponse(httpEntityResponse.getContent());
					}
				}
			}

		}  catch(Exception e){ 
			this.writeExceptionResponse(e);
			this.notifyCallbackFinished(AsyncResponseCallbackClientEvent.COMPLETED);
			this.connettore.freeResources();
			return;
		}
		
		this.handleResponse();
	}
	
	public void handlePostRedirect(IAsyncResponseCallback prevCB) {		
		
		try {
			
			if (this.connettore.getEccezioneProcessamento() != null)
				throw this.connettore.getEccezioneProcessamento();
			if (this.connettore.getMaxNumberRedirects() == this.connettore.getNumberRedirect())
				throw new ConnettoreException(ConnettoreUtils.getPrefixRedirect(this.connettore.getCodiceTrasporto(), this.connettore.getRedirectLocation())+"non consentita ulteriormente, sono già stati gestiti "+this.connettore.getMaxNumberRedirects()+" redirects: "+this.connettore.getRouteRedirect());
		} catch (Exception e) {
			this.writeExceptionResponse(e);
		} finally {
			this.request.setAsyncResponseCallback(prevCB);
			this.notifyCallbackFinished(AsyncResponseCallbackClientEvent.COMPLETED);
		}
	}
	
	private void handleResponse() {
		
		Map<String, List<String>> connettorePropertiesTrasportoRisposta = this.connettore.getPropertiesTrasportoRisposta();
		
		try {
			/* ------------  PostOutRequestHandler ------------- */
			this.connettore.postOutRequest();
	
	
	
	
			/* ------------  PreInResponseHandler ------------- */
			this.connettore.preInResponse();
	
			// Lettura risposta parametri NotifierInputStream per la risposta
			this.connettore.setNotifierInputStreamParams(null);
			if(this.connettore.getPreInResponseContext()!=null){
				this.connettore.setNotifierInputStreamParams(this.connettore.getPreInResponseContext().getNotifierInputStreamParams());
			}
	
	
			/* ------------  Gestione Risposta ------------- */
	
			this.connettore.normalizeInputStreamResponse(this.connettore.readConnectionTimeout, this.connettore.readConnectionTimeoutConfigurazioneGlobale);
	
			this.connettore.initCheckContentTypeConfiguration();
	
			if(this.connettore.isDumpBinarioRisposta() &&
				!this.connettore.dumpResponse(connettorePropertiesTrasportoRisposta)) {
				this.connettore.setAsyncInvocationSuccess(false);
				return;
			}
	
			if(this.connettore.isRest()){
	
				if(!this.connettore.doRestResponse()){
					this.connettore.setAsyncInvocationSuccess(false);
					return;
				}
	
			}
			else{
	
				if(!this.connettore.doSoapResponse()){
					this.connettore.setAsyncInvocationSuccess(false);
					return;
				}
	
			}
	
			if(this.connettoreDebug)
				this.connettoreLogger.info("Gestione invio/risposta http effettuata con successo",false);
	
			this.connettore.configureSSE();
			
			this.connettore.setAsyncInvocationSuccess(true);

		}  catch(Exception e){ 
			
			this.writeExceptionResponse(e);
			
		} finally {
			
			this.notifyCallbackFinished(AsyncResponseCallbackClientEvent.COMPLETED);
		}
	}

	@Override
	public void failed(final Exception e) {
		try {
			if(this.switchThreadContext) {
				switchThreadContext("failed");
			}
			
			if(this.connettoreDebug && this.connettoreLogger!=null) {
				this.connettoreLogger.debug("NIO - Callback Response started after 'failed' event ...");
			}
			
			this.writeExceptionResponse(e);
			
		} finally {
			
			this.notifyCallbackFinished(AsyncResponseCallbackClientEvent.FAILED);
			
		}
	}

	@Override
	public void cancelled() {
		
		try {
			if(this.switchThreadContext) {
				switchThreadContext("cancelled");
			}
			
			if(this.connettoreDebug && this.connettoreLogger!=null) {
				this.connettoreLogger.debug("NIO - Callback Response started after 'cancelled' event ...");
			}
			
			this.writeExceptionResponse(new Exception("Cancelled") );
			
		} finally {
			
			this.notifyCallbackFinished(AsyncResponseCallbackClientEvent.CANCELLED);
			
		}
		
	}
	
	private void switchThreadContext(String method) {
		try {
			this.connettore.checkThreadLocalContext(this.function, this.switchThreadLocalContextDoneHolder);
		} catch(Exception eError){ 
			if(this.connettoreLogger!=null) {
				this.connettoreLogger.error("gestione checkThreadLocalContext in '"+method+"' terminata con errore: "+eError.getMessage(),eError);
			}
		}
	}

	private void notifyCallbackFinished(AsyncResponseCallbackClientEvent clientEvent) {
		// se per caso non l'ho ancora chiuso lo faccio
		this.connettore.freeResources();
		this.connettore.asyncComplete(clientEvent);
		if(this.connettoreDebug) {
			this.connettoreLogger.debug("NIO - Callback Response finished");
		}
	}
	
	private void writeExceptionResponse(final Exception e) {
		this.connettore.setEccezioneProcessamento(e);
		String msgErrore = this.connettore.readExceptionMessageFromException(e);
		
		boolean connect = this.connettore.processConnectionTimeoutException(this.connettore.connectionTimeout, this.connettore.connectionTimeoutConfigurazioneGlobale, e, msgErrore);
		
		boolean read = this.connettore.processReadTimeoutException(this.connettore.readConnectionTimeout, this.connettore.readConnectionTimeoutConfigurazioneGlobale, e, msgErrore);
		
		msgErrore = ConnettoreHTTPCORE.correctMessageTimeout(connect, read, msgErrore);
		
		if(this.connettore.isGenerateErrorWithConnectorPrefix()) {
			this.connettore.setErrore("Errore avvenuto durante la consegna HTTP: "+msgErrore);
		}
		else {
			this.connettore.setErrore(msgErrore);
		}
		this.connettoreLogger.error("Errore avvenuto durante la consegna HTTP: "+msgErrore,e);
		
		this.connettore.setAsyncInvocationSuccess(false);
	}
}