2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

Shale Dialog Manager

Introduction

One of the frustrating aspects of organizing the flow of control in a web based application is that fact that it is composed of completely disconnected interactions with the client (via the HTTP protocol). The popularity of application frameworks based on model-view-controller (MVC) principles, and particularly the emergence of the front controller design pattern, have become the de facto standard architectural approach.

Like other frameworks, JavaServer Faces supports a mechanism to define navigation rules for transitions between views. The actual processing is performed by an implementation of the javax.faces.application.NavigationHandler. The standard implementation provided by the framework (which can be customized via a pluggable API) performs transitions from one view to another based on three inputs:

  • What view is currently processing this form submit?
  • Which of the potentially several actions were invoked? (This allows you to support different "submit" buttons with different functionality, or share actions between, say, a "Save" button at the top and bottom of a table.)
  • What "logical outcome" was returned by the action that was invoked?
Basing navigation on outcomes, by the way, assists in reducing the coupling between pages, because the developer that writes the action method is only focused on reporting "what happened" rather than worrying about "where do I go next". This concept is also found in the way Struts has Action.execute() methods that return a logical ActionForward describing the outcome of performing the action.

However, it is still difficult to reuse individual views in more than one "conversation" or "dialog" with the user, nor to treat one dialog as a "black box" subroutine that can be called by more than one calling dialog. To address these needs, Shale offers Dialog Manager support.

The functionality of this feature was heavily inspired by the implementation of Spring Webflow (Preview 2), whose home page is:

http://opensource.atlassian.com/confluence/spring/display/WEBFLOW/Home

API and Implementations

The Shale Dialog Manager defines an API that supports access to an abstract "execution engine" that manages the processing flow through a dialog. In addition, multiple implementations of this API are provided that offer different sets of unique features:

  • Basic Implementation A relatively simple implementation that models a dialog as a state diagram with four types of states:
    • Action - Execute an arbitrary method
    • Exit - Terminate execution of this dialog
    • Subdialog - Execute another dialog as a subroutine
    • View - Display a JSF view (page) and wait for the following submit to execute an application action method
    This implementation supports a superset of the functionality that was present in versions of Shale up through 1.0.3.
  • State Chart XML Implementation A more sophisticated implementation based on state charts modelled with State Chart XML, which is currently a Working Draft published by the W3C. This technology has grown out of the use of similar techniques in the telephony industry, and Shale uses the Jakarta Commons SCXML library to provide the required execution engine.
The remainder of this document describes services that are available no matter which implementation you choose. See the module descriptions for the implementation modules for details of configuration, as well as the unique features provided by that implementation.

Services Provided

The fundamental APIs that an application interacts with are simple and straightforward:

  • DialogContext - Represents the state of an active dialog with the user. There will be one such instance for each window or frame running a dialog, stored in session scope.
  • DialogContextManager - Factory for creating new DialogContext instances. The Shale Dialog implementation that you select will provide a suitable factory as a session scope managed bean under a well-known key. At most one active DialogContext instance can be associated with each window or frame that the user is operating, in association with the same session.
  • DialogListener.html - An active DialogContext fires events that document interesting changes in the state of the dialog. Interested objects can ask to be notified of such events by implementing this interface, and registering themselves with the DialogContext using standard JavaBeans event listener design patterns.
DialogContextManager provides public methods that support the following functionality:
  • Create and return a new DialogContext instance, optionally associated with a parent DialogContext (useful for popup windows that need to coordinate their behavior with the underlying page).
  • Retrieve an active DialogContext for the current user, based on a specified dialog identifier.
  • Remove an active DialogContext instance, denoting that this instance is no longer active. As a side effect, the content of the data property of this DialogContext will be made available for garbage collection, as long as the application does not maintain any other references to the data object.
DialogContext provides the following public properties:
  • active - Flag indicating that this DialogContext instance has been started but not yet stopped.
  • data - General purpose object made available for storing state information related to a particular active dialog instance. Details of how this property is implemented are specific to the implementation you choose, but will generally default to being an instance of java.util.Map. You can also replace this object at runtime with an object that contains state properties specific to a particular use case.
  • id - An identifier, unique within the scope of the current user, for this particular instance. The Dialog Manager framework promises to transport this identifier along with the JSF component tree, and will use it to regain access to the corresponding DialogContext instance on a postback.
  • name - The logical name of the dialog definition being executed by this instance. This will typically map to configuration information that is specific to the implementation you select.
  • parent - Optional reference to a parent DialogContext instance that we were associated with when this instance was created.
DialogContext provides public methods to support the following functionality:
  • Start the active execution represented by this instance, advancing until the instance has displayed a JSF view and needs to wait for the user to fill out a form and submit it.
  • Advance the state of the computation represented by this instance, passing in the logical outcome that was returned by the application's action method.
  • Stop the execution of the computation represented by this instance, which will cause it to be passed to the remove() method on the DialogContextManager instance for this user.
DialogListener is an event listener interface that follows standard JavaBean design patterns. Interested objects can register themselves as a listener on a DialogContext instance, and be notified of the occurrence of the following events:
  • This DialogContext instance was started.
  • This DialogContext instance was stopped.
  • An exception was thrown by this DialogContext instance.
  • A named "state" was entered (details are specific to the selected implementation).
  • A named "state" was exited (details are specific to the selected implementation).
  • A transition from one named "state" to another was performed (details are specific to the selected implemenation).
