Thursday, March 25, 2010

CDI @ConversationScoped y JSF 2.0

En entradas anteriores hemos visto cuales son las ventajas de CDI 1.0 y como podemos utilizarlo para definir nuestros backing beans JSF 2.0.

En esta entrada vamos a ver que son las conversaciones en CDI y como podemos utilizarlas en aplicaciones JSF 2.0.

Siguiendo con la filosofía de este blog, el proyecto completo del ejemplo, puede ser descargado de http://kenai.com/projects/javiermartinezblog/downloads/download/JSFCDIConversations.zip.


Conversaciones y ámbito de conversación en CDI

Contexts and Dependency Injection (CDI) ofrece un conjunto de servicios que permiten que clases manejadas puedan existir durante el ciclo de vida de la aplicación en ámbitos bien definidos.

Uno de los ámbitos de CDI es el ámbito de conversación. En una entrada anterior, hablamos sobre los ámbitos de CDI, por lo que no voy a explicar cuales son en esta entrada.

El ámbito de conversación permite que las instancias existan durante mas de una petición. Una conversación puede estar compuesta de varias peticiones y pueden existir varias conversaciones por sesión.

Realmente, cada petición tiene asociada una conversación. Solamente que si la conversación no es marcada como "long-running" el contexto de conversación se libera al termino de la petición.

Por el contrario si la conversación fue marcada como "long-running" en alguna petición, el contexto de conversación se propaga a las siguientes peticiones hasta que este contexto sea destruido explícitamente.

Vemos que todas las peticiones tienen asociada una conversación, y esta puede estar en uno de estados:
  • Transient: La conversación no fue iniciada explícitamente y de manera implícita existe durante el tiempo de vida de la petición.
  • Long running: La conversación fue marcada como long running y existe hasta que sea finalizada explícitamente. Todas las demás peticiones van a estar dentro del ámbito de la conversación

Por lo tanto, las conversaciones se utilizan para mantener mantener un estado entre distintas peticiones. Para esto se tiene que establecer dos cosas:

1- Que el managed bean CDI tiene un ámbito de conversación.

2- Se tiene que marcar la conversación como "long-running".

A continuación vamos a ver como se realizan ambas cosas en CDI.

1 - Especificar que el ámbito de un managed bean CDI es de conversación:

CDI tiene varias anotaciones que definen tipos de ámbito, la anotacion para definir que una clase tiene un ámbito de conversación es:


@javax.enterprise.context.ConversationScoped


2- Marcar la conversación como long-running:

CDI cuenta con un bean para controlar el ciclo de vida de las conversaciones, esta clase es:


javax.enterprise.context.Conversation


Para que una conversación se propague a las demás peticiones, hay que marcarla como long-running.

Esto se hace primero obteniendo el bean que controla la conversación a través de inyección:


@Inject Conversation conversation;


y luego ejecutando el método begin() que marca la conversación como long-running:


converstaion.begin();


Para que la conversación se destruya al termino de la petición actual se ejecuta el método end():


converstaion.end();


Aplicación de ejemplo

Como se comento anteriormente, se ilustra el uso de conversaciones CDI en aplicaciones JSF 2.0, a través de un ejemplo.

La aplicacion de ejemplo es muy sencilla, solamente cuenta con dos paginas y lo único que hace es incrementar un contador cada vez que se ejecuta un botón. Además, cuenta con una segunda pagina que muestra el valor del contador.

El backing bean Controller tiene un ámbito de conversación, por lo que la clase tiene que ser decorada con la anotacion @SessionScoped y ademas tiene que implementar la interfaz Serializable (esto es requerido para cualquier managed bean CDI con un ámbito mayor que petición). A continuacion podemos ver las porciones relevantes de la clase Controller:


@javax.inject.Named
@javax.enterprise.context.ConversationScoped
public class Controller implements Serializable {
private int number = 0;

@Inject
private Conversation conversation;

/* getters, setters y metodos omitidos */
}


La clase tiene dos campos, number que representa el contador y es donde se guarda el estado de la cantidad de veces que se presiono el botón y conversation que es el bean que controla el ciclo de vida de la conversación y se obtiene a través de inyección.

La pagina principal index.html tiene un botón que al presionarlo ejecuta el método count() del backing bean Controller. Este método lo único que hace es incrementar el valor del campo number en uno. Además, un tag JSF esta asociado al campo number del backing bean.

A continuación se pueden ver las porciones relevantes del código fuente de la pagina index.xhtml, así como los métodos del backing bean utilizados:


<h:commandButton action="#{controller.count}" value="Set count"/>
<h:outputLabel value="Count:"/>
<h:outputText value="#{controller.number}"/>



public void count()
{
number++;
}

public int getNumber()
{
return number;
}


Si la conversación no fue propagada a long-running, al termino de cada petición la conversación implícita asociada a la petición es destruida y se pierde el estado del backing bean. Por lo tanto también se pierde el contador y cada vez que se realice una solicitud, el contador tendra el mismo valor=1.

Por otro lado si marcamos la conversación como long-running, el estado del backing bean existe entre peticiones por lo que cada vez que se presiona el botón, el contador se incrementara en uno y la pagina va a desplegar el nuevo valor.

Por lo tanto la pagina index.html tambien cuenta con dos botones que permiten iniciar y terminar la conversacion, estos botones tienen asociados metodos en el backing bean que realizan estas funciones.

A continuacion se ilustra el codigo fuente de los botones, asi como los metodos en el backing bean que inician y terminan la conversacion:


<h:commandButton action="#{controller.beginConversation}" value="Begin Conversation"/>
<h:commandButton action="#{controller.endConversation}" value="End Conversation"/>



public void beginConversation()
{
if(conversation.isTransient())
conversation.begin();
}

public void endConversation()
{
if(!conversation.isTransient())
conversation.end();
}


Algo importante de destacar es que solamente se puede marcar una conversación como long-running si esta es transient. El querer marcar como long-running una conversación que ya lo es, se levanta una excepción. De igual manera solamente se puede terminar una conversación que es long-running.

Por este motivo primero se pregunta si la conversación isTransiente() o no, antes de tomar cualquier acción.


Además, la pagina principal cuenta con otro botón que permite ver el valor del contador en otra pagina result.xhtml. Esta pagina hace referencia a un backing bean (ResultController) CDI con ámbito de petición.

Como la conversación se propaga a las demás peticiones, el backing bean ResultController utiliza inyección para obtener la instancia del backing bean Controller que existe en su ámbito de petición.

A continuación se muestra el código fuente del backing bean ResultController:


@Named
@RequestScoped
public class ResultController {

private @Inject Controller controller;

public int getCount()
{
return controller.getNumber();
}
}


Eso es todo, en próximas entradas vamos a seguir viendo otras características de JSF 2.0, CDI 1.0 y Java EE 6 en general.