Skip to main content

Comparing webapp frameworks : WebWork

Posted by simongbrown on March 24, 2006 at 3:02 PM PST

Like Struts, WebWork is a framework that is fairly established within the J2EE webapp space although it's interesting that I've only ever come across two types of WebWork users - those that have never heard of it and those that love it. WebWork, like most other frameworks, is designed around the web MVC pattern and uses the command and controller implementation strategy. What's slightly different about WebWork is that it's built on top of XWork, a separate framework that provides an implementation of the command pattern that is independent of the Servlet API.

In XWork, actions (or commands, depending on your terminology) implement the com.opensymphony.xwork.Action interface, which specifies a single zero argument method called execute(). Since this same interface is used in WebWork, it's possible to write actions without knowledge that they are even running within the context of a web application. From a testability perspective this is very desirable since it allows you to simply instantiate actions and execute them. Of course, most actions typically tend to operate upon data provided as parameters in the HTTP request and, like Stripes, WebWork actions declare their dependency on those parameters by providing JavaBeans style properties with the same names. At runtime, WebWork automatically extracts parameters from the HTTP request and injects them into your actions. While you can get access to the Servlet API inside of a WebWork action, WebWork doesn't force this upon you, leaving that choice to you as the developer.

Right, let's get on with seeing how a WebWork implementation of the sample application compares to the others. I'm using WebWork 2.1.7 and installing it is a simple matter of copying a few JAR files into the /WEB-INF/lib directory and adding the front controller component and WebWork tag library into the web.xml file as follows.

<servlet>
  <servlet-name>webwork</servlet-name>
  <servlet-class>com.opensymphony.webwork.dispatcher.ServletDispatcher</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>webwork</servlet-name>
  <url-pattern>*.action</url-pattern>
</servlet-mapping>

<taglib>
  <taglib-uri>webwork</taglib-uri>
  <taglib-location>/WEB-INF/lib/webwork-2.1.7.jar</taglib-location>
</taglib>

Two additional configuration files that we need are called validators.xml and xwork.xml. The first of these defines a number of validator components that can be used by the framework to validate incoming requests through an interceptor. Each action can define it's own set of specific validation rules based upon the validators configured in the validators.xml file. Since this is a readonly webapp, we don't really have any need for validation, but we'll be coming back to this when we add some user interactivity. For now, we don't need to declare any validators so the file looks as follows.

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">

<validators>
</validators>

The other file (xwork.xml) defines the actions that our web application will use, along with their outcomes/results and the type of view component that is responsible for rendering those outcomes. If you've read Comparing Webapp Frameworks : Struts or are familiar with Struts, you can think of this as being similar to the struts-config.xml file. Let's see this in action by diving into the code.

Home page

The home page presents the user a list of recent blog entries and with WebWork, the code that finds these blog entries get wrappeds up inside an XWork action as follows.

package action;

import com.opensymphony.xwork.ActionSupport;
import domain.Blog;
import domain.BlogService;

/**
* Action responsible for finding blog entries, ready to be displayed.
*
* @author    Simon Brown
*/
public class ViewBlogEntriesAction extends ActionSupport {

  /** the blog that owns the blog entries */
  private Blog blog;

  /**
   * Performs the processing associated with this action.
   *
   * @return  a String defining the outcome of this action
   */
  public String execute() throws Exception {

    BlogService blogService = new BlogService();
    this.blog = blogService.getBlog();

    return SUCCESS;
  }

  /**
   * Gets the blog that this action is operating upon.
   *
   * @return  a Blog instance
   */
  public Blog getBlog() {
    return blog;
  }

}

This implementation is very straightforward, just looking up the Blog instance and returning the predefined SUCCESS outcome. Notice here that the Blog instance isn't inserted into the HTTP request. Instead it's assigned to an instance variable and we'll be seeing shortly how exactly this is used in the JSP page representing the view. Speaking of which, how does the SUCCESS outcome get mapped to that JSP? Here's where the xwork.xml file fits in.

<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" 
  "http://www.opensymphony.com/xwork/xwork-1.0.dtd">

<xwork>
  <include file="webwork-default.xml" />

  <package name="default" extends="webwork-default">

    <default-interceptor-ref name="defaultStack" />

    <action name="viewBlogEntries" class="action.ViewBlogEntriesAction">
        <result name="success" type="dispatcher">viewBlogEntries.jsp</result>
    </action>

  </package>
</xwork>

Here, an action called viewBlogEntries is defined (which maps onto a URI of /viewBlogEntries.action) and given the fully qualified name of the class that implements the action. In addition to this, the possible outcomes of the action are defined, mapping a simple string value onto the name of the JSP page that is responsible for rendering that particular outcome. The value of the type attribute instructs WebWork to use the Servlet request dispatcher and, as we'll see in the next blog entry, there are some other possible values that allow you to use alternative view technologies. So, what does the JSP page look like? Well it's pretty similar to the previous versions although I've chosen to show the WebWork tag library in use. It is also possible to use a pure JSP/JSTL combination.

