Tuesday, July 18, 2006

Problem with mapping Nested Objects using Struts

I was working on designing a specific requirement where UI was a four step wizard. User will provide information in this four pages and submit the information to be stored in database. The way information was organized on UI (jsp) was different than the way it needed to be represented in DTO (or domain objects).

However, a good review pointed out that use of series of request.getParameter is not good idea. There must be good way to get the data map to Domain objects by using struts. If the data elements were linear ( not mapped and not indexed) Struts does map it quite well with ActionForm. You can use commons beanutils' BeanProperties.copyProperties(Object dest, Object source). But normally the object representation is not linear. Objects have association and multiplicity. (See simple example below)

It seems there is a problem with struts, when it comes to populating action form when jsp contains nested beans (or for that matter indexed beans). It simply does not work. Howerver, using nested tag library, i was able to get the JSP displayed properly with correct parameter name. The problem occurs when i try to submit the JSP page. It throws IllegalArgumentException: No Bean specified. I think I am not doing anything wrong. But, not sure.

Here is what I am trying to do. I have a JSP page with customer information(name, phone, email and address) where address contains(addres1, address2, city, state and zipcode). For purpose of simplicity I have kept all parameters as String. (Couldn't figure out easy way to link files, so I just pasted here.)
Address.java
package com.learning.struts.action;

public class Address {
private String address1;
private String address2;
private String city;
private String state;
private String zipcode;
public String getAddress1() {
return address1;
}
public void setAddress1(String address1) {
this.address1 = address1;
}
public String getAddress2() {
return address2;
}
public void setAddress2(String address2) {
this.address2 = address2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}

CustomerDTO.java
package com.learning.struts.action;

public class CustomerDTO implements java.io.Serializable{
private String name;
private String phone;
private String email;
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
strust-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">

<struts-config>
<form-beans>
<form-bean name="customerForm" type="org.apache.struts.action.DynaActionForm">
<form-property name="page" type="java.lang.String"/>
<form-property name="customer" type="com.learning.struts.action.CustomerDTO"/>
</form-bean>
<form-bean name="customerForm1" type="com.learning.struts.action.PageActionForm">
<form-property name="page" type="java.lang.String"/>
<form-property name="customer" type="com.learning.struts.action.CustomerDTO"/>
</form-bean>
</form-beans>
<global-forwards>
</global-forwards>
<action-mappings>
<action path="/submit"
type="com.learning.struts.action.IndexedNestedFormStrutsAction"
validate="false"
input="customer-address1.jsp"
name="customerForm1">
<forward name="success" path="/jsp/index.jsp"/>
</action>
</action-mappings>
</struts-config></blockquote>


customer-address.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">
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-nested.tld" prefix="nested" %>
<html:html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<html:form action="submit.do" method="post">
<html:hidden property="page" value="page-1"/>
customer.name :<html:text property="customer.name" value="k"/>
customer.phone: </p><html:text property="customer.phone" value="k"/>
customer.email: </p><html:text property="customer.email" value="k"/>
<nested:nest property="customer.address">
customer.address.address1: </p><nested:text property="address1" value="k" />
customer.address.address2: </p><nested:text property="address2" value="k"/>
customer.address.city: </p><nested:text property="city" value="k" />
customer.address.state: </p><nested:text property="state" value="k" />
customer.address.zipcode: </p><nested:text property="zipcode" value="k" />
</nested:nest>
<html:submit></html:submit>
</html:form>
</body>
</html:html>
IndexedNestedFormStrutsAction.java

public class IndexedNestedFormStrutsAction extends Action {

public ActionForward execute(ActionMapping map, ActionForm form, HttpServletRequest req, HttpServletResponse res) throws Exception {

DynaActionForm daForm = (DynaActionForm)form;
CustomerDTO customer = (CustomerDTO)daForm.get("customer");
System.out.println("Customer : " + customer);
return map.findForward("success");
}
}

On debugging this, it throws up folloing exception :

java.lang.IllegalArgumentException: No bean specified
at org.apache.commons.beanutils.PropertyUtilsBean.getPropertyDescriptor(PropertyUtilsBean.java:751)
at org.apache.commons.beanutils.BeanUtilsBean.setProperty(BeanUtilsBean.java:937)
at org.apache.commons.beanutils.BeanUtilsBean.populate(BeanUtilsBean.java:811)
at org.apache.commons.beanutils.BeanUtils.populate(BeanUtils.java:298)
at org.apache.struts.util.RequestUtils.populate(RequestUtils.java:493)
at org.apache.struts.action.RequestProcessor.processPopulate(RequestProcessor.java:816)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:203)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196)
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:709)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:178)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:126)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:107)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
at java.lang.Thread.run(Thread.java:595)

But I thought that it may be problem with DynaActionForm so I created ActionForm derivative and try if it properly maps the nested bean. Here is the code for that:

PageActionFrom.java
package com.learning.struts.action;

import org.apache.struts.action.ActionForm;

