About XWork

XWork 2 is a generic command pattern framework. It forms the core of Struts 2. It features:

  • Flexible and customizable configuration based on a simple Configuration interface, allowing you to use XML , programmatic, or even product-integrated configuration
  • Core command pattern framework which can be customized and extended through the use of interceptors to fit any request / response environment
  • Built in type conversion and action property validation using OGNL
  • Powerful validation framework based on runtime attributes and a validation interceptor

Overview

Xwork is a command pattern framework centralized around an Action interface. You define action classes by implementing an Action interface, then XWork will setup and execute your actions. XWork is most widely known from the web MVC framework called Webwork. However, XWork can be used by itself, so its important to understand the XWork layers and how actions are set up and executed. This section describes the core layers within Xwork and provides a simple example of how to execute actions.

  • Action Interface
  • ActionProxy interface
  • ActionInvocation interface
  • ActionContext
  • A simple example



Actions

Actions are the basic unit of execution...

The Action Interface

The basic interface which all XWork actions must implement. It provides several standard return values like SUCCESS and INPUT, and only contains one method:

Action.java
public interface Action {
    public static final String SUCCESS = "success";
    public static final String NONE = "none";
    public static final String ERROR = "error";
    public static final String INPUT = "input";
    public static final String LOGIN = "login";
    
    public String execute() throws Exception;

In general, Actions can simply extend the com.opensymphony.xwork.ActionSupport class, which implements the Action interface and provides default behavior for the most common actions.

ActionProxy

Action lifecycles are maintained thru the ActionProxy interface. ActionProxy is the top layer in the Xwork API and should be the starting point to setup and execute actions. XWork provides a factory as an entry point to instantiate action proxies. Most of the implementations of each xwork layer are hidden behind interfaces making it very easy to override the default implementations for complete customization.

Example how to obtain the default impl of ActionProxy (DefaultActionProxy.java)

ActionProxyFactory.getFactory().createActionProxy("", "viewBook", objectMap);

If I need to register my own implementation of ActionProxy, then I may do so within the factory

class CustomizedActionProxyFactory extends DefaultActionProxyFactory{
	createActionProxy(...){ return new CustomizedActionProxy(...); }
}
ActionProxyFactory.setFactory(new CustomizedActionProxyFactory());
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(...);

ActionInvocation

Underneath the ActionProxy layer, exists the ActionInvocation interface. ActionInvocation represents the execution state of an action holding the action instance along with any interceptors that wrap before/after processing of the action.

ActionContext

ActionContext provides access to the execution environment in the form of named objects during an Action invocation. A new ActionContext is created for each invocation allowing developers to access/modify these properties in a thread safe manner. The ActionContext makes a number of properties available that are typically set to appropriate values by the framework. In WebWork 2 for example, the ActionContext session map wraps an underlying HttpSession object. This allows access to environment specific properties without tying the core framework to a specific execution environment.

The ActionContext is acquired through the static ActionContext.getContext() method. The ActionContext is a thread local variable and thus the properties of the ActionContext will be relative to the current request thread. The ActionContext has several methods for commonly used properties as well as get() and set() methods which can be used for application specific properties.

A simple example

Lets look at a simple example starting with a simple javabean.

public class Book {
	String id;
	String title;
	Set authors;
	public void setId(id){ this.id = id; }
	public void setTitle(String title){ this.title = title; }
	public void setAuthors(Set authors){ this.authors = authors; }
	public String getId(){ }
	public String getTitle{ }
	public Set getAuthors{ }
}

Lets say that we need to retrieve a book object from a data source and pass it back to the caller. We can write an action to handle this. An action in xwork is typically a very simple class. The only requirement is that it implements the Action interface. These days you'll see actions as simple as javabeans with an execute method (Validation, Type conversion, and so forth can all be seperated out to provide a good separation of concerns). The purpose of action execution is typically to provide access and manipulation to your data. The result of the action execution is a simple string representation that should define delegation of the action after invocation. So a result could be a success string, a failure string, a forward string, or what ever. In our current example, a book object can be populated in the action if found with a result of "success" or if the book is not found then a "notFound" can be returned. From this, you can easily have a controlling object setup to return the book or possible forward the request off to a different inventory source if the book isn't found.

com.opensymphony.xwork.example.ViewBookAction
public class ViewBookAction  implements Action{
	Book book;
	String id;

	public String execute() throws Exception{

		// lets pretend we have a data access object that will return a book from storage
		book = bookDAO.findById(id, Book.class);
		if(book != null) return "success";
		return "notFound";
	}
	public Book getBook(){ return this.book; }
	public setId(String id){this.id = id; }
}

Now that we have an action defined with a simple model, lets setup an action proxy and execute the action.
Setting up XWork to execute the action:

// obtain inputs from the caller. For this example, we can just define some dummy params.
Map paramMap = new HashMap();
paramMap.put("id", "0123456789");

// set the ActionContext parameters
Map context = new HashMap();
context.put(ActionContext.PARAMETERS, paramMap);

// create an action proxy with no namespace, action alias (defined in xwork.xml), and a map of the context info
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy("","viewBook", context);

// we have the action proxy instance, lets execute it and retrieve the action
String result = proxy.execute();
if ("success".equals(result)){
   ViewBookAction action = (ViewBookAction) proxy.getAction();
   
   // return info back to caller or just print to screen for this example
   System.out.println(action.getBook().getTitle());
} else if("notFound".equals(result){
   // forward to another inventory source
} else {
   throw new RuntimeException("Im lazy");
}

Not quite done yet, we need to define some configuration in xwork.xml so XWork can find the appropriate class to execute based on the action alias we provided within the createActionProxy(...) method.

xwork.xml
<xwork>
    <include file="xwork-default.xml"/>
    <package name="default" extends="xwork-default">
       <action name="viewBook" class="com.opensymphony.xwork.example.ViewBookAction"/>
    </package>
</xwork>

Next: XWork Core Concepts