The advent of Struts as a framework was revolutionary from the first day it arrived on the technology scene. Many projects in Java™/J2EE adopted Struts, and it has subsequently become one of the most stable frameworks in the gamut of J2EE-based applications. While it only provides controller support, this can be an advantage to developers desiring to build custom components for the M (Model) and V (View) sections. In this article, we highlight one of the most important components of Struts 2.0, Object-Graph Navigation Language (OGNL) and its features.

Share:

Rudranil Dasgupta (rudranil.dasgupta@in.ibm.com), Associate IT Architect, Advisory IT Specialist, GBS, IBM

Rudranil Dasgupta is an IBM associate IT architect and advisory-accredited IT specialist working as project lead in the electronics industry. He has almost seven years' experience in design and development, architecting, technical strategy, solutions for creating and leveraging assets in client solution, and providing technical leadership. He has in-depth experience working with WebSphere solutions, SOA, Web 2.0, content management and Java/J2EE. He has multiple publications in IBM forums, including IBM developerWorks. He has also taken multiple sessions on Web 2.0 and social collaboration in esteemed forums.



Kaushik Dutta (kaushik_dutta@in.ibm.com), IBM Associate IT Architect, IBM

Kaushik Dutta is an application architect and currently works as a technical architect for the GBSC Asset Reuse Enablement Initiative. He has more than 10 years' professional experience in software design, development, analysis, and architecting solutions. He has been involved in analysis, architecture, and system design and development of web applications using Java technology, J2EE, EJB, SOA, Web 2.0, WebSphere Adapter, JMS, Web Services, JSP, Servlet, JDBC, RMI, XML, Oracle, HTML, and JavaScript.



Sudip Dutta (suddutt1@in.ibm.com), IBM Senior System Engineer, IBM

Sudip Dutta is a senior developer in the electronics industry. He has approximately seven years' experience in design and development, prototyping, providing technical strategy, solutions for creating and leveraging assets in client solutions, as well as providing technical leadership. He has in-depth experience working with WebSphere solutions, SOA, Web 2.0, Java/J2EE, .NET and DB2 solutions. He has a master's of computer applications degree and a bachelor's of mathematics degree.



08 March 2011

Also available in Japanese

Introduction

Struts 2 was launched in early February 2007 and had a host of exciting features. In the scope of this article, we dive into one of the newest additions of the Struts 2 framework: Object-Graph Navigation Language (OGNL). This is the newer form of expression languages and has excellent collection, object graph traversal, and indexing support.

We will define hierarchical-based web pages and how we can perform data extraction using the minimal code of the Struts 2 OGNL framework. We will discuss how important business concepts are being implemented on the web using these constructs. We will demonstrate how data extraction from these pages were being handled by an earlier framework (Struts 1.x) and how migrating to the OGNL framework of Struts 2 helps minimize development effort. In this regard, we will also touch upon the basics of OGNL and Struts 2 and how we have leveraged them to create a lightweight methodology to handle hierarchical pages. We also demonstrate a comparative study between the features of Struts 1.x and Struts 2, which have helped in the migration to a lesser code environment.


Case 1. Study of an existing scenario

Let us consider the following scenario of multi-level hierarchical data display and data updating in a web page. An investment adviser is preparing an report for a client. The screenshot shows a data update screen for the client's investment portfolio. The data entry fields are top-level attributes of the portfolio. The rows in purple are the first-level data items under the portfolio, and the fields Target Investment Amount and Target Profit% are the attributes of the each of the first-level items. Similarly, rows in green are second-level items (and the fields under that are attributes at that level). Finally, yellow rows are showing third-level items. In this article, we assume that all the fields can be updated, and when the user submits the page, the data in the back end should be updated accordingly. Also note that the numbers of first-, second-, and third-level rows are dynamic in nature.

Figure 1. Pictorial depiction
Diagram showing the relationships between various rows of data in a database

Current implementation to solve the problem (with Struts 1.x)

Listing 1 shows the data model for the above page.

Listing 1. Earlier data structure
public class ClientPortfolioDetailsBean 
{
	//Domain model for the client portfolio details
	
	//Portfolio level attributes
	private String investorName = null;
	private String emailId = null;
	private String contactNumber = null;
	//..... Other portfolio level attributes
	//List to hold the details of Stocks / Mutual Funds etc investment category 
	private List <InvestmentCategoryDetailsBean> invCategoryList = null;
}
public class InvestmentCategoryDetailsBean {
	
