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
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.
}
}
|
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.
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
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.
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.
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.
Learn
-
Learn about Struts 2 and Internet
stats.
-
Visit Struts 2.Net.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
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.
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 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 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.




