package es.ejie.frmk.infraestructura.traza;

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

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.Q70FactoriaConstantes;
import es.ejie.frmk.control.excepciones.Q70FrameworkException;
import es.ejie.frmk.control.excepciones.Q70GestorDeExcepcionesServidor;
import es.ejie.frmk.infraestructura.logs.base.Q70ConfiguracionLog;
import es.ejie.frmk.infraestructura.logs.base.Q70ConstantesLogs;
import es.ejie.frmk.infraestructura.logs.base.Q70LogInterno;
import es.ejie.frmk.infraestructura.logs.base.Q70Mensaje;
import es.ejie.frmk.infraestructura.logs.base.impl.Q70FactoriaLogImpl;
import es.ejie.frmk.infraestructura.logs.base.impl.Q70GestorDatosComunesImpl;
import es.ejie.frmk.infraestructura.logs.base.interfaces.Q70GestorDatosComunes;
import es.ejie.frmk.infraestructura.logs.base.interfaces.Q70Log;
import es.ejie.frmk.infraestructura.logs.base.interfaces.Q70LogFactory;
import es.ejie.frmk.infraestructura.utilidades.concurrencia.Q70Lock;

/**
 * El <b>Gestor de Trazas</b> es un componente que permite grabar información técnica.
 * <p> 
 * Esta información es acerca de los pasos por los que va pasando Geremua y las aplicaciones, 
 * con el objetivo de poder detectar posibles situaciones problemáticas durante la ejecución.
 * Cuando se realiza una invocación a este servicio para la grabación de una cierta información 
 * se debe especificar un nivel de severidad (información, aviso, error, error grave, ...).
 * Todos los componentes de Geremua realizan llamadas a este servicio en puntos estratégicos 
 * de su ejecución incluyendo la información adecuada para la posible identificación de los errores. 
 * Asimismo se deja la posibilidad a las aplicaciones a que efectúen sus propias trazas, 
 * muy útiles durante el período de desarrollo / pruebas.
 * <p>
 * Es un servicio que en producción se activará únicamente en situaciones puntuales 
 * y proporciona una traza condicionada discriminando por tipo de operación, 
 * código de usuario o nivel de severidad.
 * <p>
 * La traza se realiza si se cumplen un conjunto de condiciones booleanas 
 * que pueden englobar (con “and” y “or”) alguno de los conceptos siguientes:
 * <p>
 * <ul>
 * <li>Nivel de severidad
 * <li>Componente que origina la traza
 * <li>Código de usuario
 * <li>Aplicación
 * <li>Operación 
 * <li>Código de Sesión
 * <li>Versión de Navegador
 * <li>…
 * </ul>
 * <p>
 * Su implementación está basada el componente de logs, 
 * es decir, es un caso particular de log al que se le incorpora un mecanismo que permite interrogar 
 * al componente en tiempo de ejecución si para unas condiciones dadas 
 * (severidad, usuario, entorno, etc.) el componente está activado como traceable, 
 * en cuyo caso se construirían los parámetros necesarios para reflejarse en la traza, 
 * pero que si fuera el caso contrario, no se daría esta construcción. 
 * Esto se hace definiendo la condición de traceo en el fichero de configuración.
 *
 * @author UFRM004
 */
public abstract class Q70TrazaBase implements Q70TrazaServidorInterface
{
	static final String componente = "TRAZAS ";
	private Q70LogFactory logFactory = new Q70FactoriaLogImpl();
	private Q70GestorDatosComunes commonDataManager = new Q70GestorDatosComunesImpl();
	private Q70Log log;
	private Q70ConfiguracionLog conf = null;
	private ArrayList postFixCondition;

	private Q70Lock lock = null;

	/**
	 * @return Returns the lock.
	 */
	protected Q70Lock getLock() {
		return lock;
	}
	
	/**
	 * @param lock The lock to set.
	 */
	protected void setLock(Q70Lock lock) {
		this.lock = lock;
	}
	
	/**
	* Proporciona acceso a los datos comunes que comparten los logs (id_usuario, id_sesion...)
	* @return la instancia del Q70GestorDatosComunes
	*/
	public Q70GestorDatosComunes getCommonDataManager()
	{
		getLock().entradaZonaCritica();		
		try {
			return commonDataManager;			
		} catch (RuntimeException e) {
			throw e;
		} finally {
			getLock().salidaZonaCritica();
		}
	}
	