	//Bean to hold the information about investment category details
	//like Stocks, Mutual Funds etc. Represents the first level rows
	private String categoryName = null;
	private double targetInvstAmt  = 0.0;
	private double expectedAnnualReturn   = 0.0;
	//Other attributes follows 
	//List of stocks and fund details bean to hold the 
	//second level items 
	private List <StocksAndFundDetailsBean> itemList = null;
}
public class StocksAndFundDetailsBean {
	
	//Bean to hold the information about investment category details
	//like Stocks, Mutual Funds etc. Represents the second level rows
	
	//Attributes at that level
	private String stockFundCategoryName = null;
	private double targetInvstAmt = 0.0;
	private double targetProfit =0.0;
	//Other attribute follows
	//List to hold the stock details 
	private List <StocksDetailsBean> stockDetailsList = null;
}
public class StocksDetailsBean {
	
	//Represents the third level item details
	//Details of individual stock/fund details
	private String stockFundName = null;
	private double noOfStocks = 0.0;
	private double buyingPrice =0.0;
	private Date buyingDate = null;
}

In Struts 1.x, the solution for the first case study requires the definition of a form bean in a flat single-level manner. To obtain the updated values entered by the user, Listing 2 provides the required form bean structure.

Listing 2. Structure of Struts 1.x form beans
public class PortfolioDetailsBean extends ActionForm

{

	//Top level attributes 
	
	private String investorName = null;
	private String emailId = null;
	//........... Other Top level attributes 
	
	//Fields to capture Attributes of First level rows 
	private double[] firstLevelTargetAmt = null;
	private double[] firstLevelExpectedAnnlReturn = null;
	//......... Other First Level row attributes

	//Fields to capture Attributes of Second level rows 
	private double[] secondLevelTargetAmt = null;
	private double[] secondLevelTargetInvPeriod = null;
	//......... Other Second level row attributes

	//Fields to capture Attributes of Second level rows 
	private double[] thirdLevelNoOfStocks = null;
	private double[] thirdLevelByingPrice = null;
	//......... Other Third level row attributes
}

To convert these single-level form bean elements into the multi-level domain model shown in Listing 1, the action class requires additional coding as shown in Listing 3. Please note that the logic applied here depends on the structure of the domain model, and as the level and number of attributes increase, the complexity of conversion increases in the action class.

Listing 3. Customized code to convert single-level form bean elements to multi-level domain model
public class UpdatePortfolioAction extends Action

{

    public ActionForward execute(ActionMapping mapping, ActionForm form,
	    HttpServletRequest request, HttpServletResponse response)
	    throws Exception {

	ActionErrors errors = new ActionErrors();
	ActionForward forward = new ActionForward(); // return value
	PortfolioDetailsBean portfolioDetailsBean = (PortfolioDetailsBean) form;

	
	try {
		//Counter to track index of 2nd level index
		int counter2ndLevelIndex= 0;
		//Converting the form bean to the domain model 
		ClientPortfolioDetailsBean clientBean = new ClientPortfolioDetailsBean();
		//Setting the top level attributes 
		clientBean.setEmailId(portfolioDetailsBean.getEmailId());
		//.........................................
		//Similarly other top level fields are created 
		//Get the size of first level row sizes from session
		int sizeInvestmentCategory = getInvestmentCategorySize
           (request.getSession());
		clientBean.setInvCategoryList(new ArrayList
           <InvestmentCategoryDetailsBean>(sizeInvestmentCategory));
		for(int firstLevelIndex=0;firstLevelIndex<
           sizeInvestmentCategory;firstLevelIndex++)
		{
			//Create the updated first level bean
			InvestmentCategoryDetailsBean invCatBean = 
               new InvestmentCategoryDetailsBean();
			//Set the investment category Bean in the parent bean
			clientBean.getInvCategoryList().add(invCatBean);
			//set the updated properties 
			invCatBean.setExpectedAnnualReturn(
               portfolioDetailsBean.getFirstLevelExpectedAnnlReturn()[firstLevelIndex]);
			//.........................................
			//Set the other properties at first level 
			//Get the size of 2nd level row sizes from session
	        int sizeStockAndFundDetails = getStockAndFundDetailsSize
               (request.getSession(),firstLevelIndex);
              	invCatBean.setItemList(
                     new ArrayList<StocksAndFundDetailsBean>(sizeStockAndFundDetails));
			//Fill Up the second level objects
	        for(int secondLevelIndex=0;secondLevelIndex<
               sizeStockAndFundDetails;secondLevelIndex++)
			{
	StocksAndFundDetailsBean stkFundDetails = 
	   new StocksAndFundDetailsBean();
    //Set the object to the parent Bean
    invCatBean.getItemList().add(stkFundDetails);
    //Set the individual attributes 
	stkFundDetails.setTargetInvstAmt(
       portfolioDetailsBean.getSecondLevelTargetAmt()[counter2ndLevelIndex++]);
       //............... Other fields follows
	   // Setting up of 3rd level list follows here
	   //......................................
	}			
   }		
} catch (Exception e) {

   // Report the error using the appropriate name and ID.

   }
}

Drawbacks of current scenario

One of the tedious tasks of developing web applications is transferring data from form beans to data beans. Converting from Strings to Java types and vice-versa increases the complication. Parsing string values into doubles and integers is required, as well as resolving exceptions that may arise from bad data.

Data transfer and type conversions happen on both ends of the request processing cycle. When the result is rendered, the data has been reconverted from the Java type back out to a string format. This process takes place with nearly every request in a web application, and it is an integral part of the domain. Supporting dynamic forms with dynamic sets of records requires complicated code and logic. The development and maintenance of such logical code becomes intricate.


Struts 2 and OGNL

The automation of data transfer and type conversion is one of the most powerful features of Struts 2. With the help of OGNL, the Struts 2 framework allows transfer of data onto more complex Java-side types like List, Map, etc. Custom converters can also be developed to extend the type conversion mechanism and it can handle any data type, including user-defined types.

OGNL is the interface between the Struts 2 framework string-based HTTP Input and Output and the Java-based internal processing. For developers who are conversant with OGNL, it will substantially improve efficiency and reduce maintenance headaches.

Figure 2. OGNL stack
Diagram shows how the various context elements map into the OGNL structure

From the developer's perspective, OGNL has two components:

