This article explains my hardships while trying to get the example work. It is still not a fully working solution but a solution which takes you through steps any new guys go through while trying to configure struts2
using annotation.
With introduction of annotation concept in Java5, many frameworks tried to exploit it very well to make themselves developer friendly. With respect to Web Framework's Spring MVC is the leader as well as frontrunner in using this. Struts2
has tried to implement the same concepts but for any new developer it be little hard to get it to work as there are not many working articles over the web. I never suggest using any framework tags in building UI elements which these framework's profess (It would be difficult to reuse/migrate if at all we have to migrate in future). I never support people/frameworks that push UI development in hands of developers rather than to designers.
Having said this, here is an example of a sample struts2
application where I have an HTML named index.jsp which calls a Struts2
action in the back-end to get data and presentation as defined in the HTML.
First of all, I'll define by build structure and dependencies. As I'm planning to use Maven, my build structure is default maven build structure. Here is my pom.xml.
<project xmlns=http://maven.apache.org/POM/4.0.0
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.linkwithweb.struts2</groupId>
<artifactId>Struts2Example</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>Struts2Example Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-json-plugin</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>Struts2Example</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.0</version>
<configuration></configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.1.4.v20100610</version>
<configuration>
<webApp>${basedir}/target/medihits-admin
</webApp>
</configuration>
</plugin>
</plugins>
</build>
</project>
The struts2-convention-plugin-version jar file is needed if you are using annotations.
Struts2
has made life a little simple by avoiding all the Configuration files it needed before this version by providing configuration by annotation if we just see our web.xml, it's pretty straightforward where we configure Struts2
Filter to route all requests to corresponding actions.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Struts 2 Sample Web Application</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/struts/*</url-pattern>
</filter-mapping>
</web-app>
If you observe web.xml, I'm filtering all requests that are starting with /struts/* using struts filter. This is the entry point of struts application. Now let me describe how we define an Action
in Struts2
.
package com.linkwithweb.user.action;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.ResultPath;
import com.opensymphony.xwork2.ActionSupport;
@Namespace("/struts/User")
@ResultPath(value="/")
public class WelcomeUserAction extends ActionSupport{
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Action(value="Welcome", results={
@Result(name="success",location="pages/welcome_user.jsp")
})
public String execute() {
return SUCCESS; }
}
Looking at the above code, by now you might have understood how URLs are mapped in Struts2
.
Now let me describe my effort to add JSON-Plugin. If you have previously observed my pom, I have added JSON plugin dependency.
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-json-plugin</artifactId>
<version>2.1.8</version>
</dependency>
Here is some background on how struts2
works:
The Convention plug-in is the one which does everything in the background. The Convention plug-in does the following things:
- By default, the Convention plug-in looks for the action classes inside the following packages
strut
, struts2
, action
or actions
. Here our package name is com.vaannila.action. Any package that matches these names will be considered as the root package for the Convention plug-in. - The
action
class should either implement com.opensymphony.xwork2.Action
interface or the name of the action class should end with Action
. Here, we extend our WelcomeUser
class from com.opensymphony.xwork2.ActionSupport
which in turn implements com.opensymphony.xwork2.Action
. - The Convention plug-in uses the
action
class name to map the action URL. Here our action
class name is WelcomeUser and the URL is welcome-user. The plug-in converts the camel case class name to dashes to get the request URL. - Now the Convention plug-in knows which
Action
class to call for a particular request. The next step is to find which result to forward based on the return value of the execute
method. By default, the Convention plug-in will look for result pages in WEB-INF/content directory. - Now the Convention plug-in knows where to look for results, but it doesn't know which file to display inside the content directory. The Convention plug-in finds this with the help of the result code returned by the
Action
class. If “success” is returned, then the Convention plug-in will look for a file name welcome-user-success.jsp or welcome-user.jsp . It need not be a jsp file, it can be even a velocity or freemaker files. If the result value is “input
”, it will look for a file name welcome-user-input.jsp or welcome-user-input.vm or welcome-user-input.ftl.
Now id we need to define a Action which returns JSON String, we need to define that action implements json-convention which is defined this way:
package com.linkwithweb.ajax.action;
import java.util.ArrayList;
import java.util.List;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import com.opensymphony.xwork2.ActionSupport;
@Namespace("/struts/Ajax")
@Result(name = "success", type = "json")
@ParentPackage("json-default")
public class AjaxAction extends ActionSupport {
@Action(
value = "grid",
results = { @Result(name = "success", type = "json") })
public String execute() {
return "success";
}
public String getPage() {
return "1";
}
public int getTotal() {
return 2;
}
public String getRecords() {
return "13";
}
public List<String> getRows() {
List<String> arrayList = new ArrayList<String>();
arrayList.add("{'id':'13','cell':
['13','2007-10-06','Client 3','1000.00','0.00','1000.00',null]}");
arrayList.add("{'id':'12','cell':
['12','2007-10-06','Client 2','700.00','140.00','840.00',null]}");
arrayList.add("{'id':'11','cell':
['11','2007-10-06','Client 1','600.00','120.00','720.00',null]}");
arrayList.add("{'id':'10','cell':
['10','2007-10-06','Client 2','100.00','20.00','120.00',null]}");
arrayList.add("{'id':'9','cell':
['9','2007-10-06','Client 1','200.00','40.00','240.00',null]}");
arrayList.add("{'id':'8','cell':
['8','2007-10-06','Client 3','200.00','0.00','200.00',null]}");
arrayList.add("{'id':'7','cell':
['7','2007-10-05','Client 2','120.00','12.00','134.00',null]}");
arrayList.add("{'id':'6','cell':
['6','2007-10-05','Client 1','50.00','10.00','60.00','']}");
arrayList.add("{'id':'5','cell':
['5','2007-10-05','Client 3','100.00','0.00',
'100.00','no tax at all']}");
arrayList.add("{'id':'4','cell':
['4','2007-10-04','Client 3','150.00','0.00','150.00','no tax']}");
return arrayList;
}
}
If you observe the above code, any URL request of form /struts/Ajax/grid will come to this Action
class and as we defined it follows json-default convention and so this class acts as bean and this Java bean is converted to json string in response. Here is a sample response for the above class:
http://localhost:8080/struts/Ajax/grid
{"page":"1","records":"13","rows":["{'id':'13',
'cell':['13','2007-10-06','Client 3','1000.00','0.00','1000.00',null]}",
"{'id':'12','cell':['12','2007-10-06','Client 2','700.00','140.00','840.00',null]}",
"{'id':'11','cell':['11','2007-10-06','Client 1','600.00','120.00','720.00',null]}",
"{'id':'10','cell':['10','2007-10-06','Client 2','100.00','20.00','120.00',null]}",
"{'id':'9','cell':['9','2007-10-06','Client 1','200.00','40.00','240.00',null]}",
"{'id':'8','cell':['8','2007-10-06','Client 3','200.00','0.00','200.00',null]}",
"{'id':'7','cell':['7','2007-10-05','Client 2','120.00','12.00','134.00',null]}",
"{'id':'6','cell':['6','2007-10-05','Client 1','50.00','10.00','60.00','']}",
"{'id':'5','cell':['5','2007-10-05','Client 3','100.00','0.00','100.00',
'no tax at all']}","{'id':'4','cell':['4','2007-10-04','Client 3','150.00',
'0.00','150.00','no tax']}"],"total":2}
I have integrated JQGrid
multiselect functionality in index.jsp. Here is the sample code which configures jqgrid
multiselect:
jQuery("#list9").jqGrid({
url:'response.jsp?time='+new Date().getTime(),
datatype: "json",
colNames:['Inv No','Date', 'Client', 'Amount','Tax','Total','Notes'],
colModel:[
{name:'id',index:'id', width:55},
{name:'invdate',index:'invdate', width:90},
{name:'name',index:'name', width:100},
{name:'amount',index:'amount', width:80, align:"right"},
{name:'tax',index:'tax', width:80, align:"right"},
{name:'total',index:'total', width:80,align:"right"},
{name:'note',index:'note', width:150, sortable:false}
],
rowNum:10,
rowList:[10,20,30],
pager: '#pager9',
sortname: 'id',
recordpos: 'left',
viewrecords: true,
sortorder: "desc",
multiselect: true,
caption: "Multi Select Example"
});
jQuery("#list9").jqGrid('navGrid','#pager9',
{add:false,del:false,edit:false,position:'right'});
jQuery("#m1").click( function() {
var s;
s = jQuery("#list9").jqGrid('getGridParam','selarrrow');
alert(s);
});
jQuery("#m1s").click( function() {
jQuery("#list9").jqGrid('setSelection',"13");
});
Here is how index.jsp looks like after running (you can use mvn -Djetty.port=9090 jetty:run or mvn tomcat:run):
Will make it more professional and fix all issues along with the article in the coming week. Got to work on something else, so leaving it here.
The code has been checked into the following svn location: https://linkwithweb.googlecode.com/svn/trunk/Struts
So play with this as you want.