Apache MyFaces
UI-Component Sets
MyFaces Orchestra
Project Documentation

The Conversation Scope Problem

Often a logical operation consists of multiple JSF requests to the server. For example, purchasing an insurance policy requires completing a number of related forms (often referred to as a "conversation", "workflow" or "dialog"), during which the same java objects need to be kept in memory.

However JSF provides only three scopes for data to be stored in:

  • Application scope

  • Session scope

  • Request scope

Application scope is only rarely of use; such data is shared across all users of that JSF application. Request scope is not useful for the above scenario; all data stored in request scope is discarded at the end of each "command".

Session scope can be used to hold data across multiple requests (a conversation), but suffers from a number of other issues:

  • When the conversation is complete, it is best to discard all the related objects in order to save memory. However this is quite difficult to do when using session scope.

  • When a user performs some task for a second time (eg purchasing a second insurance policy), it is usually better for the backing beans to be new instances rather than having whatever state they had at the end of the previous pass. This is difficult to achieve when the beans are in session scope; every relevant bean needs to be explicitly deleted or reset. However when these objects are stored in a conversation this happens automatically as the conversation (with all its beans) has been discarded.

  • The user cannot have multiple windows open on the same site. Sessions are typically tracked using cookies, and all windows associated with the same browser instance share cookies and therefore are within the same "session". If two windows are created for the same session then very strange effects can be caused due to the same "backing beans" being used by the two windows. Note that JSF implementations generally provide support for multiple concurrent windows (eg MyFaces and Sun RI) but this only means that the JSF *components* are safe for use with multiple windows; any application that uses only request-scope beans will therefore work correctly but apps with session-scoped beans will still suffer confusion.

The Tomahawk library provides a partial solution to this conversation problem with the t:saveState tag, which allows data to be bound to a JSF View; it is then available across multiple consecutive requests to the same view. It can also be "passed" to a following view when navigation occurs. However this can be difficult to use, as every bean that needs to be part of the conversation needs to be explicitly tracked.

The draft JSF 2.0 specification currently defines a new "view" scope which also provides a partial solution to this issue that is similar to the Tomahawk t:saveState tag.

The Orchestra library provides another alternative. This solution works across all JSF implementations (particularly Apache MyFaces and the Sun Reference Implementation). It works for Java 1.4 or later. If java1.5 is being used then custom annotations are available to make its use even easier.

Orchestra does require conversation-scoped managed beans to be declared via a good dependency-injection (aka IOC) framework with AOP support. The standard JSF managed-beans facility does not provide sufficient flexibility. While it should be possible for Orchestra to be integrated with any appropriate such framework it initially supports only Spring 2.x. This is no great drawback as there are many other good reasons to use Spring! In the remainder of this document we shall assume Spring is the dependency-injection framework being used.

Various other projects (JBoss Seam, Apache Shale Dialogs, Spring WebFlow) provide conversation/dialog support that is similar to Orchestra. See the Orchestra wiki pages for up-to-date comparisons of Orchestra with other projects.

Orchestra Conversation Scope Features

The normal behaviour for JSF is that when an EL expression references a bean that cannot be found anywhere in the current scopes, the managed bean declarations are searched for the specified name. If a match is found then the bean declaration is used to create an appropriate object instance and insert it into the appropriate scope. The JSF standard provides a way for variable lookup to be extended, and Spring provides an adapter that makes Spring bean declarations accessable to JSF just like managed beans declared in the standard manner.

While "managed beans" declared using the standard JSF syntax can only be declared with app, session or request scope it is possible with Spring 2.0 to declare custom scopes. Orchestra makes "conversation scopes" available for use. When a bean is instantiated which is declared to be part of "conversation Foo" then the conversation with that name is looked up and the bean inserted into it. This scope is user-specific (ie is a child of the session scope) and is created if it doesn't yet exist.

So far, the effect is just the same as using session scope for these beans. However a conversation acts as a container for all the beans configured with a particular conversation name. When a conversation ends, all beans associated with that conversation can then be discarded together which is difficult to achieve with simple session storage. A conversation can be terminated in a number of ways:

  • access-scoped conversations end when a request occurs that does not access any bean in that conversation;

  • a JSF endConversation component is provided that can be inserted into a page;

  • a direct call can be made from a backing bean, eg after performing a "save" or "cancel" operation;

  • a conversation timeout can be configured to automatically expire conversations after a specified time limit.

