package es.ejie.frmk.control.apilamiento;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import javax.naming.NamingException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.DynaBean;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.upload.MultipartRequestWrapper;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.conectores.conectorJDBC.Q70ConectorJDBC;
import es.ejie.frmk.control.arranque.Q70Arrancable;
import es.ejie.frmk.control.excepciones.Q70FrameworkException;
import es.ejie.frmk.control.excepciones.Q70GestorDeExcepciones;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70Traza;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;
import es.ejie.frmk.infraestructura.utilidades.constantes.Q70UtilidadesConstantes;
import es.ejie.frmk.infraestructura.utilidades.general.Q70ObjectInputStream;
import es.ejie.frmk.infraestructura.utilidades.mapeador.Q70GestorMapeador;
import es.ejie.frmk.listeners.base.Q70ListenerUtils;
import es.ejie.frmk.presentacion.base.Q70Constantes;
import es.ejie.frmk.presentacion.base.Q70PasosOPMapping;
import es.ejie.frmk.presentacion.sesiondatos.Q70ContextoEJIE;
import es.ejie.frmk.presentacion.sesiondatos.Q70SesionDatos;

/**
 * Clase que se encarga de apilar/desapilar contextos de operación de presentación.
 * <p> 
 * Se encarga de gestionar la pila de estructuras de diálogo (contexto e información adicional)
 * que se van generando en la ejecución de las interacciones con el usuario dentro de una misma sesión.
 * El Procesador de Operaciones de Presentación, por cada paso de Operación de Presentación, colabora con
 * esta clase para analizar si se trata de un paso que desencadena o acaba un nivel de apilamiento. 
 * Depende de que tipo se trate, realizará unas tareas de apilamiento/desapilamiento u otras.
 * Para ello averigua en qué punto sensible del flujo de ejecución se encuentra el usuario:
 * <ul>
 * <li>Salida de operación de presentación invocante hacia operación de presentación invocada</li>
 * <li>Entrada a operación de presentación invocada desde operación de presentación invocante</li>
 * <li>Salida de operación de presentación invocada hacia operación de presentación invocante</li>
 * <li>Entrada a operación de presentación invocante desde operación de presentación invocada</li>
 * </ul>
 * y delega en un método privado de procesamiento especializado en cada uno de las cuatro casuísticas
 * anteriores que apilarán/desapilarán contextos serializados y aplicarán mapas de transferencia 
 * entre contextos invocantes e invocados, según corresponda. 
 * A continuación devolverá el flujo al Procesador de Operaciones de Presentación. 
 * Si el paso de Operación de Presentación analizado no es ninguno de los casos antes citados 
 * (es decir, se trata de un paso intermedio cualquiera dentro del flujo normal de la operación invocada
 * o invocante), entonces no hará nada y devolverá el flujo al Procesador de Operaciones de Presentación
 * directamente.
 * <p>
 * Cuando se debe apilar un contexto de operación invocante, éste se serializa y se deposita 
 * en un objeto de tipo <code>Q70EstructuraDeDialogo</code>, que será añadido en la cima de la pila de 
 * contextos aparcados o apilados de la sesión de usuario. Cuando se da la casuística de que las operaciones 
 * de presentación invocante e invocada pertenecen a diferentes web-applications, como la sesión de usuario 
 * no se comparte, el objeto EstructuraDeDialogo se deposita en una tabla de base de datos bajo una clave única
 * generada a modo de <b>Hash</b> por el Gestor de Apilamiento y conocida por las dos Operaciones de Presentación.
 * En ambos casos (pila o tabla) se mantienen los contextos de las operaciones aparcadas por el usuario 
 * a la espera de ser reactivadas por el Gestor de Apilamiento. 
 * <p>
 * La clase implementa el patrón de singleton.
 * 
 * @version 1.0
 * Fecha: 15-dic-03
 */
public class Q70GestorDeApilamiento implements Q70Arrancable {
	
	private static final String SUBOPERACION_REMOTA = "subOperacionRemota";

	private static final String SUBOPERACION_LOCAL = "subOperacionLocal";

	private static final String REENVIO_REMOTO = "reenvioRemoto";

	private static final String REENVIO_LOCAL = "reenvioLocal";
	
	private static final String PREFIJO_REENVIO = "reenvio";
	
	private static final String PREFIJO_SUBOP = "subOperacion";
	
	private static final String HASH_CADUCADO = "hashCaducado";

	/**
	 * Constante que indica el componente al que pertenece dicha clase. Es
	 * necesario para el uso del log de trazas.
	 */
	private static final String COMPONENTE = "GESTOR_APILAMIENTO";
	
	/**
	 * Instancia del log de trazas
	 */
	private static Q70Traza tr = Q70Traza.getSingleton();
		
	/**
	 * Única instancia de la clase <code>Q70GestorDeApilamiento</code>
	 */
	private static Q70GestorDeApilamiento instance = null;
	
	/**
	 * Clave de pila de estructuras de dialogo de operaciones de presentación
	 * en <code>HttpSession</code>. 
	 */
	public static final String PILA_DIALOGO_KEY = "es.ejie.frmk.control.apilamiento.pila";
	
	/**
	 * Constructor privado necesario para ocultar el constructor por defecto.
	 */
	private Q70GestorDeApilamiento ()
	{
	}

	/**
	 * Método estático que devuelve la única instancia del
	 * <code>Q70GestorDeApilamiento</code>.
	 * 
	 * @return Única instancia del <code>Q70GestorDeApilamiento</code>.
	 */
	public static Q70GestorDeApilamiento getSingleton()
	{
		if (instance == null)
			instance = new Q70GestorDeApilamiento();
		return instance;
	}
	
	/**
	 * Método de acceso en lectura al datasource donde se serializa la info de apilamiento en BBDD
	 * @return dataSource Nombre del datasource
	 */
	private String getDataSource() {
		return Q70GestorDeApilamientoServidor.getSingleton().getDataSource();
	}

	/**
	 * Método de acceso en escritura al datasource donde se serializa la info de apilamiento en BBDD
	 * @param ds Nombre del datasource
	 */
	protected void setDataSource(String ds) {
		Q70GestorDeApilamientoServidor.getSingleton().setDataSource(ds);
	}
	
	/**
	 * Método de acceso en lectura al nombre de la tabla de BBDD donde se serializa la info de apilamiento
	 * @return Nombre de la tabla
	 */
	private String getTable() {
		return Q70GestorDeApilamientoServidor.getSingleton().getTable();
	}

