Struts2 OGNL is the expression language where OGNL stands for Object-Graph Navigation Language. OGNL is tightly coupled in Struts2 and used to store form parameters as java bean variables in ValueStack and to retrieve the values from ValueStack in result pages.
Struts2 OGNL
Struts2 OGNL performs two important tasks – data transfer and type conversion.
OGNL in Struts2 takes the request parameters from the servlet request and transfer it to corresponding java variable.
Since we get request params as String but java bean variables can be String, int, array, list or any custom object, type conversion is also an important task and OGNL takes care of type conversion through it’s built-in type converters.
Struts2 OGNL is flexible and we can easily extend it to create our own custom converter class. We will first look into the OGNL usage with basic data types such as String, boolean, int, arrays and lists. Then we will create our own converter class for a custom java bean variable.
Struts2 OGNL Example
Our final project structure for Struts2 OGNL example looks like below image.
Struts 2 Configuration Files
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://java.sun.com/xml/ns/javaee" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Struts2OGNLExample</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> |
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>Struts2OGNLExample</groupId> <artifactId>Struts2OGNLExample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-core</artifactId> <version>2.3.15.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <warSourceDirectory>WebContent</warSourceDirectory> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> <finalName>${project.artifactId}</finalName> </build> </project> |
struts.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "https://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="struts.devMode" value="false"></constant> <constant name="struts.convention.result.path" value="/"></constant> <package name="user" namespace="/" extends="struts-default"> <action name="home"> <result>/home.jsp</result> </action> <action name="welcome" class="com.journaldev.struts2.actions.WelcomeAction"> <result name="success">/welcome.jsp</result> </action> </package> </struts> |
Configuration files are self understood and they are just to configure our application to use Struts 2 framework.
Struts2 OGNL Example Model Classes
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.journaldev.struts2.model; public class Data { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.journaldev.struts2.model; public class Rectangle { private int x; private int y; public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
package com.journaldev.struts2.model; import java.util.Date; import java.util.List; import java.util.Map; public class MyJavaBean { private String name; private boolean flag; private Integer age; private Date date; private String[] stocks; //roles array needs to initialize because it's used with index in form private String[] roles = new String[5]; //do not preinitialize lists or any collections private List<Data> usersList; private List<Data> fruitsList; private Map<String, Data> usersMap; //custom type converter example private Rectangle rectangle; public List<Data> getFruitsList() { return fruitsList; } public void setFruitsList(List<Data> fruitsList) { this.fruitsList = fruitsList; } public List<Data> getUsersList() { return usersList; } public void setUsersList(List<Data> users) { this.usersList = users; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String[] getStocks() { return stocks; } public void setStocks(String[] stocks) { this.stocks = stocks; } public String[] getRoles() { return roles; } public void setRoles(String[] roles) { this.roles = roles; } public Map<String, Data> getUsersMap() { return usersMap; } public void setUsersMap(Map<String, Data> usersMap) { this.usersMap = usersMap; } public Rectangle getRectangle() { return rectangle; } public void setRectangle(Rectangle rectangle) { this.rectangle = rectangle; } } |
MyJavaBean
is the action bean class that we will use, notice the variable of type Rectangle
. Since it’s a custom class, we need to implement our own converter class for this. We will look into this later.
Struts2 OGNL Example Action Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.journaldev.struts2.actions; import com.journaldev.struts2.model.MyJavaBean; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; public class WelcomeAction extends ActionSupport implements ModelDriven<MyJavaBean>{ public String execute(){ return SUCCESS; } private MyJavaBean bean = new MyJavaBean(); @Override public MyJavaBean getModel() { return bean; } } |
Action class just returns the success page, there is no logic done here.
Struts2 OGNL Example Result Pages
home.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
<%@ page language="java" contentType="text/html; charset=US-ASCII" pageEncoding="US-ASCII"%> <%@ taglib uri="/struts-tags" prefix="s" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title>Home Page Form</title> </head> <body> <h3>Struts 2 OGNL Examples</h3> <s:form action="welcome" method="post"> <table> <s:textfield name="name" label="Name" ></s:textfield> <s:textfield name="flag" label="True/False?"></s:textfield> <s:textfield name="age" label="Current Year?"></s:textfield> <s:textfield name="date" label="Todays Date (mm/dd/yyyy)?"></s:textfield> </table> <h3>Struts 2 Array OGNL Example</h3> <strong>Array with same name example</strong><br> <table> <s:textfield name="stocks" label="Stock 1"></s:textfield> <s:textfield name="stocks" label="Stock 2"></s:textfield> <s:textfield name="stocks" label="Stock 3"></s:textfield> </table> <strong>Array with indexed name example</strong><br> <table> <s:textfield name="roles[0]" label="Role 1"></s:textfield> <s:textfield name="roles[1]" label="Role 2"></s:textfield> <s:textfield name="roles[2]" label="Role 3"></s:textfield> </table> <h3>Struts 2 List OGNL Example</h3> <strong>List with same name example</strong><br> <table> <s:textfield name="usersList.name" label="User 1 Name"></s:textfield> <s:textfield name="usersList.name" label="User 2 Name"></s:textfield> <s:textfield name="usersList.name" label="User 3 Name"></s:textfield> </table> <strong>List with indexed name example</strong><br> <table> <s:textfield name="fruitsList[0].name" label="Fruit 1"></s:textfield> <s:textfield name="fruitsList[1].name" label="Fruit 2"></s:textfield> <s:textfield name="fruitsList[2].name" label="Fruit 3"></s:textfield> </table> <strong>Map Example</strong> <table> <s:textfield name="usersMap['first'].name" label="User 1"></s:textfield> <s:textfield name="usersMap['second'].name" label="User 2"></s:textfield> <s:textfield name="usersMap['third'].name" label="User 3"></s:textfield> </table> <strong>Custom Converter Example</strong> <table> <s:textfield name="rectangle" label="Rectangle in format R:x,y"></s:textfield> </table> <s:submit label="Submit" align="left"></s:submit> </s:form> </body> </html> |
home.jsp is used as input page where user can provide values and invoke welcome action.
welcome.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<%@ page language="java" contentType="text/html; charset=US-ASCII" pageEncoding="US-ASCII"%> <%@ taglib uri="/struts-tags" prefix="s" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII"> <title>Welcome Page Example</title> </head> <body> <h3>Struts 2 OGNL Examples</h3> Name = <s:property value="name"></s:property><br> True/False? = <s:property value="flag" ></s:property><br> Current Year? = <s:property value="age"></s:property><br> Todays Date (mm/dd/yyyy)? = <s:date name="date" format="MM/dd/yyyy"></s:date><br><br> Stocks Array = <s:property value="stocks"/><br> Roles Array = <s:property value="roles"/><br><br> Users List = <s:iterator value="usersList"><s:property value="name"/> </s:iterator><br> Fruits List = <s:iterator value="fruitsList"><s:property value="name"/> </s:iterator><br> Fruit 1 Name = <s:property value="fruitsList[0].name"/><br><br> Users Map = <s:iterator value="usersMap">{<s:property value="key"/>,<s:property value="value.name"/>} </s:iterator><br><br> Rectangle Dimensions: x = <s:property value="rectangle.x"/> and y = <s:property value="rectangle.y"/><br> </body> </html> |
welcome.jsp is just used to show the values used by the user as a proof that OGNL is taking care of data transfer as well as type conversion.
Before we move on to discuss on custom converter classes, let’s look at some important points in above implementation.
- Basic data types conversion is automatic, we don’t need to follow any special rules for them.
- Struts 2 takes care of converting String to Date also and we can use s:date to display it with specific format.
- Arrays and Lists can be used with name as well as index. If we use index, then we need to initialize the array in the bean. That’s why roles array is initialized in the bean class.
- Do not initialize the list variables in bean else it will throw error. OGNL takes care of initialization and populating values.
- OGNL in Struts2 also provides built-in support for Map that we can use in result pages.
- We can use iterator with multi-values data types such as List, Map, Array to traverse through them. We can use index or key to get specific values from these variables.
Struts2 OGNL Custom Type Converter
Creating and configuring custom type converter class is very easy. First step is to fix the input format for the custom class. For my example, I have fixed the user input to be R:x,y
where x and y are rectangle variables and should be integers.
Second step is to implement the converter class. Type converter classes should implement com.opensymphony.xwork2.conversion.TypeConverter
interface.
Since in web application, we always get the request in form of String and send response in the form of String, Struts 2 API provides a default implementation of TypeConverter interface, StrutsTypeConverter
.
StrutsTypeConverter contains two abstract methods – convertFromString to convert String to Object and convertToString to convert Object to String. We will extend this class for custom type converter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.journaldev.struts2.typeconverters; import java.util.Map; import org.apache.struts2.util.StrutsTypeConverter; import com.journaldev.struts2.model.Rectangle; import com.opensymphony.xwork2.conversion.TypeConversionException; /** * Custom type converter to convert user input to Rectangle * Format is R:x,y where x and y are int defining Rectangle dimensions * @author pankaj * */ public class RectangleTypeConverter extends StrutsTypeConverter { @Override public Object convertFromString(Map arg0, String[] inputs, Class arg2) { String input = inputs[0]; if(!input.startsWith("R:")) throw new TypeConversionException("invalid input"); input = input.substring(2); String[] dimensions = input.split(","); int x = Integer.parseInt(dimensions[0]); int y = Integer.parseInt(dimensions[1]); Rectangle rect = new Rectangle(); rect.setX(x); rect.setY(y); return rect; } @Override public String convertToString(Map arg0, Object obj) { Rectangle rect = (Rectangle) obj; String output = "R:" + rect.getX() + "," + rect.getY(); return output; } } |
Notice that code is very simple and parse input string to object and vice versa.
Next step is to configure the type converter to be used for Rectangle type variables. There are two ways to configure this – first is to configure for specific action and second way is to configure globally.
For action specific converter, we can use com.opensymphony.xwork2.conversion.annotations.TypeConversion
annotation and change the setter method like below.
1 2 3 4 5 6 |
@TypeConversion(converter="com.journaldev.struts2.typeconverters.RectangleTypeConverter") public void setRectangle(Rectangle rectangle) { this.rectangle = rectangle; } |
Custom Type Converter for ModelDriven Action Classes
If Action class is implementing ModelDriven interface for java bean, another way is to create property file with name as {JavaBeanName}-conversion.properties and put it in the same package as java bean class, so we can create MyJavaBean-conversion.properties and put it in com.journaldev.struts2.model
package with below data.
MyJavaBean-conversion.properties
1 2 3 4 5 |
#For Action Classes implementing ModelDriven<MyJavaBean> #variable-name=TypeConverter class name rectangle=com.journaldev.struts2.typeconverters.RectangleTypeConverter |
For global conversion, as I have done in this project, we need to create xwork-conversion.properties properties file and make sure it’s in WEB-INF/classes directory. We need to provide the class name and converter as key-value pair. For us it’s
xwork-conversion.properties
1 2 3 4 |
#Application level custom converter configuration com.journaldev.struts2.model.Rectangle=com.journaldev.struts2.typeconverters.RectangleTypeConverter |
Now when we will run our application, we will get following response pages.
That’s all for Struts2 OGNL example tutorial, I hope you liked it. Download project from below link and run yourself.