HttpServletConnectorOutMessage.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.pdd.services.connector.messages;

import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.core.config.PortaApplicativa;
import org.openspcoop2.core.config.PortaDelegata;
import org.openspcoop2.core.config.Proprieta;
import org.openspcoop2.core.id.IDPortaApplicativa;
import org.openspcoop2.core.id.IDPortaDelegata;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.OpenSPCoop2MessageProperties;
import org.openspcoop2.message.OpenSPCoop2RestMessage;
import org.openspcoop2.message.OpenSPCoop2SoapMessage;
import org.openspcoop2.message.constants.ServiceBinding;
import org.openspcoop2.message.exception.MessageException;
import org.openspcoop2.pdd.config.ConfigurazionePdDManager;
import org.openspcoop2.pdd.config.CostantiProprieta;
import org.openspcoop2.pdd.config.OpenSPCoop2Properties;
import org.openspcoop2.pdd.services.connector.ConnectorException;
import org.openspcoop2.protocol.sdk.IProtocolFactory;
import org.openspcoop2.protocol.sdk.constants.IDService;
import org.openspcoop2.protocol.sdk.state.RequestInfo;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.io.DumpByteArrayOutputStream;
import org.openspcoop2.utils.resources.Charset;
import org.openspcoop2.utils.transport.http.RFC2047Encoding;
import org.openspcoop2.utils.transport.http.RFC2047Utilities;

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

	protected HttpServletResponse res;
	protected OutputStream outNullable;
	protected IProtocolFactory<?> protocolFactory;
	protected RequestInfo requestInfo;
	protected String idModulo;
	protected IDService idModuloAsIDService;
	protected OpenSPCoop2Properties openspcoopProperties;
	
	
	public HttpServletConnectorOutMessage(RequestInfo requestInfo,IProtocolFactory<?> protocolFactory, HttpServletResponse res,
			IDService idModuloAsIDService, String idModulo) throws ConnectorException{
		try{
			this.res = res;
			this.protocolFactory = protocolFactory;
			this.requestInfo = requestInfo;
			this.idModuloAsIDService = idModuloAsIDService;
			this.idModulo = idModulo;
			this.openspcoopProperties =  OpenSPCoop2Properties.getInstance();
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}

	private void sendHeadersEngine(OpenSPCoop2Message msg) throws ConnectorException, MessageException {
		if(msg==null) {
			throw new ConnectorException("Message is null");
		}
		// Propago eventuali header http
		OpenSPCoop2MessageProperties forwardHeader = null;
		if(ServiceBinding.REST.equals(msg.getServiceBinding())) {
			forwardHeader = msg.getForwardTransportHeader(this.openspcoopProperties.getRESTServicesHeadersForwardConfig(false));
		}
		else {
			forwardHeader = msg.getForwardTransportHeader(this.openspcoopProperties.getSOAPServicesHeadersForwardConfig(false));
		}
		if(forwardHeader!=null && forwardHeader.size()>0){
			Iterator<String> keys = forwardHeader.getKeys();
			while (keys.hasNext()) {
				String key = keys.next();
				List<String> values = forwardHeader.getPropertyValues(key);
				if(values!=null && !values.isEmpty()) {
					for (String value : values) {
						this.addHeader(key, value);	
					}
				}
			}
		}
	}
	
	@Override
	public void sendResponse(OpenSPCoop2Message msg, boolean consume) throws ConnectorException {
		try{
			if(msg==null) {
				throw new ConnectorException("Message is null");
			}
			
			// Propago eventuali header http
			this.sendHeadersEngine(msg);
			
			boolean hasContent = false;
			
			// il save e' necessario con i connettori directVM in caso di errori di validazione
			if(ServiceBinding.SOAP.equals(msg.getServiceBinding())){
				hasContent = true;
				OpenSPCoop2SoapMessage soap = msg.castAsSoap();
				if(soap.hasSOAPFault()){
					soap.saveChanges();
				}
			}
			if(ServiceBinding.REST.equals(msg.getServiceBinding())){
				OpenSPCoop2RestMessage<?> rest = msg.castAsRest();
				hasContent = rest.hasContent();
			}
			
			if(hasContent) {
				this.outNullable = this.res.getOutputStream();
				msg.writeTo(this.outNullable,consume);
			}
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	
	@Override
	public void sendResponse(DumpByteArrayOutputStream message) throws ConnectorException{
		try{
			if(message!=null && message.size()>0) {
				this.outNullable = this.res.getOutputStream();
				if(message.isSerializedOnFileSystem()) {
					try(FileInputStream fin = new FileInputStream(message.getSerializedFile())) {
						Utilities.copy(fin, this.outNullable);
					}
				}
				else {
					this.outNullable.write(message.toByteArray());
				}
			}
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	
	@Override
	public void sendResponseHeaders(OpenSPCoop2Message message) throws ConnectorException{
		try{
			// Propago eventuali header http
			this.sendHeadersEngine(message);
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	
	@Override
	public void setHeader(String key,String value) throws ConnectorException{
		putHeaderEngine(key,value,false);
	}
	@Override
	public void addHeader(String key,String value) throws ConnectorException{
		putHeaderEngine(key,value,true);
	}
	private void putHeaderEngine(String key,String value, boolean add) throws ConnectorException{
		try{
			if(value==null) {
				return;
			}
			
			boolean encodingRFC2047 = false;
			Charset charsetRFC2047 = null;
			RFC2047Encoding encodingAlgorithmRFC2047 = null;
			boolean validazioneHeaderRFC2047 = false;
			List<Proprieta> listProprieta = null;
			if(this.idModuloAsIDService!=null){
				switch (this.idModuloAsIDService) {
				case PORTA_DELEGATA:
				case PORTA_DELEGATA_INTEGRATION_MANAGER:
				case PORTA_DELEGATA_XML_TO_SOAP:
					encodingRFC2047 = this.openspcoopProperties.isEnabledEncodingRFC2047HeaderValueRicezioneContenutiApplicativi();
					charsetRFC2047 = this.openspcoopProperties.getCharsetEncodingRFC2047HeaderValueRicezioneContenutiApplicativi();
					encodingAlgorithmRFC2047 = this.openspcoopProperties.getEncodingRFC2047HeaderValueRicezioneContenutiApplicativi();
					validazioneHeaderRFC2047 = this.openspcoopProperties.isEnabledValidazioneRFC2047HeaderNameValueRicezioneContenutiApplicativi();
					listProprieta = readProprietaPortaDelegata();
					break;
				case PORTA_APPLICATIVA:
					encodingRFC2047 = this.openspcoopProperties.isEnabledEncodingRFC2047HeaderValueRicezioneBuste();
					charsetRFC2047 = this.openspcoopProperties.getCharsetEncodingRFC2047HeaderValueRicezioneBuste();
					encodingAlgorithmRFC2047 = this.openspcoopProperties.getEncodingRFC2047HeaderValueRicezioneBuste();
					validazioneHeaderRFC2047 = this.openspcoopProperties.isEnabledValidazioneRFC2047HeaderNameValueRicezioneBuste();
					listProprieta = readProprietaPortaApplicativa();
					break;
				default:
					break;
				}
			}
			
			encodingRFC2047 = CostantiProprieta.isConnettoriHeaderValueEncodingRFC2047ResponseEnabled(listProprieta, encodingRFC2047);
			charsetRFC2047 = CostantiProprieta.getConnettoriHeaderValueEncodingRFC2047ResponseCharset(listProprieta, charsetRFC2047);
			encodingAlgorithmRFC2047 = CostantiProprieta.getConnettoriHeaderValueEncodingRFC2047ResponseType(listProprieta, encodingAlgorithmRFC2047);
			validazioneHeaderRFC2047 = CostantiProprieta.isConnettoriHeaderValidationResponseEnabled(listProprieta, validazioneHeaderRFC2047);
			/**System.out.println("@@@@ encodingRFC2047["+encodingRFC2047+"] charsetRFC2047["+charsetRFC2047+"] encodingAlgorithmRFC2047["+encodingAlgorithmRFC2047+"] validazioneHeaderRFC2047["+validazioneHeaderRFC2047+"]");*/
			if(encodingRFC2047){
				/**System.out.println("@@@@ CONTROLLO '"+value+" rispetto a '"+charsetRFC2047+"': "+RFC2047Utilities.isAllCharactersInCharset(value, charsetRFC2047));*/
				if(!RFC2047Utilities.isAllCharactersInCharset(value, charsetRFC2047)){
					String encoded = RFC2047Utilities.encode((value+""), charsetRFC2047, encodingAlgorithmRFC2047);
					/**System.out.println("@@@@ RESPONSE CODIFICA ["+value+"] in ["+encoded+"]");*/
					this.putResponseHeader(validazioneHeaderRFC2047, key, encoded, add);
				}
				else{
					this.putResponseHeader(validazioneHeaderRFC2047, key, value, add);
				}
			}
			else{
				this.putResponseHeader(validazioneHeaderRFC2047, key, value, add);
			}	
			
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	
	private List<Proprieta> readProprietaPortaApplicativa(){
		if(this.requestInfo!=null && this.requestInfo.getProtocolContext()!=null && this.requestInfo.getProtocolContext().getInterfaceName()!=null && 
				StringUtils.isNotEmpty(this.requestInfo.getProtocolContext().getInterfaceName())) {
			IDPortaApplicativa idPA = new IDPortaApplicativa();
			idPA.setNome(this.requestInfo.getProtocolContext().getInterfaceName());
			try {
				PortaApplicativa pa = ConfigurazionePdDManager.getInstance().getPortaApplicativaSafeMethod(idPA, this.requestInfo);
				if(pa!=null && pa.sizeProprieta()>0) {
					return pa.getProprieta();
				}
			}catch(Exception e) {
				if(this.protocolFactory!=null && this.protocolFactory.getLogger()!=null) {
					this.protocolFactory.getLogger().error("Accesso porta applicativa ["+this.requestInfo.getProtocolContext().getInterfaceName()+"] fallito: "+e.getMessage(),e);
				}
			}
		}
		return null;
	}
	private List<Proprieta> readProprietaPortaDelegata(){
		if(this.requestInfo!=null && this.requestInfo.getProtocolContext()!=null && this.requestInfo.getProtocolContext().getInterfaceName()!=null && 
				StringUtils.isNotEmpty(this.requestInfo.getProtocolContext().getInterfaceName())) {
			IDPortaDelegata idPD = new IDPortaDelegata();
			idPD.setNome(this.requestInfo.getProtocolContext().getInterfaceName());
			try {
				PortaDelegata pd = ConfigurazionePdDManager.getInstance().getPortaDelegataSafeMethod(idPD, this.requestInfo);
				if(pd!=null && pd.sizeProprieta()>0) {
					return pd.getProprieta();
				}
			}catch(Exception e) {
				if(this.protocolFactory!=null && this.protocolFactory.getLogger()!=null) {
					this.protocolFactory.getLogger().error("Accesso porta applicativa ["+this.requestInfo.getProtocolContext().getInterfaceName()+"] fallito: "+e.getMessage(),e);
				}
			}
		}
		return null;
	}
	
	private void putResponseHeader(boolean validazioneHeaderRFC2047, String key, String value, boolean add) {
    	
    	if(validazioneHeaderRFC2047){
    		try{
        		RFC2047Utilities.validHeader(key, value);
        		if(add) {
        			this.res.addHeader(key,value);
        		}
        		else {
        			this.res.setHeader(key,value);
        		}
        	}catch(UtilsException e){
        		if(this.protocolFactory!=null && this.protocolFactory.getLogger()!=null){
        			this.protocolFactory.getLogger().error(e.getMessage(),e);
        		}
        		else{
        			LoggerWrapperFactory.getLogger(HttpServletConnectorOutMessage.class).error(e.getMessage(),e);		
        		}
        	}
    	}
    	else{
    		if(add) {
    			this.res.addHeader(key,value);
    		}
    		else {
    			this.res.setHeader(key,value);
    		}
    	}
    	
    }
	
	@Override
	public void setContentLength(int length) throws ConnectorException{
		try{
			this.res.setContentLength(length);
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	
	@Override
	public void setContentType(String type) throws ConnectorException{
		try{
			this.res.setContentType(type);
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	
	private int status = -1;
	@Override
	public void setStatus(int status) throws ConnectorException{
		try{
			this.res.setStatus(status);
			this.status = status;
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}
	}
	@Override
	public int getResponseStatus() throws ConnectorException{
		return this.status;
	}
	
	@Override
	public void flush(boolean throwException) throws ConnectorException{
		try{
			// Flush and close response
			// NOTA: per poter ottenere l'errore di BrokenPipe sempre, deve essere disabilitato il socketBufferOutput sul servlet container.
			// Se non lo si disabilta, l'errore viene ritornato solo se il messaggio supera la dimensione del buffer (default: 8192K)
			// Ad esempio in tomcat utilizzare (socketBuffer="-1"): 
			//    <Connector protocol="HTTP/1.1" port="8080" address="${jboss.bind.address}" 
            //       connectionTimeout="20000" redirectPort="8443" socketBuffer="-1" />
			if(this.res!=null){
				try{
					this.res.flushBuffer();
				}catch(Exception e){
					if(throwException){
						throw e;
					}
				}
			}
			if(this.outNullable!=null){
				try{
					this.outNullable.flush();
				}catch(Exception e){
					if(throwException){
						throw e;
					}
				}
			}
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}	
	}
	
	@Override
	public void close(boolean throwException) throws ConnectorException{
		try{
			if(this.outNullable!=null){
				try{
					this.outNullable.close();
					this.outNullable = null;
				}catch(Exception e){
					if(throwException){
						throw e;
					}
				}
			}
		}catch(Exception e){
			throw new ConnectorException(e.getMessage(),e);
		}	
	}
}