Intro
One of of Seam 2′s coolest features was the workspace management feature. (this must me the billionth times someone says that )
With the advent of Java EE 6 & and CDI, one would expect that the implementation of such a cool feature would be top priority to the Seam 3 tem, but – hey! – It’s JBoss we are talking about! So no, there is no conversation/workspace management in Seam 3 :-/
Andy Gibson’s solution
Andy Gibson implemented a basic workspace manager; I tested it out with JBoss AS 7.0.2.Final and it worked. Here are the pros and cons of Andy’s solution:
Pros:
- It uses the standard CDI specification API.
- It’s portable across different Java EE 6 application servers.
- It’s extensible in that you can, for example, associate descriptions (i.e. use case names) to conversations.
Cons:
- You have to clutter your CDI bean’s code with workspace management code – which is poor separation of concerns.
Here comes Weld
Weld (JBoss’ implementation of CDI) offers the possibility to implement workspace management in a different, more elegant way. Weld introduces the notion of a ManagedConversation
, which extends the Conversation
interface with the abilty to lock, unlock and touch (update the last used timestamp) a conversation; it also adds a method to get the timestamp of the moment when conversation was last used. Finally, all non-transient conversations in a session can be obtained from the ConversationContext
, as can the current conversation.
My workspace management solution is build upon Weld’s perks. Here are its pros and cons:
Pros:
- You don’t have to add workspace management code to your CDI bean.
- You don’t even have to inject anything into yout CDI beans.
- It allows for separation of concerns.
Cons:
- It’s depends on Weld.
- It only works with JBoss AS 6/7.
- It’s not portable across different CDI implementations (i.e. won’t work on Glassfish, WebSphere, etc…).
My solution’s source code
package cdi; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; import org.jboss.solder.logging.Logger; import org.jboss.weld.context.ConversationContext; import org.jboss.weld.context.ManagedConversation; import org.jboss.weld.context.http.Http; import util.ManagedConversationComparator; /** * This bean needs <strong>Weld</strong>. * * @author Luca * */ @Named @RequestScoped public class WorkspaceBean implements Serializable { private static final long serialVersionUID = -457959303651081423L; @Inject @Http private ConversationContext conversationContext; @Inject private Logger logger; /** * Tells whether a ManagedConversation has timed out. * * @param conv * @return <i>true</i> if it has timed out; <i>false</i> if it has not timed * out yet */ public static boolean timedOut(ManagedConversation conv) { return conv.getTimeout() < (new Date().getTime() - conv.getLastUsed()); } /** * Get the list of active (long running OR once long-running, then timed-out * but still active) conversation Id's. * * @return a List<String> of conversation ids */ public List<String> getActiveConversationIds() { List<String> result = new ArrayList<String>(); for (Iterator<ManagedConversation> i = getActiveConversations() .iterator(); i.hasNext();) { result.add(i.next().getId()); } return result; } /** * Get a list of the active ManagedConversation. To be in the list, a * conversations must satisfy one of these two requirements: * <ul> * <li>It's long running and has not timed out yet;</li> * <li>It was long running and has timed out.</li> * </ul> * The list does NOT contain transient conversations. * * @return a Collection of org.jboss.weld.context.ManagedConversation, * sorted by conversation id. */ public Collection<ManagedConversation> getActiveConversations() { List<ManagedConversation> result = new ArrayList<ManagedConversation>( conversationContext.getConversations()); // Weld's documentation reads: // "conversations are not assigned ids until they become non-transient." // Actually, that's not the case! ConversationContext.getConversations() // lacks the current conversation even though it's already long running! // // We workaround by adding the current conversation, if it's long // running. ManagedConversation currentConversation = conversationContext .getCurrentConversation(); if (!currentConversation.isTransient()) { if (!result.contains(currentConversation)) result.add(currentConversation); } // Sort the conversations by conversation id in ascending order Collections.sort(result, new ManagedConversationComparator()); return result; } /** * Get the list of active, long running, not yet timed-out conversation * Id's. * * @return */ public List<String> getLongRunningConversationIds() { List<String> result = new ArrayList<String>(); for (Iterator<ManagedConversation> i = getLongRunningConversations() .iterator(); i.hasNext();) { result.add(i.next().getId()); } return result; } /** * Get all AND ONLY the <strong>long running</strong> active conversations * that haven't timed out yet. * * @return a Collection of long running * org.jboss.weld.context.ManagedConversation, sorted by * conversation id. */ public Collection<ManagedConversation> getLongRunningConversations() { Collection<ManagedConversation> result = new ArrayList<ManagedConversation>(); ManagedConversation conv; // Get all the active conversations Collection<ManagedConversation> activeConversations = getActiveConversations(); for (Iterator<ManagedConversation> i = activeConversations.iterator(); i .hasNext();) { conv = i.next(); // If the conversation is STILL long running, add it to the result if (!timedOut(conv)) { result.add(conv); } } return result; } /** * Logs info about the workspace */ private void output() { logger.info("Current Conversation: " + conversationContext.getCurrentConversation()); logger.info("Long Running Conversations: "); Iterator<ManagedConversation> i = getLongRunningConversations() .iterator(); while (i.hasNext()) { logger.info(i.next()); } logger.info("Active Conversations: "); i = getActiveConversations().iterator(); while (i.hasNext()) { logger.info(i.next()); } } /* * Life cycle methods */ @PostConstruct public void postConstruct() { logger.info("postConstruct()"); output(); } @PreDestroy public void preDestroy() { logger.info("preDestroy()"); output(); } }
Here’s an .xhtml snippet that displays the workspace:
<h1>Long Running Conversations</h1> <ui:repeat var="_conv" value="#{workspaceBean.longRunningConversations}"> <h:link outcome="wizard2" value="Goto Conversation #{_conv.id}"> <f:param name="cid" value="#{_conv.id}" /> </h:link> <br/> </ui:repeat> <h1>Active</h1> <ui:repeat var="_conv" value="#{workspaceBean.activeConversations}"> <h:link outcome="wizard2" value="Goto Conversation #{_conv.id}"> <h:outputText value=" (timedout)" rendered="#{workspaceBean.timedOut(_conv)}"/> <f:param name="cid" value="#{_conv.id}" /> </h:link> <br /> </ui:repeat>
Some remarks
- Weld’s
ManagedConversation
andConversationContext
are cool; however, they are not part of the CDI specification; I’d prefer them to be part of some CDI extension – Seam Solder, for instance – and not of Weld itself. - It would be useful if the
ManagedConversation
had methods to set and get the conversation description (i.e. use case name), because it’s needed by 90% of Web apps. - A
boolean ManagedConversation.isTimedOut()
method would be very useful too (we would not need to implement a Comparator) - It would also be very useful to be able to observe/intercept
Conversation.begin()
,Conversation.timeout()
,Conversation.end()