WebWorks


Those who have used Struts know it is an action based framework. You write action classes that do certain work. You have an XML configuration file where you register the actions, the forms (beans) that the actions expect and the response mechanisms. Also you will then be painfully aware of some of the configuration headaches on large projects. One of the advantages of Struts is that it can provide a level of certainty to your web based application development model. What does that mean! For most web projects one of the existing web frameworks will suffice. Struts is well-known, well documented and relatively simple to use. You can find good people to write applications with Struts and also you will find enough good people to maintain those applications.

The power of a framework is not in how cool it is during development but how easy it is for others to pick it up and be productive.

Having said all of that about Struts I do think if you are coding a new project you should probably look at Tapestry or WebWorks as your framework of choice. I blogged on Tapestry in my previous blog. So let me blog on about WebWorks now. Point to remember: WebWorks is the new Struts 2.0 (still not in final form). The two frameworks have joined hands to give us a new more easy to use web framework under the Struts 2.0 banner.

Meanwhile for those starting new projects now you can still use the released versions of WebWorks. Just keep a watch on news about Struts 2.0. But if you look at WebWorks it is immediately obvious that its a great framework. The port over to Struts 2.0 will be a lot easier if you start with WebWorks. Lets begin by writing up a sample application.
The Action Class:
Here is the action class for our little application from bullet two onwards.
package com.aver.web;

import com.aver.service.timer.TimeService;
import com.opensymphony.xwork.ActionSupport;

public class Timer extends ActionSupport {

private String userName;

private String message;

private TimeService timeService;

public String getTime() {
message = timeService.getTime();
return SUCCESS;
}

public String getMessage() {
return message;
}

public void setTimeService(TimeService timeService) {
this.timeService = timeService;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}
}

The JSP's

Here is the JSP with a form that you submit to get the time.
<%@ taglib prefix="ww" uri="/webwork" %>
<html>
<head>
<title>Get Time</title>
</head>
<body>
<form action="showTime.action" method="post">
<p>User Name:<input type="text" name="userName"></p>
<p><input type="submit" value="Get Time" /></p>
</form>
</body>
</html>

The JSP file that serves up the response HTML is:
<%@ taglib prefix="ww" uri="/webwork" %>
<html>
<head>
<title>Show Time</title>
</head>
<body>
<ww:property value="userName"/>, it's now <ww:property value="message"/>
</body>
</html>
Interceptors:
Here is another must-know feature of WebWorks. Interceptors. These are classes you can intercept the request before and after processing so that you may do useful work (like apply security, do some global logging, or say attach a hibernate session to the thread, etc.). You can create any number of interceptors and stack them in order for execution. You will always end up using the predefined interceptors provided by WebWorks. More when I cover xworks.xml. Ya I know this file seems to have some magic right! Be patient.
package com.aver.web;

import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.interceptor.AroundInterceptor;

public class ExecutionTimeInterceptor extends AroundInterceptor {

protected void after(ActionInvocation arg0, String arg1) throws Exception {
}

protected void before(ActionInvocation arg0) throws Exception {
}
}
Xwork.xml:
Now for the glue that glues all of this together...the xwork.xml file.
<!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">

<!-- =================================================== -->
<!-- INTERCEPTORS -->
<!-- =================================================== -->
<interceptors>
<interceptor name="appUserInterceptor" class="com.aver.web.ExecutionTimeInterceptor">
</interceptor>
<interceptor-stack name="appInterceptorStack">
<interceptor-ref name="appUserInterceptor"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>

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

<!-- =================================================== -->
<!-- ACTIONS -->
<!-- =================================================== -->
<action name="showTime" class="com.aver.web.Timer" method="getTime">
<result name="success">showtime.jsp</result>
</action>
<action name="askForTime" class="com.aver.web.Timer" method="askForTime">
<result name="success">askfortime.jsp</result>
</action>

</package>
</xwork>
Spring Integration:
We need the file named webworks.properties in the web-inf/classes folder to configure Spring.
webwork.objectFactory=spring
webwork.objectFactory.spring.autoWire=type
I do not go into details of the Spring back end as this blog is not about Spring. The packaged example has a sample TimeService with the following interface and a configuration file named as service-registry.xml.
package com.aver.service.timer;

public interface TimeService {
public String getTime();
}

Web.xml
Finally here is the web.xml file.
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name>Sample WebWorks App</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:service-registry.xml
</param-value>
</context-param>

<filter>
<filter-name>webwork</filter-name>
<filter-class>com.opensymphony.webwork.dispatcher.FilterDispatcher</filter-class>
</filter>


<filter-mapping>
<filter-name>webwork</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- The following taglib directive would be needed if your servlet container would comply
to Servlet Spec <= 2.2 -->
<taglib>
<taglib-uri>/webwork</taglib-uri>
<taglib-location>/WEB-INF/lib/webwork-2.2.4.jar</taglib-location>
</taglib>
</web-app>

Once deployed you should be able to get to the action directly by
http://localhost:7001/webworks/showTime.action?userName=Matt

I have not show any of the Spring related configuration. I will assume that the reader knows how to do that. The complete application can be downloaded with its ant build script by clicking here. If you download the sample source code it will contain the Spring service implementation too. Ignore any Tiles related stuff in the downloaded zip. I have not completed WebWorks and Tiles integration yet. Though it looks relatively simple.

Summary:
You should by now have a good idea (and a working sample) of building an end-to-end web application with WebWorks and Spring. We are not configuration free yet. We have to maintain the xworks.xml file. My recommendation is to use XDoclet to generate the action details. That will greatly simplify things. I will not go into recommending anything to anyone (it's your application you make the call and live with that). I will say this though; if it's a new project stay away from regular old Struts and use WebWorks or Tapestry.

In comparison to Tapestry I did find WebWorks a lot more easier to get started. Learning curve is a lot smaller. Myself included, I have seen a lot of people praising Tapestry but always being cautious regarding it's learning curve. The thing I like most about Tapestry is the fact that there are no custom tag libs. Instead they throw all that is required as part of the standard HTML tag vocabulary. That makes the page easily editable in any HTML editor and also easier to work with between the web developer and the Java developer.