Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents
stylenone
minLevel1
outlinetrue

Introduction

The Convention Plugin is bundled with Struts since 2.1 and replaces the Codebehind Plugin and Zero Config plugins. It provides the following features:

...

In order to use the Convention plugin, you first need to add the JAR file to the WEB-INF/lib directory of your application or include the dependency in your project's Maven POM file.

Code Block
xml

<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-convention-plugin</artifactId>
    <version>2<version>X.1X.6<X</version>
</dependency>

Where X.X.X is the current version of Struts 2. Please remember that the Convention Plugin is available from version 2.1.6.

Converting a Codebehind based application to Convention

...

If you are using REST with the Convention plugin, make sure you set these constants in struts.xml:

Code Block
xml

<constant name="struts.convention.action.suffix" value="Controller"/>
<constant name="struts.convention.action.mapAllMatches" value="true"/>
<constant name="struts.convention.default.parent.package" value="rest-default"/>

...

Now that the Convention plugin has been added to your application, let's start with a very simple example. This example will use an actionless result that is identified by the URL. By default, the Convention plugin assumes that all of the results are stored in WEB-INF/content. This can be changed by setting the property struts.convention.result.path in the Struts properties file to the new location. Don't worry about trailing slashes, the Convention plugin handles this for you. Here is our hello world JSP:

Code Block
html

<html>
<body>
Hello world!
</body>
</html>

If you start Tomcat (or whichever J2EE container you are using) and type in http://localhost:8080/hello-world (assuming that your context path is "/", ie. starting application from Eclipse) into your browser you should get this result:

Code Block
borderStylesolid
titleWEB-INF/content/hello-world.jsp

Hello world!

This illustrates that the Convention plugin will find results even when no action exists and it is all based on the URL passed to Struts.

...

These packages are located by the Convention plugin using a search methodology. First the Convention plugin finds packages named struts, struts2, action or actions. Any packages that match those names are considered the root packages for the Convention plugin. Next, the plugin looks at all of the classes in those packages as well as sub-packages and determines if the classes implement com.opensymphony.xwork2.Action or if their name ends with Action (i.e. FooAction). Here's an example of a few classes that the Convention plugin will find:

Code Block
titleClasses

com.example.actions.MainAction
com.example.actions.products.Display (implements com.opensymphony.xwork2.Action)
com.example.struts.company.details.ShowCompanyDetailsAction

...

Code Block
titleNamespaces

com.example.actions.MainAction -> /
com.example.actions.products.Display -> /products
com.example.struts.company.details.ShowCompanyDetailsAction -> /company/details

Next, the plugin determines the URL of the resource using the class name. It first removes the word Action from the end of the class name and then converts camel case names to dashes. In our example the full URLs would be:

Code Block
titleFull URLs

com.example.actions.MainAction -> /main
com.example.actions.products.Display -> /products/display
com.example.struts.company.details.ShowCompanyDetailsAction -> /company/details/show-company-details

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;


public class HelloWorld extends ActionSupport {
  private String message;

  public String getMessage() {
    return message;
  }

  public String execute() {
    message = "Hello World!";
    return SUCCESS;
  }
}

...

Code Block
borderStylesolid
titleWEB-INF/content/hello-world.jsp

<html>
<body>
The message is ${message}
</body>
</html>
Note

Please notice that the expression ${message} will work without adding JSP directive isELIgnored="false".

 

If start up the application server and open up http://localhost:8080/hello-world in our browser, we should get this result:

Code Block
borderStylesolid
titleResult

The message is Hello World!

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;


public class HelloWorld extends ActionSupport {
  private String message;

  public String getMessage() {
    return message;
  }

  public String execute() {
    if (System.currentTimeMillis() % 2 == 0) {
      message = "It's 0";
      return "zero";
    }

    message = "It's 1";
    return SUCCESS;
  }
}

...

Code Block
titleWEB-INF/content/hello-world.jsp

