GestoreLoadBalancerCaching.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;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.openspcoop2.core.commons.CoreException;
import org.openspcoop2.core.config.PortaApplicativa;
import org.openspcoop2.core.config.PortaApplicativaServizioApplicativo;
import org.openspcoop2.core.config.Proprieta;
import org.openspcoop2.core.config.constants.CostantiConfigurazione;
import org.openspcoop2.core.config.constants.StatoFunzionalita;
import org.openspcoop2.core.config.constants.TipoBehaviour;
import org.openspcoop2.core.id.IDPortaApplicativa;
import org.openspcoop2.message.OpenSPCoop2Message;
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.built_in.load_balance.health_check.HealthCheckUtils;
import org.openspcoop2.pdd.core.behaviour.built_in.load_balance.sticky.StickyConnector;
import org.openspcoop2.pdd.core.behaviour.built_in.load_balance.sticky.StickyResult;
import org.openspcoop2.pdd.core.behaviour.built_in.load_balance.sticky.StickyUtils;
import org.openspcoop2.pdd.core.behaviour.conditional.ConditionalFilterResult;
import org.openspcoop2.pdd.core.behaviour.conditional.ConditionalUtils;
import org.openspcoop2.pdd.logger.MsgDiagnostico;
import org.openspcoop2.pdd.logger.OpenSPCoop2Logger;
import org.openspcoop2.protocol.engine.ProtocolFactoryManager;
import org.openspcoop2.protocol.sdk.Busta;
import org.openspcoop2.protocol.sdk.IProtocolFactory;
import org.openspcoop2.protocol.sdk.state.IState;
import org.openspcoop2.protocol.sdk.state.RequestInfo;
import org.openspcoop2.protocol.utils.PorteNamingUtils;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.cache.Cache;
import org.openspcoop2.utils.cache.CacheAlgorithm;
import org.openspcoop2.utils.cache.CacheType;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.date.DateUtils;
import org.slf4j.Logger;

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

	/** Chiave della cache */
	private static final String LOAD_BALANCER_CACHE_NAME = "consegnaApplicativi";
	/** Cache */
	private static Cache cache = null;
	private static final org.openspcoop2.utils.Semaphore lock = new org.openspcoop2.utils.Semaphore("GestoreLoadBalancerCaching");
	

	/* --------------- Cache --------------------*/
	public static boolean isCacheAbilitata() {
		return cache!=null;
	}
	public static void resetCache() throws Exception{
		try{
			if(cache!=null){
				cache.clear();
			}
		}catch(Exception e){
			throw new Exception("Reset della cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	public static String printStatsCache(String separator) throws Exception{
		try{
			if(cache!=null){
				try{
					return cache.printStats(separator);
				}catch(Exception e){
					throw new Exception(e.getMessage(),e);
				}
			}else{
				throw new Exception("Cache non abilitata");
			}
		}catch(Exception e){
			throw new Exception("Visualizzazione Statistiche riguardante la cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	public static void abilitaCache() throws Exception{
		try{
			if(cache!=null)
				throw new Exception("Cache gia' abilitata");
			else{
				_abilitaCache();
			}
		}catch(Exception e){
			throw new Exception("Abilitazione cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	private static synchronized void _abilitaCache() throws Exception{
		try{
			if(cache==null) {
				cache = new Cache(CacheType.JCS, LOAD_BALANCER_CACHE_NAME);  // lascio JCS come default abilitato via jmx
				cache.build();
			}
		}catch(Exception e){
			throw new Exception("Abilitazione cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	public static void abilitaCache(Long dimensioneCache,Boolean algoritmoCacheLRU,Long itemIdleTime,Long itemLifeSecond, Logger log) throws Exception{
		try{
			if(cache!=null)
				throw new Exception("Cache gia' abilitata");
			else{
				int dimensione = -1;
				if(dimensioneCache!=null){
					dimensione = dimensioneCache.intValue();
				}
				initCache(CacheType.JCS, dimensione, algoritmoCacheLRU, itemIdleTime, itemLifeSecond, log);  // lascio JCS come default abilitato via jmx
			}
		}catch(Exception e){
			throw new Exception("Abilitazione cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	public static void disabilitaCache() throws Exception{
		try{
			if(cache==null)
				throw new Exception("Cache gia' disabilitata");
			else{
				_disabilitaCache();
			}
		}catch(Exception e){
			throw new Exception("Disabilitazione cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}	
	private static synchronized void _disabilitaCache() throws Exception{
		try{
			if(cache!=null) {
				cache.clear();
				cache = null;
			}
		}catch(Exception e){
			throw new Exception("Disabilitazione cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}	
	public static String listKeysCache(String separator) throws Exception{
		try{
			if(cache!=null){
				try{
					return cache.printKeys(separator);
				}catch(Exception e){
					throw new Exception(e.getMessage(),e);
				}
			}else{
				throw new Exception("Cache non abilitata");
			}
		}catch(Exception e){
			throw new Exception("Visualizzazione chiavi presenti nella cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	public static List<String> keysCache() throws Exception{
		if(cache!=null){
			try{
				return cache.keys();
			}catch(Exception e){
				throw new Exception(e.getMessage(),e);
			}
		}else{
			throw new Exception("Cache non abilitata");
		}
	}
	public static String getObjectCache(String key) throws Exception{
		try{
			if(cache!=null){
				try{
					Object o = cache.get(key);
					if(o!=null){
						return o.toString();
					}else{
						return "oggetto con chiave ["+key+"] non presente";
					}
				}catch(Exception e){
					throw new Exception(e.getMessage(),e);
				}
			}else{
				throw new Exception("Cache non abilitata");
			}
		}catch(Exception e){
			throw new Exception("Visualizzazione oggetto presente nella cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	
	public static void removeObjectCache(String key) throws Exception{
		try{
			if(cache!=null){
				try{
					cache.remove(key);
				}catch(Exception e){
					throw new Exception(e.getMessage(),e);
				}
			}else{
				throw new Exception("Cache non abilitata");
			}
		}catch(Exception e){
			throw new Exception("Rimozione oggetto presente nella cache per i dati contenenti i dati di bilanciamento del carico non riuscita: "+e.getMessage(),e);
		}
	}
	
	
	
	
	
	/*----------------- CLEANER --------------------*/
	
	public static void removePortaApplicativa(IDPortaApplicativa idPA) throws Exception {
		
		if(cache!=null){
			
			IProtocolFactory<?> protocolFactory = null;
			PorteNamingUtils namingUtils = null;
			try {
				ProtocolFactoryManager protocolFactoryManager = ProtocolFactoryManager.getInstance();
				String protocol = protocolFactoryManager.getProtocolByOrganizationType(idPA.getIdentificativiErogazione().getIdServizio().getSoggettoErogatore().getTipo());
				protocolFactory = protocolFactoryManager.getProtocolFactoryByName(protocol);
				namingUtils = new PorteNamingUtils(protocolFactory);
			}catch(Throwable t) {
				OpenSPCoop2Logger.getLoggerOpenSPCoopCore().error("Errore durante la comprensione del protocol factory della PA ["+idPA+"]");
			}
			
			String nomePorta = idPA.getNome(); 
			String nomePorta_normalized = null; 
			if(namingUtils!=null) {
				nomePorta_normalized = namingUtils.normalizePA(nomePorta);
			}
			
			String porta = _toKey_getKeyCacheNomePorta(nomePorta);
			String porta_normalized = null;
			if(nomePorta_normalized!=null){
				porta_normalized = _toKey_getKeyCacheNomePorta(nomePorta_normalized);
			}
			
			List<String> keyForClean = new ArrayList<>();
			List<String> keys = GestoreLoadBalancerCaching.keysCache();
			if(keys!=null && !keys.isEmpty()) {
				for (String key : keys) {
					if(key!=null) {
						if(key.contains(porta)) {
							keyForClean.add(key);
						}
						else if(porta_normalized!=null && key.contains(porta_normalized)) {
							keyForClean.add(key);
						}
					}
				}
			}
			if(keyForClean!=null && !keyForClean.isEmpty()) {
				for (String key : keyForClean) {
					removeObjectCache(key);
				}
			}
			
		}
	}
	
	
	

	/*----------------- INIZIALIZZAZIONE --------------------*/

	public static void initialize(Logger log) throws Exception{
		GestoreLoadBalancerCaching.initialize(null, false, -1,null,-1l,-1l, log);
	}
	public static void initialize(CacheType cacheType, int dimensioneCache,String algoritmoCache,
			long idleTime, long itemLifeSecond, Logger log) throws Exception{
		GestoreLoadBalancerCaching.initialize(cacheType, true, dimensioneCache,algoritmoCache,idleTime,itemLifeSecond, log);
	}

	private static void initialize(CacheType cacheType, boolean cacheAbilitata,int dimensioneCache,String algoritmoCache,
			long idleTime, long itemLifeSecond, Logger log) throws Exception{

		// Inizializzazione Cache
		if(cacheAbilitata){
			GestoreLoadBalancerCaching.initCache(cacheType, dimensioneCache, algoritmoCache, idleTime, itemLifeSecond, log);
		}

	}
	private static void initCache(CacheType cacheType, Integer dimensioneCache,String algoritmoCache,Long itemIdleTime,Long itemLifeSecond,Logger alog) throws Exception{
		initCache(cacheType, dimensioneCache, CostantiConfigurazione.CACHE_LRU.toString().equalsIgnoreCase(algoritmoCache), itemIdleTime, itemLifeSecond, alog);
	}
	
	private static void initCache(CacheType cacheType, Integer dimensioneCache,boolean algoritmoCacheLRU,Long itemIdleTime,Long itemLifeSecond,Logger alog) throws Exception{
		
		cache = new Cache(cacheType, LOAD_BALANCER_CACHE_NAME);
	
		// dimensione
		if(dimensioneCache!=null && dimensioneCache>0){
			try{
				String msg = "Dimensione della cache (ResponseCaching) impostata al valore: "+dimensioneCache;
				alog.info(msg);
				cache.setCacheSize(dimensioneCache);
			}catch(Exception error){
				String msg = "Parametro errato per la dimensione della cache (ResponseCaching): "+error.getMessage();
				alog.error(msg);
				throw new Exception(msg,error);
			}
		}
		
		// algoritno
		String msg = "Algoritmo di cache (ResponseCaching) impostato al valore: LRU";
		if(!algoritmoCacheLRU){
			msg = "Algoritmo di cache (ResponseCaching) impostato al valore: MRU";
		}
		alog.info(msg);
		if(!algoritmoCacheLRU)
			cache.setCacheAlgoritm(CacheAlgorithm.MRU);
		else
			cache.setCacheAlgoritm(CacheAlgorithm.LRU);
		
		
		// idle time
		if(itemIdleTime!=null && itemIdleTime>0){
			try{
				msg = "Attributo 'IdleTime' (ResponseCaching) impostato al valore: "+itemIdleTime;
				alog.info(msg);
				cache.setItemIdleTime(itemIdleTime);
			}catch(Exception error){
				msg = "Parametro errato per l'attributo 'IdleTime' (ResponseCaching): "+error.getMessage();
				alog.error(msg);
				throw new Exception(msg,error);
			}
		}
		
		// LifeSecond
		long longItemLife = -1; 
		if(itemLifeSecond!=null && itemLifeSecond>0){
			longItemLife = itemLifeSecond.longValue();
		}
		try{
			msg = "Attributo 'MaxLifeSecond' (ResponseCaching) impostato al valore: "+longItemLife;
			alog.info(msg);
			cache.setItemLifeTime(longItemLife);
		}catch(Exception error){
			msg = "Parametro errato per l'attributo 'MaxLifeSecond' (ResponseCaching): "+error.getMessage();
			alog.error(msg);
			throw new Exception(msg,error);
		}
		
		cache.build();
	}
	
	@SuppressWarnings("deprecation")
	@Deprecated
	public static void disableSyncronizedGet() throws UtilsException {
		if(GestoreLoadBalancerCaching.cache==null) {
			throw new UtilsException("Cache disabled");
		}
		GestoreLoadBalancerCaching.cache.disableSyncronizedGet();
	}
	@SuppressWarnings("deprecation")
	@Deprecated
	public static boolean isDisableSyncronizedGet() throws UtilsException {
		if(GestoreLoadBalancerCaching.cache==null) {
			throw new UtilsException("Cache disabled");
		}
		return GestoreLoadBalancerCaching.cache.isDisableSyncronizedGet();
	}
	
	

	
	
	private static GestoreLoadBalancerCaching staticInstance = null;
	public static synchronized void initialize() {
		if(staticInstance==null){
			staticInstance = new GestoreLoadBalancerCaching();
		}
	}
	public static GestoreLoadBalancerCaching getInstance() throws CoreException{
		if(staticInstance==null){
			// spotbugs warning 'SING_SINGLETON_GETTER_NOT_SYNCHRONIZED': l'istanza viene creata allo startup
			synchronized (GestoreLoadBalancerCaching.class) {
				throw new CoreException("GestoreKeystore non inizializzato");
			}
		}
		return staticInstance;
	}
	
	@SuppressWarnings("unused")
	private Logger log;
	
	private GestoreLoadBalancerCaching() {
		this.log = OpenSPCoop2Logger.getLoggerOpenSPCoopCore();
	}
	
	
	
	
	
	
	
	
	
	/* ********************** ENGINE ************************** */
	
	public static LoadBalancerInstance getLoadBalancerInstance(PortaApplicativa pa, OpenSPCoop2Message message, Busta busta, 
			RequestInfo requestInfo, PdDContext pddContext, 
			MsgDiagnostico msgDiag, Logger log,
			LoadBalancerType loadBalancerType, IState state) throws BehaviourException, BehaviourEmitDiagnosticException{
		
		LoadBalancerInstance instance = new LoadBalancerInstance();
		
		ConditionalFilterResult filterResult = 
				ConditionalUtils.filter(pa, message, busta, requestInfo, pddContext, msgDiag, log, 
						TipoBehaviour.CONSEGNA_LOAD_BALANCE, state);
		
		String keyCache = getKeyCache(pa, message, busta, 
				requestInfo, pddContext, 
				msgDiag, log,
				filterResult);
		
		String keyCacheLoadBalancerPool = "[POOL] "+keyCache;
		instance.setLoadBalancerPool(getLoadBalancerPool(pa, message, busta, 
				requestInfo, pddContext, 
				msgDiag, log, 
				keyCacheLoadBalancerPool, filterResult));
		
		StickyResult stickyResult = null;
		if(LoadBalancerType.IP_HASH.equals(loadBalancerType)) {
			String clientIp = LoadBalancer.getIpSourceFromContet(pddContext);
			stickyResult = new StickyResult();
			stickyResult.setCondition(clientIp);
			stickyResult.setFound(true);
		}
		else {
			stickyResult = getStickyInfo(pa, message, busta, 
					requestInfo, pddContext, 
					msgDiag, log,
					state);
		}
		
		if(stickyResult!=null && stickyResult.isFound()) {
			String keyCacheSticky = "[STICKY '"+stickyResult.getCondition()+"'] "+keyCache;
			instance.setConnectorSelected(getNomeConnettore(pa, message, busta, 
					requestInfo, pddContext, 
					msgDiag, log, 
					keyCacheSticky, instance.getLoadBalancerPool(), loadBalancerType, stickyResult));
		}
		
		if(instance.getConnectorSelected()==null) {
			instance.setConnectorSelected(selectConnector(loadBalancerType, instance.getLoadBalancerPool(), pddContext));
		}
		
		return instance;
	}
	
	private static StickyResult getStickyInfo(PortaApplicativa pa, OpenSPCoop2Message message, Busta busta, 
			RequestInfo requestInfo, PdDContext pddContext, 
			MsgDiagnostico msgDiag, Logger log,
			IState state) throws BehaviourException, BehaviourEmitDiagnosticException{
		
		if(StickyUtils.isConfigurazioneSticky(pa, log)) {
			return StickyUtils.getStickyResult(pa, message, busta, requestInfo, pddContext, msgDiag, log, state);
		}
		
		return null;
		
	}
	
	protected static String _toKey_getKeyCacheNomePorta(String nomePorta) {
		return " pa:"+nomePorta+" ";
	}
	
	private static String getKeyCache(PortaApplicativa pa, OpenSPCoop2Message message, Busta busta, 
			RequestInfo requestInfo, PdDContext pddContext, 
			MsgDiagnostico msgDiag, Logger log,
			ConditionalFilterResult filterResult) throws BehaviourException, BehaviourEmitDiagnosticException {
		
		String keyCache = "["+pa.getBehaviour().getNome()+"] "+pa.getTipoSoggettoProprietario()+"/"+pa.getNomeSoggettoProprietario()+
				_toKey_getKeyCacheNomePorta(pa.getNome());
		if(filterResult!=null) {
			if(filterResult.isByFilter()) {
				keyCache = keyCache +" [Conditional-By-Filter] ";
    		}
			else {
				keyCache = keyCache +" [Conditional-By-Name] ";
			}
			if(filterResult.getRegola()!=null) {
				keyCache = keyCache +"[Regola "+filterResult.getRegola()+"] ";
			}
			if(filterResult.getCondition()!=null) {
				keyCache = keyCache +" "+filterResult.getCondition();
			}
		}
		
		return keyCache;
		
	}
	
	private static LoadBalancerPool getLoadBalancerPool(PortaApplicativa pa, OpenSPCoop2Message message, Busta busta, 
			RequestInfo requestInfo, PdDContext pddContext, 
			MsgDiagnostico msgDiag, Logger log,
			String keyCache, ConditionalFilterResult filterResult) throws BehaviourException, BehaviourEmitDiagnosticException{
    	
    	if(GestoreLoadBalancerCaching.cache==null){
    		throw new BehaviourException("La funzionalità di Load Balancer richiede che sia abilitata la cache dedicata alla funzionalità");
		}
    	else{
    		
    		// Fix: devo prima verificare se ho la chiave in cache prima di mettermi in sincronizzazione.
    		org.openspcoop2.utils.cache.CacheResponse response = 
				(org.openspcoop2.utils.cache.CacheResponse) GestoreLoadBalancerCaching.cache.get(keyCache);
			if(response != null){
				if(response.getObject()!=null){
					log.debug("Oggetto (tipo:"+response.getObject().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
					return (LoadBalancerPool) response.getObject();
				}else if(response.getException()!=null){
					log.debug("Eccezione (tipo:"+response.getException().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
					throw new BehaviourException( (Exception) response.getException() );
				}else{
					log.error("In cache non e' presente ne un oggetto ne un'eccezione.");
				}
			}
    		
			String idTransazione = (pddContext!=null && pddContext.containsKey(org.openspcoop2.core.constants.Costanti.ID_TRANSAZIONE)) ? PdDContext.getValue(org.openspcoop2.core.constants.Costanti.ID_TRANSAZIONE, pddContext) : null;
			//synchronized (GestoreLoadBalancerCaching.cache) {
			GestoreLoadBalancerCaching.lock.acquireThrowRuntime("getLoadBalancerPool", idTransazione);
			try {
				
				response = 
					(org.openspcoop2.utils.cache.CacheResponse) GestoreLoadBalancerCaching.cache.get(keyCache);
				if(response != null){
					if(response.getObject()!=null){
						log.debug("Oggetto (tipo:"+response.getObject().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
						return (LoadBalancerPool) response.getObject();
					}else if(response.getException()!=null){
						log.debug("Eccezione (tipo:"+response.getException().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
						throw new BehaviourException( (Exception) response.getException() );
					}else{
						log.error("In cache non e' presente ne un oggetto ne un'eccezione.");
					}
				}

				// Effettuo la query
				log.debug("oggetto con chiave ["+keyCache+"] (method:getLoadBalancerPool) ricerco nella configurazione...");
				LoadBalancerPool lbPool = readLoadBalancerPool(log, pa, filterResult);
				
				// Aggiungo la risposta in cache (se esiste una cache)	
				// Sempre. Se la risposta non deve essere cachata l'implementazione può in alternativa:
				// - impostare una eccezione di processamento (che setta automaticamente noCache a true)
				// - impostare il noCache a true
				if(lbPool!=null){
					log.info("Aggiungo oggetto ["+keyCache+"] in cache");
					try{	
						org.openspcoop2.utils.cache.CacheResponse responseCache = new org.openspcoop2.utils.cache.CacheResponse();
						responseCache.setObject(lbPool);
						GestoreLoadBalancerCaching.cache.put(keyCache,responseCache);
					}catch(UtilsException e){
						log.error("Errore durante l'inserimento in cache ["+keyCache+"]: "+e.getMessage());
					}
					return lbPool;
				}else{
					throw new BehaviourException("Metodo (getLoadBalancerPool) non è riuscito a costruire un pool");
				}
			}finally {
				GestoreLoadBalancerCaching.lock.release("getLoadBalancerPool", idTransazione);
			}
    	}
    	
    }
	
	
	private static LoadBalancerPool readLoadBalancerPool(Logger log, PortaApplicativa pa, ConditionalFilterResult filterResult) throws BehaviourException {
		
		List<PortaApplicativaServizioApplicativo> listSA = null;
		if(filterResult==null) {
			listSA = pa.getServizioApplicativoList();
		}
		else {
			listSA = filterResult.getListServiziApplicativi();
		}
		
		LoadBalancerPool pool = new LoadBalancerPool(HealthCheckUtils.read(pa, log));
		if(!listSA.isEmpty()) {
			for (PortaApplicativaServizioApplicativo servizioApplicativo : listSA) {
				if(servizioApplicativo.getDatiConnettore()==null || servizioApplicativo.getDatiConnettore().getStato()==null || 
						StatoFunzionalita.ABILITATO.equals(servizioApplicativo.getDatiConnettore().getStato())) {
					int weight = -1;
					if(servizioApplicativo.getDatiConnettore()!=null && servizioApplicativo.getDatiConnettore().sizeProprietaList()>0) {
						String weightDefined = null;
						for (Proprieta p : servizioApplicativo.getDatiConnettore().getProprietaList()) {
							if(Costanti.LOAD_BALANCER_WEIGHT.equals(p.getNome())) {
								weightDefined = p.getValore();
							}
						}
						if(weightDefined!=null) {
							try {
								weight = Integer.valueOf(weightDefined);
							}catch(Exception e) {}
						}
					}
					String nomeConnettore = null;
					if(servizioApplicativo.getDatiConnettore()!=null) {
						nomeConnettore = servizioApplicativo.getDatiConnettore().getNome();
					}
					if(nomeConnettore==null) {
						nomeConnettore = org.openspcoop2.pdd.core.behaviour.built_in.Costanti.NOME_CONNETTORE_DEFAULT;
					}
					if(weight>0) {
						pool.addConnector(nomeConnettore, weight);
					}
					else {
						pool.addConnector(nomeConnettore);
					}
				}
			}
		}
		
		return pool;
	}
	
	private static String getNomeConnettore(PortaApplicativa pa, OpenSPCoop2Message message, Busta busta, 
			RequestInfo requestInfo, PdDContext pddContext, 
			MsgDiagnostico msgDiag, Logger log,
			String keyCache, LoadBalancerPool loadBalancerPool, LoadBalancerType loadBalancerType, StickyResult stickyResult) throws BehaviourException, BehaviourEmitDiagnosticException{
    	
    	if(GestoreLoadBalancerCaching.cache==null){
    		throw new BehaviourException("La funzionalità di Load Balancer richiede che sia abilitata la cache dedicata alla funzionalità");
		}
    	else{
    		
    		// Fix: devo prima verificare se ho la chiave in cache prima di mettermi in sincronizzazione.
    		
    		org.openspcoop2.utils.cache.CacheResponse response = 
				(org.openspcoop2.utils.cache.CacheResponse) GestoreLoadBalancerCaching.cache.get(keyCache);
			if(response != null){
				if(response.getObject()!=null){
					log.debug("Oggetto (tipo:"+response.getObject().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
					
					StickyConnector stickyConnector = (StickyConnector) response.getObject();
					if(stickyConnector.getExpirationDate()!=null) {
						Date now = DateManager.getDate();
						if(stickyConnector.getExpirationDate().after(now)){
							String c = stickyConnector.getConnector();
							if(loadBalancerPool.getConnectorNames(true).contains(c)) {
								return c;
							}
							else {
								// eseguo operazione in synchronized mode
							}
						}
						else {
							// eseguo operazione in synchronized mode
						}
					}
					else {
						String c = stickyConnector.getConnector();
						if(loadBalancerPool.getConnectorNames(true).contains(c)) {
							return c;
						}
						else {
							// eseguo operazione in synchronized mode
						}
					}
					
				}else if(response.getException()!=null){
					log.debug("Eccezione (tipo:"+response.getException().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
					throw new BehaviourException( (Exception) response.getException() );
				}else{
					log.error("In cache non e' presente ne un oggetto ne un'eccezione.");
				}
			}
    		
			String idTransazione = (pddContext!=null && pddContext.containsKey(org.openspcoop2.core.constants.Costanti.ID_TRANSAZIONE)) ? PdDContext.getValue(org.openspcoop2.core.constants.Costanti.ID_TRANSAZIONE, pddContext) : null;
			//synchronized (GestoreLoadBalancerCaching.cache) {
			GestoreLoadBalancerCaching.lock.acquireThrowRuntime("getNomeConnettore", idTransazione);
			try {
				
				response = 
					(org.openspcoop2.utils.cache.CacheResponse) GestoreLoadBalancerCaching.cache.get(keyCache);
				if(response != null){
					if(response.getObject()!=null){
						log.debug("Oggetto (tipo:"+response.getObject().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
						
						StickyConnector stickyConnector = (StickyConnector) response.getObject();
						if(stickyConnector.getExpirationDate()!=null) {
							Date now = DateManager.getDate();
							if(stickyConnector.getExpirationDate().after(now)){
								String c = stickyConnector.getConnector();
								if(loadBalancerPool.getConnectorNames(true).contains(c)) {
									return c;
								}
								else {
									String msg = "Connettore '"+stickyConnector.getConnector()+"' per sticky '"+stickyResult.getCondition()+"' non risulta più valido dopo il passive health check";
									log.debug(msg);
									try {
										GestoreLoadBalancerCaching.cache.remove(keyCache);
									}catch(Exception e) {
										throw new BehaviourException(msg+". Rimozione dalla cache non riuscita: "+e.getMessage(),e);
									}
								}
							}
							else {
								String msg = "Connettore '"+stickyConnector.getConnector()+"' scaduto per sticky '"+stickyResult.getCondition()+"' in data "+
										DateUtils.getSimpleDateFormatMs().format(stickyConnector.getExpirationDate())+"";
								log.debug(msg);
								try {
									GestoreLoadBalancerCaching.cache.remove(keyCache);
								}catch(Exception e) {
									throw new BehaviourException(msg+". Rimozione dalla cache non riuscita: "+e.getMessage(),e);
								}
							}
						}
						else {
							String c = stickyConnector.getConnector();
							if(loadBalancerPool.getConnectorNames(true).contains(c)) {
								return c;
							}
							else {
								String msg = "Connettore '"+stickyConnector.getConnector()+"' per sticky '"+stickyResult.getCondition()+"' non risulta più valido dopo il passive health check";
								log.debug(msg);
								try {
									GestoreLoadBalancerCaching.cache.remove(keyCache);
								}catch(Exception e) {
									throw new BehaviourException(msg+". Rimozione dalla cache non riuscita: "+e.getMessage(),e);
								}
							}
						}
						
					}else if(response.getException()!=null){
						log.debug("Eccezione (tipo:"+response.getException().getClass().getName()+") con chiave ["+keyCache+"] (method:getLoadBalancerPool) in cache.");
						throw new BehaviourException( (Exception) response.getException() );
					}else{
						log.error("In cache non e' presente ne un oggetto ne un'eccezione.");
					}
				}

				// Effettuo la query
				log.debug("oggetto con chiave ["+keyCache+"] (method:getNomeConnettore) ricerco nella configurazione...");
				String selectedConnector = selectConnector(loadBalancerType, loadBalancerPool, pddContext);
				if(selectedConnector==null) {
					throw new BehaviourException("Metodo (getNomeConnettore) non è riuscito a selezionare un connettore");
				}
				StickyConnector stickyConnector = new StickyConnector();
				stickyConnector.setConnector(selectedConnector);
				if(stickyResult.getMaxAgeSeconds()!=null && stickyResult.getMaxAgeSeconds().intValue()>0) {
					Date expire = new Date((DateManager.getTimeMillis() + (1000*stickyResult.getMaxAgeSeconds().intValue())));
					stickyConnector.setExpirationDate(expire);
				}
				
				// Aggiungo la risposta in cache (se esiste una cache)	
				// Sempre. Se la risposta non deve essere cachata l'implementazione può in alternativa:
				// - impostare una eccezione di processamento (che setta automaticamente noCache a true)
				// - impostare il noCache a true
				log.info("Aggiungo oggetto ["+keyCache+"] in cache");
				try{	
					org.openspcoop2.utils.cache.CacheResponse responseCache = new org.openspcoop2.utils.cache.CacheResponse();
					responseCache.setObject(stickyConnector);
					GestoreLoadBalancerCaching.cache.put(keyCache,responseCache);
				}catch(UtilsException e){
					log.error("Errore durante l'inserimento in cache ["+keyCache+"]: "+e.getMessage());
				}
				return stickyConnector.getConnector();

			}finally {
				GestoreLoadBalancerCaching.lock.release("getNomeConnettore", idTransazione);
			}
    	}
    	
    }

	private static String selectConnector(LoadBalancerType loadBalancerType, LoadBalancerPool pool, PdDContext pddContext) throws BehaviourException {
		LoadBalancer lb = new LoadBalancer(loadBalancerType, pool, pddContext);
		String nomeConnettore = lb.selectConnector();
		if(nomeConnettore==null) {
			throw new BehaviourException("Nessun connettore selezionato");
		}
		return nomeConnettore;
	}
}