by Venkatray Kamath

Struts 2.0 in action

how-to
Oct 9, 200719 mins

Custom tags, themes, and code reuse aid developer productivity

Whether or not you’ve used version 1 of the Struts Web development framework, you should check out Struts 2.0 to see if it will be useful for your MVC-based projects. In this article, Venkatray Kamath walks you through the high points of a Struts 2.0 application, demonstrating how it can help boost your productivity.

A Web-based application is a crucial part of any enterprise solution, and to develop such an application, you need a good Model-View-Controller (MVC) framework in place. For Java Web-based applications, there are lots of frameworks that you can choose from: Struts, Spring MVC, WebWork, and Tapestry are some of the most popular, and version 2.0 of the Struts framework has recently been released by the Apache Foundation.

This article will explore the new Struts 2 framework and attempt to highlight the core features that you can take advantage of in implementing your own Web application. This article doesn’t attempt to compare the various MVC frameworks available; rather, I will focus on sharing my experiences in working with Struts 2 that will help you get started with the framework.

You’ll need to have a good understanding of how an MVC framework works to quickly grasp the contents of this article. However, you don’t specifically need background in version 1.x of the Struts framework. If you’re already familiar with Struts 1.x, or looking to migrate code that was built for that framework, you might want to read “Adopting Struts 2.0,” an JavaWorld article written specifically to compare the two versions of the framework (see the Resources section for a link).

Struts: An architectural overview

In this article, you’ll walk through the implementation of a sample application to really get a feel for how Struts 2 works and what it can do for you. Before diving into these specifics, though, you should get a bird’s-eye view of the framework’s architecture. Figure 1 offers a visual outline of how Struts 2 works.

Figure 1. Struts 2 architecture (click for a larger image)

As you can see in Figure 1, an initial request from the browser goes to the servlet container. The Struts FilterDispatcher is a standard servlet filter that intercepts this request. FilterDispatcher delegates control to the ActionProxy class, which consults the framework configuration files manager; this is initialized from struts.xml, a configuration file that stores the request-URL-to-Action-class mapping and lots of other information that you’ll learn more about later on in this article.

The interceptors that implement common concerns across actions are then called (in the before() method) in advance of invoking the Action itself. Interceptors built into Struts 2 can perform core processing, like populating request parameters into Action classes, performing validation, uploading files, and so on. You can also define custom interceptors, as you’ll see when you examine this article’s sample application.

The Action class typically invokes the business layer and populates the model objects, which are instances variables of the Action class. Next, the request is dispatched to the view layer (which is built on a technology like JavaServer Pages, FreeMarker, or Velocity), which renders the GUI. The interceptors are executed again (in reverse order, calling the after() method). Finally, the response returns through the servlet filter chain.

This all probably seems pretty abstract at the moment, but you can get a better feel for how it works in practice by taking a look at a sample application.

The Customization application

In this article, you’ll examine a sample application called Customization. This app is a Web module that was developed with Struts 2. It’s designed to help various users of internal Web applications customize those applications’ GUIs to match their own preferences. To get a feel for what the application looks like in practice, take a look at Figure 2. It illustrates example of a Loan Queue screen, part of a banking application, which displays a list of loans for a bank officer to work on. Figure 2 displays five loans sorted by origin date, which is the default behavior.

Figure 2. Loan Queue screen (click for a larger image)

A loan coordinating officer might want to customize the Loan Queue page to suit his preferences; for instance, he might want to set the number of loans that should be displayed per page, the number of columns to display, or the default sort order. He can do so by clicking the customization link at the top right corner, which will display the customization page shown in Figure 3. This feature — providing a customization page for each application screen that saves the user preferences — constitutes the Customization Web application. The customization framework will be designed so that a developer doesn’t have to code this customization page for each application screen; instead, it will be generated on the fly.

Figure 3. Customization for the Loan Queue screen (click for a larger image)

From a technical standpoint, the customization application needs to provide a repository for storing customization metadata, and a GUI to render and save customization data for the logged-in user. It also needs to provide an API for the developer to read the customization values to customize the application.

