package es.ejie.frmk.negocio.operacioninterna;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.infraestructura.logs.incidencias.Q70LogIncidenciasInterface;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;
import es.ejie.frmk.infraestructura.utilidades.mapeador.Q70GestorMapeador;
import es.ejie.frmk.negocio.adaptadorinvocacion.Q70AdaptadorInvocacion;
import es.ejie.frmk.presentacion.sesiondatos.Q70ContextoEJIE;

/**
 * La clase implementa la lógica del componente <b>Operación Interna</b>, permitiendo que las 
 * aplicaciones la extiendan y sobrecarguen sus métodos <b>hook</b> y así permitir añadir procesamiento extra.
 * <p> 
 * Su funcionalidad se resume a continuación:
 * <UL>
 * <li>Implementar cierta lógica impuesta por el Canal</li>
 * <li>Actuar como intermediario entre la Op. De Presentación y la Función de Negocio:</li>
 * <UL>
 * 	<li>Recoger los datos de entrada provenientes de la Operación de Presentación</li>
 * 	<li>Transformar ciertos datos de entrada</li>
 * 	<li>Invocar a una Función de Integración vía Adaptador de Invocaciones</li>
 * 	<li>Esperar síncronamente la respuesta del Adaptador de Invocaciones</li>
 * 	<li>Encapsular los resultados de salida del Adaptador de Invocaciones</li>
 * 	<li>Transformar ciertos datos de salida</li>
 * 	<li>Devolver los datos de salida a la Operación de Presentación, vía contexto</li>
 * 	<li>Devolver errores a la Operación de Presentación</li>
 *  <li>Procesar la Exception producida</li>
</UL>
</UL>
 */
public class Q70OperacionInterna extends Q70OperacionInternaBase {
	
	/**
	 * Etiqueta para errores de lógica de negocio.
	 */
	protected static final String OI_ERROR_KEY = Q70ConstantesOI.OI_ERROR_KEY;
	
	/**
	 * Constante que indica el componente al que pertenece dicha clase. Es
	 * necesario para el uso del log de trazas.
	 */
	private static final String COMPONENTE = "OPERACION_INTERNA";

