Struts 2 OGNL Tutorial with Custom Type Converter Example

OGNL stands for Object-Graph Navigation Language that is an expression language used in Struts 2. OGNL is tightly coupled in Struts 2 and used to store form parameters as java bean variables in ValueStack and to retrieve the values from ValueStack in result pages.

OGNL performs two important tasks in Struts 2 – data transfer and type conversion. Struts 2 OGNL 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.

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 and then we will create our own converter class for a custom java bean variable.

Our final project structure looks like below image.

Struts2-OGNL-Example-Project

Struts 2 Configuration Files

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://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>
<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/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>
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://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.

Model Classes

package com.journaldev.struts2.model;

public class Data {

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	
}
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;
	}
	
	
}
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.

Action Class

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.

Result Pages

<%@ 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" "http://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.

<%@ 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" "http://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.

  1. Basic data types conversion is automatic, we don’t need to follow any special rules for them.
  2. Struts 2 takes care of converting String to Date also and we can use s:date to display it with specific format.
  3. 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.
  4. Do not initialize the list variables in bean else it will throw error. OGNL takes care of initialization and populating values.
  5. OGNL also provides built-in support for Map that we can use in result pages.
  6. 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.

Custom Type Converter Implementation

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.

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.

	@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.

#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

#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.

Struts2-OGNL-Custom-Type-Converter-Input

Struts2-OGNL-Custom-Type-Converter-Response

Thats all for Struts2 OGNL example tutorial, I hope you liked it. Download project from below link and run yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current ye@r *

Subscribe to JournalDev Newsletter
Get the FREE access to Monthly Newsletter and Free PDF eBooks
*No Spam Guaranteed. By entering your email address, you agree also subscribing to our newsletter.
Oops! - Something went wrong.
Close
Free Java/Java EE PDF eBooks Download Now
Exclusive Offer: Citrix™ Whitepaper on Denial of Service Attack Download Now