StickyUtils.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.behaviour.built_in.load_balance.sticky;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.openspcoop2.core.config.PortaApplicativa;
import org.openspcoop2.core.config.Proprieta;
import org.openspcoop2.core.id.IDPortaApplicativa;
import org.openspcoop2.message.OpenSPCoop2Message;
import org.openspcoop2.message.constants.MessageType;
import org.openspcoop2.message.constants.ServiceBinding;
import org.openspcoop2.pdd.config.ConfigurazionePdDManager;
import org.openspcoop2.pdd.config.OpenSPCoop2Properties;
import org.openspcoop2.pdd.core.CostantiPdD;
import org.openspcoop2.pdd.core.PdDContext;
import org.openspcoop2.pdd.core.behaviour.BehaviourEmitDiagnosticException;
import org.openspcoop2.pdd.core.behaviour.BehaviourException;
import org.openspcoop2.pdd.core.behaviour.BehaviourPropertiesUtils;
import org.openspcoop2.pdd.core.behaviour.conditional.ConditionalUtils;
import org.openspcoop2.pdd.core.dynamic.DynamicUtils;
import org.openspcoop2.pdd.core.dynamic.ErrorHandler;
import org.openspcoop2.pdd.core.dynamic.MessageContent;
import org.openspcoop2.pdd.core.dynamic.Template;
import org.openspcoop2.pdd.logger.MsgDiagnosticiProperties;
import org.openspcoop2.pdd.logger.MsgDiagnostico;
import org.openspcoop2.pdd.services.connector.FormUrlEncodedHttpServletRequest;
import org.openspcoop2.protocol.sdk.Busta;
import org.openspcoop2.protocol.sdk.state.IState;
import org.openspcoop2.protocol.sdk.state.RequestInfo;
import org.openspcoop2.utils.regexp.RegExpNotFoundException;
import org.openspcoop2.utils.regexp.RegularExpressionEngine;
import org.openspcoop2.utils.transport.TransportUtils;
import org.openspcoop2.utils.transport.http.HttpServletTransportRequestContext;
import org.openspcoop2.utils.xml.AbstractXPathExpressionEngine;
import org.openspcoop2.utils.xml2json.JsonXmlPathExpressionEngine;
import org.slf4j.Logger;

