MTOMProcessor.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.core;

import org.slf4j.Logger;
import org.openspcoop2.core.config.constants.MTOMProcessorType;
import org.openspcoop2.core.constants.Costanti;
import org.openspcoop2.core.constants.TipoPdD;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.OpenSPCoop2SoapMessage;
import org.openspcoop2.message.constants.MessageRole;
import org.openspcoop2.message.constants.ServiceBinding;
import org.openspcoop2.pdd.config.MTOMProcessorConfig;
import org.openspcoop2.pdd.config.MessageSecurityConfig;
import org.openspcoop2.pdd.core.transazioni.Transaction;
import org.openspcoop2.pdd.core.transazioni.TransactionContext;
import org.openspcoop2.pdd.core.transazioni.TransactionNotExistsException;
import org.openspcoop2.pdd.logger.MsgDiagnosticiProperties;
import org.openspcoop2.pdd.logger.MsgDiagnostico;
import org.openspcoop2.protocol.sdk.constants.RuoloMessaggio;

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

	private MTOMProcessorConfig config;
	private MessageSecurityConfig secConfig;
	private TipoPdD tipoPdD;
	private MsgDiagnostico msgDiag;
	private Logger log;
	private PdDContext pddContext;
	private Transaction transactionNullable;
	
	public MTOMProcessor(MTOMProcessorConfig config, MessageSecurityConfig secConfig, TipoPdD tipoPdD, 
			MsgDiagnostico msgDiag, Logger log, PdDContext pddContext){
		this.config = config;
		this.secConfig = secConfig;
		this.tipoPdD = tipoPdD;
		this.msgDiag = msgDiag;
		this.log = log;
		this.pddContext = pddContext;
		if(this.pddContext!=null && this.pddContext.containsKey(Costanti.ID_TRANSAZIONE)) {
			String idTransazione = (String) this.pddContext.getObject(Costanti.ID_TRANSAZIONE);
			try {
				this.transactionNullable = TransactionContext.getTransaction(idTransazione);
			}catch(TransactionNotExistsException e) {
				// Puo' succedere nelle comunicazioni stateful
			}
		}
	}
	
	
	public MTOMProcessorType getMTOMProcessorType(){
		if(this.config!=null)
			return this.config.getMtomProcessorType();
		else
			return null;
	}
	
	public void mtomBeforeSecurity(OpenSPCoop2Message msg,RuoloMessaggio tipo) throws Exception{
		
		if(msg==null) {
			return;
		}
		if(!ServiceBinding.SOAP.equals(msg.getServiceBinding())){
			return;
		}
		
		boolean emitDiagDisabled = false;
		
		if(this.isEngineEnabled()){
			
			if(this.isMTOMBeforeSecurity(tipo)){
				
				if(this.transactionNullable!=null) {
					switch (tipo) {
					case RICHIESTA:
						this.transactionNullable.getTempiElaborazione().startGestioneAttachmentsRichiesta();
						break;
					case RISPOSTA:
						this.transactionNullable.getTempiElaborazione().startGestioneAttachmentsRisposta();
						break;
					}
				}
				try {
				
					this.setProcessorTypeIntoDiagnostic(tipo);
					
					this.emitDiagnostic(tipo, 
							"mtom.processamentoRichiestaInCorso",
							"mtom.processamentoRispostaInCorso");
					
					try{
						
						this.mtomApply(msg.castAsSoap());
						
						this.emitDiagnostic(tipo, 
								"mtom.processamentoRichiestaEffettuato",
								"mtom.processamentoRispostaEffettuato");
						
					}catch(Exception e){
						
						if(MessageRole.REQUEST.equals(msg.getMessageRole())) {
							this.pddContext.addObject(org.openspcoop2.core.constants.Costanti.ERRORE_ALLEGATI_MESSAGGIO_RICHIESTA, "true");
						}
						else {
							this.pddContext.addObject(org.openspcoop2.core.constants.Costanti.ERRORE_ALLEGATI_MESSAGGIO_RISPOSTA, "true");
						}
						
						this.msgDiag.addKeywordErroreProcessamento(e);
						this.log.error("[MTOM BeforeSecurity "+tipo.getTipo()+"] "+e.getMessage(),e);
						
						this.emitDiagnostic(tipo, 
								"mtom.processamentoRichiestaInErrore",
								"mtom.processamentoRispostaInErrore");
						
						throw e;
					}
					
				}
				finally {
					if(this.transactionNullable!=null) {
						switch (tipo) {
						case RICHIESTA:
							this.transactionNullable.getTempiElaborazione().endGestioneAttachmentsRichiesta();
							break;
						case RISPOSTA:
							this.transactionNullable.getTempiElaborazione().endGestioneAttachmentsRisposta();
							break;
						}
					}	
				}
				
			}
			else{
				emitDiagDisabled = true;
			}
			
		}
		else{
			emitDiagDisabled = true;
		}
		
		if(emitDiagDisabled){
			this.emitDiagnostic(tipo, 
					"mtom.beforeSecurity.processamentoRichiestaDisabilitato", 
					"mtom.beforeSecurity.processamentoRispostaDisabilitato");			
		}
		
	}
	
	public void mtomAfterSecurity(OpenSPCoop2Message msg,RuoloMessaggio tipo) throws Exception{
		
		if(msg==null) {
			return;
		}
		if(!ServiceBinding.SOAP.equals(msg.getServiceBinding())){
			return;
		}
		
		boolean emitDiagDisabled = false;
		
		if(this.isEngineEnabled()){
			
			if(this.isMTOMBeforeSecurity(tipo)==false){
				
				if(this.transactionNullable!=null) {
					switch (tipo) {
					case RICHIESTA:
						this.transactionNullable.getTempiElaborazione().startGestioneAttachmentsRichiesta();
						break;
					case RISPOSTA:
						this.transactionNullable.getTempiElaborazione().startGestioneAttachmentsRisposta();
						break;
					}
				}
				try {
					
					this.setProcessorTypeIntoDiagnostic(tipo);
					
					this.emitDiagnostic(tipo, 
							"mtom.processamentoRichiestaInCorso",
							"mtom.processamentoRispostaInCorso");
					
					try{
						
						this.mtomApply(msg.castAsSoap());
						
						this.emitDiagnostic(tipo, 
								"mtom.processamentoRichiestaEffettuato",
								"mtom.processamentoRispostaEffettuato");
						
					}catch(Exception e){
						
						this.msgDiag.addKeywordErroreProcessamento(e);
						this.log.error("[MTOM AfterSecurity "+tipo.getTipo()+"] "+e.getMessage(),e);
						
						this.emitDiagnostic(tipo, 
								"mtom.processamentoRichiestaInErrore",
								"mtom.processamentoRispostaInErrore");
						
						throw e;
					}
				
				}
				finally {
					if(this.transactionNullable!=null) {
						switch (tipo) {
						case RICHIESTA:
							this.transactionNullable.getTempiElaborazione().endGestioneAttachmentsRichiesta();
							break;
						case RISPOSTA:
							this.transactionNullable.getTempiElaborazione().endGestioneAttachmentsRisposta();
							break;
						}
					}	
				}
			}
			else{
				emitDiagDisabled = true;
			}
			
		}
		else{
			emitDiagDisabled = true;
		}
		
		if(emitDiagDisabled){
			this.emitDiagnostic(tipo, 
					"mtom.afterSecurity.processamentoRichiestaDisabilitato", 
					"mtom.afterSecurity.processamentoRispostaDisabilitato");			
		}
		
	}
	
	
	/* **** UTILITIES INTERNE ***** */
	
	private void setProcessorTypeIntoDiagnostic(RuoloMessaggio tipo){
		switch (tipo) {
		case RICHIESTA:
			this.msgDiag.addKeyword(CostantiPdD.KEY_TIPO_PROCESSAMENTO_MTOM_RICHIESTA, this.config.getMtomProcessorType().getValue());
			this.pddContext.addObject(CostantiPdD.TIPO_PROCESSAMENTO_MTOM_RICHIESTA, this.config.getMtomProcessorType().getValue());
			break;
		case RISPOSTA:
			this.msgDiag.addKeyword(CostantiPdD.KEY_TIPO_PROCESSAMENTO_MTOM_RISPOSTA, this.config.getMtomProcessorType().getValue());
			this.pddContext.addObject(CostantiPdD.TIPO_PROCESSAMENTO_MTOM_RISPOSTA, this.config.getMtomProcessorType().getValue());
			break;
		}	
	}
	
	private void emitDiagnostic(RuoloMessaggio tipo, String idDiagnosticRichiesta, String idDiagnosticRisposta){
		
		// Il set del prefisso viene fatto poichè il processor viene usato anche in moduli (es. LocalForward) dove non è correttamente impostato
		
		String originalPrefix = this.msgDiag.getPrefixMsgPersonalizzati();
		try{
			switch (this.tipoPdD) {
			case DELEGATA:
				this.msgDiag.setPrefixMsgPersonalizzati(MsgDiagnosticiProperties.MSG_DIAG_INOLTRO_BUSTE);
				break;
			case APPLICATIVA:
				this.msgDiag.setPrefixMsgPersonalizzati(MsgDiagnosticiProperties.MSG_DIAG_RICEZIONE_BUSTE);
				break;
			default:
				return; // nessun diagnostico
			}
			
			switch (tipo) {
			case RICHIESTA:
				this.msgDiag.logPersonalizzato(idDiagnosticRichiesta);
				break;
			case RISPOSTA:
				this.msgDiag.logPersonalizzato(idDiagnosticRisposta);
				break;
			}	
		}finally{
			this.msgDiag.setPrefixMsgPersonalizzati(originalPrefix);
		}
	}
	
	private void mtomApply(OpenSPCoop2SoapMessage msg) throws Exception{
		switch (this.config.getMtomProcessorType()) {
		case PACKAGING:
			
			if(this.config.getInfo()!=null && this.config.getInfo().size()>0){
				msg.mtomPackaging(this.config.getInfo());
			}
			
			break;
			
		case UNPACKAGING:
			
			msg.mtomUnpackaging();
			
			break;
			
		case VERIFY:
			
			if(this.config.getInfo()!=null && this.config.getInfo().size()>0){
				msg.mtomVerify(this.config.getInfo());
			}
			
			break;

		default:
			break;
		}
	}
	
	private boolean isMTOMBeforeSecurity(RuoloMessaggio tipoTraccia) throws Exception{
		
		MTOMProcessorType processorType = null;
		if(this.config!=null && this.config.getMtomProcessorType()!=null){
			processorType = this.config.getMtomProcessorType();
		}
		else{
			processorType = MTOMProcessorType.DISABLE;
		}
		
		// NOTA: per default la sicurezza viene sempre applicato prima del processo di packaging 
		//		e dopo il processo di unpackaging trattando di fatto l'MTOM come un mero trasporto.
		Boolean applyToMtom = null;
		if(this.secConfig!=null && this.secConfig.getApplyToMtom()!=null){
			applyToMtom = this.secConfig.getApplyToMtom();
		}
		
		
		switch (this.tipoPdD) {
		
		case DELEGATA:
		
			switch (tipoTraccia) {
			
			case RICHIESTA:
				
				switch (processorType) {
				
				case DISABLE:
					// caso che non puo' avvenire grazie al metodo isEngineEnabled
					throw new Exception("Caso non previsto ["+processorType+"] Delegata.richiesta.disabile");
					
				case PACKAGING:
					if(applyToMtom==null){
						return false; // (role:sender) primo cifro/firmo e poi applico packaging, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return true; // (role:sender) prima applico il packaging in modo da applicare la sicurezza sul messaggio mtom
					}else{
						return false; // (role:sender) prima cifro/firmo e poi applico packaging, in pratica mtom e' un mero trasporto
					}
					
				case UNPACKAGING:
					if(applyToMtom==null){
						return false; // (role:sender) primo cifro/firmo e poi applico packaging, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return false; // (role:sender) primo cifro/firmo e poi effettuo unpacking. Scenario senza senso !!!!
					}else{
						return true; // (role:sender) prima applico unpackaging poi cifro e firmo, in pratica mtom e' un mero trasporto (Sembra poco indicato al contesto della PdD)
					}
					
				case VERIFY: 
					return true; // (role:sender) prima verifico le references e poi applico la sicurezza (in modo da leggere eventuali elementi che saranno poi cifrati)

				default:
					throw new Exception("Caso non previsto Delegata.richiesta.["+processorType+"]");
				}
				
			case RISPOSTA:
				
				switch (processorType) {
				
				case DISABLE:
					// caso che non puo' avvenire grazie al metodo isEngineEnabled
					throw new Exception("Caso non previsto ["+processorType+"] Delegata.risposta.disabile");
					
				case PACKAGING:
					if(applyToMtom==null){
						return true; // (role:receiver) prima applico il packaging e solo dopo verifico firma cifratura, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return true; // (role:receiver) prima applico il packaging e solo dopo verifico firma e cifratura
					}else{
						return false;  // (role:receiver) prima verifico firma cifratura, poi applico il packaging
					}
					
				case UNPACKAGING:
					if(applyToMtom==null){
						return true; // (role:receiver) primo applico l'unpackaging e solo dopo verifico firma cifratura, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return false; // (role:receiver) prima verifico firma cifratura, poi applico il l'unpackaging
					}else{
						return true; // (role:receiver) prima applico l'unpackaging e solo dopo verifico firma e cifratura
					}
					
				case VERIFY:
					return true; // (role:receiver) prima applico la sicurezza e solo dopo verifico (in modo da leggere eventuali elementi cifrati)

				default:
					throw new Exception("Caso non previsto Delegata.risposta.["+processorType+"]");
				}
					
			default:
				throw new Exception("Tipo non gestito ["+tipoTraccia+"] in Delegata.risposta");
			}
			
			
		case APPLICATIVA:
			
			switch (tipoTraccia) {
			
			case RICHIESTA:
				
				switch (processorType) {
				
				case DISABLE:
					// caso che non puo' avvenire grazie al metodo isEngineEnabled
					throw new Exception("Caso non previsto ["+processorType+"] Applicativa.richiesta.disabile");
					
				case PACKAGING:
					if(applyToMtom==null){
						return true; // (role:receiver) prima applico il packaging e solo dopo verifico firma cifratura, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return true; // (role:receiver) prima applico il packaging e solo dopo verifico firma e cifratura
					}else{
						return false;  // (role:receiver) prima verifico firma cifratura, poi applico il packaging
					}
					
				case UNPACKAGING:
					if(applyToMtom==null){
						return true; // (role:receiver) primo applico l'unpackaging e solo dopo verifico firma cifratura, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return false; // (role:receiver) prima verifico firma cifratura, poi applico il l'unpackaging
					}else{
						return true; // (role:receiver) prima applico l'unpackaging e solo dopo verifico firma e cifratura
					}
					
				case VERIFY:
					return true; // (role:receiver) prima applico la sicurezza e solo dopo verifico (in modo da leggere eventuali elementi cifrati)

				default:
					throw new Exception("Caso non previsto Applicativa.richiesta.["+processorType+"]");
				}
				
			case RISPOSTA:
				
				switch (processorType) {
				
				case DISABLE:
					// caso che non puo' avvenire grazie al metodo isEngineEnabled
					throw new Exception("Caso non previsto ["+processorType+"] Applicativa.risposta.disabile");
					
				case PACKAGING:
					if(applyToMtom==null){
						return false; // (role:sender) primo cifro/firmo e poi applico packaging, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return true; // (role:sender) prima applico il packaging in modo da applicare la sicurezza sul messaggio mtom
					}else{
						return false; // (role:sender) prima cifro/firmo e poi applico packaging, in pratica mtom e' un mero trasporto
					}
					
				case UNPACKAGING:
					if(applyToMtom==null){
						return false; // (role:sender) primo cifro/firmo e poi applico packaging, in pratica mtom e' un mero trasporto (come se venisse chiamato sul connettore)
					}
					else if(applyToMtom){
						return false; // (role:sender) primo cifro/firmo e poi effettuo unpacking. Scenario senza senso !!!!
					}else{
						return true; // (role:sender) prima applico unpackaging poi cifro e firmo, in pratica mtom e' un mero trasporto (Sembra poco indicato al contesto della PdD)
					}
					
				case VERIFY: 
					return true; // (role:sender) prima verifico le references e poi applico la sicurezza (in modo da leggere eventuali elementi che saranno poi cifrati)

				default:
					throw new Exception("Caso non previsto Applicativa.risposta.["+processorType+"]");
				}
					
			default:
				throw new Exception("Tipo non gestito ["+tipoTraccia+"] in Applicativa.risposta");
			}
			
		case INTEGRATION_MANAGER:
		case ROUTER:
		default:
				throw new Exception("Ruolo ["+this.tipoPdD+"] non gestito");
			
		}
		
		
	}
	
	private boolean isEngineEnabled(){
		if(!TipoPdD.DELEGATA.equals(this.tipoPdD) && !TipoPdD.APPLICATIVA.equals(this.tipoPdD)){
			return false;
		}
		if(this.config!=null && this.config.getMtomProcessorType()!=null && !MTOMProcessorType.DISABLE.equals(this.config.getMtomProcessorType())){
			return true;
		}
		return false;
	}
}