Skip to main content

Transparent State Management Using the Decorator Pattern

January 9, 2007

alt="{cs.r.title}" border="0" align="left" hspace="10" vspace="0">






What Is Transparent State Management?

In the context of a Java EE web application, the term
transparent state management refers to a mechanism that is
capable of maintaining state across multiple requests, at the same
time remaining completely invisible to the underlying components
that make use of it. In other words, if such a mechanism is in
place, then web applications can acquire statefulness without
having to explicitly deal with state management APIs such as
HttpSession. In many scenarios, this would provide a
better alternative to the traditional way of explicitly handling
session management APIs. The article explains where transparent
state management would be useful, and discusses a reusable solution to
implement transparent state management. It should be noted that for
the purpose of understanding, this article uses Struts as an example web framework
to explain the subsequent sections.

Where Do We Need Transparency in State Management?

Before we go further into transparent state management, we need
to make sense of the context where actually this could be useful.
So let us look at two example scenarios that will help us
understand the context.

First, let us consider a typical shopping cart application--this application allows the user to browse a limited number of
pages without requiring him to be logged in. Each of these pages
also has a separate link to a login page, which will conditionally
appear if the user has not logged in yet. If the user wants to
access restricted pages of this application, he will click on the
login link, which will take him to a login page. After a successful
authentication, the login page will bring the user back to the
previous page from where he accessed the login link.

Another example is a typical business application with lots of
data entry forms. Some of these data entry forms have links to
other pages. For the sake of understanding, let us call the data
entry page the parent page and the pages to which we have
links child pages. Users who are in the middle of
entering some data in the parent page may optionally access the
link to a child page in order to verify or collect some additional
details and can always return back to the parent page to continue with
their current activity.

The two example scenarios described have similar user
interaction patterns: users who are working in a given page make a
short trip to another page and come back to the original page. In
the shopping cart application, they go to the login page and come
back, while in the business application they go to a child page and
return back to the parent page. In both cases, when the users
return back to their respective previous pages, the application
should display all the input values entered in the previous page,
which requires the state of the page the user is leaving to be
saved, so as to show those values when he returns.

Generally this is achieved by storing all input fields of the
current page in the session as an object before navigating to
another page. When the user returns, we retrieve the values from
the session and show them. If the application has one or two
interactions, then the general solution of storing the form values
explicitly in the session and retrieving it back won't add any
overhead in terms of effort. But if the same pattern repeats across
several pages in the application, then it would force us to repeat
the same scaffolding logic of storing objects in the session when
the user temporarily moves out of the main page and retrieve it
when she returns back.

It is exactly these kinds of scenarios where managing state
transparently would provide a better solution. Essentially,
transparent state management provides a design to abstract the task
of storing and retrieving the information in the session without
any coding effort. It could be argued that frameworks like Struts
have already simplified and made session management declarative by
simply requiring the developer to configure the scope of the form
to session. However, in the context of the examples described
earlier, it is still not the best approach for the following
reasons.

  • Frameworks like Struts support declarative session management by
    providing a configuration file where we specify the action form in
    the action mapping to be session scoped. Similarly, JSF,
    Tapestry, and other popular frameworks all have their own
    configuration files where we specify the scope of the object. The
    framework uses this object to bind the request parameters for this
    specific flow, which is known either by URL or by any other
    information passed as part of the request. After data binding, this
    object will be stored in the session for future reference. It
    should be noted that every object that needs to stored in the
    session should have a corresponding entry in the configuration
    file. For example, if we were to use Struts, we would need to
    configure which form object should be used to bind request
    parameters against a certain URL.

    Given our login page and parent-child data entry examples, if
    the same scenario repeats in several pages, then the need to store
    the state of the form in session scope would force us to repeat the
    same logic in terms of configuring additional action mappings for
    every page. What this implies is that a single solution cannot be
    reused in all the pages that require the same functionality,
    though the pattern of the user interaction is similar in all these
    scenarios.

  • Though many frameworks provide a simple configuration option to
    specify the object corresponding to the HTML form to have session
    scope, they can't decide when to remove the objects from the
    session, leaving that decision to the developer. However, if the
    developer explicitly removes the form object from the session every
    time after it is used, then the solution is not transparent
    anymore. On the other hand, if the objects are not removed from the
    session after they're used, they remain in memory as long as the
    session is alive and increase the memory footprint of the
    server.

    The requirement to support this kind of user interaction in many
    pages would make their corresponding objects session scoped, and if
    we combine this with the fact that web frameworks don't
    transparently remove the objects from session once they are used,
    we may soon realize that this way of solving the problem might turn
    the larger part of an otherwise stateless application into a
    stateful application.