<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

  <head>
    <title><ww:property value="blog.name" /></title>
    <link rel="stylesheet" href="screen.css" type="text/css" />
  </head>

  <body>
    <div id="container">
      <h1><ww:property value="blog.name" /></h1>
      <h2><ww:property value="blog.description" /></h2>

      <ww:iterator id="blogEntry" value="blog.blogEntries">
        <div class="blogEntry">
          <h3><ww:property value="title"/></h3>

          <ww:if test="excerpt != null">
            <ww:property value="excerpt"/>
            <p>
            <a href="<ww:url value="'viewBlogEntry.action'">
              <ww:param name="'id'" value="id"/>
            </ww:url>">Read more</a>
            </p>
          </ww:if>
          <ww:else>
            <ww:property value="body"/>
          </ww:else>

          <p>
            Posted on <ww:property value="date" />
          </p>
        </div>
      </ww:iterator>
    </div>
  </body>

</html>

If you're familiar with something like the Struts bean/logic tags or JSTL, you'll probably pick up the WebWork tags pretty quickly. ww:property outputs the value of the named property, ww:if lets you perform conditional logic and ww:iterator provides a simple looping mechanism. You may be wondering exactly where the blog object comes from since we never injected it into the HTTP request or the JSP page. Behind the scenes, WebWork uses something called a Value Stack to maintain references to objects and make them available within the various supported view technologies. You can think of the value stack as a collection of objects, and one of those objects is the action that was responsible for dispatching us to the page. Accessing objects within the stack is achieved using a language called OGNL (the Object Graph Navigation Language). During the dispatching process, the action becomes the "root" node, meaning you can easily access any properties of the action with a simple object.property syntax. Something worth pointing out is that when you use the ww:iterate tag, the "current object" in the value stack changes to reflect the current object exposed during iteration, but you can get back to any object in the stack by prefixing the expression with the # character.

Blog entry detail page
At a high level, that's pretty much it so it won't surprise you that the blog entry detail page is very similar. First the action class.

package action;

import com.opensymphony.xwork.ActionSupport;
import domain.Blog;
import domain.BlogEntry;
import domain.BlogService;

/**
* Responsible for finding a specific blog entry.
*
* @author    Simon Brown
*/
public class ViewBlogEntryAction extends ActionSupport {

  /** the ID of the blog entry to display */
  private String id;

  /** the blog that owns the blog entries */
  private Blog blog;

  /** the blog entry to be displayed */
  private BlogEntry blogEntry;

  /**
   * Performs the processing associated with this action.
   *
   * @return  a String defining the outcome of this action
   */
  public String execute() throws Exception {
    BlogService blogService = new BlogService();
    blog = blogService.getBlog();
    blogEntry = blog.getBlogEntry(id);

    if (blogEntry == null) {
      return "notfound";
    } else {
      return SUCCESS;
    }
  }

  /**
   * Setter for the id property.
   *
   * @param id    a String
   */
  public void setId(String id) {
    this.id = id;
  }

  /**
   * Gets the blog that this action is operating upon.
   *
   * @return  a Blog instance
   */
  public Blog getBlog() {
    return blog;
  }

  /**
   * Gets the blog entry to be displayed.
   *
   * @return  a BlogEntry instance
   */
  public BlogEntry getBlogEntry() {
    return blogEntry;
  }

}

Next up is the fragment of XML that needs to be inserted into the xwork.xml file to register the action and define its outcomes.

    <action name="viewBlogEntry" class="action.ViewBlogEntryAction">
      <result name="success" type="dispatcher">viewBlogEntry.jsp</result>
      <result name="notfound" type="dispatcher">404.jsp</result>
    </action>

And finally is the JSP page itself.

<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

  <head>
    <title><ww:property value="blogEntry.title" /> : <ww:property value="blog.name" /></title>
    <link rel="stylesheet" href="screen.css" type="text/css" />
  </head>

  <body>
    <div id="container">
      <h1><ww:property value="blog.name" /></h1>
      <h2><ww:property value="blog.description" /></h2>

      <div class="blogEntry">
        <h3><ww:property value="blogEntry.title" /></h3>

        <ww:property value="blogEntry.body" />

        <p>
        Posted on <ww:property value="blogEntry.date" />
        </p>
      </div>
    </div>
  </body>

</html>

Summary
Where actions in frameworks like Struts only have responsibility for the business logic associated with processing a request, in WebWork (and Stripes) those actions also act as custodians for the data used in processing the request. Incoming data on the request gets automatically mapped onto JavaBean properties on the action class, which can subsequently be accessed and displayed in the various view technologies. If you're not used to this approach, it can seem a little odd at first. However, it doesn't take very long to get used to and having both the logic and data in the same place proves very useful to support a hierarchy of similar actions. All in all, WebWork is a very sophisticated web application framework that lets you build easily testable actions and provides a way for you to use multiple view technologies. In the next comparison, we'll take a break from JSP to look at another WebWork implementation that uses Velocity.

Related Topics >>