  • Expression language — Normally used in form input field names and JSP tags. OGNL expressions are used to bind Java-side data properties to strings in the text-based view layers.
  • Type converters — Responsible for data type conversion. Each time data moves to or from the Java environment, a conversion must occur between the string version of that data residing in the HTML and the appropriate Java data type. The framework provides built-in converters to handle much more than we have been asking of it, or the developer has the option of creating custom converters.

The framework automatically transfers data and data conversion from request parameters, but where does the data go and how does OGNL figure out the target? ValueStack in Struts 2 is the construct that is a conglomeration of the properties of objects as properties of a single virtual object. In case of duplicate properties (i.e., two objects in the stack with employeeId property), the property of the highest object in the stack will be used. Struts 2 framework has built-in support for converting between the HTTP native strings and the following list of Java types:

  • Array
  • boolean/Boolean
  • Char/Character
  • int/Integer, float/Float, long/Long, double/Double
  • Date
  • String
  • Map
  • List

Using OGNL to solve transformation difficulties

Struts 2 OGNL expressions reduce the difficulty of writing complex code to transform single-level form beans to multi-level domain objects. In Struts 2, the HTML input field names could be generated as OGNL expressions, which in turn helps eliminate complex coding in the action class. As an example, if an HTML input field name (such as a text box) is set to clientPortfolioDetailsBean.investorName, then getClientPortfolioDetailsBean() of the action class is invoked. This should return ClientPortfolioDetailsBean, followed by the invocation of setInvestorName() method with the value given by the user in the screen. Intelligent naming of HTML input fields updated by users negates writing complex code in the action class. The domain model is populated appropriately inside the action class.

We need to add an attribute type ClientPortfolioDetailsBean in the action class, along with its get method. Inside the get method, a bean should be returned from the session if its instance is not set in the current action class instance. During the page load, the client-detailed bean instance should be kept in the session object. In case of larger objects, getClientDetailsFromSession() method could be replaced with getClientDetailsFromDB(), where data should be fetched from the database.

Listing 4. Code snippet for Case 1
public class ClientPortfolioAction extends ActionSupport {

	private ClientPortfolioDetailsBean clientDetailsBean = null;
		
	//Accessor for the clientDetailsBean. 
	public ClientPortfolioDetailsBean getClientDetailsBean() {
		if(clientDetailsBean==null)
		{
			//If the bean is null for this action class instance
			//load the data from the session 
			clientDetailsBean = getClientDetailsFromSession();
		}
		return clientDetailsBean;
	}
}
Listing 5. Code snippet for Case 2
<%@ taglib prefix="s" uri="/struts-tags" %>