This article provides a reusable design to address the
aforementioned issues in the following section.

Design Approach

The proposed design has two main components:

  • First, when the user navigates to a child page from the parent
    page, the input parameters of the parent page are captured in a
    Map structure and stored in session by a servlet
    filter called RequestRetainAndRestoreFilter.The
    decision to store the input request parameters in a generic
    Map instead of using objects obviates the need to have
    different object structures to store details pertaining to
    different pages.

  • Secondly, when the user comes back to the parent page after
    visiting the child page, the previously stored Map of
    parameters are exposed as part of the current request to the parent
    page. This ensures the values entered in the parent page are not
    lost when the user comes back from the child page. The piece of
    functionality that combines the current request as well as the
    previously stored requests is handled by a component called
    SessionStateDecorator.

RequestRetainAndRestoreFilter

The purpose of this filter is to copy the request parameters
during a particular request and resend them as part of another
request. In order to do this, we need to specify when the servlet
filter should perform these two operations. Generally, this can be
achieved by mapping these two operations against two different
URLs. They are:

  • retainRequestAndForward.do: When the filter
    intercepts this URL, it copies all the request parameters in a
    Map and stores them in session, as depicted in Figure
    1.

    Retain sequence
    Figure 1. Retain sequence

  • restoreRequestAndForward.do: When the filter
    intercepts this URL, it puts all the copied parameters back into
    the current request. This is achieved by decorating the current
    request object using the already-captured request parameters, as
    depicted in Figure 2. At the end of this request, the servlet
    filter also removes the parameter map stored in the session. By
    doing this, the filter relieves the developer from the
    responsibility of manually removing the session and eliminates the
    possibility of too many objects floating around and eventually
    bloating the memory footprint of the server.

    restore sequence
    Figure 2. Restore sequence

All other URLs of the application are simply delegated down the
filter chain without any specific action taken by the filter. If we
extend this same concept to multiple scenarios, then each such
scenario would require a separate pair of URLs in this list, which
will do the job of intercepting and restoring their corresponding
request values. However, doing so will make the filter code
cumbersome and difficult to maintain.

In order to overcome this problem, a generic action class is
mapped against these two URLs, which will in turn forward to the
actual target action based on a runtime parameter. This particular
action class is called Proxy Action. So if the current page's
parameters need to be stored in the session before moving to
another page, then the current page should call
retainRequestAndForward.do and it should pass the name
of the actual child page (or another action) as an additional
parameter that will then be forwarded by the Proxy Action.

This design achieves two purposes: first, by referring to
retainRequestAndForward.do, the servlet filter would
know that it has to store the current request parameters, and
secondly, since retainRequestAndForward.do is mapped
against a Proxy Action, the target page (action) will finally be
forwarded by this Proxy Action. This class can also be used as a
placeholder if additional logic is required while navigating to a
different page and reverting back to the original page. When the
user wants to come back to the parent page, we would refer to
restoreRequestAndForward.do and pass the parent page's
action as the runtime parameter; this time, the servlet filter would
take the stored parameter map from session and pass it as part of
current request. Since the runtime parameter now refers to the
parent page, the proxy action will forward the request to that
page.

Configuring web.xml

Instead of directly referring to
retainRequestAndForward.do and
restoreRequestAndForward.do, the servlet filter uses
two init parameters called retain and restore to get the
corresponding values of the URL. This indirection allows us to
specify URLs adhering to naming conventions specific to different
frameworks. The examples accompanying the article uses the
following configuration values:

<filter>

<filter-name> RequestRetainRestoreFilter</filter-name>

<filter-class>
decoratorexample.filter.RequestRetainRestoreFilter
</filter-class>

<init-param>
<param-name> retain</param-name>
<param-value> retainRequestAndForward.do</param-value>
</init-param>

<init-param>
<param-name> restore</param-name>
<param-value> restoreRequestAndForward.do</param-value>
</init-param>

</filter>

SessionStateDecorator

The SessionStateDecorator exposes stored session
parameters as part of the HttpRequestRequest, thereby
obviating the need to explicitly deal with the
HttpSession object. However, modifying the
HttpServletRequest object to include the stored
parameters taken from the session is not possible, because the
HttpServletRequest interface doesn't have a provision
to set request parameters manually. It is also not possible to
extend the HttpServletRequest interface to add the
desired functionality because the responsibility of creating the
actual instance of HttpServletRequest lies with the
servlet container; what we need is the ability to dynamically add
behavior in runtime, to an already created object.