Applications that wish to implement listeners are encouraged to subclass AbstractDialogListener instead of implementing the interface described above. In addition to only needing to implement event handling methods you are interested in, this protects the ability to compile your application against future versions of the listener interface, if more event handling methods are added in the future.

Using Dialog Manager

The following paragraphs describe functionality that works no matter which specific Dialog Manager implementation you have selected. Be sure to consult the page for your selected implementation for additional features and capabilities.

Starting A New DialogContext Instance

At most one DialogContext instance can be active, in a particular window or frame, at one time. There are several ways in which an application can cause such a DialogContext instance can be activated and associated with the current window or frame. Each technique is described below.

Starting A DialogContext Via Navigation

This technique is very useful if your application contains a mixture of pages managed by standard JavaServer Faces navigation, and pages managed by the Shale Dialog Manager. To enter a dialog whose logical name is foo, simply have one of your application actions return a logical outcome string of dialog:foo, and a new DialogContext instance will be started for you.

In the example above, we used the default dialog: prefix value to trigger starting a dialog. You can also specify your own prefix by setting a context init parameter whose name is defined by the symbolic constant Constants.DIALOG_PREFIX_PARAM (i.e. org.apache.shale.dialog.DIALOG_PREFIX). By convention this value should end with a ':' character to look like a namespace, but this is not required.

Starting A DialogContext Programmatically

Under some circumstances, it may be preferable for your application's event handler to decide programmatically which dialog to use, and start it programmatically. To start a dialog named foo programmatically, code something like this in your action method:

  FacesContext context = FacesContext.getCurrentInstance();
  DialogContextManager manager =
    DialogHelper.getDialogContextManager(context);
  DialogContext dcontext = manager.create(context, "foo");
  dcontext.start();

(If you are using a version of Shale before 1.1, you have to work slightly harder to achieve the same effect:)

  FacesContext context = FacesContext.getCurrentInstance();
  DialogContextManager manager = (DialogContextManager)
    context.getApplication().getVariableResolver().
    resolveVariable(context, Constants.MANAGER_BEAN);
  DialogContext dcontext = manager.create(context, "foo");
  dcontext.start(context);
  return null;
Starting A DialogContext Via URL Parameters

In a use case like a pop-up window, the first request served by the application will be to a new window that is not currently associated with a current dialog. In order for such a window to immediately become associated with a DialogContext instance, the Dialog Manager also recognizes the following request parameters, if they are present, and if there is no active DialogContext already:

  • org.apache.shale.dialog.DIALOG_NAME - The logical name of the dialog to be created and started.
  • org.apache.shale.dialog.PARENT_ID - (Optional) the logical dialog identifier of a parent DialogContext instance that should become the parent of the newly created one. This allows, for example, a popup window to be associated with the data element for the DialogContext instance associated with the parent window.

Request Processing for an Active DialogContext Instance

Once a DialogContext instance has been associated with the current window, the Dialog Manager performs the following tasks automatically, with no need for application interaction:
  • Cause a dialog identifier for this DialogContext instance to be saved and restored as part of the JSF compoennt tree and associated state.
  • When a POST request for this window is processed, the active DialogContext instance for the logical dialog identifier for this window will be retrieved from the DialogContextManager for this user, and stored as a request scope attribute under key dialog for easy reference in expressions.
  • During Invoke Application phase of the request processing lifecycle, the logical outcome returned by the action method will be intercepted by the Dialog Manager, rather than being fed into the standard JSF navigation handler. Instead, the outcome will be passed in as a parameter to the advance() method on the current DialogContext instance, which will advance the state of the computation until a further interaction with the user is required. Then, the Dialog Manager will create the requested JSF view, and forward to Render Response phase so that this view may be rendered.

Accessing Per-DialogContext State Information

The Dialog Manager provides a convenient place for an active DialogContext instance to maintain state information that lasts only for the lifetime of the instance. This is the data property of the DialogContext instance for the currently active dialog. Each Dialog Manager implementation will provide a default data structure (typically an instance of java.util.Map) for this purpose, but you may also provide a JavaBean class that is application specific if you wish.

Note that, due to the combination of the current DialogContext instance being exposed as a request scoped attribute under key dialog, and the fact that data is a standard Java Bean property on this instance, you can conveniently use JSF value binding expressions to bind component values to state information. For example, assume you have provided an application specific Java Bean class for the state information, and it has a name property to contain a customer name. You can easily bind an input component to that name, like this:

  <h:inputText id="name" ... value="#{dialogScope.name}"/>

(Prior to version 1.1, use this approach instead:)

  <h:inputText id="name" ... value="#{dialog.data.name}"/>

As an extra value-added feature, if the object you store as the data property is of a class that implements the DialogContextListener interface, your data object will also be automatically registered to receive the corresponding events. This can be useful (for example), when your application needs to know when a particular "state" has been entered, or whether a transition to a particular "state" came from some other particular "state".

If you intend to leverage this feature, you can optionally make your data class extend AbstractDialogContextListener instead of implementing the interface directly. If you do this, you only need to implement the event handling methods that you are interested in, rather than all of them.

Stopping An Active DialogContext Instance

There are two general approaches to stopping the processing of an active DialogContext instance:

  • Each Dialog Manager implementation will typically define a particular state as an "exit" or "end" state. When a transition to such a state occurs, the Dialog Manager implementation will call stop() on the active DialogContext instance, which will take it out of service.
  • You can also cause the current DialogContext instance to be aborted by calling stop() on it yourself.