And as conversations have names, multiple conversations (bean groupings) can exist concurrently.

Conversation names are declared simply by specifying attribute orchestra:conversationName on the Spring bean definition. If no name is provided, then the bean is placed in its own private conversation (which happens to have a name equal to the bean name).

A conversation can have a lifetime of "access" or "manual". An access-scoped conversation is automatically ended (ie deleted) if a request is executed which does not reference any bean in that conversation's scope. This is very convenient when a sequence of pages all have at least one reference to a bean of that conversation scope. If the user navigates to any other page (via direct url entry, or clicking a link, etc) then after that new page is rendered the old (obsolete) conversation scope is automatically discarded. Only when a user's path through the application can reference pages that do not reference conversation-scoped beans is the "manual" conversation necessary - and in that case, an explicit endConversation component (or direct API call) must be used to discard beans when no longer needed.

Orchestra also provides the concept of a "conversation context", which holds a set of named conversations. A "separateConversationContext" JSF component creates a new context. When this is a parent of any command component (eg a commandLink) then a new conversation context is automatically created when that command is executed. This allows multiple windows to access the same site while having completely independent sets of objects that are of "conversation scope". A hidden "id" emitted into pages specifies what the current conversation context is, ensuring the new windows "sticks" with its associated conversation context.

Orchestra Conversation Scope Limitations

Conversation-scoped beans are always stored (indirectly) in the user's http session. This means that Orchestra requires server-side storage.

Notes for Seam Users

The JBoss Seam library also provides a conversation scope. The purpose of this scope is the same in Orchestra and Seam, but the details are a little different. We aren't Seam experts, so this section is just our best attempt at describing the fundamental differences; please contact the mailing list if you are a Seam expert and have corrections for this section...

Seam's conversations are "request-centric", ie a request always has an associated conversation (which may be "transient", ie of request scope!). Beans can be bound to the current conversation via "out-jection" annotations. Therefore it makes sense to ask "what is the current conversation for this request" from anywhere, even when the object making the call is not "in" a conversation.

Orchestra instead is "bean-centric", ie a bean instance may have an associated conversation, but a request does not. A request can access multiple beans, and each of those beans can potentially be in a different conversation. A method can ask "what conversation am I being invoked in", ie what conversation-scoped bean has invoked the current method (and the answer may be "none"). Note that having multiple conversations is really useful for pages where different parts of the page (different "panels") have different lifetimes.

Seam's concept of "nested" conversations isn't generally needed in Orchestra, as Orchestra supports multiple concurrent conversations (each with a separate "name"). In the case where a page calls itself, Orchestra does then need to "nest" data, as the beans will try to use the same conversation. In this case, a nested conversation-context must be used.

Seam uses an @Begin annotation on a method (or a non-standard "begin-conversation" JSF navigation rule) to create a long-lived conversation. Orchestra does it automatically; whenever an instance of a bean is created which is marked as being in a conversation "foo" then conversation "foo" starts. Seam uses out-jection annotations to indicate which beans belong in the conversation; instead with Orchestra the bean declarations themselves indicate which conversation the bean instance is in.

Both Seam and Orchestra need a mechanism to end a conversation. For Orchestra's "manual-lifetime" conversations, the o:endConversation tag or a call to method Conversation.getCurrentInstance().invalidate() are pretty much equivalent to the Seam @End annotation. For Orchestra's "access-lifetime" conversations, the conversation is terminated automatically; we are not aware of any Seam equivalent for this.

Seam's conversation-context is almost identical to Orchestra's conversation-context. However with Seam, pages need to explicitly include the context id into urls, by using EL expression #{conversation.id} or the s:conversationPropagation tag where appropriate. Orchestra automatically does this; instead, only links that should not propagate the conversation context need to be marked (using the o:separateConversationContext tag). This works for all JSF components as long as they call ExternalContext.encodeURL(), which all code that generates URLs should do.

Seam's "conversation switcher" functionality is equivalent to switching between different Orchestra "conversation contexts".

Notes for Spring WebFlow Users

The Spring WebFlow library also provides a conversation scope. The purpose of this scope is the same in Orchestra and Seam, but the details are a little different.

WebFlow appears to have a similar approach to Seam (request-centric rather than Orchestra's bean-centric approach). See the first couple of paragraphs above describing the Seam/Orchestra differences for the implications of this.