<html>
<body>
The error message is ${message}
</body>
</html>

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionSupport;


public class HelloAction extends ActionSupport {
    @Action("foo")
    public String foo() {
        return "bar";
    }

    @Action("foo-bar")
    public String bar() {
        return SUCCESS;
    }
}

...

Code Block
titleXWork package naming

<java-package>#<namespace>#<parent-package>

...

Code Block
borderStylesolid
titleXWork package naming

com.example.actions#/#conventionDefault

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;

public class HelloWorld extends ActionSupport {
  @Action("/different/url")
  public String execute() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

public class HelloWorld extends ActionSupport {
  @Actions({
    @Action("/different/url"),
    @Action("/another/url")
  })
  public String execute() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

public class HelloWorld extends ActionSupport {
  @Action("/different/url")
  public String execute() {
    return SUCCESS;


  }

  @Action("url")
  public String doSomething() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

public class HelloWorld extends ActionSupport {
  @Action(interceptorRefs={@InterceptorRef("validation"), @InterceptorRef("defaultStack")})
  public String execute() {
    return SUCCESS;
  }

  @Action("url")
  public String doSomething() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

public class HelloWorld extends ActionSupport {
  @Action(interceptorRefs=@InterceptorRef(value="validation",params={"programmatic", "false", "declarative", "true}))
  public String execute() {
    return SUCCESS;
  }

  @Action("url")
  public String doSomething() {
    return SUCCESS;
  }
}

If interceptors are not specified, the default stack is applied.

Info

You can specify className parameter which can be especially useful when Spring Framework is used to instantiate actions.

Applying @Action and @Actions at the class level

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;

@InterceptorRefs({
    @InterceptorRef("interceptor-1"),
    @InterceptorRef("defaultStack")
})
public class HelloWorld extends ActionSupport {
  @Action(value="action1", interceptorRefs=@InterceptorRef("validation"))
  public String execute() {
    return SUCCESS;
  }

  @Action(value="action2")
  public String doSomething() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

@Results({
  @Result(name="failure", location="fail.jsp")
})
public class HelloWorld extends ActionSupport {
  @Action(value="/different/url",
     results={@Result(name="success", location="http://struts.apache.org", type="redirect")}
  )
  public String execute() {
    return SUCCESS;
  }

  @Action("/another/url")

  public String doSomething() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

public class HelloWorld extends ActionSupport {
  @Action(value="/different/url",
     results={@Result(name="success", type="httpheader", params={"status", "500", "errorMessage", "Internal Error"})}
  )
  public String execute() {
    return SUCCESS;
  }

  @Action("/another/url")
  public String doSomething() {
    return SUCCESS;
  }
}

...

Code Block
titlecom.example.actions.HelloWorl

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;

@Namespace("/custom")
public class HelloWorld extends ActionSupport {
  @Action("/different/url")
  public String execute() {
    return SUCCESS;
  }

  @Action("url")
  public String doSomething() {
    return SUCCESS;
  }
}

...

Code Block
titlecom/example/actions/package-info.java

@org.apache.struts2.convention.annotation.Namespace("/custom")
package com.example.actions;

...

Code Block
titlecom.example.actions.HelloWorl

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.ResultPath;

@ResultPath("/WEB-INF/jsps")
public class HelloWorld extends ActionSupport {
  public String execute() {
    return SUCCESS;
  }
}

...

The ParentPackage annotation allows applications to define different parent XWork packages Struts package for specific action classes or Java packages. Here is an example of using the annotation on an action class:

Code Block
titlecom.example.actions.HelloWorld

package com.example.actions;

import com.opensymphony.xwork2.ActionSupport;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.ParentPackage;

@ParentPackage("customXWorkPackage")
public class HelloWorld extends ActionSupport {
  public String execute() {
    return SUCCESS;
  }
}

To apply this annotation to all actions in a package (and subpackages), add it to package-info.java. An alternative to this annotation is to set struts.convention.default.parent.package in XML.

ExceptionMapping Annotation

...

Code Block
titleExceptionsActionLevelAction.java

@ExceptionMappings({
    @ExceptionMapping(exception = "java.lang.NullPointerException", result = "success", params = {"param1", "val1"})
})
public class ExceptionsActionLevelAction {

    public String execute() throws Exception {
        return null;
    }
}

The parameters defined by params are passed to the result. Exception mappings can also be applied to the action level:

Code Block

public class ExceptionsMethodLevelAction {
    @Action(value = "exception1", exceptionMappings = {
            @ExceptionMapping(exception = "java.lang.NullPointerException", result = "success", params = {"param1", "val1"})
    })
    public String run1() throws Exception {
        return null;
    }
}

...

By default the Convention plugin will not scan jar files for actions. For a jar to be scanned, its URL needs to match at least one of the regular expressions in struts.convention.action.includeJars. In this example myjar1.jar and myjar2.jar will be scanned:

Code Block
xml

<constant name="struts.convention.action.includeJars" value=".*?/myjar1.*?jar(!/)?,.*?/myjar2*?jar(!/)?"

...

The Convention plugin can automatically reload configuration changes, made in classes the contain actions, without restarting the container. This is a similar behavior to the automatic xml configuration reloading. To enable this feature, add this to your struts.xml file:

Code Block
xml

<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.classes.reload" value="true" />

This feature is experimental and has not been tested on all container, and it is strongly advised not to use it in production environments.

...

When using this plugin with JBoss, you need to set the following constants:

Code Block
xml

<constant name="struts.convention.exclude.parentClassLoader" value="true" />
<constant name="struts.convention.action.fileProtocols" value="jar,vfsfile,vfszip" />

You can also check the JBoss 5 page for more details.

Jetty (embedded)

When using this plugin with Jetty in embedded mode, you need to set the following constants:

Code Block
xml
<constant name="struts.convention.exclude.parentClassLoader" value="false" />
<constant name="struts.convention.action.fileProtocols" value="jar,code-source" />

Troubleshooting

Tips

Tip
titleNamespaces and Results

Make sure the namespace of the action is matched by one of the locators. The rest of the namespace after the locator, will be the namespace of the action, and will be used to find the results. For example, a class called "ViewAction" in the package "my.example.actions.orders" will be mapped to the URL /orders/view.action, and the results must be under /WEB-INF/content/orders, like /WEB-INF/content/orders/view-success.jsp.

...

The Convention plugin can be extended in the same fashion that Struts does. The following beans are defined by default:

Code Block
xml

<bean type="org.apache.struts2.convention.ActionConfigBuilder" name="convention" class="org.apache.struts2.convention.PackageBasedActionConfigBuilder"/>
This interface defines how the action configurations for the current web application can be constructed. This must find all actions that are not specifically defined in the struts XML files or any plugins. Furthermore, it must make every effort to locate all action results as well.

<bean type="org.apache.struts2.convention.ActionNameBuilder" name="convention" class="org.apache.struts2.convention.SEOActionNameBuilder"/>
This interface defines the method that is used to create action names based on the name of a class.

<bean type="org.apache.struts2.convention.ResultMapBuilder" name="convention" class="org.apache.struts2.convention.DefaultResultMapBuilder"/>
This interface defines how results are constructed for an Action. The action information is supplied and the result is a mapping of ResultConfig instances to the result name.

<bean type="org.apache.struts2.convention.InterceptorMapBuilder" name="convention" class="org.apache.struts2.convention.DefaultInterceptorMapBuilder"/>
This interface defines how interceptors are built from annotations.

<bean type="org.apache.struts2.convention.ConventionsService" name="convention" class="org.apache.struts2.convention.ConventionsServiceImpl"/>
This interface defines the conventions that are used by the convention plugin. In most cases the methods on this class will provide the best default for any values and also handle locating overrides of the default via the annotations that are part of the plugin.

<constant name="struts.convention.actionConfigBuilder" value="convention"/>
<constant name="struts.convention.actionNameBuilder" value="convention"/>
<constant name="struts.convention.resultMapBuilder" value="convention"/>
<constant name="struts.convention.interceptorMapBuilder" value="convention"/>
<constant name="struts.convention.conventionsService" value="convention"/>

To plugin a different implementation for one of these classes, implement the interface, define a bean for it, and set the appropriate constant's value with the name of the new bean, for example:

Code Block
xml

<bean type="org.apache.struts2.convention.ActionNameBuilder" name="MyActionNameBuilder" class="example.SultansOfSwingNameBuilder"/>
<constant name="struts.convention.actionNameBuilder" value="MyActionNameBuilder"/>

...

Add a constant element to your struts config file to change the value of a configuration setting, like:

Code Block
xml

<constant name="struts.convention.result.path" value="/WEB-INF/mytemplates/"/>

Name

Default Value

Description

struts.convention.action.alwaysMapExecute

true

Set to false, to prevent Convention from creating a default mapping to "execute" when there are other methods annotated as actions in the class

struts.convention.action.includeJars

 

Comma separated list of regular expressions of jar URLs to be scanned. eg. ".*myJar-0\.2.*,.*thirdparty-0\.1.*"

struts.convention.action.packages

 

An optional list of action packages that this should create configuration for (they don't need to match a locator pattern)

struts.convention.result.path

/WEB-INF/content/

Directory where templates are located

struts.convention.result.flatLayout

true

If set to false, the result can be put in its own directory: resultsRoot/namespace/actionName/result.extension

struts.convention.action.suffix

Action

Suffix used to find actions based on class names

struts.convention.action.disableScanning

false

Scan packages for actions

struts.convention.action.mapAllMatches

false

Create action mappings, even if no @Action is found

struts.convention.action.checkImplementsAction

true

Check if an action implements com.opensymphony.xwork2.Action to create an action mapping

struts.convention.default.parent.package

convention-default

Default parent package for action mappins

struts.convention.action.name.lowercase

true

Convert action name to lowercase

struts.convention.action.name.separator

-

Separator used to build the action name, MyAction -> my-action. This character is also used as the separator between the action name and the result in templates, like action-result.jsp

struts.convention.package.locators

action,actions,struts,struts2

Packages whose name end with one of these strings will be scanned for actions

struts.convention.package.locators.disable

false

Disable the scanning of packages based on package locators

struts.convention.exclude.packages

org.apache.struts.*,
org.apache.struts2.*,
org.springframework.web.struts.*,
org.springframework.web.struts2.*,
org.hibernate.*

Packages excluded from the action scanning, packages already excluded cannot be included in other way, eg. org.demo.actions.exclude is specified as a part of the struts.convention.exclude.packages so all packages below are also excluded, eg. org.demo.actions.exclude.include even if include is specified as a struts.convention.package.locators or struts.convention.action.packages

struts.convention.package.locators.basePackage

 

If set, only packages that start with its value will be scanned for actions

struts.convention.relative.result.types

dispatcher,velocity,freemarker

The list of result types that can have locations that are relative and the result location (which is the resultPath plus the namespace) prepended to them

struts.convention.redirect.to.slash

true

A boolean parameter that controls whether or not this will handle unknown actions in the same manner as Apache, Tomcat and other web servers. This handling will send back a redirect for URLs such as /foo to /foo/ if there doesn't exist an action that responds to /foo

struts.convention.classLoader.excludeParent

true

Exclude URLs found by the parent class loader from the list of URLs scanned to find actions (needs to be set to false for JBoss 5)

struts.convention.action.eagerLoading

false

If set, found action classes will be instantiated by the ObjectFactory to accelerate future use, setting it up can clash with Spring managed beans