/**
 * ConditionalUtils
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class StickyUtils  {
	
	public static StickyResult getStickyResult(PortaApplicativa pa, OpenSPCoop2Message message, Busta busta, 
			RequestInfo requestInfo, PdDContext pddContext, 
			MsgDiagnostico msgDiag, Logger log,
			IState state) throws BehaviourException, BehaviourEmitDiagnosticException {
		
		if(isConfigurazioneSticky(pa, log)==false) {
			return null; // non vi è da fare alcun filtro condizionale
		}
		
		StickyResult result = new StickyResult();
		
		StickyConfigurazione config = read(pa, log);

		StickyTipoSelettore tipoSelettore = config.getTipoSelettore();
		String patternSelettore = config.getPattern();
		
		String pattern = "";
		try {
			Map<String, List<String>> pTrasporto = null;
			String urlInvocazione = null;
			Map<String, List<String>> pQuery = null;
			Map<String, List<String>> pForm = null;
			if(requestInfo!=null && requestInfo.getProtocolContext()!=null) {
				pTrasporto = requestInfo.getProtocolContext().getHeaders();
				urlInvocazione = requestInfo.getProtocolContext().getUrlInvocazione_formBased();
				pQuery = requestInfo.getProtocolContext().getParameters();
				if(requestInfo.getProtocolContext() instanceof HttpServletTransportRequestContext) {
					HttpServletTransportRequestContext httpServletContext = (HttpServletTransportRequestContext) requestInfo.getProtocolContext();
					HttpServletRequest httpServletRequest = httpServletContext.getHttpServletRequest();
					if(httpServletRequest!=null && httpServletRequest instanceof FormUrlEncodedHttpServletRequest) {
						FormUrlEncodedHttpServletRequest formServlet = (FormUrlEncodedHttpServletRequest) httpServletRequest;
						if(formServlet.getFormUrlEncodedParametersValues()!=null &&
								!formServlet.getFormUrlEncodedParametersValues().isEmpty()) {
							pForm = formServlet.getFormUrlEncodedParametersValues();
						}
					}
				}
			}
			MessageContent messageContent = null;
			boolean bufferMessage_readOnly =  OpenSPCoop2Properties.getInstance().isReadByPathBufferEnabled();
			if(StickyTipoSelettore.CONTENT_BASED.equals(tipoSelettore) || tipoSelettore.isTemplate()) {
				if(ServiceBinding.SOAP.equals(message.getServiceBinding())){
					messageContent = new MessageContent(message.castAsSoap(), bufferMessage_readOnly, pddContext);
				}
				else{
					if(MessageType.XML.equals(message.getMessageType())){
						messageContent = new MessageContent(message.castAsRestXml(), bufferMessage_readOnly, pddContext);
					}
					else if(MessageType.JSON.equals(message.getMessageType())){
						messageContent = new MessageContent(message.castAsRestJson(), bufferMessage_readOnly, pddContext);
					}
					else{
						if(StickyTipoSelettore.CONTENT_BASED.equals(tipoSelettore) 
								// Nei template potrei utilizzare gli header o altre informazioni che non entrano nel merito del contenuto //|| tipoSelettore.isTemplate()
								) {
							throw new Exception("Selettore '"+tipoSelettore.getValue()+"' non supportato per il message-type '"+message.getMessageType()+"'");
						}
					}
				}
			}
			
			msgDiag.addKeyword(CostantiPdD.KEY_TIPO_SELETTORE, tipoSelettore.getValue());
			msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern); // per eliminare @@ dove non serve
			
			String condition = null;
			switch (tipoSelettore) {
			
			case COOKIE_BASED:
				pattern = " (Cookie: "+patternSelettore+")";
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				StickyCookieConfig stickyCookie = new StickyCookieConfig(patternSelettore);
				if(requestInfo!=null && requestInfo.getProtocolContext()!=null && requestInfo.getProtocolContext().getHttpServletRequest()!=null) {
					Cookie [] cookies = requestInfo.getProtocolContext().getHttpServletRequest().getCookies();
					if(cookies!=null && cookies.length>0) {
						for (Cookie cookie : cookies) {
							if(cookie.getName().equalsIgnoreCase(stickyCookie.getName())) {
								
								// NOTA: Nelle richieste il domani e il path non dovrebbero mai esserci. Lascio comunque il codice.
								
								if(!StringUtils.isEmpty(stickyCookie.getDomain())) {
									if(!stickyCookie.getDomain().equals(cookie.getDomain())) {
										continue; // il cookie non ha un match sul domain
									}
								}
								if(!StringUtils.isEmpty(stickyCookie.getPath())) {
									if(!stickyCookie.getPath().equals(cookie.getPath())) {
										continue; // il cookie non ha un match sul domain
									}
								}
								
								condition = cookie.getValue();
								
								if(cookie.getMaxAge()>0) {
									result.setMaxAgeSeconds(cookie.getMaxAge());
								}
								
								break;
							}
						}
					}
				}
				if(condition==null) {
					throw new Exception("cookie non presente");
				}
				break;
			
			case HEADER_BASED:
				pattern = " (Header HTTP: "+patternSelettore+")";
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				condition = TransportUtils.getFirstValue(pTrasporto, patternSelettore);
				if(condition==null) {
					throw new Exception("header non presente");
				}
				break;
				
			case URLBASED:
				pattern = " (Espressione Regolare: "+patternSelettore+")";
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				try{
					condition = RegularExpressionEngine.getStringMatchPattern(urlInvocazione, patternSelettore);
				}catch(RegExpNotFoundException notFound){}
				break;
				
			case FORM_BASED:
				pattern = " (Parametro URL: "+patternSelettore+")";
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				condition = TransportUtils.getFirstValue(pQuery, patternSelettore);
				if(condition==null) {
					throw new Exception("parametro della url non presente");
				}
				break;
				
			case CONTENT_BASED:
				AbstractXPathExpressionEngine xPathEngine = null;
				if(messageContent==null) {
					throw new Exception("messaggio non presente");
				}
				if(messageContent.isXml()) {
					pattern = " (xPath: "+patternSelettore+")";
					msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
					xPathEngine = new org.openspcoop2.message.xml.XPathExpressionEngine(message.getFactory());
					condition = AbstractXPathExpressionEngine.extractAndConvertResultAsString(messageContent.getElement(), xPathEngine, patternSelettore,  log);
				}
				else {
					pattern = " (jsonPath: "+patternSelettore+")";
					msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
					condition = JsonXmlPathExpressionEngine.extractAndConvertResultAsString(messageContent.getElementJson(), patternSelettore, log);
				}
				break;
				
			case INDIRIZZO_IP:
				if(pddContext!=null && pddContext.containsKey(org.openspcoop2.core.constants.Costanti.CLIENT_IP_REMOTE_ADDRESS)) {
					condition = (String) pddContext.getObject(org.openspcoop2.core.constants.Costanti.CLIENT_IP_REMOTE_ADDRESS);
				}
				break;
				
			case INDIRIZZO_IP_FORWARDED:
				if(pddContext!=null && pddContext.containsKey(org.openspcoop2.core.constants.Costanti.CLIENT_IP_TRANSPORT_ADDRESS)) {
					condition = (String) pddContext.getObject(org.openspcoop2.core.constants.Costanti.CLIENT_IP_TRANSPORT_ADDRESS);
				}
				break;
				
			case TEMPLATE:
				if(patternSelettore.length()<50) {
					pattern = " ("+patternSelettore+")";
				}
				else {
					pattern = "";
				}
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				Map<String, Object> dynamicMap = new HashMap<>();
				ErrorHandler errorHandler = new ErrorHandler();
				DynamicUtils.fillDynamicMapRequest(log, dynamicMap, pddContext, urlInvocazione,
						message,
						messageContent, 
						busta, 
						pTrasporto, 
						pQuery,
						pForm,
						errorHandler);
				condition = DynamicUtils.convertDynamicPropertyValue("ConditionalConfig.gwt", patternSelettore, dynamicMap, pddContext);
				if(condition!=null) {
					condition = ConditionalUtils.normalizeTemplateResult(condition);
				}
				break;
				
			case FREEMARKER_TEMPLATE:
				if(patternSelettore.length()<50) {
					pattern = " ("+patternSelettore+")";
				}
				else {
					pattern = "";
				}
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				dynamicMap = new HashMap<>();
				errorHandler = new ErrorHandler();
				DynamicUtils.fillDynamicMapRequest(log, dynamicMap, pddContext, urlInvocazione,
						message,
						messageContent, 
						busta, 
						pTrasporto, 
						pQuery,
						pForm,
						errorHandler);
				ByteArrayOutputStream bout = new ByteArrayOutputStream();
				ConfigurazionePdDManager configurazionePdDManager = ConfigurazionePdDManager.getInstance(state);
				IDPortaApplicativa idPA = new IDPortaApplicativa();
				idPA.setNome(pa.getNome());
				Template template = configurazionePdDManager.getTemplateConnettoreMultiploSticky(idPA, patternSelettore.getBytes(), requestInfo);
				DynamicUtils.convertFreeMarkerTemplate(template, dynamicMap, bout);
				bout.flush();
				bout.close();
				condition = bout.toString();
				if(condition!=null) {
					condition = ConditionalUtils.normalizeTemplateResult(condition);
				}
				break;
				
			case VELOCITY_TEMPLATE:
				if(patternSelettore.length()<50) {
					pattern = " ("+patternSelettore+")";
				}
				else {
					pattern = "";
				}
				msgDiag.addKeyword(CostantiPdD.KEY_PATTERN_SELETTORE, pattern);
				dynamicMap = new HashMap<>();
				errorHandler = new ErrorHandler();
				DynamicUtils.fillDynamicMapRequest(log, dynamicMap, pddContext, urlInvocazione,
						message,
						messageContent, 
						busta, 
						pTrasporto, 
						pQuery,
						pForm,
						errorHandler);
				bout = new ByteArrayOutputStream();
				configurazionePdDManager = ConfigurazionePdDManager.getInstance(state);
				idPA = new IDPortaApplicativa();
				idPA.setNome(pa.getNome());
				template = configurazionePdDManager.getTemplateConnettoreMultiploSticky(idPA, patternSelettore.getBytes(), requestInfo);
				DynamicUtils.convertVelocityTemplate(template, dynamicMap, bout);
				bout.flush();
				bout.close();
				condition = bout.toString();
				if(condition!=null) {
					condition = ConditionalUtils.normalizeTemplateResult(condition);
				}
				break;
			}
		
			if(condition==null || "".equals(condition)) {
				throw new Exception("Nessuna condizione estratta");
			}
			else {
				result.setCondition(condition);
				result.setFound(true);				
				
				msgDiag.addKeyword(CostantiPdD.KEY_CONDIZIONE_STICKY, condition);
				
				msgDiag.logPersonalizzato(MsgDiagnosticiProperties.MSG_DIAG_CONSEGNA_CONTENUTI_APPLICATIVI, 
						"connettoriMultipli.loadBalancer.sticky.identificazioneRiuscita");
				
			}
			
		}catch(Exception e) {
			
			msgDiag.addKeyword(CostantiPdD.KEY_ERRORE_PROCESSAMENTO, e.getMessage());
			
			msgDiag.logPersonalizzato(MsgDiagnosticiProperties.MSG_DIAG_CONSEGNA_CONTENUTI_APPLICATIVI, 
							"connettoriMultipli.loadBalancer.sticky.identificazioneFallita");
		}
		
		
		if(result.isFound()) {
			if(config.getMaxAgeSeconds()!=null) {
				if(result.getMaxAgeSeconds()==null) {
					result.setMaxAgeSeconds(config.getMaxAgeSeconds());
				}
			}
		}
		
		return result;
	}
	
	
	public static boolean isConfigurazioneSticky(PortaApplicativa pa, Logger log) {
		if(pa.getBehaviour()==null || pa.getBehaviour().sizeProprietaList()<=0) {
			return false;
		}
		String type = null;
		for (Proprieta p : pa.getBehaviour().getProprietaList()) {
			if(StickyCostanti.STICKY.equals(p.getNome())) {
				type = p.getValore();
				break;
			}
		}
		if(type==null) {
			return false;
		}
		return "true".equals(type);
	}
		
	public static StickyConfigurazione read(PortaApplicativa pa, Logger log) throws BehaviourException {
		StickyConfigurazione config = new StickyConfigurazione();
		if(pa.getBehaviour()==null || pa.getBehaviour().sizeProprietaList()<=0) {
			throw new BehaviourException("Configurazione sticky non disponibile");
		}
		
		for (Proprieta p : pa.getBehaviour().getProprietaList()) {
			
			String nome = p.getNome();
			String valore = p.getValore().trim();
			
			try {
				if(StickyCostanti.STICKY.equals(nome)) {
					config.setStickyEnabled("true".equals(valore));
				}
				else if(StickyCostanti.STICKY_TIPO_SELETTORE.equals(nome)) {
					config.setTipoSelettore(StickyTipoSelettore.toEnumConstant(valore, true));
				}
				else if(StickyCostanti.STICKY_PATTERN.equals(nome)) {
					config.setPattern(valore);
				}
				else if(StickyCostanti.STICKY_EXPIRE.equals(nome)) {
					config.setMaxAgeSeconds(Integer.valueOf(valore));
				}
			}catch(Exception e) {
				throw new BehaviourException("Configurazione sticky non corretta (proprietà:"+p.getNome()+" valore:'"+p.getValore()+"'): "+e.getMessage(),e);
			}
			
		}

		return config;
	}
	

	public static void save(PortaApplicativa pa, StickyConfigurazione configurazione) throws BehaviourException {
		
		if(pa.getBehaviour()==null) {
			throw new BehaviourException("Configurazione behaviour non abilitata");
		}
		if(configurazione==null) {
			throw new BehaviourException("Configurazione condizionale non fornita");
		}
		BehaviourPropertiesUtils.addProprieta(pa.getBehaviour(),StickyCostanti.STICKY, configurazione.isStickyEnabled()+"");
		
		BehaviourPropertiesUtils.removeProprieta(pa.getBehaviour(),StickyCostanti.STICKY_TIPO_SELETTORE);
		BehaviourPropertiesUtils.removeProprieta(pa.getBehaviour(),StickyCostanti.STICKY_PATTERN);
		BehaviourPropertiesUtils.removeProprieta(pa.getBehaviour(),StickyCostanti.STICKY_EXPIRE);
		
		if(configurazione.isStickyEnabled()) {
			if(configurazione.getTipoSelettore()!=null) {
				BehaviourPropertiesUtils.addProprieta(pa.getBehaviour(),StickyCostanti.STICKY_TIPO_SELETTORE, configurazione.getTipoSelettore().getValue());
			}
	
			if(StringUtils.isNotEmpty(configurazione.getPattern())) {
				BehaviourPropertiesUtils.addProprieta(pa.getBehaviour(),StickyCostanti.STICKY_PATTERN, configurazione.getPattern());
			}
			
			if(configurazione.getMaxAgeSeconds()!=null && configurazione.getMaxAgeSeconds().intValue()>0) {
				BehaviourPropertiesUtils.addProprieta(pa.getBehaviour(),StickyCostanti.STICKY_EXPIRE, configurazione.getMaxAgeSeconds().intValue()+"");
			}
		}
		
	}
	
}