public class PageActionForm extends ActionForm {
private String page;
private CustomerDTO customer;
public CustomerDTO getCustomer() {
return customer;
}
public void setCustomer(CustomerDTO customer) {
this.customer = customer;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
}

But it does not work and though it generated the JSP correctly. When you do view source on generated html it will create proper dot notaion name for each input element. However, submitting and getting the values even inside ActionForm parameter of Action does not work.
I did find out some open source project in works which are working to deal with this problem. But they are in still 0.x version. I think this is something struts should do automatically rather than any plugin framework. Please correct me if I am wrong.

Tuesday, July 11, 2006

Random Thoughts: EJB3 Vs Spring

Following are just my random thoughts. I am not trying to determine which one is better. I think it is pretty much decided that both are going co-exists.

Spring: IoC Container and Framework Library

I think it is good idea to understand Spring in two aspects.
  • First it is IoC container. This container is its core concepts. EJB as the SPEC is in direct competition. Question is: Do you want to use Spring IoC container or EJB Container?
  • Second aspects of the Spring Framework is all the helper classes which makes the development so easy. Like JdbcTemplate, Hibernate helper classes, etc. EJB does not provide such classes. Those gaps are filled up by the JBoss Seam Framework and the like. You can use EJB Container and still use Spring Framework Libraries.
So along with Spring Vs EJB, it is also Spring vs JBoss Seam and the like.

Spring Not a Spec
Spring, though opensource, is controlled by Interface 21, a commercial company. Interface 21 controls (atleast decides) what needs to be in next phase and hires programmer to do it. Don't get me wrong, I really like Spring. As a developer, it is really nice experience. How simple it makes to develop application and test it. But the fact remains that in the long run, we have to "wish" (or influence) that Interface 21 makes correct choices. Also remember that once we use spring we are using "tool specific classes and libraries" so we cannot decide to change it to different platform overnight.
Even though it uses its own library. It is based on very sound design principle. Design by Interface. Framework actually builds on top of it to provide re-usable code. For example, never worrying about handling connections.


With EJB, if you dont like a vendor you can still move to different vendor. If you dont like EJB at all depending upon your design you can still convert it to different technologies as there is not dependance on specific libraries. (Meaning dependency is there but on little higher level)

EJBs dont provide that kind of framework. It does not provide you reusable classes library. It does not provide helper classes for sending email for different implemenations.

Will Spring remain light?
But with all these introduction in Spring, how light is spring or how light will spring remain. But the answer lies in its core design. Since the core concepts behind it is Design by Interface and Inversion of Control. It allows to selectively use areas which we require. You dont want to use MVC dont incldue that jar. If you only want to use with regards to JDBC operations, only use spring-jdbc.jar. It will stay light as much your requirements demand it to remain light.

EJB: Market Availability
Though it is too early to say how light is JEE. It is just out and there are really few implementations available.

EJB still implements "Remote-ness"
One difference between these two choices still exists, that EJB3 will still do remoting. Spring on the other hand does not do remoting by default. So decision has to be made what kind of architecture is required keeping in mind business application scenario.

Thoughts about RPC
Also one thing to notice here is that RPC eventually will go out of fashion (IMHO). Ultimately, architectures like SOA where core concept is to exchange data rather than invoking behavior remotely. It is more loosely coupled, more reusable, and it also has advantage of platform independance.

Separating JPA out of EJB
One good things EJB Spec3 has done has separated JPA out of EJB Spec.
Again Hibernate will be JPA compliant in coming days. There are already articles floating around how to use Spring with JPA.

What are the other benefits of EJB3?
Container Managed Persistence
If you are using Container Managed Persistence, it manages the state of entities. Meaning it calls update insert methods on its own. It has container managed persistence. Meaning you dont have to call your insert update delete. Container will manage that for you. This is a really good advantage. With Spring Framework, you still need to call specific classes, SimpleJDBCTemplate.update, or session.update if you are using Hibernate. With EJB once configured with CMP, you dont do anything. You save that one method call. Calls will be made for you before and after business methods.

Message Driven Beans
With EJB Container you will have MessageDrivenBeans. i.e. you will have mechanisms to write consumers of Messages. With Spring you have to choose a separate provider for the same. Which gives you more flexibility. Spring delegates that work. Again principle on which spring is based: Do not re-write it if somebody is already doing it in good way. Rather than that just provide the loose interface and bunch of classes with re-usable code.

Clustering
Benefits of clustering and are anyways provided by application server. Even for that matter Tomcat provides clustering now. You can still develop Spring application and deploy it in different clusters in Tomcat. You dont need EJB Container to have component level clustering.

Spring is already a winner it. It has made EJB people change the spec. EJB3 now looks less similar to EJB2 and more similar to Spring. They have almost every aspect which spring got it correct. Aspects are now Interceptors. (Though this was any ways used by EJB container provider in their implementations, no programmatic mechanisms were provided for Bean Developer though)

XML vs Annotations
Transactions
With regards to Transaction declaration as Annotations. I must say, I like XML. No matter how much verbose. It is still good. Rather than my transaction strategy littered over code, i would like to have it one place/area.

Security
Dont let me wander in code to find out what security rules are implemented. Anyways, Spring has also come us with Annotations specific declarations allowing you to do quick and dirty work though.

Do we need IoC Container Spec?
Do we need a spec so other providers can come up with their own implementations of IoC container? Or have we already convoluted J2EE area with so many specifications and implementation?

Preference
Spring will work inside container and outside container. Spring is really great for small application because it is really easy to develop. With Spring 2.0's goal being easier XML Config files and allowing Annotations and its integration with AspectJ. Spring does have capability of using it in bits and pieces, meaning you may just use it in Middel Tier, you choose to use JMS features.

JEE spec will need a container. Also the choice depend upon the business case for which the application is getting developed. If the application is big in terms of scope or it is expected that it live long time while going multiple phase changes then I think it is better to stick with Standard Spec.