	/**
	 * Este método procesa los pasos necesarios para preparar la invocación a una <b>Función de Integración</b> y
	 * devolver el objeto de negocio a la Operación de Presentación, depositándolo en el Contexto.
	 * <p>
	 * La secuencia de ejecución es la siguiente:
	 * <UL>
	 * <li>Pre hook para lógica antes del mapeo de entrada: <code>preProcessMappingEntrada()</code></li>
	 * <li>Mapeo de parámetros entrada: <code>processMappingEntrada()</code></li>
	 * <li>Post hook para lógica después del mapeo de entrada: <code>postProcessMappingEntrada()</code></li>
	 * <li>Llamada al Adaptador de Invocación: <code>processAdaptadorInvocacion()</code></li>
	 * <li>Pre hook para operaciones antes del mapeo de salida: <code>preProcessMappingSalida()</code></li>
	 * <li>Mapeo de parámetros de salida / de retorno: <code>processMappingSalida()</code></li>
	 * <li>Post hook para operaciones después del mapeo de parámetros de salida al contexto: <code>postProcessMappingSalida()</code></li>
	 * <li>Hook para procesar la Exception producida en el metodo processOI</li>
	 * </UL>
	 * <p>
	 * Si la funcionalidad demandada por la aplicación permite usar la propia clase <code>Q70OperacionInterna</code>
	 * para implementarla, los métodos hook se ejecutarán vacíos. Si la funcionalidad implica el uso de alguno de los hooks, éstos deberán ser sobrecargados por la clase que extendienda 
	 * a <code>Q70OperacionInterna</code>.
	 * 
	 * @param contexto Contexto genérico con acceso a toda la jerarquía de contextos del usuario  
	 * @return Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 */  
	public Q70OperacionInternaErrors processOI(Q70ContextoEJIE contexto)
	{
		
		Q70OperacionInternaErrors errores = null;
		try
		{
			errores = new Q70OperacionInternaErrors();
			Object resultado = null;
			//pre hook para operaciones antes del mapeo de entrada.
			preProcessMappingEntrada(contexto, getConfiguracion(), errores);
			//mapeo de parametros entrada
			Q70InformacionInvocacion informacionParametros = new Q70InformacionInvocacion();
			processMappingEntrada(informacionParametros, contexto, getConfiguracion().getFiMapaInvocacion(), errores);
			//post hook para completar otros mapeos
			postProcessMappingEntrada(informacionParametros, contexto, errores);
			
			//Llamada al Adaptador de invocación					
			resultado = processAdaptadorInvocacion (getConfiguracion().getFiNombre(), getConfiguracion().getFiMetodo(),
					informacionParametros.getParametros(), informacionParametros.getTipoParametros(), errores,
					getConfiguracion().getFiInitialContextFactory(), getConfiguracion().getFiProviderURL(),
					getConfiguracion().getOiExceptionType(), getConfiguracion().getOiTransaction());
		
			
			//pre hook para operaciones antes del mapeo de salida.
			preProcessMappingSalida(resultado, contexto, getConfiguracion(), errores);
			//mapeo de parametros de salida / de retorno.
			processMappingSalida(resultado, contexto, getConfiguracion().getFiMapaRetorno(), errores);
			//post hook paara operaciones después del mapeo de parametros de salida al contexto.
			postProcessMappingSalida(contexto, errores);
		}
		catch(Exception e)
		{
			// Logueamos la excepción que se ha producido
			Q70TrazaInterface tr = Q70Factoria.getTraza();
			Q70LogIncidenciasInterface logInc = Q70Factoria.getLogIncidencias();
			
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.WARN, "Excepción al procesar una Q70OperacionInterna");
			}
			//processOIException
			try {
				processOIException(contexto, e ,errores);
				if (logInc.isEnabled())
				{
					Q70OperacionInternaException operacionInternaException = new Q70OperacionInternaException(this.getClass().toString(),"processOI","Excepción al procesar una Q70OperacionInterna.",e);
					logInc.log(operacionInternaException, COMPONENTE, "WARN");
				}
			} catch (Exception e1) {
				//Logueamos la excepción que se ha producido sin detener la navegación
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción al procesar la excepción en el método processOIException.");
				}
				if (logInc.isEnabled())
				{
					Q70OperacionInternaException operacionInternaException = new Q70OperacionInternaException(this.getClass().toString(),"processOIException","Excepción al procesar la excepción en el hook.",e1);
					logInc.log(operacionInternaException, COMPONENTE, "ERROR");
				}
			}
		}
		return errores;
	}
	
	/**
	 * Método pre-hook para procesamiento previo al mapeo de entrada.
	 * <p>
	 * Si la aplicación necesita incluir lógica en este punto, extenderá esta clase y sobrecargará este método.
	 * 
	 * @param contexto Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param configuracion Información configurada con los datos a enviar al Adaptador de Invocación.
	 * @param errores Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @throws Exception
	 */
	protected void preProcessMappingEntrada (Q70ContextoEJIE contexto, Q70OperacionInternaConfigBean configuracion, Q70OperacionInternaErrors errores) throws Exception
	{
	}
	/**
	 * Método que realiza el mapeo de entrada a atributos necesarios para la Función de Integración.
	 * <p>
	 * Para ello mapea (transfiere y transforma) atributos entre el contexto de la Operación de Presentación
	 * y algunos de los objetos envíados como argumento al Adaptador de Invocación para que se invoque a una Función de
	 * Integración.
	 *  
	 * @param <code>informacionParametros</code> Instancia de <code>Q70InformacionInvocacion</code>
	 * @param <code>contexto</code> Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param <code>mapaInvocacion</code> Nombre del mapa a aplicar
	 * @param <code>errores</code> Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @throws <code>Exception</code>
	 */
	protected void processMappingEntrada (Q70InformacionInvocacion informacionParametros, Q70ContextoEJIE contexto, String mapaInvocacion, Q70OperacionInternaErrors errores) throws Exception
	{
		Q70TrazaInterface tr = Q70Factoria.getTraza();
		try {
			if ((null != mapaInvocacion) && (mapaInvocacion.trim().length() != 0))
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Realizando mapeo de entrada entre contexto de Op y adaptador de invocación.");
				}
				//Se hace el mapeo de entrada
				Q70GestorMapeador.getSingleton().mapObjects(mapaInvocacion, contexto, informacionParametros);
			}
			else
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, "No se ha definido mapeo de entrada entre contexto de Op y adaptador de invocación.");
				}
			}
		} catch (Exception e) {
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción procesando el mapeo de parametros del contexto a la operación interna.");
			}
			throw new Q70OperacionInternaException(this.getClass().toString(),"processMappingEntrada","Excepción procesando el mapeo de parametros del contexto a la operación interna.",e);
		}		
	}
	
	/**
	 * Método post-hook para procesamiento posterior al mapeo de entrada.
	 * <p>
	 * Si la aplicación necesita incluir lógica en este punto, extenderá esta clase y sobrecargará este método.
	 * @param informacionParametros Instancia de <code>Q70InformacionInvocacion</code>
	 * @param contexto Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param errores Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @throws Exception
	 */
	protected void postProcessMappingEntrada (Q70InformacionInvocacion informacionParametros, Q70ContextoEJIE contexto, Q70OperacionInternaErrors errores) throws Exception {
	}
	
	/**
	 * Método que utiliza el componente <b>Adaptador de Invocaciones</b> para invocar a una <b>Función
	 * de Integración</b>.
	 * <p>
	 * @param <code>ejbName</code> Nombre del EJB a invocar
	 * @param <code>method</code> Método del EJB a invocar
	 * @param <code>args</code> Array de objetos envíados como argumentos al método del EJB a invocar
	 * @param <code>argTypes</code> Array de clases de los objetos envíados como argumentos al método del EJB a invocar
	 * @param <code>errores</code> Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @param <code>initialCtxFactory</code> InitialContextFactory configurado en el fichero de configuración de la OI o null en caso de que no se haya definido
	 * @param <code>providerURL</code> ProviderURL configurado en el fichero de configuración de la OI o null en caso de que no se haya definido 
	 * @return <code>Object</code> Objeto devuelto por el método del EJB a invocar
	 * @throws <code>Exception</code>
	 */
	protected Object processAdaptadorInvocacion(String ejbName, String method, 
			Object [] args, Class [] argsTypes, Q70OperacionInternaErrors errores,
			String initialCtxFactory, String providerURL,String oiExceptionType,String oiTransaction) throws Exception
	{
		Object objectoNegocio = null;
		
		try
		{
			objectoNegocio = Q70AdaptadorInvocacion.invokeFunction(ejbName, method, args, argsTypes,initialCtxFactory,providerURL,oiExceptionType,oiTransaction);
			
			if (objectoNegocio instanceof Throwable) 
				throw new Q70OperacionInternaException(this.getClass().toString(),"processAdaptadorInvocacion","El método invocado por el Adaptador de Invocaciones no ha devuelto el objeto esperado.",(Throwable)objectoNegocio);
		} 
		catch (Throwable e)
		{ 
			Q70TrazaInterface tr = Q70Factoria.getTraza();
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción al llamar al Adaptador de Invocación.");
			}
			throw new Q70OperacionInternaException(this.getClass().toString(),"processAdaptadorInvocacion","Excepción al llamar al Adaptador de Invocación.",e);
		}
		
		return objectoNegocio;
	}
	
	/**
	 * Método pre-hook para procesamiento previo al mapeo de salida tras la respuesta de la Lógica de Negocio.
	 * <p>
	 * Si la aplicación necesita incluir lógica en este punto, extenderá esta clase y sobrecargará este método.
	 * 
	 * @param resultado Objeto devuelto por el Adaptador de Invocación desde la lógica de negocio
	 * @param contexto Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param configuracion Información configurada con los datos a enviar al Adaptador de Invocación.
	 * @param errores Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @throws Exception
	 */
	protected void preProcessMappingSalida (Object resultado, Q70ContextoEJIE contexto, Q70OperacionInternaConfigBean configuracion, Q70OperacionInternaErrors errores) throws Exception {
	}
	
	/**
	 * Método que realiza el mapeo de salida a atributos necesarios para la Operación de Presentación.
	 * <p>
	 * Para ello mapea (transfiere y transforma) atributos entre el objeto devuelto por el Adaptador de Invocación y el 
	 * contexto de la Operación de Presentación.
	 * 
	 * @param <code>resultado</code> Objeto devuelto por el Adaptador de Invocación desde la lógica de negocio
	 * @param <code>contexto</code> Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param <code>mapaRetorno</code> Nombre del mapa a aplicar
	 * @param <code>errores</code> Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @throws Exception</code>
	 */
	protected void processMappingSalida (Object resultado, Q70ContextoEJIE contexto, String mapaRetorno, Q70OperacionInternaErrors errores) throws Exception
	{
		Q70TrazaInterface tr = Q70Factoria.getTraza();
		try {
			if ((null != mapaRetorno) && (mapaRetorno.trim().length() != 0))
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Realizando mapeo de salida entre adaptador de invocación y contexto de Op.");
				}
				//Se hace el mapeo de salida
				Q70GestorMapeador.getSingleton().mapObjects(mapaRetorno, resultado, contexto);
			}
			else
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, "No se ha definido mapeo de salida entre adaptador de invocación y contexto de Op.");
				}
			}
		} catch (Exception e) {
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción procesando el mapeo de resultados de la operación interna al contexto.");
			}
			throw new Q70OperacionInternaException(this.getClass().toString(),"processMappingSalida","Excepción procesando el mapeo de resultados de la operación interna al contexto.",e);
		}
	}
	
	/**
	 * Método post-hook para procesamiento posterior al mapeo de salida.
	 * <p>
	 * Si la aplicación necesita incluir lógica en este punto, extenderá esta clase y sobrecargará este método.
	 * @param contexto Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param errores Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio.
	 * @throws Exception
	 */
	protected void postProcessMappingSalida (Q70ContextoEJIE contexto, Q70OperacionInternaErrors errores) throws Exception {
	}
	
	/**
	 * Método hook que permite procesar la Exception producida en el metodo processOI.
	 * <p>
	 * Si la aplicación necesita añadir funcionalidad, debera de extender de esta clase.
	 * </p>
	 * @param contexto Contexto genérico con acceso a toda la jerarquía de contextos del usuario
	 * @param exception Excepcion producida, que se va a tratar en este metodo
	 * @param errores Lista de errores de tipo <code>Q70OperacionInternaError</code> creados tras la respuesta de la lógica de negocio. 
	 * @throws Exception
	 */
	protected void processOIException(Q70ContextoEJIE contexto, Exception exception, Q70OperacionInternaErrors errores) throws Exception 
	{
		// Añadimos el error que verá el usuario en la página correspondiente.
		errores.add(OI_ERROR_KEY, new Q70OperacionInternaError(getConfiguracion().getOiErrorKey()));
	}
	
}