To highlight the Struts 2 features of this sample application, in the remainder of this article you’ll walk through the implementation of the following features:

  • Logging users in. The user will be prompted for a username and password, and every request will be intercepted by the Struts 2 interceptor to check for a valid session. You’ll learn more about interceptors and their benefits.
  • Rendering the customization page with attribute names and values using Struts 2 UI tags. By seeing how this works, you’ll learn about several important Struts 2 concepts: custom tags, the action value stack, OGNL expressions, and themes.
  • Tabular display of the customization catalog. This will illustrate Struts 2 tabbed panels and the Ajax theme.
  • Saving the customization values and triggering an acknowledgment e-mail. This discussion will touch on Struts 2 support for FreeMarker templates.

You can download the source code for the Customization application in the Resources section. When the article refers to any class name, or JSP or XML resource, you can find the complete file in this code package. In the remainder of this article, you’ll get a closer look at the implementation of each of the above features in Struts 2 in turn.

Logging in

In this section, you will learn how to use and configure interceptors. Interceptors can execute code before and after an Action is invoked. The Customization application defines a new interceptor, LoginSessionInterceptor, which intercepts every request from the user and checks to see whether that user is logged in. A code snippet from LoginSessionInterceptor is shown in Listing 1, with line numbers added for clarity.

Listing 1. Snippet from LoginSessionInterceptor

public String intercept(ActionInvocation actionInvocation) throws Exception {
1.      logger.debug("Enter LoginSessionInterceptor");
2.      String result = null;
3.      if (isLoginAction(actionInvocation)) {
4.          result = actionInvocation.invoke();
5.      } else if (CustomizationSessionContext.isNGSessionContextPresent()) {
6.          result = actionInvocation.invoke();
7.      } else {
8.          return "loginPage";
9.      }
10      logger.debug("Exit LoginSessionInterceptor");
11.     return result;
}

Line 3 checks to see if the request is for rendering the login page; if so, then the request is serviced successfully. Line 5 checks to see if the user is logged in; if so, then the request is sent through for further processing; otherwise, the user is redirected to a login page.

The application needs to instruct the Struts 2 framework to invoke this interceptor for every action. So it defines this configuration in struts-base.xml (a custom file included in struts.xml), as shown in Listing 2.

Listing 2. Configuration in struts-base.xml

<package name="NG" namespace="/NG" extends="struts-default">
<interceptors>
    <interceptor name="NGLoggingInterceptor"
        class="com.project.customization.interceptors.LoggingInterceptor" />
    <interceptor name="NGLoginSessionInterceptor"
        class="com.project.customization.interceptors.LoginSessionInterceptor" />

    <interceptor-stack name="myStack">

        <interceptor-ref name="NGLoggingInterceptor" />
        <interceptor-ref name="NGLoginSessionInterceptor" />
        <interceptor-ref name="defaultStack" />
    </interceptor-stack>
</interceptors>
</package>

The interceptors are defined in a stack called myStack, which specifies the execution order and is defined within the package named NG, as shown in Listing 3. The NG package is the base package that all modules of the application will inherit. All the action mapping specific to the customization module is defined in struts-Customization.xml. The action mappings in Struts 2 are grouped in packages, each having a unique namespace. By defining all actions of a JEE Web module within a separate package having a namespace, you can also configure JEE Web layer security for the URL pattern //*.

Listing 3. Action mapping entry in struts.xml defining a separate package

<package name="Customization" namespace="/customization" extends="NG">
    <default-interceptor-ref name="myStack" />

    <action name="login" class="customizationAction" method="login">
        <result>/WEB-INF/web/jsp/common/Login.jsp</result>
    </action>

</package>

The URL for the above action should include the defined namespace. For instance, the URL for the login action will end with /customization/login.action. You may notice that the application declares a default interceptor stack, myStack, for all actions defined in the Customization package.

Because security is a common concern that is shared by all actions, interceptors are used to implement this aspect of the application. Along similar lines, you can log all requests and responses using interceptors.