	<s:textfield name="clientDetailsBean.investorName" 
value="%{clientDetailsBean.investorName}"/%>
	<s:textfield name="clientDetailsBean.emailId" 
value="%{clientDetailsBean.emailId}"/%>
	<s:textfield name="clientDetailsBean.contactNumber" 
value="%{clientDetailsBean.contactNumber}"/%>
		
	<s:iterator value="clientDetailsBean.invCategoryList" 
var="invCatDetails" status="firstLevelIdx"%>
		 
		<s:property value="%{#invCatDetails.categoryName}"/%>
		 
	<s:textfield name="clientDetailsBean[%{#firstLevelIdx.index}]
.targetInvstAmt" 
value="%{invCatDetails.targetInvstAmt}"/%>
								
<s:textfield name="clientDetailsBean[%{#firstLevelIdx.index}]
.expectedAnnualReturn" 
value="{invCatDetails.expectedAnnualReturn}"/%>
											
			<s:iterator value="#invCatDetails.itemList"
var="stkFundDetails" status="secondLevelIndx"%>
				
			<s:property value="%{#stkFundDetails.
stockFundCategoryName}"/%>
	<s:textfield name="clientDetailsBean
[%{#firstLevelIdx.index}].itemList[%{secondLevelIndx.index}].
targetInvstAmt" value="%{#stkFundDetails.targetInvstAmt}" /%>
	<s:textfield name="clientDetailsBean[%{#firstLevelIdx.index}]
.itemList[%{secondLevelIndx.index}].
targetProfit" value="%{#stkFundDetails.targetProfit}" /%>
											
	<s:iterator value="#stkFundDetails.stockDetailsList" 
var="stkDetails" status="thirdLevelIndx"%>
	<s:property value="%{stkDetails.stockFundName}" /%>
	<s:textfield name="clientDetailsBean[%{#firstLevelIdx.index}]
.itemList[%{secondLevelIndx.index}].stockDetailsList
[%{thirdLevelIndx.index}].noOfStocks" value="%{#stkDetails.noOfStocks}" /%>
	<s:textfield name="clientDetailsBean[%{#firstLevelIdx.index}]
.itemList[%{secondLevelIndx.index}].stockDetailsList
[%{thirdLevelIndx.index}].buyingPrice" value="%{#stkDetails.buyingPrice}"/%>
					
				</s:iterator>				
			</s:iterator>
		
	</s:iterator>

Now let's consider a JSP page where the input text box names are generated per valid OGNL expressions and how the "no of Stocks field" of "ABC Incorporated" is updated by the OGNL expression:

                <s:textfield
                name="clientDetailsBean[%{#firstLevelIdx.index}]
                .itemList[%{secondLevelIndx.index}].stockDetailsList
                [%thirdLevelIndx.index}].noOfStocks}'/>

This line will be translated to the following HTML code:

<input type="text"
                name="clientDetailsBean[0].itemList[0].stockDetailsList[0].noOfStocks"
                value="<somevalue>" />

The OGNL expression clientDetailsBean[0].itemList[0].stockDetails[0].noOfStocks is equivalent to the following code invocation: getClientDetailsBean().getItemList().get(0).getStockDetailList(0).get(0).setNoOfStocks(<some value>) on the action class instance. Similarly, other fields will be updated accordingly in the domain model. In the action method, if getClientDetailsBean() is called, all the fields will be updated by the Struts 2 framework in place. This allows avoidance of complex coding in the action class (as shown in the Struts 1 example) for converting the form bean in to the related domain model object.


Benefits of current scenario

Advantages of the Struts 2 framework include:

  • Expression language of OGNL enables you to map form fields to Java-side properties.
  • Type converters of OGNL automatically convert the data (in string form) from request parameters to the actual Java types of the properties.
  • While rendering the view, expression language and type converter again converts from Java types to string value automatically.
  • Supports flexible and wide set of conversions to and from Collection and Array.
  • Allows complex websites to be built easily, which reduces the complexity of back-end codes.
  • Reduces development time with reusable automatic data transfer.
  • Significant reduction of tedious code for data conversion and exception handling, allowing you to focus on core business logic.

Conclusion

In this article, we have discussed the OGNL aspect of Struts 2 and how it assists practitioners to implement more streamlined business applications. In upcoming articles, we will review Struts 2 from a development point of view. Struts 2 is here to stay as it becomes increasingly stable and we hope to help you leverage the maximum benefits of the enhanced features of Struts 2.

Resources

Learn

Get products and technologies

Discuss

  • Participate in developerWorks blogs and get involved in the developerWorks community.
  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=630303
ArticleTitle=Struts 2.0 with OGNL
publish-date=03082011