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:
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
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:
The fundamental APIs that an application interacts with are simple and straightforward:
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.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.DialogContext
instance,
optionally associated with a parent DialogContext
(useful for popup windows that need to coordinate their
behavior with the underlying page).DialogContext
for the current
user, based on a specified dialog identifier.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.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.remove()
method on the
DialogContextManager
instance for this user.DialogContext
instance,
and be notified of the occurrence of the following events:
DialogContext
instance was started.DialogContext
instance was stopped.DialogContext
instance.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.
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.
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.
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;
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:
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.DialogContext
instance has been associated with
the current window, the Dialog Manager performs the following tasks
automatically, with no need for application interaction:
DialogContext
instance to be saved and restored as part of the JSF compoennt
tree and associated state.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.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.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.
There are two general approaches to stopping the processing of an
active DialogContext
instance:
stop()
on the active DialogContext
instance, which will take it out of service.DialogContext
instance to be aborted by calling stop()
on it
yourself.