	/**
	 * Se envían los mensajes de logs que queden pendientes por envíar antes de liberar los recursos 
	 */
	protected void flushPendingMessages()
	{
		log.flush();	
	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#inicializar()
	 */
	public void inicializar() throws Q70FrameworkException
	{
		if (!getLock().isBloqueo()) {
			throw new Q70FrameworkException(componente,this.getClass().toString(),"inicializar","Se ha entrado por un método que necesita que el bloqueo del componente esté activo.",null);
		}
		if (Q70LogInterno.isEnabled())
			Q70LogInterno.log("inicializar called: " + "\n        configuracion= " + getConfiguracionLog());

		try {
			log = logFactory.getLog(getNombreTraza(getConfiguracionLog()), getConfiguracionLog(), commonDataManager, getLock());
			parseCondition();
		} catch (Q70FrameworkException e) {
			throw new Q70FrameworkException(Q70ConstantesLogs.TRAZA,this.getClass().toString(),"inicializar","Se ha producido un error en el método inicializar.",e);		
		}
	}

	/**
	 * Obtiene el nombre de la traza
	 * @param configuracionLog
	 * @return String
	 */
	protected String getNombreTraza(Q70ConfiguracionLog configuracionLog) {
		return Q70ConstantesLogs.TRAZA + configuracionLog.getProperty(Q70ConstantesLogs.CODIGO_APLICACION);
	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#liberar()
	 */
	public void liberar() throws Q70FrameworkException
	{
		if (!getLock().isBloqueo()) {
			throw new Q70FrameworkException(Q70FactoriaConstantes.ID_GESTOR_TRAZA,this.getClass().toString(),"liberar","Se ha entrado por un método que necesita que el bloqueo del componente esté activo.",null);
		}
		//Se envían/escriben los mensajes de logs pendientes de escribir en el fichero de logs
		flushPendingMessages();
		
		if (Q70LogInterno.isEnabled())
			Q70LogInterno.log("liberar called");
	}
	
	protected abstract void doDestruir();

	/**
	 * Evalúa la condición 
	 * @param componente nombre del componente
	 * @param nivel nivel
	 * @return boolean
	 * @throws Q70FrameworkException
	 */
	private boolean evaluateCondition(String componente, String nivel) throws Q70FrameworkException
	{
		//agilizar la condición vacia
		if (postFixCondition == null || postFixCondition.size() == 0)
			return true;

		Q70EvaluadorCondicion evaluator = new Q70EvaluadorCondicion();
		HashMap variables = new HashMap();
		Map global = commonDataManager.getDataMap();
		if (global != null)
			variables.putAll(global);
		variables.put(Q70ConstantesLogs.COMPONENTE, componente);
		variables.put(Q70ConstantesLogs.NIVEL, nivel);

		evaluator.setVariables(variables);
		try
		{
			return evaluator.evaluateBoolExpr(postFixCondition);

		}
		catch (Q70ExcepcionEvaluadorCondicion e)
		{
			if (Q70LogInterno.isEnabled())
				Q70LogInterno.log("Error al evaluar la expresión de activación. " + e.getMessage());
			
			throw new Q70FrameworkException(Q70ConstantesLogs.TRAZA,this.getClass().toString(),"evaluateCondition","Se ha producido un error en el método evaluateCondition.",e);
		}
	}
	/**
	 * Parsea la condición
	 * @throws Q70FrameworkException
	 */
	private void parseCondition() throws Q70FrameworkException
	{
		Q70AnalizadorCondicion parser = new Q70AnalizadorCondicion();
		String condicion = getConfiguracionLog().getProperty("CONDICION");
		parser.setCondition(condicion);
		String attributes = getConfiguracionLog().getProperty("ATRIBUTOS_GENERICOS").trim();
		if (!attributes.equals(""))
			attributes += ",";
		attributes += "COMPONENTE,NIVEL";
		parser.setVariableNames(attributes);
		try
		{
			parser.parseBoolExpr();
			postFixCondition = parser.getParsedCondition();
		}
		catch (Q70ExcepcionAnalizadorCondicion e)
		{
			Q70LogInterno.log(e.getMessage());
			throw new Q70FrameworkException(Q70ConstantesLogs.TRAZA,this.getClass().toString(),"parseCondition","Se ha producido un error en el método parseCondition.",e);
		}
	}

/**
 * Devuelve true si la traza está activada.
 * @return boolean
 */
	public boolean isTraceActive()
	{
		if (null == log) {
			System.err.println("Error. El servicio de traza no está disponible. Debe configurarse antes de ser utilizado.");
		} else {
			return log.isEnabled();
		}
		return false;
	}
/**
 * Escribe una traza.
 * <p>
 * Escribe en la salida de la traza la información recogida en los datos genericos 
 * definidos en el fichero XML de configuración y los pasados como parámetros. 
 * Antes de realizar la escritura, se comprueba la condición y que el nivel esté abierto.
 * <p>
 * Posibles valores de nivel de traza:
 * <ul>
 * <li>Q70TraceLevel.DEBUG
 * <li>Q70TraceLevel.WARN
 * <li>Q70TraceLevel.INFO
 * <li>Q70TraceLevel.ERROR
 * <li>Q70TraceLevel.FATAL
 * </ul>
 * <p>
 * El modo de actuar es el siguiente:
 * <p>
 * <b>
 *	private static Traza traza = Traza.getSingleton();<br>
 *	if (traza.isTraceActive())<br>
 *  &nbsp;&nbsp;&nbsp;&nbsp;traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.DEBUG,"texto");
 * </b>
 * <p>
 * De este modo se mejora el rendimiento para casos en que la traza no debe estar habilitada, 
 * porque la máquina virtual se ahorra trabajar en la construcción de los parámetros.
 *
 * @param componente
 * @param nivel
 * @param mensaje
 */
	public void trace(String componente, Q70TraceLevel nivel, String mensaje)
	{
		if (null==nivel)
		{
			nivel=Q70TraceLevel.DEBUG;
		}
		if (log.isEnabled())
		{
			try {
				if (evaluateCondition(componente, nivel.toString()))
				{
					HashMap map = new Q70Mensaje();
					map.put(Q70ConstantesLogs.COMPONENTE, componente);
					map.put(Q70ConstantesLogs.NIVEL, nivel.toString());
					map.put(Q70ConstantesLogs.MENSAJE, mensaje);
					log.log(map);
				}
			} catch (Exception e) {
				System.out.println("Se ha producido un error en el método trace de la clase Q70TrazaBase.La excepción es: "+ e.getMessage());
				Q70GestorDeExcepcionesServidor.getSingleton().recogerThrowable(new Q70FrameworkException(Q70ConstantesLogs.TRAZA,this.getClass().toString(),"trace","Se ha producido un error en el método trace.",e));
			}
		}
	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#activar()
	 */
	public void activar() {
		getLock().activar();
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#suspender()
	 */
	public void suspender() throws Q70FrameworkException{	
		try {
			getLock().suspender();
		} catch (Q70FrameworkException e) {
			Q70LogInterno.log("\nSe ha producido un error en el método suspender de la clase Q70TrazaBase.\n");
			throw new Q70FrameworkException(Q70ConstantesLogs.TRAZA,this.getClass().toString(),"suspender","Se ha producido un error en el método suspender.",e);
		}
	}

	/**
	 * @see es.ejie.frmk.infraestructura.utilidades.configurador.Q70Administrable#destruir()
	 */
	public void destruir() throws Q70FrameworkException{
		try {
			liberar();
			doDestruir();
			activar();
		} catch (Throwable e) {
 			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(componente, Q70TraceLevel.ERROR, "Se ha producido un error en el método destruir de la clase Q70TrazaBase");
			}
			throw new Q70FrameworkException(componente,this.getClass().toString(),"destruir","Se ha producido un error en el método destruir.",e);
		}
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.traza.Q70TrazaServidorInterface#getConfiguracionLog()
	 */
	public Q70ConfiguracionLog getConfiguracionLog(){
		return conf;	
	}
	
	/**
	 * @see es.ejie.frmk.infraestructura.traza.Q70TrazaServidorInterface#setConfiguracionLog(es.ejie.frmk.infraestructura.logs.base.Q70ConfiguracionLog)
	 */
	public void setConfiguracionLog(Q70ConfiguracionLog confLog) throws Q70FrameworkException{
		if (!getLock().isBloqueo()) {
			throw new Q70FrameworkException(Q70FactoriaConstantes.ID_GESTOR_TRAZA,this.getClass().toString(),"setConfiguracionLog","Se ha entrado por un método que necesita que el bloqueo del componente esté activo.",null);
		}
		conf = confLog;
	}

}
