Tuesday, May 18, 2010

Conversation scope: CDI (JSR-299) vs Seam 2

CDI (JSR-299) is the new Java standard for contextual type-safe dependency injection and as such it is an integral part of Java EE 6 platform. Still, it will also work in any other environment, be it a servlet container or simple Java SE. It was insipered by Seam 2 (and what is therein known as 'bijection') and Guice.

The Naming Game


Initially, CDI was known as WebBeans but the name had been changed before the spec was finalized. The reference implementation is called Weld but was also at some point called WebBeans. Yet again, that name is now obsolete as should not be used. Thus, WebBeans now refers to... well... nothing, so you're free to forget all about it. You will still see the old name popping up around the Web occasionally but it should fade into oblivion soon enough. More info on this story can be found at the very beginning of the reference documentation.

Conversations


The existance of a built-in conversation scope in CDI will immediately lure most of the Seam 2 developers into a wrong assumption of familiarity. Wrong because although they serve the same purpose conversations in CDI and Seam 2 do not always behave identically.
While all Seam's conversations, both transient and long-running, invariantly survive redirects (unless specifically instructed not to through endBeforeRedirect=true), conversations in CDI will survive only if they are promoted to long-running (through conversation.begin() ) during the original request. A transient conversation will be destroyed at the end of the original request and a new one will be spawned for each redirect. In other words, without a long-running conversation, conversation scoped components in Seam 2 will behave (by default) as if they were in what you might know as flash scope from other frameworks and languages (this scope will also exist in Seam 3 with the related annotation @FlashScoped). Under the same conditions, @ConversationScoped beans in CDI will behave as if they were @RequestScoped. In case a long-running conversation is present, the behavior will be the same in both Seam 2 and CDI.

Reference documentation for some reason never states this explicitly so it can be very misleading for Seam 2 developers. Luckily though, there's a table on the official site mapping Seam 2 scopes to their CDI equivalents where possible and gives brief description of the differences.

Example


To further clarify this (probably needlessly), let's illustrate with a simple example implemented both in Seam 2 and CDI.
In order to keep everything at a bare minimum, we'll just create a single component (or bean if you prefer), named MessageBean, and a single Facelets view that accepts the new input and attempts to display the previous one after a redirect.
Note: Import and package statements are omitted from the following code listings.

Seam 2 version

MessageBean class:

@Name("msg")
@Scope(ScopeType.CONVERSATION)
public class MessageBean implements Serializable {

private String message;

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public String redir() {
return "redir";
}
}

CDI version


You'll notice a striking resemblance to the Seam 2 version.

MesageBean class:

@Named("msg")
@ConversationScoped
public class MessageBean implements Serializable {

// @Inject Conversation conversation;
private String message;

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public String redir() {
//if (conversation.isTransient())
// conversation.begin();
return "redir";
}
}

Facelets view (the same in both cases):

Note: Again, extra headers are omitted.

input.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<div>
Previous input: #{msg.message}
</div>

<h:form>
<h:inputText id="messageText" value="#{msg.message}" required="true"/>
<h:commandButton id="redirectButton" value="Redirect" action="#{msg.redir}"/>
</h:form>
</html>
Configuration (the same in both cases):

faces-config.xml:

<navigation-rule>
<navigation-case>
<from-outcome>redir</from-outcome>
<to-view-id>/input.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>

If you were to execute both these examples, you'd soon realize the difference between the frameworks. While Seam 2 version keeps your input across the redirect and displays it on the next rendering, even without promoting the conversation, CDI will destroy the context on redirect leaving you with no message to display. Note that if the commented lines in the CDI version of MessageBean were to be uncommented, thus promoting the conversation to a long-running one, the message would survive, together with the whole conversation context (which would afterwards be reused in the next cycle in this case). Normally, you'd also want a way to end the conversation at some point through conversation.end() instead of waiting for the timeout (which is ultimately decided by the server).

Download


Below you can find both implementations as war files ready for deployment. The examples are meant to be deployed to JBoss AS so some tweaks (like adding Weld or any other CDI implementation jar to the classpath) might be needed in order to get it running on Tomcat or other servers/containers.

conversationCDI.war (tested on JBoss AS 6)
conversationsSeam.war (tested on JBoss AS 5, does not work on JBoss AS 6 for some reason)

References


Weld main site
Seam 2 tutorial