Rendering the customization page

The next thing the sample application needs is a GUI for rendering the customization page shown in Figure 3. The user preference data (the number of rows per page, the column to sort by, the fields to show, and so on) displayed on the customization page for the Loan Queue screen is driven by a configuration file, Customization.xml, whose contents are displayed in Listing 4.

Listing 4. Customization.xml

<ng:uiContainer id="loans.queue">
<ng:displayName>Loan Queue Customization</ng:displayName>
<ng:attributes>

                <ng:attribute id="rows.per.page">
                <ng:displayName>Rows Per Page</ng:displayName>
                    <ng:displayType>text</ng:displayType>
                </ng:attribute>
                <ng:attribute id="sort.by">

                    <ng:displayName>Sort By</ng:displayName>
                    <ng:displayType>select</ng:displayType>
                    <ng:enumValues>
                        <ng:value id="origin.date">Origin Date</ng:value>

                        <ng:value id="loan.status">Status</ng:value>
                    </ng:enumValues>
                </ng:attribute>
                <ng:attribute id="show.fields">
                    <ng:displayName>Show Fields</ng:displayName>

                    <ng:displayType>check</ng:displayType>
                    <ng:enumValues>
                        <ng:value id="origin.date">Origin Date</ng:value>
                        <ng:value id="customer.name">Primary Borrower</ng:value>

                        <ng:value id="loan.no">Loan #</ng:value>
                        <ng:value id="loan.status">Status</ng:value>
                        <ng:value id="email.id">Email Id</ng:value>
                        <ng:value id="loan.amt">Loan Amt</ng:value>

                        <ng:value id="updated.by">Update By</ng:value>
                    </ng:enumValues>
                </ng:attribute>
</ng:attributes>
</ng:uiContainer>

Now take a look at the implementation details. A service interface, CustomizationService, has been defined with methods to load and save the customization. (The implementation details of the service layer are beyond the scope of this article; you can take a look at the details of the service layer by examining the files in this article’s sample code package with *Impl.java filenames.) The domain object that represents the customization entity is CustomizationVO, which has a one-to-n relationship with CustomizationAttributeVO. A Struts Action class named CustomizationAction must be defined with the job of processing user input, invoking the service layer, and then dispatching the request to the view layer for rendering. The instance variables of this class could be domain objects or value objects, which could be referred from the view layer. In this case, CustomizationVO has been defined as an instance variable of the CustomizationAction class.

In order to understand how the data objects (domain objects or value objects) are accessible to the view layer, you need to understand the concept of a Struts 2 value stack. Struts 2 uses OGNL (Object Graph Navigation Language; see the Resources for more information) expressions to map HTTP request parameters to value objects on Action instances, and to read value objects in the view layer. The value stack is a set of objects, and by default the Action instance is pushed on the top of the stack. The OGNL expressions specified in the attribute value of Struts custom tags are evaluated against this value stack. Thus, from the view layer, you can access the action instance object, along with all of the standard HTTP objects, like application, session, and request contexts, using OGNL expressions.

The sequence diagram for this functionality is shown in Figure 4.

Figure 4. Sequence diagram (click for a larger image)

The display method in the Action class invokes the service layer and populates CustomizationVO, and then forwards the request to CustomizationDisplay.jsp. The JSP page uses Struts UI tags to display the attributes, as shown in Listing 5.

Listing 5. Struts UI tags in action in a JSP page

<s:form action="submit" validate="false" namespace="/customization" method="POST" theme="ajax" onclick="return false;">
<s:hidden name="ngContainerVO.id" value="%{ngContainerVO.id}" />
<br />
<table class="legacyTable">
<tr>
    <td colspan="2" align="left"><font size="+1"><s:property
        value="ngContainerVO.displayName" /></font></td>

<tr>
    <td colspan="2" align="left"><i>Description: <s:property
        value="ngContainerVO.description" /></i></td>
</tr>

