Introduction
Struts 2.0 is the popular Open Source Presentation Tier framework developed by Apache Group. It is based on MVC Model 2 design pattern. Dispatcher Filter is the front controller for the struts 2.0 based applications. Struts 2.0 has simplified web development for its users by introducing POJO based actions, interceptors, flexible validation and support for many different result types. Struts can be used to build the user interface tier of the enterprise application. Interceptors can be used to embed the cross cutting or plumbing logic of the application in a clean and reusable manner. This article explains the concept of custom interceptors.
also read:
Development Environment
- NetBeans IDE 6.8
- GlassFish V2.x
- Apache Derby Database
Project Structure
The sample application developed in this article is “StrutsLoginDemo” where a user can log in to the application by specifying a valid userId and password. Custom Interceptor developed in the application will be calculating the time spent in the request processing.
Libraries/Jar Files Required
- Struts 2.0 jar files
The complete application structure is shown below:
- The User Interface (JSP pages) is created in the “Web Pages” directory. The java classes (Actions, Interceptors and struts.xml) are created in the “Source Packages” directory. The required jar files are present in the “Libraries” directory. The web application deployment descriptor “web.xml” is created by the IDE in the “WEB-INF” subdirectory of “Web Pages”.
Action Class
Struts does the request processing with the help of Action classes. Here’s the code for the LoginAction.java which handles the
request to login to our application. This class is created in the package “demo”.
LoginAction.java
package demo; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ActionContext; import java.util.*; public class LoginAction extends ActionSupport { private String userId; private String password; public String execute() throws Exception { if ("admin".equals(userId) && "admin".equals(password)) { Map session = ActionContext.getContext().getSession(); session.put("logged-in", "true"); return SUCCESS; } else { return ERROR; } } public String logout() throws Exception { Map session = ActionContext.getContext().getSession(); session.remove("logged-in"); return SUCCESS; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } }
Let us understand the request processing in the action class. The action class handles the login request in the execute method, where we are validating that both the userId and password values must be “admin”. If the values do not match, then the user is forwarded to “Login_Failure.jsp” page. Upon successful validation, the user is forwarded to “Login_Success.jsp” page.Also, in the execute method we have added a sleep interval of 2 seconds, to show the time spent in the action class via interceptor.
Please note that there’s no validation logic in the action class. We have put the validation logic in the “LoginAction-validation.xml” file kept in the “demo” package. Please note that the validation.xml file should always be kept in the same package where your action class is
present.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"> <validators> <field name="userId"> <field-validator type="requiredstring"> <message>User Id is a mandatory field</message> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message>Password is required</message> </field-validator> <field-validator type="stringlength"> <param name="maxLength">10</param> <param name="minLength">5</param> <param name="trim">true</param> <message> Enter password between 5 and 10 characters </message> </field-validator> </field> </validators>
The data validation is performed with the help of “LoginAction-validation.xml” file. Different validation routines are configured for the both the attributes (userId and password).
The Custom Interceptor
In this application, we have created a custom interceptor “MyCustomInterceptor” which keeps track of the time spent in the action class. The interceptor is created in the interceptors package inside Source Packages sub directory.The custom interceptor provides the implementation of Interceptor interface and provides the implementation of init(),destory() and intercept() methods.
Here’s the code for the MyCustomInterceptor.java which handles the logic to calculate the time spent in the action. This class is created in the package “interceptors”.
MyCustomInterceptor.java
package interceptors; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class MyCustomInterceptor implements Interceptor { public String intercept(ActionInvocation next) throws Exception { System.out.println("*****************************"); SimpleDateFormat sdf = new SimpleDateFormat(" MM/dd/yyyy HH:mm:ss "); Calendar cal = Calendar.getInstance(); Date initDate = cal.getTime(); String newdate = sdf.format(initDate); System.out.println("In the interceptor in pre processing logic"); System.out.println("Request reached the custom interceptor @ " + newdate + " time"); System.out.println("*****************************"); Thread.sleep(2000); String s = next.invoke(); System.out.println("*****************************"); System.out.println("Back in the interceptor in post processing logic"); Calendar cal1 = Calendar.getInstance(); String finalDate = sdf.format(cal1.getTime()); System.out.println("Request returned in the custom interceptor @ " + finalDate + " time"); long milliseconds1 = cal.getTimeInMillis(); long milliseconds2 = cal1.getTimeInMillis(); long timeDiff = milliseconds2 - milliseconds1; long timeDiffSeconds = timeDiff / 1000; long timeDiffMinutes = timeDiff / (60 * 1000); System.out.println("Time in milliseconds: " + timeDiff + " milliseconds."); System.out.println("Time in seconds: " + timeDiffSeconds + " seconds."); System.out.println("Time in minutes: " + timeDiffMinutes + " minutes."); System.out.println("*****************************"); return s; } public void init() { System.out.println("Interceptor is initialized"); } public void destroy() { System.out.println("In the destroy method of Interceptor"); } }
The User Interface Tier
The user interface part of the application is created in JSP. The home page of the application is “Login.jsp”, where a form is displayed to the users asking for userId and password for login purpose. PFB the code of “Login.jsp”
<%@ taglib prefix="s" uri="/struts-tags"%> <%@ page language="java" contentType="text/html"%> <html> <head> <title>Login Page!</title> </head> <body> <s:form action="login.action" method="post"> <s:textfield name="userId" label="Login Id" /> <br> <s:password name="password" label="Password" /> <br> <s:submit value="Login" align="center" /> </s:form> <s:actionerror /> </body> </html>
Once the user fills in the both userId and password & clicks on the “Submit” button, the request is submitted to the action class. Upon
successful validation, the user is forwarded to “Login_Success.jsp”page. Here’s the code of “Login_Success.jsp”.
<%@ taglib prefix="s" uri="/struts-tags" %> <%@ page language="java" contentType="text/html" import="java.util.*"%> <html> <head> <title>Welcome, you have logged in!</title> </head> <body> Welcome, you have logged in. <br /> <b>Session Time: </b><%=new Date(session.getLastAccessedTime())%> <br /><br /> <a href="<%= request.getContextPath()%>/logout.action">Logout</a> <br /> </body> </html>
In case, the userId and password values are invalid, the user is forwarded to Login_Failure.jspHere’s the code of “Login_Failure.jsp”.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Login Failure</title> </head> <body> Invalid Username/Password. Please Retry!!!! <a href="Login.jsp">Logout</a> </body> </html>
The Configuration Files
Here’s the configuration added in the “web.xml” file:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" 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_2_5.xsd"> <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>/*</url-pattern> </filter-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>Login.jsp</welcome-file> </welcome-file-list> </web-app>
In the deployment descriptor, we have added the following configurations:
- The Welcome File of the application
- Struts2 Dispatcher Filter (Controller element of Struts2
applications)
Here’s the configuration added in the “struts.xml” file kept in the default package in the “Source Packages” directory.
<?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> <constant name="struts.devMode" value="false"/> <package name="default" extends="struts-default" namespace="/"> <interceptors> <interceptor name="customtimer" class="interceptors.MyCustomInterceptor"/> <interceptor-stack name="timerstack"> <interceptor-ref name="defaultStack" /> <interceptor-ref name="customtimer"/> </interceptor-stack> </interceptors> <default-interceptor-ref name="timerstack" /> <action name="login" class="demo.LoginAction"> <result name="success">/Login_Success.jsp</result> <result name="error"> /Login_Failure.jsp </result> <result name="input"> /Login.jsp </result> </action> <action name="logout" class="demo.LoginAction" method="logout"> <result name="success" type="redirect"> Login.jsp </result> <result name="input" type="redirect">/Login.jsp</result> </action> </package> </struts>
In the struts.xml, we have added the following configurations:
- The custom interceptor is configured and given an alias “customtimer”
- A custom stack of interceptors is created and given an alias “timerstack”. The same stack is also configured to be the default interceptor stack of the application.
- “execute” method of “LoginAction” class is configured to handled the login attempt to the application.
- “logout” method of “LoginAction” class is configured to handled the logout attempt from the application.
Executing The Application
Finally, we are ready to execute the application. Deploy the application to the container and execute.
Open the Login.jsp page.
Enter some invalid data, The snapshot given below shows the validation error messages
Once, all the validation error messages have been rectified, the user is forwarded to Login_Success.jsp page, where the welcome message is displayed.
Click on the “Logout” button and the user is redirected back to Login.jsp page.
Check the server console. It shows the messages from our custom timer interceptor. The time in the preprocessing and postprocessing is displayed along with the time spent in action method in milliseconds, seconds and minutes format.
Conclusion
Thus, any Struts application can take benefit of the built in interceptors and also take control of request processing by creating custom interceptors.