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.
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.