Tuesday, March 28, 2006

Comparing Web Frameworks: WebWork

Lately there have been Java Web framework comparisons on theServerSide.com. These comparisons revolve around requirements on Simon Brown's Blog. I am not sure that the application is very relevant and has limited functionality. However, I decided I would try this with WebWork. I have been using WebWork for about 5 months now and have been very productive with the framework's capabilities and ease of use. I took the requirements for the read only blog and reproduced it with WebWork. There is really very little that needed to be coded to make this happen and I did this on a plane trip from Atlanta to Las Vegas where I am attending TSSS with only one battery in my notebook.

To save time I copied some of the components from other people's implementations that were not framework specific.This included the domain objects Blog, BlogEntry, and BlogService. Theseare straightforward and I could have generated them in IntelliJ in about 2 minutes so no big deal. I also copied the stylesheet and image that was used to pretty up the blog look and feel. Again, no big deal and not really relevant to the framework comparison. So let me take a moment to preface some of my gripes with this little project. First, I do not consider someof the methods I used to code this application to be ideal. Since this is a read only blog application I did not use a persistence mechanism for the blog entries.

Nevertheless, I coded the application to the specifications required using only the WebWork web tier components no more. The main WebWork component that needed to be designed was the action class. This is fairly lean and should be
easy to interpret. Here is the code:

public class BlogAction implements Preparable {

private Blog blog;
private BlogEntry blogEntry;
private String blogEntryId;
private BlogService blogService;

public void prepare() throws Exception {
blog = getBlogService().getBlog();
}

public String execute() {
// simply display the default page -- the data was setup in prepare()
return "success";
}

public String viewBlogEntry() {
this.blogEntry = blog.getBlogEntry(this.blogEntryId);

return "entry";
}

public void setBlogEntryId(String blogEntryId) {
this.blogEntryId = blogEntryId;
}

public Blog getBlog() { return blog; }

public BlogEntry getBlogEntry() { return blogEntry; }

protected BlogService getBlogService() {
if (blogService == null) {
blogService = new BlogService();
}
return blogService;
}

public void setBlogService(BlogService blogService) {
this.blogService = blogService;
}

}

This is about 40 lines of code and most of that is comments, fields, and accessors. Really there are only about 5 lines of code of any logic. The prepare method is called before the action executes to setup the action. This gets called because the action implements the Preparable interface. The Prepare interceptor is invoked as one of the interceptors in the default interceptor stack. The service should really be injected into the action by an IoC container such as Spring (WebWork has good integration support with Spring). Also notice that other than implementing the Preparable interface this action is just a POJO.

Here is the action configuration in the xwork.xml file:
   <action name="Blog" class="com.meagle.wwblog.web.webwork.actions.BlogAction">
<result name="success">/pages/index.vm</result>
<result name="blogentry">/pages/entry.vm</result>
</action>
As for the views there were two files. The first one shows the blog and associated entries. I used SiteMesh todecorate the common elements and JSP as the view resolver to display the stack elements. WebWork can also use Freemarker, Velocity, and other view resolvers as well. Here is what the Sitemesh decorator looks like:
     <%@ page pageEncoding="UTF-8"%>
<%@ page contentType="text/html; charset=utf-8"%>

<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
<%@ taglib prefix="ww" uri="/webwork" %>
<?xml version="1.0" encoding="utf-8"?>
<!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"
xml:lang="en"
lang="en">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title><decorator:title default="Welcome!" /></title>
<meta http-equiv="Content-Style-Type" content="text/css">
<link rel="stylesheet" href="/css/screen.css" type="text/css"/>
</head>

<body>
<div id="container">
<h1><decorator:title default="Welcome!" /></h1>

<h2><ww:property value="%{blog.description}"/></h2>

<decorator:body />
</div>
</body>

</html>

So this takes care of the blog title, name, and description that appears on each page. This also leaves a placeholder for the content of each page. Here is what was required to display the entries in the blog:

     <%@ page pageEncoding="UTF-8"%>
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib uri="/webwork" prefix="ww" %>
<html>

<head>
<title><ww:property value="%{blog.name}"/></title>
</head>
<body>
<table>

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

<div>
<ww:if test="excerpt != null">
<ww:property value="excerpt" escape="false"/>
</ww:if>
<ww:else>
<ww:property value="body" escape="false"/>

</ww:else>
</div>

<p>
<ww:if test="excerpt != null">
<ww:url id="readMoreUrl" action="Blog!viewBlogEntry">
<ww:param name="blogEntryId" value="id"/>

</ww:url>
<a href="<ww:property value="readMoreUrl" escape="false"/>">Read More</a>
</ww:if>
</p>

<p>
Posted on <ww:date name="%{date}" format="dd MMMM yyyy hh:mm:ss z"/>
</p>
</div>
</td>
</tr>

</ww:iterator>
</table>
</body>
</html>

Again, this is straightforward and captures the requirements of the application. The only interesting lines here that allows the user to conditionally view more of a blog entry is this:

      <ww:url id="readMoreUrl" action="Blog!viewBlogEntry">
<ww:param name="blogEntryId" value="id"/>
</ww:url>
<a href="<ww:property value="readMoreUrl" escape="false"/>">Read More</a>

These lines create a link that calls the viewBlogEntry method of the Blog action by using a WebWorkconvention. The blog entry ID is appended as a parameter so that we can display the correct blog entry. You may notice the "action" in the tag references our Action class method viewBlogEntry(); it's in this method that we pull the specific entry and expose it (via a class variable) so that the view layer can render the entry. Here is the code for the view for displaying the entry.

     <%@ page pageEncoding="UTF-8"%>

<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="ww" uri="/webwork" %>
<html>
<body>
<ww:if test="blogEntry != null">
<div class="blogEntry">

<h3><ww:property value="blogEntry.title" escape="false"/></h3>

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

<p>
Posted on <ww:date name="%{blogEntry.date}" format="dd MMMM yyyy hh:mm:ss z"/>

</p>
</div>
</ww:if>
<ww:else>
<div class="blogEntry">
<h3>Sorry we could not locate that blog entry...</h3>

</div>
</ww:else>
<div><a href="JavaScript:void(0)" onclick="history.go(-1)">Back</a></div>
</body>
</html>

That is really all there was to coding the requirements of the application. The rest of the components were skeleton elements from a base WebWork configuration.

Conclusion

While this is a very simplistic web application with little functionality it does show how little coding is required by WebWork to make it happen. There is so much more that I would have like to have shown such as some of the data binding capabilities inside WebWork when submitting form information with complex object graph information.

This trivial application really does not show WebWork's full capabilities. If you think there is value in this example then you should give WebWork a try on more complex applications.
WebWork makes coding web development with Java simple and very productive with the framework's capabilities and ease of use.