	/**
	 * Método de acceso en escritura a la tabla de BBDD donde se serializa la info de apilamiento
	 * @param table Nombre de la tabla
	 */
	protected void setTable(String table) {
		Q70GestorDeApilamientoServidor.getSingleton().setTable(table);
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#afterCreate(java.lang.String)
	 */
	public void afterCreate(String file) throws Q70FrameworkException{
		try {
			Q70GestorDeApilamientoServidor.getSingleton().setXmlConfiguracion(file);
			Q70ConfiguradorApilamiento conf = new Q70ConfiguradorApilamiento(file);
			conf.lanzarConfigurador();
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método afterCreate de la clase Q70GestorDeApilamiento");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"afterCreate","Se ha producido un error en el método afterCreate.",e);
		}
		
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#beforeDestroy()
	 */
	public void beforeDestroy() {
	}

	/**
	 * @see es.ejie.frmk.control.arranque.Q70Arrancable#reConfigure()
	 */
	public void reConfigure() throws Q70FrameworkException{
		try {
			Q70GestorDeApilamientoServidor.getSingleton().setDataSource(null);
			afterCreate(Q70GestorDeApilamientoServidor.getSingleton().getXmlConfiguracion());
		} catch (Q70FrameworkException e) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			if (traza.isTraceActive()){
				traza.trace(COMPONENTE, Q70TraceLevel.ERROR, "Se ha producido un error en el método reConfigure de la clase Q70GestorDeApilamiento");
			}
			throw new Q70FrameworkException(COMPONENTE,this.getClass().toString(),"reConfigure","Se ha producido un error en el método reConfigure.",e);
		}
	}
	
	/**
	 * Método que desapila la estructura de la cima de la pila
	 * <p>
	 * La estructura de dialogo desapilada se corresponde a una
	 * superoperación (operación que invoca a una suboperación).
	 * 
	 * @param session Sesión de la operación correspondiente donde se encuentra la pila.
	 * @return Estructura de dialogo correspondiente a una superoperación
	 * (operación que invoca a una suboperación).
	 */
	protected Q70EstructuraDialogo popDialog(HttpSession session) {
		Stack pila = (Stack)session.getAttribute(PILA_DIALOGO_KEY);
		if ((null == pila) || (0 == pila.size()))
			return null;
		else
			return (Q70EstructuraDialogo)pila.pop();
	}
	
	/**
	 * Método que devuelve la estructura de la cima de la pila
	 * <p>
	 * Devuelve la estructura de dialogo correspondiente a una
	 * superoperación (operación que invoca a una suboperación) sin desapilarla.
	 * 
	 * @param session Sesión de la operación correspondiente donde se encuentra la pila.
	 * @return Estructura de dialogo correspondiente a una superoperación
	 * (operación que invoca a una suboperación).
	 */
	
	protected Q70EstructuraDialogo peekDialog(HttpSession session) {
		Stack pila = (Stack)session.getAttribute(PILA_DIALOGO_KEY);
		if ((null == pila) || (0 == pila.size()))
			return null;
		else
			return (Q70EstructuraDialogo)pila.peek();
	}
	
	/**
	 * Método que apila la estructura de dialogo correspondiente a una
	 * superoperación (operación que invoca a una suboperación).
	 * 
	 * @param session Sesión de la operación correspondiente donde se encuentra la
	 * pila.
	 * @param EstructuraDialogo que va a ser apliada,
	 * correspondiente a una superoperación (operación que invoca a una
	 * suboperación).
	 * @return Estructura de dialogo correspondiente a una superoperación
	 * (operación que invoca a una suboperación).
	 */
	protected Q70EstructuraDialogo pushDialog(HttpSession session, Q70EstructuraDialogo dialogo) {
		if (null != dialogo)
		{
			Stack pila = (Stack)session.getAttribute(PILA_DIALOGO_KEY);
			if (null == pila)
				pila = new Stack();
			pila.push(dialogo);
			session.setAttribute(PILA_DIALOGO_KEY, pila);
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "Apilando dialogo de operación. "+dialogo.toString());
			}
		}
		return dialogo;
	}

	/**
	 * Método que reinicializa la pila
	 * <p>
	 * Vacia la pila del Gestor de Apilamiento, recuperandóla de la sesión y reiniciándola. 
	 * @param session Sesión de la operación correspondiente donde se encuentra la
	 * pila.
	 */	
	protected void reset(HttpSession session) {
		Stack pila = (Stack)session.getAttribute(PILA_DIALOGO_KEY);
		if (null != pila)
		{
			pila.clear();
			pila = null;
			session.setAttribute(PILA_DIALOGO_KEY, pila);
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.INFO, "La pila del gestor de apilamiento ha sido reiniciada.");
			}
		}
	}
	
	/**
	 * Método que calcula si hay un retorno de una suboperación a una
	 * superoperación.
	 * <p>
	 * @param HttpServletRequest Petición a procesar
	 * @param subOp valor de la propiedad que indica el inicio/final de subOperación.
	 * @return true -> si hay retorno de una suboperación 
	 * @throws Q70FrameworkException
	 */
	public boolean isReturnToSuperOp(HttpServletRequest request, String subOp) throws Q70FrameworkException
	{
		boolean resultado = false;
		try {
			if (null != subOp && subOp.equalsIgnoreCase("end"))
			{
				Object estructuraDialogo = peekDialog(request.getSession());
				if (null != estructuraDialogo)
				{
					resultado = true;
				}
			}
		} catch (Exception e) {
			throw new Q70ApilamientoException(this.getClass().toString(),"isReturnToSuperOp","Error al comprobar si era un retorno de una suboperación.",e);
		}
		return resultado;
	}
	
	/**
	 * Este método procesa el punto final dentro de la sub-operación.
	 * <p>
	 * Procesará el retorno a la superOperación (op. invocante) si el
	 * paso de Operación de Presentación tratado presenta una propiedad de estado final de
	 * subOperacion (subOpEnd = "true") y además ha sido invocado en modo
	 * subOperación (si la pila de estructuras de diálogo no está vacía).
	 * <p> 
	 * Este método aplica, si fuese necesario, una transferencia de atributos del contexto de la operación 
	 * invocada sobre el contexto depositado en la cima de la pila (contexto aparcado de la operación invocante) 
	 * y realiza la redirección sobre el paso de vuelta a la operación invocante.
	 *  
	 * @param request Petición a procesar
	 * @param response Respuesta a crear
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @param servlet ActionServlet encargado de retornar a la superOperación (op. invocante)
	 * @return false -> Si ha habido un retorno de suboperación.
	 * @throws Q70FrameworkException
	 */
	protected boolean processSubOpEnd(HttpServletRequest request, HttpServletResponse response, Q70PasosOPMapping opMapping, ActionServlet servlet) throws Q70FrameworkException
	{
		Q70EstructuraDialogo estructuraDialogo = null;
		try {
			if (null != opMapping.getSubOp() && opMapping.getSubOp().equalsIgnoreCase("end"))
			{
				// Unwrap the multipart request, if there is one.
				if (request instanceof MultipartRequestWrapper) {
					request = ((MultipartRequestWrapper) request).getRequest();
				}

				// Desapilamiento de Estructura de Dialogo. Recupero el contexto de la
				// superOperación (op. invocante) de la estructura de dialogo para que sea
				// mapeado con el contexto de la subOperación actual.
				try {
					HttpSession session = request.getSession();
					
					Q70ContextoEJIE contextoSubOP = (Q70ContextoEJIE)session.getAttribute(Q70Constantes.CONTEXT);
					
					estructuraDialogo = (Q70EstructuraDialogo)this.popDialog(session);
					if (null != estructuraDialogo)
					{
						Q70ContextoEJIE contextoSuperOP = estructuraDialogo.getContexto();
						
						//desde una invocacion local 
						if (null == estructuraDialogo.getHash())
						{
							//Recuperamos el contexto de aplicación
							List contextoAplicacion = new ArrayList(1);
							contextoAplicacion.add(Q70SesionDatos.getSingleton().getContexto(contextoSubOP, Q70SesionDatos.CTX_APLICACION));

							//Salva lista anterior de antecesores
							List listaContextosAntecesores = (List)Q70SesionDatos.getSingleton().getListaContextos(contextoSubOP);
							//La lista de antecesores sólo contendrá al contexto de aplicación,
							//de esta manera sólo serializamos los contextos de aplicación y de op presentación
							contextoSubOP = (Q70ContextoEJIE)Q70SesionDatos.getSingleton().addListaContextos(contextoSubOP, contextoAplicacion);
							Q70GestorMapeador mapeador = Q70GestorMapeador.getSingleton();
							mapeador.mapObjects(Q70UtilidadesConstantes.MAPA_TODAS_PROPIEDADES, (DynaBean)contextoSubOP, contextoSuperOP);
							//Añade al contexto los contextos jerárquicos superiores anteriormete salvados
							contextoSubOP = (Q70ContextoEJIE)Q70SesionDatos.getSingleton().addListaContextos(contextoSubOP, listaContextosAntecesores);
							
							session.setAttribute(Q70Constantes.CONTEXT, contextoSuperOP);
							
							if (tr.isTraceActive())
							{
								tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "Contexto de superOperacion "+contextoSuperOP.getIdContexto()+" antes de retorno de la subOperación "+contextoSubOP.getIdContexto());
							}
							
						//desde una invocacion remota 
						}else{
							estructuraDialogo.setContexto(contextoSubOP);
						}
						
						
					}
				} catch (Exception e) {
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción mapeando el contexto de la suboperación al contexto de la operación.");
					}
					throw new Q70ApilamientoException(this.getClass().toString(),"processSubOpEnd","Excepción mapeando el contexto de la suboperación al contexto de la operación.",e);
				}
				
				//Opreación invocada como operación normal (sin apilamiento)
				if (null == estructuraDialogo)
				{
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Operación no invocada como suboperación");
					}
					return true;
				}
				else
				{
					//Suboperación
					String uri = estructuraDialogo.getRetPath();
					if (null == uri)
					{
						return true;
					}
					//Averiguamos si es un apilamiento remoto
					if (null != estructuraDialogo.getHash())
					{
						if (tr.isTraceActive())
						{
							tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Operación invocada como suboperación remota [Retorno: "+uri+"]");
						}
						//Actualizamos el contexto de la suboperación persistente en BD.
						updateEstructuraDialogoEnBD(estructuraDialogo);
						//Invalidamos la session
						request.getSession().invalidate();
						//Volvemos a la superoperacion remota
						String urlRemota = uri.indexOf("?") == -1 ? uri + "?" : uri + "&";
						urlRemota += getNombreParametroUrlRemota(estructuraDialogo) + estructuraDialogo.getHash();
						response.sendRedirect(urlRemota);
						return false;
					}
					else
					{
						//Suboperación sin apilamiento remoto
						if (tr.isTraceActive())
						{
							tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Operación invocada como suboperación [Retorno: "+uri+"]");
						}
						RequestDispatcher rd = servlet.getServletContext().getRequestDispatcher(uri);
						if (rd == null) {
							response.sendError(
								HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
								servlet.getInternal().getMessage("requestDispatcher", uri));
							return true;
						}
						rd.forward(request, response);
						return false;
					}
				}
			}
			return true;
		} catch (IOException ioe) {
			throw new Q70ApilamientoException(this.getClass().toString(),"processSubOpEnd","Excepción de IO",ioe);
		} catch (ServletException se) {
			throw new Q70ApilamientoException(this.getClass().toString(),"processSubOpEnd","Excepción de Servlet",se);
		} catch (Exception e) {
			throw new Q70ApilamientoException(this.getClass().toString(),"processSubOpEnd","Excepción al procesar el retorno de la subOperación a la superOperación (op. invocante)",e);
		}
	}

	/**
	 * Este método procesa el punto de arranque de una sub-Operación.
	 * <p>
	 * Aplica una transformación de atributos del contexto invocante si la aplicación ha decidido 
	 * mediante configuración que ésto es necesario para el invocado y aparca serializando el 
	 * contexto de operación invocante en el objeto estructura de diálogo, depositándolo 
	 * en la cima de la pila creada en la httpSession si las dos operaciones están en el mismo web-application,
	 * o guardando esa info (en tantos registros como atributos tenga el contexto) 
	 * en una tabla de BBDD tras generar un identificativo único o <b>hash</b>.
	 * A continuación se redirecciona la petición al primer paso de operación de la OP invocada,
	 * enviándole el código HASH si ésta pertenece a diferente war.
	 * <p>
	 * @param session Sesion donde reside la pila.
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @param contexto Contexto de la superoperación (op. invocante)
	 * @throws Q70FrameworkException
	 */
	protected void processSuperOpInit (HttpSession session, Q70PasosOPMapping opMapping, Q70ContextoEJIE contexto) throws Q70FrameworkException
	{
		//variable para diferenciar entre suboperacion y reenvios local y remoto
		String tipoOperacion = dameTipoOperacion(opMapping,null);
		//Antes de hacer el apilamiento, comprobamos si hay que realizar un mapeo
		if (null != opMapping.getMapa() && (!opMapping.getMapa().trim().equals("")))
		{
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.DEBUG, "Procesando mapeo para el contexto. Mapeo: "+opMapping.getMapa());
			}
			try {
				// Realiza el mapeo de unos atributos del contexto a otros, segun el mapping definido en el action
				Q70GestorMapeador.getSingleton().mapObjects(opMapping.getMapa(), contexto, contexto);
				// Actualiza el contexto
				session.setAttribute(Q70Constantes.CONTEXT,contexto);
			} catch (Exception e) {
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "ERROR Procesando mapeo para el contexto. Mapeo: "+opMapping.getMapa());
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"processSuperOpInit","ERROR Procesando mapeo para el contexto. Mapeo: "+opMapping.getMapa(),e);
			}
		}
		
		String mensajeTraza = null;
		
		//Salvamos el contexto de la Operación de Presentación y el Path de retorno
		//en la estructura de pila de diálogos, para poder anidar/apilar suboperaciones.
		Q70EstructuraDialogo dialogo = new Q70EstructuraDialogo();
		try {
			if(tipoOperacion.startsWith(PREFIJO_SUBOP)){
				String retPath = opMapping.getSubOpRetPath();
				if (tipoOperacion.equals(SUBOPERACION_REMOTA)){ //se tratar de suboperacion remota
					//Quitamos el prefijo a la uri de retorno
					retPath = Q70ListenerUtils.getRemoteURI(retPath,session);
				}
				dialogo.setRetPath(retPath);
			}
			dialogo.setContexto(contexto);
			
			//Si además tiene la propiedad callOpRemota es que es un apilamiento
			//remoto por lo que generaremos un hash y guardaremos la estructura de
			//dialogo en BD
			if (null != opMapping.getCallOpRemota() && 
			   (opMapping.getCallOpRemota().trim().equalsIgnoreCase("init") || 
                opMapping.getCallOpRemota().trim().equalsIgnoreCase("reenvio"))){
				
				if (tr.isTraceActive()){
					if (REENVIO_REMOTO.equals(tipoOperacion))
						mensajeTraza = "Realizando un reenvío remoto con paso de parámetros.";
					else
						mensajeTraza = "Llamando a suboperación remota.";
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, mensajeTraza);
				}
				//Generamos el hash
				String hash = Q70Hash.generateHash();
				dialogo.setHash(hash);
				dialogo.setPosteriorVersionUno(isPosteriorVersionUno(opMapping));
				insertEstructuraDialogoEnBD (dialogo);
			}else{
				if (tr.isTraceActive()){
					if (REENVIO_LOCAL.equals(tipoOperacion))
						tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Realizando un reenvío local con paso de parámetros.");
				}
			}
				
			//if (!"reenvioRemoto".equals(tipoOperacion)){
				//Guardamos la estructura de dialogo en la pila
				this.pushDialog(session,dialogo);
			//}
		} catch (Exception e) {
			if (tr.isTraceActive())
			{
				if (REENVIO_REMOTO.equals(tipoOperacion))
					mensajeTraza = "Excepción en el tratamiento de la estructura de dialogo al realizar el reenvío remoto.";
				else{
					if (REENVIO_LOCAL.equals(tipoOperacion))
						mensajeTraza = "Excepción apilando la estructura de dialogo para un reenvío local.";
					else
						mensajeTraza = "Excepción apilando la estructura de dialogo para una suboperación.";
				}
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, mensajeTraza);
			}
			throw new Q70ApilamientoException(this.getClass().toString(),"processSuperOpInit",mensajeTraza,e);
		}
	}

	/**
	 * Este método procesa el punto de arranque de una operación principal.
	 * <p>
	 * Vacía la pila y en el caso de que estuviéramos en una suboperación de otro war (apilamiento remoto),
	 * elimina de la BD los registros correspondientes indicados en la estructura de diálogo
	 * <p>
	 * @param session Sesion donde reside la pila.
	 * @throws Q70FrameworkException
	 */
	public void processSuperOpReset (HttpSession session) throws Q70FrameworkException
	{

		Stack pila = (Stack)session.getAttribute(PILA_DIALOGO_KEY);
		if ((null != pila)){
			int tam = pila.size();
			for (int i=0; i<tam; i++){
				Q70EstructuraDialogo estructuraDialogo = (Q70EstructuraDialogo)this.popDialog(session);
				if (null != estructuraDialogo){
					String hash = estructuraDialogo.getHash();
					if (null!=hash && !"".equals(hash)){
						if (tr.isTraceActive())
						{
							tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Eliminando estructura de diálogo de BD.");
						}				
						deleteEstructuraDialogoEnBD(hash);
					}
				}				
			}
			this.reset(session);
		}
	}

	/**
	 * Método que procesa el primer paso de una sub-Operacion.
	 * <p>
	 * 
	 * Recupera el contexto de la super-Operación (op. invocante) de la
	 * estructura de dialogo y aplica, si fuese necesario, una transferencia de atributos del contexto 
	 * aparcado del invocante al del invocado (si la aplicación decide que se necesitan 
	 * ciertos datos de entrada provinientes del contexto invocante). 
	 * Si la invocación es remota (dos web-application distintas), se recogerá el contexto 
	 * serializado y depositado en BBDD bajo la HASH recibida y se depositará en la cima de 
	 * la pila de los contextos aparcados de ese usuario. El contexto activo será el de la 
	 * sub-operación.
	 * 
	 * @param request Petición a procesar
	 * @param contextoSubOP Contexto de la SubOperación
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @throws Q70FrameworkException
	 */
	protected void processSubOpInit (HttpServletRequest request, Q70ContextoEJIE contextoSubOP, Q70PasosOPMapping opMapping) throws Q70FrameworkException
	{
		Q70ContextoEJIE contextoSuperOP = null;
		Collection listaAtributos = null;
		Q70EstructuraDialogo estructuraDialogo = null;
		Q70GestorMapeador mapeador = null;

		String mensajeTraza = null;
		//variable para diferenciar entre suboperacion y reenvios local y remoto
		String tipoOperacion = dameTipoOperacion(opMapping, getHashNoCaducado(request));		
		
		if (REENVIO_LOCAL.equals(tipoOperacion))
			mensajeTraza = "Reenvío local con paso de parámetros.";
		else{
			if (REENVIO_REMOTO.equals(tipoOperacion)){
				mensajeTraza = "Reenvío remoto con paso de parámetros.";
			}
			else
				mensajeTraza = "Suboperación con apilamiento.";
		}
		try {
			// Si el hash no está caducado...
			if (null != getHashNoCaducado(request))
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, mensajeTraza);
				}
				//Recuperamos de BD la estructura de dialogo
				estructuraDialogo = selectEstructuraDialogoEnBD(getHashDeLaRequest(request), isPosteriorVersionUno(request));
				// Caducamos el hash
				request.setAttribute(HASH_CADUCADO,getHashDeLaRequest(request));
				//if (estructuraDialogo.getContexto()!=null)
				if (SUBOPERACION_REMOTA.equals(tipoOperacion))
					this.pushDialog(request.getSession(),estructuraDialogo);
			}
		
			// desapilar si se trata de un reenvío local
			if (REENVIO_LOCAL.equals(tipoOperacion)){
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, mensajeTraza);
				}				
				estructuraDialogo = (Q70EstructuraDialogo)this.popDialog(request.getSession());
			}
			else{
				// borrar de BD si se trata de un reenvio remoto
				if (REENVIO_REMOTO.equals(tipoOperacion)){
					deleteEstructuraDialogoEnBD(getHashDeLaRequest(request));
				}
				else
					estructuraDialogo = (Q70EstructuraDialogo)this.peekDialog(request.getSession());
			}
			//apilamiento remoto
			if (null != estructuraDialogo && estructuraDialogo.getHash()!=null)
			{
				listaAtributos = estructuraDialogo.getListaAtributos();
				mapeador = Q70GestorMapeador.getSingleton();
				mapeador.mapObjects(Q70UtilidadesConstantes.MAPA_TODAS_PROPIEDADES_LISTA, listaAtributos, contextoSubOP);
			}else{
				//apilamiento o reenvío  local
				if (null != estructuraDialogo){
					
					contextoSuperOP = estructuraDialogo.getContexto();
			
					//Salva lista anterior de antecesores
					List listaContextosAntecesores = (List)Q70SesionDatos.getSingleton().getListaContextos(contextoSuperOP);
					//Añade al contexto los contextos jerárquicos superiores = null para mapear sólo los datos del contexto de operación y no los jerarquicos superiores.
					contextoSuperOP = (Q70ContextoEJIE)Q70SesionDatos.getSingleton().addListaContextos(contextoSuperOP, null);
					mapeador = Q70GestorMapeador.getSingleton();
					mapeador.mapObjects(Q70UtilidadesConstantes.MAPA_TODAS_PROPIEDADES, (DynaBean)contextoSuperOP, contextoSubOP);
	
					//Añade al contexto los contextos jerárquicos superiores anteriormente salvados
					contextoSuperOP = (Q70ContextoEJIE)Q70SesionDatos.getSingleton().addListaContextos(contextoSuperOP, listaContextosAntecesores);					
				}else{
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.INFO, "SubOperacion invocada como operación principal.");
					}
				}
			}
		}
		catch (Exception e)
		{
			if (tipoOperacion.startsWith(PREFIJO_REENVIO))
				mensajeTraza = "l reenvío";
			else 
				mensajeTraza = " la suboperación";

			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción mapeando el contexto de la operación al contexto de" + mensajeTraza);
			}
			throw new Q70ApilamientoException(this.getClass().toString(),"processSubOpInit","Excepción mapeando el contexto de la operación al contexto de" + mensajeTraza,e);
		}
	}

	/**
	 * Este método procesa el punto de vuelta dentro de la operación invocante.
	 * <p>
	 * Recupera de la cima de la pila su contexto apilado (si es local) o de la base de datos (si es remoto),
	 * limpia la base de datos para la fila identificada por HASH y desaparca la Operación 
	 * de Presentación, activando su contexto como Contexto de Operación de Presentación actual.
	 * Si fuese necesario, aplicará sobre el Contexto un mapa con transferencia de atributos desde el contexto de la
	 * sub-Operación.
	 * 
	 * @param request Petición a procesar
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @throws Q70FrameworkException
	 */
	protected void processSuperOpEnd(HttpServletRequest request, Q70PasosOPMapping opMapping) throws Q70FrameworkException
	{
		Q70EstructuraDialogo estructuraDialogo = null;
		
		String hashPila = this.getStoredHash(request, opMapping);
		//Si hay en la pila una estructura de dialogo con hash
		if (null != hashPila)
		{
			String hash = getHashDeLaRequest(request); 
			if ((null != hash) && (hash.equals(hashPila)))
			{
				try {
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Procesando el retorno de una suboperación con apilamiento remoto.");
					}
					estructuraDialogo = popDialog(request.getSession());
					
					//Recuperamos de BD la estructura de dialogo 
					estructuraDialogo = selectEstructuraDialogoEnBD(estructuraDialogo.getHash(), estructuraDialogo.isPosteriorVersionUno());
					if (null != estructuraDialogo.getListaAtributos())
					{
						Collection listaAtributos = estructuraDialogo.getListaAtributos();
						Q70ContextoEJIE contextoSuperOP = (Q70ContextoEJIE)request.getSession().getAttribute(Q70Constantes.CONTEXT);
						Q70GestorMapeador mapeador = Q70GestorMapeador.getSingleton();
						mapeador.mapObjects(Q70UtilidadesConstantes.MAPA_TODAS_PROPIEDADES_LISTA, listaAtributos, contextoSuperOP);
						
						request.getSession().setAttribute(Q70Constantes.CONTEXT, contextoSuperOP);
						
						//Sustituimos el contexto actual por el contexto actualizado
						//request.getSession().setAttribute(Q70Constantes.CONTEXT,estructuraDialogo.getContexto());
						deleteEstructuraDialogoEnBD(hash);
						if (tr.isTraceActive())
						{
							tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Fin del apilamiento.");
						}
					}
					else
					{
						throw new Exception("El contexto que se ha recuperado de la Base de Datos es nulo.");
					}
				}
				catch (Exception e)
				{
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Excepción procesando el retorno de una suboperación remota.");
					}
					throw new Q70ApilamientoException(this.getClass().toString(),"processSuperOpEnd","Excepción procesando el retorno de una suboperación remota.",e);
				}
			}
		}
	}

	/**
	 * Método que recupera el código <b>HASH</b> de la estructura de diálogo de la cima de la pila. 
	 * <p>
	 * @param request Petición a procesar
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @return Código <b>HASH</b>
	 */
	public String getStoredHash (HttpServletRequest request, Q70PasosOPMapping opMapping)
	{
		String hash =  null;
		Q70EstructuraDialogo estructuraDialogo = null;
		
		if (null != opMapping 
				&& "reenvio".equals(opMapping.getCallOpRemota())
				&& "init".equals(opMapping.getOpReenvio())) {
			estructuraDialogo = (Q70EstructuraDialogo)this.popDialog(request.getSession());
		}
		else{
			//Recuperamos la estructura de dialogo de la cima de la pila
			estructuraDialogo = (Q70EstructuraDialogo)this.peekDialog(request.getSession());
		}
		if (null != estructuraDialogo)
		{
			hash = estructuraDialogo.getHash();
		}
		
		return hash;
	}
	
	/**
	 * Método que almacena en Base de Datos la estructura de dialogo para un
	 * apilamiento remoto.
	 * <p>
	 * Previamente serializa el contexto. Este método es sólo válido en BBDD Oracle.
	 *  
	 * @param dialogoEstructura de diálogo a serializar y almacenar en BBDD
	 * @throws Q70FrameworkException
	 */
	protected void insertEstructuraDialogoEnBD (Q70EstructuraDialogo dialogo) throws Q70FrameworkException
	{
		insertEstructuraDialogoEnBD0(dialogo, false);
	}
	
	/**
	 * Método que almacena en Base de Datos la estructura de dialogo para un
	 * apilamiento remoto.
	 * <p>
	 * Previamente serializa el contexto. Este método es sólo válido en BBDD Oracle.
	 *  
	 * @param dialogoEstructura de diálogo a serializar y almacenar en BBDD
	 * @throws Q70FrameworkException
	 */
	private void insertEstructuraDialogoEnBD0 (Q70EstructuraDialogo dialogo, boolean isUpdate) throws Q70FrameworkException
	{
		Connection conn = null;
		try {
			if (null==this.getDataSource())
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Falta la propiedad dataSource en el fichero XML de configuración del gestor de apilamiento.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"insertEstructuraDialogoEnBD","Falta la propiedad dataSource en el fichero XML de configuración del gestor de apilamiento.",null);
			}
			if (null==this.getTable())
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Falta la propiedad tabla en el fichero XML de configuración del gestor de apilamiento.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"insertEstructuraDialogoEnBD","Falta la propiedad tabla en el fichero XML de configuración del gestor de apilamiento.",null);
			}
			conn = Q70ConectorJDBC.getSingleton().getConnection(this.getDataSource());
			conn.setAutoCommit(false);
		
			if ( dialogo.isPosteriorVersionUno() ){
				insertMultiFila(conn, dialogo, isUpdate);
			}else{
				insertFilaUnica(conn, dialogo, isUpdate);
			}
			
			conn.commit();
			conn.setAutoCommit(true);
			conn.close();
		} catch (Exception e) {
			doCerrarCorrectamenteBD(null, null, conn);
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error insertando la estructura de dialogo en la Base de Datos para un apilamiento remoto.");
			}
			throw new Q70ApilamientoException(this.getClass().toString(),"insertEstructuraDialogoEnBD","Error insertando la estructura de dialogo en la Base de Datos para un apilamiento remoto.",e);
		}		
	}

	private void insertMultiFila(Connection conn, Q70EstructuraDialogo dialogo, boolean isUpdate) throws SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException, Q70ApilamientoException, NamingException {
		PreparedStatement pstmt = null;

		if (isUpdate){
			int rows = deleteEstructuraDialogoEnBD0(conn, dialogo.getHash());
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.INFO, "La estructura de dialogo para el apilamiento remoto se ha eliminado correctamente de la Base de Datos. Se han eliminado " + rows + " filas.");
			}
		}
		
		Collection listaAtributos = dialogo.getListaAtributos();
		Iterator it = listaAtributos.iterator();
		while (it.hasNext()) {
			List sublista= (List) it.next();
						
			pstmt = conn.prepareStatement("insert into " + this.getTable() + " (HASH, NBR_ATRIBUTO, RETPATH, TIMESTAMP, CTXOOPAP) values (?,?,?,?,empty_blob())");
			pstmt.setString(1,dialogo.getHash());
			pstmt.setString(2,(String)sublista.get(0));
			pstmt.setString(3,(null==dialogo.getRetPath()?"Reenvio":dialogo.getRetPath()));
			pstmt.setDate(4,new Date(System.currentTimeMillis()));
			pstmt.executeUpdate();
			pstmt.close();
		
			pstmt = conn.prepareStatement("select CTXOOPAP from " + this.getTable() + " where HASH=? and NBR_ATRIBUTO=? for update");
			pstmt.setString(1,dialogo.getHash());
			pstmt.setString(2,(String)sublista.get(0));
			ResultSet rs = pstmt.executeQuery();
			if(rs.next())
			{
				
				Object blob = rs.getBlob("CTXOOPAP");
				Class c = blob.getClass();
				Method method = c.getMethod("getBinaryOutputStream",new Class[]{});
			
				OutputStream out = (OutputStream)method.invoke(blob,new Object[]{});
				ObjectOutputStream oos = new ObjectOutputStream(out);

				try
				{
					oos.writeObject(sublista.get(1));
					oos.flush();
					oos.close();
				}
				catch (IOException ioe)
				{
					doCerrarCorrectamenteBD(rs, pstmt, null);
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error serializando el campo de contexto '"+sublista.get(0)+"' que se iba a almacenar en Base de Datos.");
					}
					throw new Q70ApilamientoException(this.getClass().toString(),"insertEstructuraDialogoEnBD","Error serializando el contexto para ser almacenado en Base de Datos.",ioe);
				}
			}
			rs.close();
			pstmt.close();
		}
		if (tr.isTraceActive())
		{
			tr.trace(COMPONENTE, Q70TraceLevel.INFO, "Se han insertado '"+ dialogo.getListaAtributos() +"' atributos del contexto en total en la estructura de dialogo para el apilamiento remoto y correctamente en la Base de Datos.");
		}
	}	
	
	/**
	 * Metodo para insertar la fila con el contexto de la forma que lo hace Geremua version 1. 
	 * <b>NOTA</b>: Para compatibilidad de versiones. Quitar cuando ya no sea necesario.
	 * 
	 * @param conn
	 * @param dialogo
	 * @throws SQLException
	 * @throws NoSuchMethodException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws IOException
	 * @throws Q70ApilamientoException
	 * @throws NamingException 
	 */
	private void insertFilaUnica(Connection conn, Q70EstructuraDialogo dialogo, boolean isUpdate) throws SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException, Q70ApilamientoException, NamingException {
		PreparedStatement pstmt = null;
		
		if (isUpdate){
			int rows = deleteEstructuraDialogoEnBD0(conn, dialogo.getHash());
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.INFO, "La estructura de dialogo para el apilamiento remoto se ha eliminado correctamente de la Base de Datos. Se han eliminado " + rows + " filas.");
			}
		}
		
		pstmt = conn.prepareStatement("insert into " + this.getTable() + " (HASH, NBR_ATRIBUTO, RETPATH, CTXOOPAP) values (?,?,?,empty_blob())");
		pstmt.setString(1,dialogo.getHash());
		pstmt.setString(2,"CONTEXTO ALMACENADO POR VERSION POSTERIOR A 1.x");
		pstmt.setString(3,dialogo.getRetPath());
		pstmt.executeUpdate();
		pstmt.close();
	
		pstmt = conn.prepareStatement("select CTXOOPAP from " + this.getTable() + " where HASH=? for update");
		pstmt.setString(1,dialogo.getHash());
		ResultSet rs = pstmt.executeQuery();
		if(rs.next())
		{
			Object blob = rs.getBlob("CTXOOPAP");
			Class c = blob.getClass();
			Method method = c.getMethod("getBinaryOutputStream",new Class[]{});
		
			OutputStream out = (OutputStream)method.invoke(blob,new Object[]{});
			ObjectOutputStream oos = new ObjectOutputStream(out);

			//Recuperamos el contexto de aplicación
			List contextoAplicacion = new ArrayList(1);
			contextoAplicacion.add(Q70SesionDatos.getSingleton().getContexto(dialogo.getContexto(), Q70SesionDatos.CTX_APLICACION));
			//Salva lista anterior de antecesores
			List listaContextosAntecesores = (List)Q70SesionDatos.getSingleton().getListaContextos(dialogo.getContexto());
			//La lista de antecesores sólo contendrá al contexto de aplicación,
			//de esta manera sólo serializamos los contextos de aplicación y de op presentación
			dialogo.setContexto((Q70ContextoEJIE)Q70SesionDatos.getSingleton().addListaContextos(dialogo.getContexto(), contextoAplicacion));
			
			//Serializamos el contexto
			try
			{
				oos.writeObject(dialogo.getContexto());
				oos.flush();
				oos.close();
			}
			catch (IOException ioe)
			{
				doCerrarCorrectamenteBD(rs, pstmt, null);
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error serializando el contexto para ser almacenado en Base de Datos.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"insertEstructuraDialogoEnBD","Error serializando el contexto para ser almacenado en Base de Datos.",ioe);
			}finally{
				//Añade al contexto los contextos jerárquicos superiores anteriormete salvados
				dialogo.setContexto((Q70ContextoEJIE)Q70SesionDatos.getSingleton().addListaContextos(dialogo.getContexto(), listaContextosAntecesores));
			}
		}
		rs.close();
		pstmt.close();
		
		if (tr.isTraceActive())
		{
			tr.trace(COMPONENTE, Q70TraceLevel.INFO, "APILAMIENTO REMOTO PROCEDENTE GEREMUA VERSION 1.x: La estructura de dialogo para el apilamiento remoto se ha almacenado correctamente en la Base de Datos.");
		}
	}	
	
	/**
	 * Método que recupera de Base de Datos la estructura de dialogo almacenada en un
	 * apilamiento remoto.
	 * <p> 
	 * Recupera la información salvada en BBDD, deserializa el contexto y devuelve
	 * la instancia de <code>Q70EstructuraDialogo</code> reconstruída.
	 * 
	 * @param hash Clave primaria del registro a buscar en BBDD
	 * @param isVersionUno Indica si se los registros estan grabados al estilo de version uno o versiones posteriores
	 * @return Estructura de Dialogo
	 * @throws Q70FrameworkException
	 */
	protected Q70EstructuraDialogo selectEstructuraDialogoEnBD (String hash, boolean isPosteriorVersionUno) throws Q70FrameworkException
	{
		Q70EstructuraDialogo dialogo = null;
		Connection conn = null;
		try {
			if (null==this.getDataSource())
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Falta la propiedad dataSource en el fichero XML de configuración del gestor de apilamiento.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"selectEstructuraDialogoEnBD","Falta la propiedad dataSource en el fichero XML de configuración del gestor de apilamiento.",null);
			}
			if (null==this.getTable())
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Falta la propiedad tabla en el fichero XML de configuración del gestor de apilamiento.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"selectEstructuraDialogoEnBD","Falta la propiedad tabla en el fichero XML de configuración del gestor de apilamiento.",null);
			}
			conn = Q70ConectorJDBC.getSingleton().getConnection(this.getDataSource());
				
			if ( isPosteriorVersionUno ){
				dialogo = selectMultiFila(conn, hash);
				if (null != dialogo){
					dialogo.setPosteriorVersionUno(true);
				}
			}else{
				dialogo = selectFilaUnica(conn, hash);
				if (null != dialogo){
					dialogo.setPosteriorVersionUno(false);
				}
			}
			
			conn.close();
			
			if (null != dialogo)
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.INFO, "La estructura de dialogo para el apilamiento remoto se ha recuperado correctamente de la Base de Datos.");
				}
				return dialogo;
			}
			else
			{
				throw new Exception("No se ha encontrado una estructura de dialogo con dicho hash.");
			}
		}
		catch (Exception e)
		{
			doCerrarCorrectamenteBD(null, null, conn);
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error recuperando la estructura de dialogo de la Base de Datos para un apilamiento remoto.");
			}
			throw new Q70ApilamientoException(this.getClass().toString(),"selectEstructuraDialogoEnBD","Error recuperando la estructura de dialogo de la Base de Datos para un apilamiento remoto.",e);
		}
	}

	private Q70EstructuraDialogo selectMultiFila(Connection conn, String hash) throws SQLException, Q70ApilamientoException {
		Q70EstructuraDialogo dialogo = null;
		PreparedStatement pstmt = null;
		
		pstmt = conn.prepareStatement("select HASH, NBR_ATRIBUTO, RETPATH, CTXOOPAP from " + this.getTable() + " where HASH = ?");
		pstmt.setString(1,hash);

		//Ejecutamos el procedimiento almacenado (select)
		ResultSet rs = pstmt.executeQuery();

		if (rs.next()){
			HashSet listaDeAtributos = new HashSet(11);
			
			dialogo = new Q70EstructuraDialogo();
			dialogo.setHash(rs.getString(1));
			dialogo.setRetPath(rs.getString(3));
			
			do {
				//Deserializamos el contexto campo a campo
				InputStream binaryStream = null;
				try {
					binaryStream = rs.getBinaryStream(4);
					Q70ObjectInputStream si = new Q70ObjectInputStream(binaryStream);
					
					Object readObjectAux = si.readObject();
					List subListaAux = new ArrayList(2);
					subListaAux.add(rs.getString(2));
					subListaAux.add(readObjectAux);
					listaDeAtributos.add(subListaAux);
					
					si.close();
				} catch (ClassNotFoundException e1) {
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.WARN, "Deserializando el contexto de Base de Datos. No existe el bean '"+e1.getMessage()+"'");
					}
				} catch (Exception e1) {
					doCerrarCorrectamenteBD(rs, pstmt, conn);
					if (tr.isTraceActive())
					{
						tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error deserializando el contexto al ser recuperado de Base de Datos.");
					}
					throw new Q70ApilamientoException(this.getClass().toString(),"selectEstructuraDialogoEnBD","Error deserializando el contexto al ser recuperado de Base de Datos.",e1);
				}
			} while (rs.next()); 
			
			dialogo.setListaAtributos(listaDeAtributos);
		}
					
		pstmt.close();
		return dialogo;
	}
	
	/**
	 * Metodo para recuperar la fila con el contexto de la forma que lo hace Geremua version 1. 
	 * <b>NOTA</b>: Para compatibilidad de versiones. Quitar cuando ya no sea necesario.
	 *
	 * @param conn
	 * @param dialogo
	 * @param hash
	 * @return
	 * @throws SQLException
	 * @throws Q70ApilamientoException
	 */
	private Q70EstructuraDialogo selectFilaUnica(Connection conn, String hash) throws SQLException, Q70ApilamientoException {
		Q70EstructuraDialogo dialogo = null;
		PreparedStatement pstmt = null;
		
		pstmt = conn.prepareStatement("select HASH, RETPATH, CTXOOPAP from " + this.getTable() + " where HASH = ?");
		pstmt.setString(1,hash);
	
		//Ejecutamos el procedimiento almacenado (select)
		ResultSet rs = pstmt.executeQuery();
		if (rs.next())
		{
			dialogo = new Q70EstructuraDialogo();
			dialogo.setHash(rs.getString(1));
			dialogo.setRetPath(rs.getString(2));
			
			//Deserializamos el contexto
			InputStream binaryStream = null;
			try {
				binaryStream = rs.getBinaryStream(3);
				Q70ObjectInputStream si = new Q70ObjectInputStream(binaryStream);
				
				dialogo.setContexto((Q70ContextoEJIE)si.readObject());
				
				si.close();
			} catch (Exception e1) {
				doCerrarCorrectamenteBD(rs, pstmt, conn);
				
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error deserializando el contexto al ser recuperado de Base de Datos.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"selectEstructuraDialogoEnBD","Error deserializando el contexto al ser recuperado de Base de Datos.",e1);
			}
		}
		
		pstmt.close();
		return dialogo;
	}	
	
	/**
	 * Método que actualiza la información de la estructura de diálogo en BBDD.
	 * <p>
	 * Se borrará e insertará la estructura de dialogo en Base de
	 * Datos (con contexto serializado) cuando se dé la casuística de apilamiento remoto.
	 * <p> 
	 * @param dialogo Instancia de <code>Q70EstructuraDialogo</code> a actualizar.
	 * @throws Q70FrameworkException
	 */
	protected void updateEstructuraDialogoEnBD (Q70EstructuraDialogo dialogo) throws Q70FrameworkException
	{
		insertEstructuraDialogoEnBD0(dialogo, true);
	}
	
	/**
	 * Método que borra la estructura de dialogo en Base de Datos perteneciente
	 * a un apilamiento remoto.
	 * <p>
	 * Solo BBDD Oracle
	 * @param hash de la instancia de Q70EstructuraDialogo a borrar.
	 * @throws Q70FrameworkException
	 */
	protected void deleteEstructuraDialogoEnBD (String hash) throws Q70FrameworkException
	{
		Connection conn = null;
		try {
			if (null==this.getDataSource())
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Falta la propiedad dataSource en el fichero XML de configuración del gestor de apilamiento.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"deleteEstructuraDialogoEnBD","Falta la propiedad dataSource en el fichero XML de configuración del gestor de apilamiento.",null);
			}
			if (null==this.getTable())
			{
				if (tr.isTraceActive())
				{
					tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Falta la propiedad tabla en el fichero XML de configuración del gestor de apilamiento.");
				}
				throw new Q70ApilamientoException(this.getClass().toString(),"deleteEstructuraDialogoEnBD","Falta la propiedad tabla en el fichero XML de configuración del gestor de apilamiento.",null);
			}
			
			conn = Q70ConectorJDBC.getSingleton().getConnection(this.getDataSource());
			conn.setAutoCommit(false);
			
			int rows = deleteEstructuraDialogoEnBD0(conn, hash);

			conn.commit();
			conn.setAutoCommit(true);
			conn.close();
			
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.INFO, "La estructura de dialogo para el apilamiento remoto se ha eliminado correctamente de la Base de Datos. Se han eliminado " + rows + " filas.");
			}
		}
		catch (Exception e)
		{
			doCerrarCorrectamenteBD(null, null, conn);
			if (tr.isTraceActive())
			{
				tr.trace(COMPONENTE, Q70TraceLevel.ERROR, "Error eliminando la estructura de dialogo en la Base de Datos para el apilamiento remoto.");
			}
			throw new Q70ApilamientoException(this.getClass().toString(),"deleteEstructuraDialogoEnBD","Error eliminando la estructura de dialogo en la Base de Datos para el apilamiento remoto.",e);
		}
	}

	private int deleteEstructuraDialogoEnBD0(Connection conn, String hash) throws SQLException {
		try {
			PreparedStatement pstmt = conn.prepareStatement("delete from " + this.getTable() + " where HASH = ?");
			pstmt.setString(1,hash);

			//Ejecutamos la sentencia delete
			int rows = pstmt.executeUpdate ();
			pstmt.close();
			return rows;
		} catch (SQLException e) {
			throw e;
		}
	}
	
	/**
	 * Este método es el punto de entrada al procesamiento de un apilamiento/desapilamiento.
	 * <p>
	 * Averigua el punto sensible de apilamiento en que se encuentra la ejecución y delega el 
	 * procesamiento en otros métodos.
	 * <LU>
	 * <LI>processSuperOpReset()</LI>
	 * <LI>processSubOpInit()</LI>
	 * <LI>processSubOpEnd()</LI>
	 * <LI>processSuperOpInit()</LI>
	 * <LI>processSuperOpEnd()</LI>
	 * </LU>
	 * <p>
	 * @param request Petición a procesar
	 * @param response Respuesta a crear
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @param contexto Contexto genérico
	 * @param servlet Instancia de ActionServlet que atiende la petición.
	 * @return
	 * @throws Q70FrameworkException
	 */
	public boolean processApilamiento(HttpServletRequest request, HttpServletResponse response, Q70PasosOPMapping opMapping, Q70ContextoEJIE contexto, ActionServlet servlet) throws Q70FrameworkException
	{
		if (null == contexto)
		{
			//Retorno de suboperación
			return processSubOpEnd(request, response, opMapping, servlet);
			
		}
		else
		{
			if ((null != opMapping.getSubOp()) && (opMapping.getSubOp().equalsIgnoreCase("reset")))
			{
				//Inicio de suboperación, limpia la pila y la bbdd, en caso necesario  
				processSuperOpReset(request.getSession());
			}
			if (((null != opMapping.getSubOp()) && (opMapping.getSubOp().equalsIgnoreCase("init")))||
				((null != opMapping.getOpReenvio()) && (opMapping.getOpReenvio().equalsIgnoreCase("end")))){
				//Inicio de suboperación
				processSubOpInit(request, contexto, opMapping);
			}
			else if ((null != opMapping.getSubOpRetPath() && (!opMapping.getSubOpRetPath().trim().equals(""))) ||
					(null != opMapping.getOpReenvio() && opMapping.getOpReenvio().trim().equals("init")))
			{
				//Invocación de subOperación desde operacion invocante o invocación desde reenvío
				
				//Si se trata de ActionMapping que invoca a una subOperacion, 
				//salvamos el ActionMapping , el contexto de la Operación de Presentación y el Path de retorno
				//en la estructura de pila de diálogos, para poder anidar/apilar suboperaciones.
				//Si se trata de ActionMapping que realiza un reenvío,
				//salvamos el ActionMapping y el contexto de la Operación de Presentación
				processSuperOpInit(request.getSession(), opMapping, contexto);
			}
			else if (null != opMapping.getCallOpRemota() && opMapping.getCallOpRemota().trim().equalsIgnoreCase("end"))
			{
				//Retorno desde suboperacion a operacion invocante
				
				//Si se trata de un ActionMapping de retorno de suboperación con
				//apilamiento remoto (callOpRemota='end') sustituimos el contexto
				//actual por el de Base de Datos.	
				processSuperOpEnd(request, opMapping);
			}
			return true;
		}
	}
	
	private String dameTipoOperacion(Q70PasosOPMapping opMapping, String hash) {
		String tipoOperacion = null;
		
		if (null==opMapping.getCallOpRemota() && null!=opMapping.getOpReenvio() && null==hash)
			tipoOperacion = REENVIO_LOCAL;
		else{
			if (null==opMapping.getCallOpRemota() && null!=opMapping.getOpReenvio() ||
			   (null!=opMapping.getCallOpRemota() && opMapping.getCallOpRemota().trim().equalsIgnoreCase("reenvio")))
					tipoOperacion = REENVIO_REMOTO;
			else{
				if (null==opMapping.getCallOpRemota() && null==hash)
					tipoOperacion = SUBOPERACION_LOCAL;
				else 
					tipoOperacion = SUBOPERACION_REMOTA;
			}
		}
		return tipoOperacion;
	}

	private String getHashNoCaducado (HttpServletRequest request) {
		String hash = null; 
		// Si el hash no está caducado...
		if (getHashDeLaRequest(request) != null && ((request.getAttribute(HASH_CADUCADO)== null) || !(getHashDeLaRequest(request).equals(request.getAttribute(HASH_CADUCADO))))) {
			hash = getHashDeLaRequest(request);
		}
		
		return hash;
	}
	
	private boolean isPosteriorVersionUno(HttpServletRequest request) {
		// Para que sea version DOS,
		//  entonces el string que se pasa como parametro de la url remota
		//  tiene que estar contenido en los parametros de la request.
		if (request.getParameter(Q70Hash.HASH_GEREMUA_V2) != null
				&& !request.getParameter(Q70Hash.HASH_GEREMUA_V2).equals("")) {
			return true;
		}
		if (request.getParameter(Q70Hash.HASH_GEREMUA_V1) != null
				&& !request.getParameter(Q70Hash.HASH_GEREMUA_V1).equals("")) {
			return false;
		}
		return true;
	}

	private boolean isPosteriorVersionUno(Q70PasosOPMapping opMapping) {
		// Para que sea version DOS,
		//  entonces el string que se pasa como parametro de la url remota
		//  tiene que ser el mismo que el que devuelve el metodo para recuperar dicho string 
		//   segun el opMapping pasado por parametro.
		return Q70Hash.HASH_GEREMUA_V2_CON_IGUAL.equalsIgnoreCase(getNombreParametroUrlRemota(opMapping));
	}
	
	private String getHashDeLaRequest(HttpServletRequest request) {
		String hash = request.getParameter(Q70Hash.HASH_GEREMUA_V2);
		if (hash == null || hash.equals("")) {
			hash = request.getParameter(Q70Hash.HASH_GEREMUA_V1);
		}
		return hash;
	}

	/**
	 * @param opMapping Configuración de los pasos de la Operación de Presentación actual
	 * @return
	 */
	public String getNombreParametroUrlRemota(Q70PasosOPMapping opMapping) {
		return getNombreParametroUrlRemota("init".equalsIgnoreCase(opMapping.getOpVersion1()));
	}
	
	private String getNombreParametroUrlRemota(Q70EstructuraDialogo estructuraDialogo) {
		return getNombreParametroUrlRemota(!estructuraDialogo.isPosteriorVersionUno());
	}

	private String getNombreParametroUrlRemota(boolean esVersionUno) {
		if (esVersionUno) {
			return Q70Hash.HASH_GEREMUA_V1_CON_IGUAL;
		}
		return Q70Hash.HASH_GEREMUA_V2_CON_IGUAL;
	}
	
	private void doCerrarCorrectamenteBD(ResultSet rdo, PreparedStatement pstmt, Connection conn) {
		try {
			if (rdo != null)
				rdo.close();
		} catch (SQLException e) {
		}
		try {
			if (pstmt != null)
				pstmt.close();
		} catch (SQLException e) {
		}
		try {
			if (conn != null){
				conn.rollback();
				conn.close();
			}
		} catch (SQLException e) {
			Q70GestorDeExcepciones.getSingleton().recogerThrowable(e);
		}
	}
}

