Gestión de Conversaciones en Seam
— PEDRO DELGADO YARZA 2014/02/03 18:58
El ámbito conversación, proviso por Seam, es uno de los más moldeables de entre los ámbitos que tenemos disponibles en nuestro contexto de aplicación. Las conversaciones nos permiten definir rangos mayores o menores donde el contenido de las clases que contienen será mantenido en memoria, dándonos la posibilidad de destruir dicho contexto y su contenido, cuando el flujo de la aplicación nos lleve por un camino que ya no necesita dichos valores.
Existen diferentes tipos de conversaciones, y debemos conocer cómo gestionarlas para poder aprovechar el rendimiento que nos brindan.
A modo de resumen, podemos indicar que una conversación es una unidad de trabajo, compuesta por varias páginas y sus clases java asociadas, que tienen sentido bajo un contexto determinado, fuera de ese contexto el contenido cargado en memoria no tiene sentido por lo que es prescindible.
Tipos de conversaciones
Pese a que hemos estado hablando de las conversaciones como si sólo fuesen de un tipo, realmente nos encontramos con dos tipos básicos:
- Conversación temporal: Iniciada en cada petición, salvo que se detecte una conversación de largo recorrido, en ese caso se añadirá a esa conversación los nuevos objetos creados.La vida de una conversación temporal es similar a la del ámbito Página, puesto que la conversación se destruye cuando la respuesta a la petición es renderizada por el navegador. No obstante, una conversación temporal puede convertirse en una conversación de largo recorrido si lo indicamos en nuestra aplicación, de esta manera la conversación no se destruirá hasta que indiquemos que debe cerrarse.
- Conversación de largo recorrido: Iniciada como una conversación temporal, pero ampliada a través de la configuración del flujo de navegación. Presenta como identificación un número que se pasará en todas las respuestas para mantener la referencia al contexto de la conversación y poder acceder a todos los datos cargados en ella. Durante todo el flujo de navegación, mientras se mantenga la conversación activa, los nuevos datos creados se almacenarán en memoria dentro del contexto de esa conversación.
El proceso de creación y destrucción de conversaciones de manera esquemática quedaría como la siguiente figura:
El Modelo de Conversación de Seam
El modelo de conversación de Seam, sigue las siguientes reglas:
- Siempre hay un contexto de conversación activo durante la todas las fases del ciclo de vida de una solicitud de JSF.
- Al final de la fase de restauración de la vista del ciclo de vida de una solicitud de JSF, Seam intenta restaurar el contexto de conversación de largo recorrido previo. Si no existe, Seam crea un nuevo contexto de conversación temporal.
- Cuando se define un método con la anotación @Begin, el contexto de conversación temporal pasa a ser un contexto de conversación de largo recorrido.
- Cuando se define un método con la anotación @End, el contexto de conversación de largo recorrido pasa a ser un contexto de conversación temporal.
- Al final de la fase de renderizado de la respuesta del ciclo de vida de una solicitud de JSF, Seam almacena el contenido del contexto de conversación de largo recorrido o destruye el contenido del contexto de conversación temporal.
- Cualquier solicitud Faces (un postback JSF) propagará el contexto de conversación. Por defecto, las solicitudes no-Faces, (solicitud GET, por ejemplo) no propagan el contexto de conversación.
- Si el ciclo de vida de una solicitud JSF es forzado por una redirección, Seam almacena y restaura transparentemente el contexto de conversación actual, a no ser que que ya haya sido finalizado mediante la anotación @End(beforeRedirect=true).
Si se quiere propagar una conversación de Seam a lo largo de solicitudes no-Faces, se necesita especificar explicitamente el identificador de la conversación como un parametro de solicitud. El ejemplo muestra como hacerlo con la etiqueta conversationId de la libreria de Seam:
<h:outputLink value="main.jsf"> <s:conversationId/> <h:outputText value="Continue"/> </h:outputLink>
Si se desea deshabilitar la propagación del contexto de conversación para un postback de JSF, utilizarmeos la etiqueta conversationPropagation de la libreria de Seam:
<h:commandLink action="main" value="Exit"> <s:conversationPropagation type="none"/> </h:commandLink>
Hay que destacar, que deshabilitar la propagación del contexto de conversación no es absolutamente lo mismo que finalizar la conversación. La etiqueta conversationPropagation, también sirve para iniciar, finalizar o unirse a una conversación, o iniciar un anidado (nested) de conversaciones.
<h:commandLink action="main" value="Select Hotel"> <s:conversationPropagation type="begin"/> </h:commandLink> <h:commandLink action="main" value="Exit"> <s:conversationPropagation type="end"/> </h:commandLink> <h:commandLink action="main" value="Select Hotel"> <s:conversationPropagation type="join"/> </h:commandLink> <h:commandLink action="main" value="Select Child"> <s:conversationPropagation type="nested"/> </h:commandLink>
Este modelo de conversación, simplifica la construcción de aplicaciones que se comportan correctamente con respecto a una operación multi-ventana. Para muchas aplicaciones, esto es todo lo que se necesita. Algunas aplicaciones más complejas tienen uno o ambos de los comportamientos siguientes:
- Una conversación abarca pequeñas unidades de interación de usuario, que se ejecutan en serie o concurrentemente. El anidado nested de conversaciones más pequeño, tiene su propio conjunto aislado de estados de conversación , y además tiene acceso al estado de la conversación saliente.
- El usuario es capaz de conmutar entre varias conversaciones dentro la misma ventana del navegador. Esta característica se llama gestión del espacio de trabajo (workspace management).
Anidado (Nested) de Conversaciones
El anidado de conversaciones se crea invocando un método anotado con @Begin(nested=true) dentro del ámbito de una conversación existente. El anidado de conversaciones tiene su propio contexto de conversación, pero puede leer valores de otros contextos de conversación externos. Estos contextos son de solo lectura para el anidado de conversación, pero como los objetos se obtienen por referencia, cambios de los propios objetos pueden ser reflejados en el contexto externo.
- La anidamiento de una converación, inicia un contexto que es apilado sobre el contexto de la conversación original o exterior. La conversación exterior es considerada la padre.
- Cualquier valor eyectado (outjected) o directamente establecido dentro del contexto de una conversación anidada, no afecta a los objetos que estan accesibles en el contexto de la conversación padre.
- La inyección (injection) o busqueda en el contexto context lookup desde el contexto de conversación, primero buscará el valor en el contexto de la conversación actual, y si no encuentra el valor, procesará la pila de conversaciones si la conversación esta anidad. Este comportamiento se puede modificar.
Cuando posteriormente se encuentra un método anotado con @End, la conversación anidada será destruida, y la conversación exterior se reanudará, obteniendola mediante un pop de la pila de conversaciones. Las conversaciones pueden ser anidadas a cualquier profundidad.
Ciertas actividades del usuario (como utilizar el botón retroceder back) puede causar que la conversación exterior se reanude antes de que la conversación interna sea finalizada. En este caso es posible mantener concurrentemente conversaciones anidadas a lo largo de la misma conversación exterior. Si la conversación exterior finaliza antes de que la conversación anidada finalice, Seam destruye todas los contextos de conversación anidades con el contexto exterior.
La conversación del fondo de la pila de conversaciones es la conversación raíz. Destruir esta conversación, siempre destruirá a todos sus descendientes. Se puede lograr declarativamente especificando @End(root=true) en un método.
Una conversación puede permanecer en un estado continuable. Las conversaciones anidadas permiten a una aplicación capturar un estado continuable y consistente en varias puntos de la interacción del usuario, por lo tanto, asegurar un comportamiento correcto en la utilización del botón de retroceso.
Como se ha dicho anteriormente, si un componente existe en una conversación padre de una conversación actual anidada, la conversación anidada puede utilizar la misma instancia. Ocasionalmente, es util tener diferentes instancias en cada conversación anidada, pero esto hara que la instancia que existe en la conversación padre sea invisible para sus conversaciones hijas. Se puede lograr este comportamiento, anotando a un componente con @PerNestedConversation.
Iniciar una Conversación con una solicitud GET
JSF no define ninguna acción que se pueda lanzar cuando una pagina es accedida mediante una solicitud no-faces (como por ejemplo, una solicitud HTTP GET). Esto suele ocurrir cuando se utilizan marcadores (bookmark) de pagina o si navegamos mediante un <h:outputlink/>.
Muchas veces necesitamos iniciar una conversación inmediatamente al acceder a una página. Ya que no tenemos un método action de JSF, no podemos resolver el problema de manera normal, es decir, anotar el método con la anotación @Begin.
Un problema posterior surge si la pagina necesita que algún estado sea cargado dentro de una variable de contexto. Pare resolver esto podemos actuar de la siguiente manera:
- Si el estado es mantenido en un componente Seam, se puede cargar el estado con un método anotado con @Create. Se puede utilizar tanto en beans de respaldo (POJOs) como en EJBs de sesión.
- Si no se mantiene el estado, se puede definir un método anotado con @Factory para la variable de contexto. El nombre que aparece dentro de la anotación, sera el que tenga la variable de contexto. Se puede utilizar tanto en beans de respaldo (POJOs) como en EJBs de sesión.
@Create public void init() { // Logica para cargar el estado ... } @Factory("lista") public List<String> getLista() { // Logica para cargar la lista ... return lista; }
Si ninguna de estas opciones funciona, Seam permite definir un método llamado page action en el fichero pages.xml, en la propiedad action del elemento page. Esta acción se lanzará al inicio de la fase de renderizado, si el método devuelve un valor no nulo como resultado, Seam procesará las reglas de navegación, navegando a lo que indique la regla que encaje con la respuesta del método lanzado.
<pages> <page view-id="/messageList.jsp" action="#{messageManager.list}"/> ... </pages>
Si lo único que se quiere es iniciar una conversación antes de que la pagina sea renderizada, se puede utilizar el componente de Seam conversation, llamando al método begin.
<pages> <page view-id="/messageList.jsp" action="#{conversation.begin}"/> ... </pages>
Si lo que queremos en finalizar la conversación, podemos utilizar el método end del componente conversation.
<pages> <page view-id="/messageList.jsp" action="#{conversation.end}"/> ... </pages>
Si se quiere más control, para por ejemplo, unirse a una conversación existente, iniciar una conversación anidada, se puede utilizar el elemento <begin-conversation>.
<pages> <page view-id="/messageList.jsp"> <begin-conversation join="true" /> <page> ... </pages>
Tambien podemos utilizar el elemnto <end-conversation> para finalizar una conversación.
<pages> <page view-id="/home.jsp"> <end-conversation/> <page> ... </pages>
En resumen, para solventar el problema de iniciar una conversación en una solicitud no-faces, ahora dispones de cinco opciones:
- Utilizar la anotación @Begin en el método anotado con @Create
- Utilizar la anotación @Begin en el método anotado con @Factory
- Utilizar la anotación @Begin en el método action page en el fichero pages.xml
- Utilizar el elemento <begin-conversation> en el fichero pages.xml
- Utilizar #{conversation.begin} en el método action page en el fichero pages.xml
Los Componentes JSF de Seam <s:link> y <s:button>
Los command link de JSF siempre realizan un sumisión mediante JavaScript, que rompe la carasteristica “abrir en una nueva ventana” o “abrir en una nueva pestaña” del navegador web. En JSF plano, se necesita utilizar <h:outputLink> si se necesita esta funcionalidad. Pero tenemos dos limitaciones importantes:
- JSF no proporciona vias para lanzar un métdo action para un <h:outputLink>
- JSF no propaga una fila seleccionada de un DataModel si no hay submisión del formulario
Podemos utilizar los page action de Seam para resolver el primer problema. Pero para resolver el segundo problema tendremos que utilizar la etiqueta JSF <s:link> de Seam. Para esta etiqueta podemos especificar el identificador de una vista a la que navegar en la propiedad viewId.
<s:link view="/login.xhtml" value="Login"/>
También se puede especificar un método action, en este caso el resultado del método determina la página resultado en la propiedad action.
<s:link action="#{login.logout}" value="Login"/>
Si se especifican tanto el identificador de la vista como el método action, se utilizara el identificador de la vista a no ser que el método devuelva valores no nulos como resultado.
<s:link view="/login.xhtml" action="#{login.logout}" value="Login"/>
El enlace, propagará automáticamente la fila seleccionada en un DataModel utilizado dentro de un <h:dataTable>.
<s:link view="/hotel.xhtml" action="#{hotelSearch.selectHotel}" value="#{hotel.name}"/>
Se puede iniciar, unir, finalizar, anidar conversaciones o abandonar el ámbito de una conversación utilizando la propiedad propagation, que puede tener los valores.
- begin : inicia una nueva conversación
- end : finaliza la conversación actual
- join : unirse la conversación actual
- nest : inicia una conversación anidada
- none : abandona la conversación actual
Finalmente, si necesitamos que el enlace se dibuje como un botón, utilizaremos la etiqueta <s:button>
<s:button action="#{login.logout}" value="Logout"/>
También hay que indicar, que <s:button> y <s:link>, no realizan submit del formulario. Si se necesitan pasar valores, se puede utilizar las etiquetas <ui:param> de Facelets o <f:param> de JSF.
<s:link view="/paginas/varias_tablas/ComentarioList.xhtml" rendered="#{comentarioList.previousExists}" value="#{messages.left}#{messages.left} First Page" id="firstPage"> <f:param name="firstResult" value="0"/> </s:link>
Identificadores de Conversación Naturales o Personalizables
Cuando se trabaja con conversaciones que tratan con objetos persistentes, a veces, es deseable utilizar la clave de negocio natural del objeto, en lugar de la estándar, sustituyendo el identificador de la conversación. En estos casos podemos encontrar dos tipos de escenarios diferentes:
- Redirección sencilla a una Conversación existente: Si el usuario solicita la misma operación varias veces es bastante útil reutilizar la misma conversación para optimizar los recursos utilizados. Con las conversaciones naturales, es muy fácil unir a un usuario con una conversación existente, y saber donde la abandona.
- Utiliar URLs amigables: Consiste en una jerarquía navegable (se puede navegar editando la url) y en una URL con significado (como se hace en las wikis). Para muchas aplicaciones, las URLs amigables no son importantes, no obstantes en algunos casos puede ser interesante introducirlas.
Para crear conversaciones naturales, utilizamos el fichero pages.xml. En el siguiente ejemplo definimos una conversación llamada PlaceBid.
<conversation name="PlaceBid" parameter-name="auctionId" parameter-value="#{auction.auctionId}"/>
Los atributos a rellenar son los siguientes:
- name: Sirve para identificar inequívocamente la conversación, y es utilizado por la definición de la página para identificar una conversación nombrada y poder utilizarla.
- parameter-name: Define el parámetro de solicitud que contendrá el identificador natural de la conversación, en lugar del parámetro que contiene el identificador por defecto.
- parameter-value: Define una expresión EL para evaluar el valor de la clave de negocio natural a utilizar como identificador de la conversación.
A continuación,definimos qué paginas pueden participar en la conversación nombrada. Esto se hace, especificando el atributo de la conversación en la definición de la página. Como podemos observar, en la propiedad conversation del elemento page, especificamos el nombre de la conversación nombrada.
<page view-id="/bid.xhtml" conversation="PlaceBid" login-required="true"> <navigation from-action="#{bidAction.confirmBid}"> <rule if-outcome="success"> <redirect view-id="/auction.xhtml"> <param name="id" value="#{bidAction.bid.auction.auctionId}"/> </redirect> </rule> </navigation> </page>
Cuando iniciemos o redireccionemos a una conversación natural, hay varias opciones para especificar el nombre. Vamos a comenzar viendo el siguiente ejemplo de definición de una página.
<page view-id="/auction.xhtml"> <param name="id" value="#{auctionDetail.selectedAuctionId}"/> <navigation from-action="#{bidAction.placeBid}"> <redirect view-id="/bid.xhtml"/> </navigation> </page>
Podemos observar como cuando se invoca el método action #{bidAction.placeBid} seremos redirigidos a la página /bid.xhtml, que como hemos vista antes, está configurada con la conversación PlaceBid. La declaración del método action puede ser la siguiente.
@Begin(join = true) public void placeBid() { ... }
Cuando las conversaciones nombradas son especificadas en el elemento <page>, la redirección a la conversación tiene lugar como parte de las reglas de navegación, después de que el método action haya sido invocado.
Existe un problema cuando se redirige a una conversación existente, si la redirección tiene que ocurrir antes que el método sea invocado. En ese momento, es necesario especificar el nombre de la conversación cuando el método action sea invocado.
Una forma de hacer esto, es utilizar la etiqueta JSF de Seam s:conversationName, donde especificamos el nombre de la conversación a utilizar.
<h:commandButton id="placeBidWithAmount" styleClass="placeBid" action="#{bidAction.placeBid}"> <s:conversationName value="PlaceBid"/> </h:commandButton>
Otra alternativa es especificar el atributo conversationName cuando se utilizen las etiquetas JSF de Seam s:link o s:button.
<s:link value="Place Bid" action="#{bidAction.placeBid}" conversationName="PlaceBid"/> ... <s:button value="Place Bid" action="#{bidAction.placeBid}" conversationName="PlaceBid"/>
Finalizar conversacion existente e iniciar una nueva, en un mismo enlace
Cuando queremos acceder a una página destruyendo la conversación previa existente y comenzando una nueva nos encontramos con un problema. Como sabemos, si usamos la directiva <begin join=“true”…> en el fichero page.xml para comenzar una conversación de largo recorrido. Por otro lado en la página que redirige a esta nueva anotamos el método clickAndKillLastConversation, que enlaza a la página con un @End o usamos propagation=“end” en el <s:link que enlaza a la página.
En principio parece todo correcto, la página “padre” mata la conversación y la hija crea una nueva, pero el resultado real es que no se finaliza la conversación existente, por lo tanto no se crea una nueva, sino que nos unimos a la que había.
El problema viene porque a Seam le llegan dos órdenes contradictorias, por un lado <begin join=“true”…> que inidica que cree o se una a una conversación y por otro que la finalice. En ese momento Seam ha de escoger entre las dos opciones conservando la conversación existente.
Para solucionar esto, lo que se debe hacer es finalizar manualmente la conversación y mantener el begin, para que se inicie una conversación nueva, en el momento en que finaliza la fase de renderizado de la página. Para ello tenemos a disposición las clase MenuManagerBean, con el método clickAndKillLastConversation.
Si el enlace se encuentra en el menú principal de la aplicación, deberá de utilzar el método que devuelve la página al que quiere ir. Quedando los enlaces de menú de la siguiente manera:
<s:link value="#{messages['menu.link.administracion.cambioClave']}\" action="#{menuManagerBean.clickAndKillLastConversation('/paginas/administracion/cambioClave.xhtml')}" disabled="#{!identity.isEnabledMenu('1010_MEADCAMCLA')}"/>
- fdw2.0/fundeweb2.0/gt/gestion_de_conversaciones_en_seam.txt
- Última modificación: 07/11/2017 10:46
- (editor externo)