<s:iterator value="%{ngContainerVO.ngAttributes}" status="stat">

    <s:if test="displayType == 'text'">

        <s:hidden name="ngContainerVO.ngAttributes[%{#stat.index}].id"
            value="%{id}" />
        <s:textfield
            name="ngContainerVO.ngAttributes[%{#stat.index}].value"
            value="%{value}" label="%{displayName}" title="%{description}" />

    </s:if>
    <s:if test="displayType == 'select'">
        <s:hidden name="ngContainerVO.ngAttributes[%{#stat.index}].id"
            value="%{id}" />
        <s:select name="ngContainerVO.ngAttributes[%{#stat.index}].value"
            list="enumValues" label="%{displayName}" value="%{value}"
            title="%{description}" />

    </s:if>
</s:iterator>
<tr>
    <td colspan="2" align="right"><s:submit name="submit"
        theme="cust_ajax" targets="detail" value="submit"
        showLoadingText="false" /></td>
</tr>
</table>
</s:form>


Take a look at the <s:iterator> tag. It has a value attribute %{ngContainerVO.ngAttributes} that maps to the getNgContainerVO().getNgAttributes() method of the CustomizationAction object. You can specify any valid OGNL expression for the value attribute. This tag iterates through the list of CustomizationAttributeVOs, and during each iteration it pushes CustomizationAttributeVO on the value stack. Hence, the OGNL expression used by the <s:textfield> tag, like %{value}, points to the value attribute of the CustomizationAttributeVO object on the stack.

Now take a closer look at the theme attribute in Listing 5. The template code here is written in FreeMarker and decorates the Struts tags. FreeMarker is a template engine, a generic tool to generate text output (anything from HTML to autogenerated source code) based on templates (see Resources for more on FreeMarker). A collection of such templates is packaged in a theme. There are built-in themes, such as simple, XHTML, and Ajax, that are packaged with Struts 2; these themes render the custom tags in different styles. For instance, if you use theme="xhtml" with <s:form>, then the validation errors will be displayed in red above each form element. And you can always customize this by writing your own template file and creating a new theme. To demonstrate this aspect, try to customize the rendering of the Submit button. Name the theme cust_ajax, then create a directory called template/cust_ajax and place this in the classpath. Define a new template file named submit.ftl and place it in this new folder. Copy and paste the contents of the existing submit.ftl file for the Ajax theme and tinker with the CSS style for the button. Just edit the following line to add a new style for the button:

${tag.addParameter('cssClass', 'buttonClass')}

In a similar approach, you can customize the HTML decorator for each of the HTML elements by creating new templates and grouping them into a new theme.

Tabular display of customization catalog

The sample application implements a page that shows two tabs: the first, Catalog, displays a list of customizations, and the second, Detail, displays the details for the selected customization. Clicking on a customization line item on the Catalog tab will make an Ajax call to the server and render the output on the second tab. This functionality can be easily implemented using the Struts tabbed panel custom tag and Struts Ajax theme, as illustrated in Listing 6. (Again, line numbers have been added for clarity.)

Listing 6. Code snippet from CustomizationList.jsp

1. <s:tabbedPanel id="resultsPanel" theme="simple"    doLayout="false">
2.  <s:div theme="ajax" id="catalog" label="Catalog"
        executeScripts="true" errorText="errorText" loadingText="Loading..."       showLoadingText="false">
    <table class="legacyTable">
3.      <s:iterator value="uiContainerList">
            <tr><td>

4.          <a href="javascript: showDetail('<s:property value="id" />')">
            <s:property value="displayName" /></a>
            </td></tr>
        </s:iterator>
    </table>

    </s:div>
5.  <s:div theme="ajax" executeScripts="true" refreshOnShow="true"
        id="detail" label="Detail" errorText="errorText"
        showLoadingText="false" loadingText="Loading...">
        Please click on a customization link
    </s:div>
</s:tabbedPanel>

<script>

    window.showDetail = function showDetail(id) {
    var widget=dojo.widget.byId("detail");
6.  widget.href="<%=request.getContextPath()%>/
         customization/display.action?ngContainerVO.id="+id;
7.  dojo.widget.byId("resultsPanel").selectChild("detail");

}

