package es.ejie.frmk.negocio.adaptadorinvocacion;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;

import javax.ejb.EJBException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.UserTransaction;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.infraestructura.logs.base.Q70ConstantesLogs;
import es.ejie.frmk.infraestructura.logs.transacciones.Q70LogTransaccionesInterface;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;
import es.ejie.frmk.negocio.operacioninterna.Q70ConstantesOI;

/** 
 * La clase proporciona un acceso a los componentes de la capa de negocio (EJBs)
 * unificado, rápido y monitorizado a través del Log de Transacciones.
 * <p>
 * Debe ser utilizada desde las Operaciones Internas de tal modo que el
 * desarrollador de aplicaciones sobre Geremua no la utilizará
 * directamente, sino que será el propio Geremua el que invocará este clase
 * desde <code>Q70OperacionInterna</code>. Los componentes de negocio
 * invocados por el adaptador son implementados como EJBs de Sesión sin estado
 * siguiendo el patrón <b>Fachada de Sesión</b>.
 */
public class Q70AdaptadorInvocacion {
	
	public static final String COMPONENTE = "ADAPTADOR_INVOCACION";
	public static final String JNDI_TX = "java:comp/UserTransaction";

	/**
	 * @param ejbName
	 * @return
	 * @throws NamingException
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */

	private static Object createEJB(String ejbName, String initialCtxFactory,String providerURL)
			throws 
			NamingException,
			SecurityException,
			NoSuchMethodException,
			IllegalArgumentException,
			IllegalAccessException,
			InvocationTargetException {

			Method m = null;
			Object home = null;
			Q70TrazaInterface traza = Q70Factoria.getTraza();
		
			// Analizamos si la invocación es local o remota para obtener una
			// home u otra
			if ((initialCtxFactory == null) && (providerURL == null)) {
				if (traza.isTraceActive()) {
					traza.trace(COMPONENTE,Q70TraceLevel.DEBUG,"Se ha realizado una invocación local al siguiente EJB: "+ ejbName);
				}
				// Se trata de una invocación local
				home = Q70FactoriaEJBHome.getSingleton().getHome(ejbName);
			} else {
				if (traza.isTraceActive()) {
					traza.trace(COMPONENTE,Q70TraceLevel.DEBUG,"Se ha realizado una invocación remota al siguiente EJB: "+ initialCtxFactory +" "+ providerURL	+" "+ ejbName);
				}
				// Se trata de una invocación remota
				home = Q70FactoriaEJBHome.getSingleton().getRemoteHome(ejbName,	initialCtxFactory, providerURL);
			}
			// invocar al método create dinámicamente.
			m = home.getClass().getMethod("create", new Class[] {});

			return m.invoke(home, new Object[] {});

	}

	private static String buildParametersString(Object[] args) {
		// pasar los parámetros a un string
		StringBuffer paramSB = null;
		paramSB = new StringBuffer();
		if (args != null && args.length > 0) {
			for (int i = 0; i < args.length; i++) {
				paramSB.append(args[i].toString());
				if (!(i == args.length - 1))
					paramSB.append(" - ");
			}
		} else {
			return null;
		}
		return paramSB.toString();
	}

	/**
	 * El método registra el comienzo y fin de la transacción invocando un
	 * método de un EJB.
	 * <p>
	 * Dado el nombre de un EJB, el método a invocar, los valores de sus
	 * argumentos y las clases de los mismos, devuelve el resultado de la
	 * invocación al método del EJB. Únicamente permite la invocación a EJBs que
	 * se encuentran desplegados en la misma instanca de servidor de
	 * aplicaciones.
	 * <p>
	 * El proceso detallado es el siguiente:
	 * <UL>
	 * <li>Localización del EJB</li>
	 * <li>Loggear la transacción antes de realizarla en Log de Transacciones</li>
	 * <li>Abrir una transacción desde el cliente con JTA</li>
	 * <li>Invocar el método en el EJB por reflection</li>
	 * <li>Verificar el estado de la transacción (JTA - status)</li>
	 * <li>Finalizar la transacción (JTA - commit/rollback)</li>
	 * <li>Loggear despues de realizar la transacción en Log de Transacciones</li>
	 * </LU>
	 * <p>
	 * 
	 * @param ejbName Nombre del EJB a invocar
	 * @param method Método del EJB a invocar
	 * @param args Array de objetos envíados como argumentos al
	 *            método del EJB a invocar
	 * @param argTypes Array de clases de los objetos envíados como
	 *            argumentos al método del EJB a invocar
	 * @return Objeto devuelto por el método del EJB a invocar
	 * 
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NamingException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */

	public static Object invokeFunction(String ejbName, String method,Object[] args, Class[] argTypes) 
			throws
			SecurityException,
			IllegalArgumentException,
			NamingException, 
			NoSuchMethodException,
			IllegalAccessException,
			InvocationTargetException {
		
		try {
			// El nuevo método invokeFunction de la versión 2.0, que contiene
			// la funcionalidad puede propagar un Throwable. Este método por
			// retrocompatibilidad con la versión 1.0 de Geremua, no puede
			// propagar un Throwable.
			return invokeFunction(ejbName, method, args, argTypes, null, null, Q70ConstantesOI.OI_EXCEPTION_NESTED, Q70ConstantesOI.OI_DEFAULT_VALUE_TRANSACTION);
		} catch (SecurityException e) {
			throw e;
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (NamingException e) {
			throw e;
		} catch (NoSuchMethodException e) {
			throw e;
		} catch (IllegalAccessException e) {
			throw e;
		} catch (InvocationTargetException e) {
			throw e;
		} catch (Throwable e) {
			// En caso de propagarse un Throwable, se propagará una
			// InvocationTargetException con el Throwable anidado.
			throw new InvocationTargetException(e);
		}
	}

	/**
	 * El método registra el comienzo y fin de la transacción invocando un
	 * método de un EJB.
	 * <p>
	 * Dado el nombre de un EJB, el método a invocar, los valores de sus
	 * argumentos y las clases de los mismos, devuelve el resultado de la
	 * invocación al método del EJB.
	 * <p>
	 * El proceso detallado es el siguiente:
	 * <UL>
	 * <li>Localización del EJB</li>
	 * <li>Loggear la transacción antes de realizarla en Log de Transacciones</li>
	 * <li>Abrir una transacción desde el cliente con JTA</li>
	 * <li>Invocar el método en el EJB por reflection</li>
	 * <li>Verificar el estado de la transacción (JTA - status)</li>
	 * <li>Finalizar la transacción (JTA - commit/rollback)</li>
	 * <li>Loggear despues de realizar la transacción en Log de Transacciones</li>
	 * </LU>
	 * <p>
	 * 
	 * @param ejbName Nombre del EJB a invocar
	 * @param method Método del EJB a invocar
	 * @param args Array de objetos envíados como argumentos al
	 *            método del EJB a invocar
	 * @param argTypes Array de clases de los objetos envíados como
	 *            argumentos al método del EJB a invocar
	 * @param initialCtxFactory InitialContextFactory o null en caso
	 *            de que no se haya definido
	 * @param providerURL ProviderURL o null en caso de que no se
	 *            haya definido
	 * @param oiExceptionType oiExceptionType o null en caso
	 *            de que no se haya definido
	 * @param oiTransaction oiTransaction o null en caso de que no se
	 *            haya definido            
	 * @return Objeto devuelto por el método del EJB a invocar
	 * 
	 * @throws SecurityException
	 * @throws IllegalArgumentException
	 * @throws NamingException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws Throwable
	 */
	public static Object invokeFunction(String ejbName, String method,Object[] args, Class[] argTypes, String initialCtxFactory,String providerURL,String oiExceptionType,String oiTransaction)
			throws 
			SecurityException,
			IllegalArgumentException,
			NamingException,
			NoSuchMethodException,
			IllegalAccessException,
			InvocationTargetException,
			Throwable {  
		
		Q70TrazaInterface traza = Q70Factoria.getTraza();
		Q70LogTransaccionesInterface logTransacciones = Q70Factoria.getLogTransacciones();
		
		// Parametros
		if (traza.isTraceActive()) {
			StringBuffer mensaje = new StringBuffer("EjbName: "); 
			mensaje.append(ejbName);
			mensaje.append("- method: ").append(method);
			if (args != null) mensaje.append("- args: ").append(args.toString());
			if (argTypes != null) mensaje.append("- argTypes: ").append(argTypes.toString());
			if (initialCtxFactory != null) mensaje.append("- initialCtxFactory: ").append(initialCtxFactory);
			if (providerURL != null) mensaje.append("- providerURL: ").append(providerURL);
			mensaje.append("- exceptionType: ").append(oiExceptionType);
			mensaje.append("- transaccion: ").append(oiTransaction);
				
			traza.trace(COMPONENTE, Q70TraceLevel.DEBUG, mensaje.toString());
		}  

		String paramsString = null;
		Object result = null;
		String resultadoTx = null;
		UserTransaction ut = null;
		boolean transaccionActiva = false;

		try {

			Object ejbObject = createEJB(ejbName, initialCtxFactory, providerURL);

			if (traza.isTraceActive()) {
				if (ejbObject != null)
					traza.trace(COMPONENTE, Q70TraceLevel.DEBUG,"Objeto creado de tipo "+ ejbObject.getClass().getName());
				else
					traza.trace(COMPONENTE, Q70TraceLevel.DEBUG,"Objeto NO creado");
			}
			 
			// Tengo que validar si el lookup es remoto
			/*if (providerURL != null) {
				java.util.Hashtable prop = new java.util.Hashtable();
				prop.put(Context.INITIAL_CONTEXT_FACTORY,initialCtxFactory);
				prop.put(Context.PROVIDER_URL, providerURL);

				c = new InitialContext(prop);
			}*/
		
			// Inciar la transaccion en el cliente
			if (oiTransaction.equalsIgnoreCase(Q70ConstantesOI.OI_TRANSACTION_ENABLED)) {
				Context c = new InitialContext();
				ut = (UserTransaction) c.lookup(JNDI_TX);
				if (ut.getStatus() == Status.STATUS_ACTIVE)
				    transaccionActiva = true;
			}
			
			
			paramsString = buildParametersString(args);
			
			if (oiTransaction.equalsIgnoreCase(Q70ConstantesOI.OI_TRANSACTION_ENABLED)) {
				if (logTransacciones.isEnabled()) {
					// Loggear antes de invocar al metodo
					logTransacciones.log(ejbName, method, paramsString, Q70ConstantesLogs.RESPUESTA_TX_OK, transaccionActiva?Q70ConstantesLogs.RESULTADO_TX_INITIATED:Q70ConstantesLogs.RESULTADO_TX_BEGIN, null);
				}
				if (!transaccionActiva){
					// Iniciamos la transaccion
				    ut.begin();
				}
			}

			// Invocar el metodo del Ejb
			result = Q70InvocadorMetodos.invoke(ejbObject, method, args,argTypes);
			
			// chekear el estado de la transacción y finalizarla.
			if (oiTransaction.equalsIgnoreCase(Q70ConstantesOI.OI_TRANSACTION_ENABLED)) {
				switch (ut.getStatus()) {
					case Status.STATUS_ACTIVE:
						if (!transaccionActiva){
							ut.commit();
						}
						resultadoTx = Q70ConstantesLogs.RESULTADO_TX_COMMIT;
						break;
					case Status.STATUS_MARKED_ROLLBACK:
						if (!transaccionActiva){
							ut.rollback();
						}
						resultadoTx = Q70ConstantesLogs.RESULTADO_TX_ROLLBACK;
						break;
				}
			}

		}catch (Throwable e) {
			// Si la transacción está habilitada, Loggear después de realizar la tx con error y hacemos rollback
			if (oiTransaction.equalsIgnoreCase(Q70ConstantesOI.OI_TRANSACTION_ENABLED)) {
				if (logTransacciones.isEnabled()){
					logTransacciones.log(ejbName, method, paramsString,	Q70ConstantesLogs.RESPUESTA_TX_ERROR, Q70ConstantesLogs.RESULTADO_TX_ROLLBACK, null);	
				}
				if (traza.isTraceActive()){
					traza.trace(COMPONENTE, Q70TraceLevel.ERROR,"Haciendo Rollback. EJB: "+ ejbName + " Método: " + method+" Parametros :"+paramsString);
				}
				try{
					if (!transaccionActiva && null!=ut) {
						if (!(e instanceof RollbackException)) {
							ut.rollback();
						}
					}
				// Capturamos una posible Excepción al hacer rollback
				} catch (Throwable e1) {
					if (logTransacciones.isEnabled()){
						logTransacciones.log(ejbName, method, paramsString,	Q70ConstantesLogs.RESPUESTA_TX_ERROR, Q70ConstantesLogs.RESULTADO_TX_NA, null);	
					}
					if (traza.isTraceActive()){
						traza.trace(COMPONENTE,Q70TraceLevel.ERROR,"ERROR haciendo Rollback." );
					}
					throw e1;
				}
			}
			// Controlamos que tipo de excepción hay que lanzar (anidada o simple)
			if (!oiExceptionType.equalsIgnoreCase(Q70ConstantesOI.OI_EXCEPTION_SIMPLE)) {
				// Excepción anidada
				throw e;
			}else{
				/// Excepción simple
				if (e instanceof Throwable) {
					Throwable t = ((Throwable) e);
					boolean esExcepcionQ70 = false;
					while (t != null && (t instanceof InvocationTargetException	|| t instanceof RemoteException || t instanceof EJBException) && !esExcepcionQ70) {
						if (t instanceof InvocationTargetException) {
							if (((InvocationTargetException) t).getTargetException() != null)
								t = ((InvocationTargetException) t).getTargetException();
							else
								esExcepcionQ70 = true;
						} else if (t instanceof RemoteException) {
							if (((RemoteException) t).getCause() != null)
								t = ((RemoteException) t).getCause();
							else	
								esExcepcionQ70 = true;
						} else if (t instanceof EJBException) {
							if (((EJBException) t).getCausedByException() != null)
								t = ((EJBException) t).getCausedByException();
							else
								esExcepcionQ70 = true;
						}
					}
					throw t;
				}
			}
		}
		
		// Si la transacción está habilitada, loggear después de realizar la transaccion sin ningun error
		String responseString = null;
		if (result != null)
			responseString = result.toString();
		if (oiTransaction.equalsIgnoreCase(Q70ConstantesOI.OI_TRANSACTION_ENABLED)) {
			if (logTransacciones.isEnabled())
				logTransacciones.log(ejbName, method, paramsString,	Q70ConstantesLogs.RESPUESTA_TX_OK, resultadoTx,	responseString);
		}
		// Logueamos si no se ha lanzado ninguna excepción
		if (traza.isTraceActive()) {
			traza.trace(COMPONENTE,Q70TraceLevel.DEBUG,"EJB: "+ ejbName + " Método: " + method + " Resultado: "+responseString);
		}
		
		// Devolucion del resultado
		return result;
	}

}
