Writing a Struts 2 Plugin

Struts 2 has built-in support for plugins which can extend the core framework functionality. Plugins can potentially add, replace or extend Struts 2 functionality. Whenever you want to share a set of reusable components across multiple Struts 2 Web applications, it is better to package them as plugins.

Usually Struts 2 plugins are packaged as Jar files and is dropped into the Web application's library folder (WEB-INF/lib). Struts 2 plugins usually contain a configuration file named struts-plugin.xml. Struts 2 framework scans all the jar files looking for struts-plugin.xml and if it finds one, automatically configures and executes the plugin features. Struts 2 plugin can contain actions, interceptors, results, resources and javabeans.

The structure of struts-plugin.xml is similar to Struts 2 configuration file, struts.xml. Hence a Struts 2 plugin has the capability to define results, interceptors etc. The order of execution of configuration files in Struts 2 is,

  • struts-default.xml (core framework configuration)
  • struts-plugin.xml (multiple files since there could be multiple plugins used in the project)
  • struts.xml (application level configuration)

Since struts.xml is executed after plugin configuration files, plugins may expose new features to struts.xml. It can also override most of the Struts 2 core implementation by replacing core classes.

A plugin can replace a core class by providing its own customized instance under pre-defined property names. For example, by overriding the struts.objectFactory property, a plugin can provide a custom version of com.opensymphony.xwork2.ObjectFactory. See the official page for a complete list of extension points/classes in Struts 2.

It is possible to embed static content in a Struts 2 plugin jar. All the static files added under a folder "static" is available through the URL path /struts.  So if you jar has /static/main.js, you can access it through the URL - /struts/main.js.

Writing a Struts 2 Plugin

This is a step by step tutorial on writing a Struts 2 plugin. In this example, we will write a plugin which automatically defines a new xml result type. Whenever the xml result type is used, the action member variables will be automatically converted into an xml file.

We will be using NetBeans for creating the Struts 2 plugin project. We will also need a Struts 2 web application up and running in NetBeans for testing our plugin. We will modify the Hello World application written earlier (this project needs to be renamed as Struts2PluginClient) as our testing Web application. Hence we will be working with two projects in NetBeans,

  • Struts2-XML-Plugin - This is the Struts 2 plugin project and it provides a new result type "xml". If the result type is configured as xml, the response received from the request will be an XML corresponding to the member variables of the action class invoked. This implementation is only a demo and is not intended for production use.
  • Struts2PluginClient - Simple Struts 2 Web application for testing our plugin. We would change the result from the JSP to the "xml" type to get the "Hello World!" message in an XML file!

Creating Struts 2 Plugin Project in NetBeans

A Struts 2 Plugin project is just like any other Java class library. From NetBeans, click on File => New Project and select Java Class Library as shown below. Click on Next.

Creating Struts 2 Plugin Project in NetBeans

Enter the name of the project as Struts2-XML-Plugin and click on Finish.

Creating a Struts 2 Plugin

From the projects tab, right click on Libraries folder and then click on Add JAR/Folder as shown below,

Adding dependency jars to Struts 2 plugin project

Add the following jar files from the Struts 2 distribution zip file (see this article for details on downloading Struts 2 distribution file).

  • Required libraries in Struts 2 Plugin projectstruts2-core-2.3.1.2.jar
  • ognl-3.0.4.jar
  • xwork-core-2.3.1.2.jar
  • freemarker-2.3.18.jar

Right click on Libraries and then click on Add Library. From the Global Libraries list, add Java EE 6 API Library. This reference is required for compiling our plugin since we would be using servlet classes such as HttpServletRequest.

Now we have the plugin project ready for development. For this plugin project we need the following components,

  • struts-plugin.xml - This will configure a new result type "xml".
  • XMLResult.java - This will implement the XML result type. This class will implement the com.opensymphony.xwork2.Result interface.

struts-plugin.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <package name="xml-default" extends="struts-default">
        <result-types>
            <result-type name="xml" class="com.quickprogrammingtips.demo.XMLResult"/>
        </result-types>
    </package>
</struts>

We have added a new package xml-default which defines a new result type for "xml". Whenever a result of type of xml is configured, it is rendered by the XMLResult class.

XMLResult.java (Result handler for xml type)

XMLResult implements the Result interface and hence can be used as a view renderer. In the execute() method we use reflection to get the message from action class and then create the corresponding XML file.

package com.quickprogrammingtips.demo;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.StrutsStatics;
public class XMLResult implements Result{

    @Override
    public void execute(ActionInvocation ai) throws Exception {
        Object action = ai.getAction();
        ActionContext actionContext = ai.getInvocationContext();
        HttpServletResponse response = (HttpServletResponse) actionContext
                .get(StrutsStatics.HTTP_RESPONSE);
        
        Class cls = action.getClass();
        Method meth = cls.getMethod("getMessage", null);
        Object m = meth.invoke(action, null);
        
        Class cls2 = m.getClass();
        Method meth2 = cls2.getMethod("getMessage", null);
        Object m2 = meth2.invoke(m, null);
        response.getWriter().print("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
        response.getWriter().print("<xml>"+m2+"</xml>");
        
    }
    
}

Note that currently XMLResult is hardcoded to return getMessage().getMessage() from action class. In a real plugin, we should use the reflection recursively on the action class to generate the XML file.

Struts 2 Plugin and Web projects in NetBeansCreating a Plugin Client Web application in NetBeans (Struts2PluginClient)

Follow this tutorial to create the Struts2PluginClient project (rename Struts2Demo to Struts2PluginClient) in NetBeans. Right click the Libraries folder in the Struts2PluginClient Web application and click on Add Project. Select Struts2-XML-Plugin project. This creates a plugin project dependency on the Web application and latest plugin code will be deployed as a jar file in the client Web application.

Once both projects are configured, your NetBeans projects tab should like the one on the right side picture.

We need to modify the struts.xml so that xml result type is invoked instead of dispatching to JSP,

struts.xml (Struts 2 configuration)

<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <package name="struts2demo" extends="xml-default">
        <action name="HelloWorld" class="controller.HelloWorld">
            <result name="success" type="xml"></result>
        </action>
    </package>
</struts>

Note that the result type is set as xml. Also note that the package now extends from xml-default instead of struts-default.

HelloWorld.java (action class)

package controller;

import com.opensymphony.xwork2.ActionSupport;
import model.Message;

public class HelloWorld extends ActionSupport {
    private Message message;
    
    @Override
    public String execute() {
        setMessage(new Message()); // get data from model
        return SUCCESS;
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }
    
}

 

Message.java (model)

package model;

public class Message {
    private String message = "Hello World!";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <display-name>Struts2 Demo App</display-name>
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>    
</web-app>

To test the plugin, Run the Struts2PluginClient application (F6) and then invoke the HelloWorld action class using the following URL,

http://localhost:8084/Struts2PluginClient/HelloWorld.action

You should see the XML output on the browser window,

Struts 2 Plugin Example

A zip file containing both the projects can be downloaded from here. Please note in order to keep the download size small, I have removed Struts 2 jars from the zip file. You need to download them from the Struts2 repository.