It is in this context the Decorator pattern comes to our
rescue. The Decorator pattern works by wrapping the original object
with a new "decorator" object, to provide extra behavior
to the original object without subclassing it. Since the decorator
implements the same interface as that of the object that it
decorates, it has to implement all the methods of the interface,
but most of the methods simply call the original object except for
a few methods. Since we are trying to add dynamic behavior to
HttpServletRequest, our decorator class should
actually forward all methods to the original
HttpServletRequest object and customize the few
methods that are of interest to us. However, we don't have to write
the repetitive forwarding logic for each method of
HttpServletRequest, because as of version 2.3 of the
Servlet API, we are provided with a convenient implementation of
the HttpServletRequest interface called
HttpServletRequestWrapper, which by default forwards
all its method calls to the HttpServletRequest it
decorates. So the SessionStateDecorator in our example
extends this class and overrides specific methods to provide
customized logic.

While creating the SessionStateDecorator, both the
current request object as well as the parameters copied in the
session during previous request are passed to its constructor; by
doing so, the SessionStateDecorator becomes a single
repository of both the current and the previously captured
request parameters. The responsibility of the SessionStateDecorator
is to override the getParameterMap method to return a
Map that contains both the current request parameters
and the parameters stored in the previous request.

The other overridden methods such as
getParameterValues() and getParameter()
check the given parameter name against the current request object;
if the value for the parameter name is not present, then it uses
the saved parameter Map to retrieve the value
corresponding to the name. Since the decorator implements the same
HttpServletRequest interface, the components dealing
with it will continue to see the same
HttpServletRequest object and remain oblivious to the
fact that stored parameters are provided as part of the request
instead of the session. This approach completely hides the fact
that the user has moved to another page in the middle of her task,
as the server-side code doesn't have to look for any session-scoped
parameters to restore the state of the page.

SessionStateDecorator.java

The following source code shows how
SessionStateDecorator overrides the methods of
HttpServletRequestWrapper to provide additional values
stored in session as part of the current request.

public class SessionStateDecorator extends HttpServletRequestWrapper {

    Map retainedParams;

    public SessionStateDecorator(HttpServletRequest request, Map params) {

        super(request);
        this.retainedParams = params;

    }

    public String getParameter(String paramName) {

        if (!StringUtils.isEmpty(super.getParameter(paramName)))
            return super.getParameter(paramName);

        return (String) retainedParams.get(paramName);
    }

    public Map getParameterMap() {

        Map allParamsMap = new HashMap();
        allParamsMap.putAll(retainedParams);
        allParamsMap.putAll(super.getParameterMap());

        return Collections.unmodifiableMap(allParamsMap);
    }

    public Enumeration getParameterNames() {

        Vector allParamNames = new Vector();
        Enumeration parentParams = super.getParameterNames();

        while (parentParams.hasMoreElements()) {

            allParamNames.addElement(parentParams.nextElement());

        }

        Iterator paramNameItr = retainedParams.keySet().iterator();
        while (paramNameItr.hasNext()) {

            allParamNames.add(paramNameItr.next());
        }

        return allParamNames.elements();
    }

    public String[] getParameterValues(String paramName) {

        if (super.getParameterValues(paramName) != null)
            return (String[]) super.getParameterValues(paramName);

        return (String[]) retainedParams.get(paramName);

    }

}

Understanding the Examples

The example code (see Resources)
includes four JSPs. The "Customer Entry" and "Region Search" pages
show how the values entered in the Customer Entry are restored when
the user returns to this page after moving to the Region Search
page in the middle of data entry. The other example (Product Entry/Manufacturer Search) demonstrates how the same filter can be
utilized to any number of such UI interactions in the application
without adding any new classes or configuration.

Summary

This article demonstrated how to manage session state in a web
application by simply delegating the request through different
URLs. It also explained how to make use of the Decorator pattern to
provide transparency to this solution. Though the example provided
in this article is designed to manage the state of only a single
request, the concept can be easily extended to support wizard-like
scenarios by maintaining a stack of Maps of request
parameters. In this case, one more URL by the name of
retainMoreAndForward can be used to differentiate
between retaining parameters pertaining to a single page against a
series of pages as in a wizard.

Resources

Related Topics >> Struts   |