</script>

Line 2 is the DIV for displaying the contents of the Catalog tab, and line 5 declares the DIV for rendering the Detail tab content. Both tags use the Ajax theme. Clicking on the customization line item will execute a JavaScript function, showDetail(), which sets the href of the details tab widget (line 6) with the ID of the customization line item as an HTTP parameter. Line 7 selects the Details tab to be displayed; under the hood, it makes an Ajax call and displays the response in the Details tab. Hence, the Ajax theme abstracts out the Ajax invocation and handling of the HTTP response.

Confirmation page and e-mail trigger

The confirmation page displays the attributes modified by the user, and clicking Submit will save the changes and trigger an e-mail containing information about the modified attributes. Since the contents of both the GUI pages and e-mail are the same, you can use the same FreeMarker template to generate the content of both.

Listing 7. Code snippet from ReviewAndSubmitCustomization.jsp

<s:component template="displayCustomizationChanges.ftl" theme="cust">

    <s:param name="modifiedAttributes" value="%{modifiedAttributes}" />
</s:component>

In Listing 7, <s:component> is a Struts tag that invokes the FreeMarker template file displayCustomizationChanges.ftl, shown in Listing 8; this file has been placed in the folder template/cust. modifiedAttributes is a map that stores the attribute name/value pair that is being modified by the user; this map is an instance variable in the Action class and is accessible on the Struts 2 value stack. This parameter is referenced in the FTL file. The FTL file just iterates through the map and displays the contents.

Listing 8. displayCustomizationChanges.ftl

<#assign h = modifiedAttributes>
<#assign keys = h?keys>

<#list keys as key>
<tr>
<td><b>${key}</b> </td>

<td width="2%"> </td>
<td>${h[key]?if_exists}</td>
</tr> 
</#list>  


You can use the same template to generate the contents of the e-mail message that is sent, as shown in Listing 9.

Listing 9. Generating the e-mail content

private void sendEmail(String userId) {
    Configuration cfg = getFTLConfiguration();
    Map<String, Object> root = new HashMap<String, Object>();
    root.put("modifiedAttributes",  CustomizationSessionContext.getSessionContext().getAttribute("modified.attributes"));


    StringWriter sw = new StringWriter(1000);
    sw.write("<html><body>");
    sw.write("nThe acknowlegement email lists the modified customization for UserId = " + userId);
    sw.write("n<table class="legacyTable">");
    try {
        Template temp = cfg.getTemplate("displayCustomizationChanges.ftl");
        temp.process(root, sw);
    } catch (TemplateException e) {
        logger.error(e);
    } catch (IOException e) {
        logger.error(e);
    }
    sw.write("</table>n</body></html>");
    logger.debug("nnEmail Body nn" + sw.toString());
        
}


Thus the FreeMarker template can be used to generate the GUI content as well as the e-mail body, thereby promoting code reuse.

In conclusion

The features of the Struts 2 framework, including interceptors, themes, and Ajax support, can certainly help improve the productivity of Web developers. In the the sample application in this tutorial, you saw that the use of custom tags, themes, and FreeMarker templates made it possible to abstract out a lot of code in reusable components and  establish a unified look and feel across the application. Take some time to walk through all the files in this article’s code package to get a feel for how Struts 2 works in practice.

If you are using JEE 1.4 or higher, or planning to upgrade, Struts 2 is definitely a good MVC framework to consider for developing enterprise Web applications. Hopefully this article will help you hit the ground running.

Acknowledgment

The author would like to thank Amitav Chakravartty, an architect at Tavant Technologies CA, for his valuable suggestions while working on the Struts 2 framework.

Venkatray Kamath is a senior engineer at Lehman Brothers in New York. Venkat has five years of experience building Java enterprise applications on the JEE platform. In the past, he has worked on SOA-based application design and on implementing various frameworks, including logging, asynchronous correlation frameworks, entity caching, and WebLogic domain creation, to name just a few.