WebWork is a service invocation framework. It's built around the concept of actions and views.
WebWork is web-agnostic (despite the name), although it's been more or less geared for web applications. As such, we'll use the paradigms of the web to show some simple concepts about it.
WebWork uses a dispatcher to look up entry points, which resolve to Actions. Actions return simple results, which are free-form in nature, and those results map to either another action or a view. A dispatcher is the central entry point to webwork.
A view in webwork is responsible for generating output. It's also an endpoint. Views in WebWork can be done with anything: Swing, SWT, JSP, Velocity, XML/XSL, Jasper are all good examples.
A view gets its specific request data via a concept called the "ValueStack" which will be explained later.
An action in WebWork is a unit of code (usually, but not restricted to, a java class that has a definite entry point (which, oddly enough, is variable depending on the nature of the action itself). At its simplest, an Action uses a method called "execute()" to begin its processing.
Actions have a specific lifecycle in WebWork. The dispatcher takes the
request itself, and in the simplest case, maps it to a specific Action. The
Action is instantiated, and request parameters are reflectively set in the
Action; then, after all possible matches have been made, the
execute()
method is called. The execute()
method
returns a java.lang.String
, which is mapped to the "next step,"
which can be another Action, or a View.
The ValueStack is actually a set of Actions. As an Action is created, it's put in an internal list for the request, and each action is accessible via this list - so an Action that's third in a list of five has access to the data created at steps one and two, in addition to having access to the request data itself. The value stack can be interrogated via a rich expression language, so it is possible to query any particular action and its properties.
The ValueStack is also how actions provide data to views: a view takes a request for data, and looks for it on the entire value stack, returning the first (most recent) match for the data.
If you want to be up and running quickly without having to go through the hassle of creating a buildfile and project directories, webwork ships with a skeleton-project.zip file that contains a bare-bones skeleton project webapp that can be deployed into your servlet container of choice. You will need to run ant to compile the single trivial action that is included, then you're up and running. Note that this app does not contain the velocity jar files or jars for any views other than jsp, so if you want to test different views that simply copy the jar files requires for that view type into the WEB-INF/lib directory.
Here's a very simple example of a guestbook done in WebWork, to illustrate how some of this works. We have a few use cases to work with:
We're purposefully leaving some aspects unimplemented (for example, "show first visitor for all states") for the sake of simplicity.
Our first flow is very simple, "Show Markers." For this, we have a simple
path: one action, one view. The Action is ShowMarkersAction
(an
arbitrary name), and the view, in Velocity, is showmarkers.vm
.
ShowMarkersAction
takes no input, so it has no mutators -
only an accessor, public java.util.List getMarkers()
. The
execute()
method creates the list and populates it, and returns
SUCCESS
, which is one of the four predefined return types. (As
stated earlier, return values are freeform - the four predefined values are
for convenience only.)
The success response value maps to showmarkers.vm, which iterates over $markers and shows the names in order. The configuration for this, using the property file configuration format, would look like this:
show.action=ShowMarkersAction show.success=showmarkers.vm
The next flow is a little more complex. First, the configuration entries:
mark.action=IsAPreteenAction mark.ageok=savename.action mark.agenotok=underage.vm savename.action=SaveNameAction savename.success=isunder18.action isunder18.action=IsAMinorAction isunder18.ageok=savestate.action isunder18.agenotok=show.action savestate.action=SaveStateAction savestate.success=show.action
What's going on here? It's pretty simple: the first thing we do is hit a "routing" action, that returns "ageok" or "agenotok" depending on the age of the person. If the response is "agenotok" then it politely informs the user that they were underage for the purposes of tracking information.
If the age checked out (i.e., over 16), then it routes to
savename.action
- which ends up being
SaveNameAction
. SaveNameAction
stores the name and
age of the user, and then returns an indicator of success, which does the
same sort of thing with a different age.
The code for IsAPreteenAction.java
:
package com.enigmastation.webwork.example; public class IsAPreteenAction extends webwork.ActionSupport { int age; public void setAge(int newage) { age=newage; } public int getAge() { return age; } public String execute() throws Exception { if(getAge() > 12) { return "ageok"; } else { return "agenotok"; } } }
That's it. The lifecycle first creates an instance, calls
setAge(int)
(assuming, of course, an age was put in, which for
the purpose of simplicity we'll assume has been done), then calls
execute()
, and chooses the next step based on the response of
this method.
The power of WebWork manifests itself in the separation of your action
from your views. In this case, you could use the
IsAPreteenAction
as a standalone router, to echo a
preteen-friendly version of a page or a teenager-aimed version of a page;
its use of a router in this case is simply an arbitrary choice on my part.
There'd be nothing preventing this kind of flow, requiring no changes on the
part of the action:
agedhello.action=IsAPreteenAction agedhello.agenotok=mickeymousehello.vm agedhello.ageok=rock-and-roll.vm
This separation, enforced by the API itself, means that components tend to fall naturally out of the system based on use cases - a great boon for organizational and architectural strength, as a failure point is naturally isolated and can be dealt with as needed. The view neutrality means that the optimal solution for views - be it JSP, Velocity, RTF documents, etc - can be chosen at will.
Views aren't webwork-tailored. As mentioned before, you can use Velocity, JSP, FreeMarker, or any number of other technologies to render results. To do so, the dispatcher creates an environment from which the view can retrieve data, and then executes the view. For JSP, the environment is left alone (since there are WebWork tag libraries to pull out the data from the ValueStack), and in Velocity, the ValueStack is propagated to the context as well.
Here's an example of a "Hello, World" application using WebWork, from start to finish. First, our use cases:
Our configuration would look like this:
hello.action=HelloAction hello.input=hello.html hello.jsp=hello.jsp hello.vm=hello.vm
HelloAction
would look like this:
package com.enigmastation.webwork.example; public class HelloAction imports webwork.ActionSupport { protected String view=null; protected String name=null; public String getView() { return view; } public void setView(String view) { this.view=view; } public String getName() { return name; } public void setName(String name) { this.name=name; } public void execute() throws Exception { String retval=INPUT; if(!(getName()==null || "".equals(getName().trim()))) { if("jsp".equalsIgnoreCase(view)) { retval="jsp"; } else { retval="vm"; } } return retval; } }
A quick explanation: if the name isn't null, return "jsp" or "vm" depending on the view selection; otherwise, return the INPUT mapping.
Here's the input form itself, which is plain (very plain) HTML:
<html> <head> <title>Hello, World!</title> </head> <body> <form action="hello.action"> What's your name? <input type="text" name="name"><br> What view would you like? <select name="view"> <option>jsp <option>vm </select><br> <input type="submit"> </form> </body> </html>
The Velocity view is trivial to write, so here it is:
<html> <head> <title>Hello, $name!</title> </head> <body> <p>Hi there, $name! Nice to meet you. </body> </html>
As you can see, the values are pulled from the ValueStack by using normal
Velocity references, thus $name
looks in the action for an
attribute called "name" and uses that value.
In JSP, it's not that much different, except for the use of tag libraries to pull data from the ValueStack, which have their own oddities regarding namespacing:
<@ taglib uri="webwork" prefix="ww" %><html> <head> <title>Hello, <ww:property name="'name'" />!</title> </head> <body> <p>Hi there, <ww:property name="'name'" />! Nice to meet you. </body> </html>
You'll notice that the "name" property has double-quotes and single quotes; this is because the JSP tag libraries introspect values. If 'name' was left unquoted, it would be taken not as a literal name, but as the name of a property, and the tag library would look up the name of the property from that value. This may seem like a lot of extra obfuscation, but it's actually extremely useful for internationalization, where you might pull a bit of text from a property name, rather than a property value, as we're doing here.
Hopefully this brief guide has illustrated the main features of webwork. Once you go through these examples and start feeling more confident in using this framework, we encourage you to read through the rest of the manual as well as the webwork cookbook in order to ensure you get the most out of what is available. Enjoy and happy webworking!