Warning |
---|
This page is DEPRECATED, please refer to the new source http://struts.apache.org/plugins/convention/ |
Table of Contents | ||||||
---|---|---|---|---|---|---|
|
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 | ||||
---|---|---|---|---|
| ||||
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>X.X.X</version>
</dependency>
|
...
If you are using REST with the Convention plugin, make sure you set these constants in struts.xml:
Code Block | ||||
---|---|---|---|---|
| ||||
<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>
<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 | ||||||
---|---|---|---|---|---|---|
| ||||||
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 | ||
---|---|---|
| ||
com.example.actions.MainAction
com.example.actions.products.Display (implements com.opensymphony.xwork2.Action)
com.example.struts.company.details.ShowCompanyDetailsAction
|
...
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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;
}
}
|
If you compile this class and place it into your application in the WEB-INF/classes, the Convention plugin will find the class and map the URL /hello-world to it. Next, we need to update our JSP to print out the message we setup in the action class. Here is the new JSP:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<html>
<body>
The message is ${message}
</body>
</html>
|
Note |
---|
Please notice that the expression |
If start up the application server and open up http://localhost:8080/hello-world in our browser, we should get this result:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
The message is Hello World!
|
...
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
<html>
<body>
The error message is ${message}
</body>
</html>
|
...
URL | Result | File that could match | Result Type |
---|---|---|---|
/hello | success | /WEB-INF/content/hello.jsp | Dispatcher |
/hello | success | /WEB-INF/content/hello-success.htm | Dispatcher |
/hello | success | /WEB-INF/content/hello.ftl | FreeMarker |
/hello-world | input | /WEB-INF/content/hello-world-input.vm | Velocity |
/test1/test2/hello | error | /WEB-INF/content/test/test2/hello-error.html Dispatcher /test/test2/hello-error.html | Dispatcher |
Multiple names
It is possible to define multiple names for the same result:
Code Block | ||
---|---|---|
| ||
@Action(results = {
@Result(name={"error", "input"}, location="input-form.jsp"),
@Result(name="success", location="success.jsp")
}) |
Such functionality was added in Struts 2.5
Chaining
If one action returns the name of another action in the same package, they will be chained together, if the first action doesn't have any result defined for that code. In the following example:
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
<java-package>#<namespace>#<parent-package>
|
Using our example from above, the XWork package for our action would be:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
com.example.actions#/#conventionDefault
|
...
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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;
}
}
|
...
Code Block | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
@org.apache.struts2.convention.annotation.Namespace("/custom")
package com.example.actions;
|
...
Code Block | ||
---|---|---|
| ||
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;
}
}
|
...
Code Block | ||
---|---|---|
| ||
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;
}
}
|
...
Code Block | ||
---|---|---|
| ||
@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 | ||||
---|---|---|---|---|
| ||||
<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 | ||||
---|---|---|---|---|
| ||||
<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.classes.reload" value="true" />
|
...
When using this plugin with JBoss, you need to set the following constants:
Code Block | ||||
---|---|---|---|---|
| ||||
<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 | ||||
---|---|---|---|---|
| ||||
<constant name="struts.convention.exclude.parentClassLoader" value="false" />
<constant name="struts.convention.action.fileProtocols" value="jar,code-source" />
|
...
The Convention plugin can be extended in the same fashion that Struts does. The following beans are defined by default:
Code Block | ||||
---|---|---|---|---|
| ||||
<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 | ||||
---|---|---|---|---|
| ||||
<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 | ||||
---|---|---|---|---|
| ||||
<constant name="struts.convention.result.path" value="/WEB-INF/mytemplates/"/>
|
...