Thursday, July 09, 2009 | | 1 comments

Groovy AST Transformation - AOP Style

In my last blog about AST Transformation I followed a sample example and created AssertParamsNotNull local transformation. Further, I wanted to create AST Transformation which is generic enough and performs any check before method is executed. In process, goal is to learn about AST Transformation.

BeforeAdvisor AST Transformation
Use case for BeforeAdvisor AST involves:

  • Authorization Checking - Security by checking role from context
  • Print Parameter values with which the method is called
  • Asserts Parameters are not null
  • Check various entry-conditions/Pre-Conditions of the method
To make transform generic enough, idea is to inject a method call to before method of advice for each method annotated with @BeforeAdvisor(MyPreConditionAdvice). Advice method can choose to implement any checks/conditions to be performed before actual methods gets executed.

Few of the subtle characteristics of this type of solution are
  • It does not allow changing the method parameters. 
  • It does not allow to stop execution of method. However, you can throw runtime exception.
  • Advice needs no arg constructor and must implement method before

Usage and Client
Starting with class that will use this transform. Following defines script-level method with annotation having advice information.

package com.learn.sts.groovy.ast

@com.learn.sts.groovy.ast.BeforeAdvisor(value= com.learn.sts.groovy.MyAdvice)
def sayHello(name, name2)
{
    println "Hello " + name + name2
}

sayHello("World", "Groovy")
Sample Advice
The advice that we will try to invoke will be something like this. This advice just prints parameter value that method is being invoked with. But you can implement any of the use cases described above.

package com.learn.sts.groovy

public class MyAdvice
{
    def before(String methodName, List listArg)
    {
        println 'Entering Method ' + methodName + ' with params ' + listArg
    }
}
Annotation
Now lets create the Annotation

package com.learn.sts.groovy.ast;

import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import java.lang.annotation.ElementType
import org.codehaus.groovy.transform.GroovyASTTransformationClass
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass(["com.learn.sts.groovy.ast.BeforeAdvisorASTTransformation"])
public @interface BeforeAdvisor {
    Class value ();
}
One difference to this annotation is that it declares a method value(). This allows us to get value being passed during annotation declaration on method.

AST Transformation

package com.learn.sts.groovy.ast

//Imports section skipped for brevity

@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
public class BeforeAdvisorASTTransformation implements ASTTransformation
{
    public void visit(ASTNode[] nodes, SourceUnit source)
    {
        AnnotationNode node = (AnnotationNode) nodes[0];
        final Expression classNode = node.getMember("value")
       
        List<MethodNode> allMethods = source.AST?.classes*.methods.flatten()
        List annotatedMethods = allMethods.findAll{ MethodNode method ->
                 method.getAnnotations(new ClassNode(BeforeAdvisor))
                }
        annotatedMethods.each{MethodNode method ->
             List existingStatements = method.getCode().getStatements()
            Parameter[] parameters = method.getParameters()
            int i = 0;
            existingStatements.add(i++, initAdviceCall(classNode))
            existingStatements.add(i++, createMethodCall(method, parameters))
        }
      }
 
    public Statement initAdviceCall(classNode)
    {
      return new ExpressionStatement(
              new DeclarationExpression(
                      new VariableExpression("advice"),
                      new Token(Types.ASSIGNMENT_OPERATOR, "=", -1, -1),
                      new ConstructorCallExpression(classNode.getType(), new ArgumentListExpression())
                      )
              )
    }
 
  public Statement createMethodCall(method, Parameter[] parameters){
      List parameterExpressionList = new ArrayList()
      parameters.each{ parameter -> parameterExpressionList.add(new VariableExpression(parameter))}
      return new ExpressionStatement(
        new MethodCallExpression(
            new VariableExpression("advice"),
            "before",
            new ArgumentListExpression(new ConstantExpression(method.getName()),
                    new ListExpression(parameterExpressionList)
            )
        )
      )
  }
}

Couple of things to notice here:

First, creation of the AST tree involves
  • Creating instance of Advice Class - This is done through method initAdviceCall
  • Invocation of the method before with methodName and Parameter List - This is done through method createMethodCall
Creating AST structure is not the easiest task. One approach that has worked for me is the write the sample code that you are trying to generate AST for and then use Groovy AST viewer in eclipse.

Second, inspecting the value on ASTNode and getting Advice class value. First node contains information about the annotation and second node is the annotated node.

Lessons Learned

Compilation
It was required to compile Transformations files first and then compile the classes that use AST Transformation. If I compiled all three of them together the AST Transformation did not kick in. I used Eclipse IDE and it forced me to use Compile Groovy File explicitly each time I made change. "Build Automatically" or "Clean" option did not work.

Script Level Methods vs all Class Methods         
In AssertParamsNotNull AST Transformation in previous blog, I used source.getAST()?.getMethods()  what it did is that it only found top level (Script Level) methods. So AST Transformation did not apply to Class Methods, it only got applied to Script Level Method. For this one I changed to source.ast?.classes*.methods.flatten() (Line 12 in BeforeAdvisorASTTransformation). This also become apparent once you see AST structure for a given class using Groovy AST View in eclipse.

Retrive value from annotations
ASTNode being passed to visit method carries information about the annotation. First element (node[0]) contains information about annotation. You can get the values passed in to annotation during the visit method and use it during the transformation. In case above we get class passed in as value and it gets instantiated and before method gets invoked.

Groovy Compilation
When groovy compiler is invoked, any sourcefile.groovy goes under series of transformations.
From Source --> ANTLR Tokens --> ANTLR AST --> Groovy AST --> Bytecode

Using AST Transformation, we manipulate the way groovy AST gets generated. It allows to insert additonal statements.

There are various CompilerPhase that goes along with this process. I couldn't find much documentation on the process. Best information is found on Jochen Theodorou's blog post
Blogged with the Flock Browser

Saturday, June 20, 2009 | | 1 comments

JMX, Groovy, JFreeChart and Swing

Recently, I worked on creating JMX based dashboard application showing Weblogic instances' information. During the exercise I came across JMX and Groovy examples

Using JMX, Groovy, Swing and JFreeChart, I came up with a dashboard application which looks like this.

Memory-Usage

It show memory usage on two instances. It automatically updates every 10 seconds and redraws chart.

Surprisingly (or not so surprisingly), code required to be written to perform this activity is minimal. Specifically thanks to Groovy's SwingBuilder and JMX libraries. Groovy makes the task of using 4 different technologies really easy and fun.


import org.jfree.chart.ChartFactory
import javax.swing.WindowConstants as WC
import javax.management.remote.*
import javax.naming.Context;

/**
* WeblogicJMXReporter monitors Memory value for given node
* @author kartik.shah
*/
public class WeblogicJMXReporter
{

static void main(String[] args)
{
def env = [:]
env["java.naming.factory.initial"] = "weblogic.jndi.WLInitialContextFactory"
env[Context.SECURITY_PRINCIPAL] = "system"
env[Context.SECURITY_CREDENTIALS] = "*******" //Password is always stars
env[JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES] = "weblogic.management.remote"
def jmxServiceURL = new JMXServiceURL("http", "10.50.120.110", 19400,"/jndi/weblogic.management.mbeanservers.domainruntime")
def server = JMXConnectorFactory.connect(jmxServiceURL, env).MBeanServerConnection

def chart1Map = getChart("node1_svr", server);
def piedata1 = chart1Map."pieData"
def chart1 = chart1Map."Chart"
def jvmInfo1 = chart1Map."MBean"

def chart2Map = getChart("node2_svr", server);
def piedata2 = chart2Map."pieData"
def chart2 = chart2Map."Chart"
def jvmInfo2 = chart2Map."MBean"

def swing = new groovy.swing.SwingBuilder()

def frame = swing.frame(title:'Weblogic INT Memory Usage', defaultCloseOperation:WC.EXIT_ON_CLOSE,
size:[800,600], locationRelativeTo: null) {
borderLayout()
panel(id:'canvas') { rigidArea(width:700, height:250) }
}

def bounds1 = new java.awt.Rectangle(0,0, 350,250).bounds
def bounds2 = new java.awt.Rectangle(351,0, 350, 250).bounds
while(true)
{
recalculatePieData(piedata1, jvmInfo1)
recalculatePieData(piedata2, jvmInfo2)

chart1.fireChartChanged()
chart2.fireChartChanged()

frame.pack()
frame.show()
chart1.draw(swing.canvas.graphics, bounds1)
chart2.draw(swing.canvas.graphics, bounds2)
sleep(10000)
}
}

static getChart(nodeName, server)
{
def jvmInfo = new GroovyMBean(server, 'com.bea:Location='+nodeName+',Name='+nodeName+',ServerRuntime='+nodeName+',Type=JVMRuntime')
def piedata = new org.jfree.data.general.DefaultPieDataset()
piedata.setValue "Free", jvmInfo.HeapFreeCurrent
piedata.setValue "Used", jvmInfo.HeapSizeMax - jvmInfo.HeapFreeCurrent
def options = [true, true, true]
def chart = ChartFactory.createPieChart(nodeName, piedata, *options)
chart.backgroundPaint = java.awt.Color.white

["MBean":jvmInfo, "pieData":piedata, "Chart":chart]
}

static recalculatePieData(piedata, jvmInfo)
{
piedata.setValue "Free", jvmInfo.HeapFreeCurrent
piedata.setValue "Used", jvmInfo.HeapSizeMax - jvmInfo.HeapFreeCurrent
println piedata.getValue("Free")
println piedata.getValue("Used")

}
}
For purpose of this blog and keeping it simple, I kept it all in one groovy class.
Blogged with the Flock Browser

Thursday, March 26, 2009 | | 5 comments

Groovy 1.6 AST Transformation Example

With Groovy 1.6 released and this article on InfoQ got me to try some new features.

AST Transformation
With Groovy 1.6 you can define local and global transformation using annotations.
So let's define @AssertParamsNotNull annotation for method which would perform AST Transformation at compile time. It simply asserts parameters of method are not null. I am following groovy documentation here and blog entry here by Hamlet D'Arcy.

There are three components to defining local AST Transformation

Step 1: Define Annotation
Step 2: Define GroovyASTTransformation
Step 3: Test and Usage

Pre-requisites
Download Groovy 1.6
Download Groovy Eclipse plugin for 1.6, available from this update URL.
 http://dist.groovy.codehaus.org/distributions/updateDev_1.6/
This url is different from their usual update url. I believe they will update their distributions update url (http://dist.codehaus.org/groovy/distributions/update/)

Step 1: Define Annotation @AssertParamsNotNull

package com.learn.groovy16.ast.local
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import java.lang.annotation.ElementType
import org.codehaus.groovy.transform.GroovyASTTransformationClass
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass(["com.learn.groovy16.ast.local.AssertParamsNotNullASTTransformation"])
public @interface AssertParamsNotNull{
One thing to notice is here defining the ASTTransformation class. It requires complete qualified class name. Rest of the things are standard annotation declaration.

While working on getting this running in my environment I faced following errors/issues.

Unknown Type: ANNOTATION_DEF at line ...
This error comes if for some reason eclipse environment is still using older groovy installation. Check your GROOVY_HOME points to Groovy 1.6. If you are able to run this through command line, but eclipse is giving your errors. Update your Groovy Eclipse plugin from dev update URL. You may have to wipe it clean for eclipse to understand it.

Expected '{' but was found...
This error comes if for some reason there funny newline characters. I think I got this error if right after "public @interface AssertParamsNotNull" if { is in the next line. I believe I got funny character in between those two. I changed the text file encoding to UTF-8 and the error went away. It allowed me to have braces on the next line.

Step 2: Define GroovyASTTransformation


package com.learn.groovy16.ast.local
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.Parameter
import org.codehaus.groovy.ast.stmt.Statement
import org.codehaus.groovy.ast.stmt.AssertStatement
import org.codehaus.groovy.ast.expr.BooleanExpression
import org.codehaus.groovy.ast.expr.NotExpression
import org.codehaus.groovy.ast.expr.VariableExpression
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
public class AssertParamsNotNullASTTransformation implements ASTTransformation
{
public void visit(ASTNode[] nodes, SourceUnit source)
{
List methods = source.getAST()?.getMethods()
methods.findAll{MethodNode method ->
method.getAnnotations(new ClassNode(AssertParamsNotNull))
}.each{MethodNode method ->
List existingStatements = method.getCode().getStatements()
Parameter[] parameters = method.getParameters()
parameters.eachWithIndex(){ parameter, i ->
existingStatements.add(i, createAssertStatement(parameter))
}
}
}

public Statement createAssertStatement(Parameter parameter){
return new AssertStatement(
new BooleanExpression(
new VariableExpression(parameter))
}
}

This is where most of the trick is happening. This class implements ASTTransformation interface implementing visit(ASTNode[], SourceUnit source) method. This method gets all methods marked with AssertParamsNotNull annotation, iterates over all method, iterates over all method parameters and calls createAssertStatement(parameter). createAssertStatement(parameter) creates Statements equivalent to assert parameter and inserts it into the AST tree. All this happens at compile time. So notice line
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
Creating Statement and Expression using API is really cumbersome. It is good they are working on builder support for the same.
 
Step 3: Test and Run


package com.learn.groovy16.ast.local

@com.learn.groovy16.ast.local.AssertParamsNotNull

def foo(String var)
{
println var
}
foo("Hello")
foo(null)


Note the annotation with fully qualified name.

Interesting thing is that all transformation happens at compile time. It does not stop

Other interesting AST Transformations...

@CatchExceptions(list=[exception1, exception2, ..],rethrow=AppException)
Define AST transformation to remove clutterred exception handling logic and rethrow ApplicationException as noted by parameters of annotation. I think the challenge here will be to build AST Statement and Expression.

@SecureAccess(Role=Manager)
Allows to define security over method execution.

@Trace(isTraceEnabled=$GlobalTraceParameter)
Emits out trace of method. Entered, parameter values, exit, return type. This only gets emitted if GlobalTraceParameter is set to Y

Wednesday, March 11, 2009 | | 1 comments

Grails, Flex and MyEclipse

This is a small exercise that I undertook following documentation I found on grails website. Let's create small calculator application which adds two operands. The idea is to setup the flex plugin and see how it works.

I used following tools
  • MyEclipse with flex builder
  • Grails
  • Groovy Eclipse plugin
  • Grails Fex Plugin
Step 1: Install Groovy Eclipse plugin
MyEclipse 6.5 installation that I had did not came with groovy/grails plugin. One of the nice thing about MyEclipse is that it is eclipse with additional tools. Use the following plugin update URL to get groovy plugin.

Here is eclipse software update URL: http://dist.codehaus.org/groovy/distributions/update/.

This plugin is optional. You can use grails command line commands and that will work as well.

Step 2: Create Grails Project and Install Grails Flex Plugin
Next thing is to install Grails Flex plugin. This is still under development. The one I used is version 0.2.

Create Grails application using New --> Other... --> New Grails Project. This will create grails project.

Alternatively you an use grails command line to create app. Go to command line, change directory to project root folder in your workspace and type command:
              grails install-plugin flex.

This will install flex plugin for grails.

I was using MyEclipse with Flex Builder, it was easy to add flex nature to my project. To do that right click and add flex nature

Step 3: Create Calc UI
Now project is setup, we are ready to create calc.mxml. I created this under webapp folder so that it is visible.

Use following calc.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:RemoteObject id="ro" destination="calcService"/>
    <mx:Model id="myCalcData">
        <calc>
            <operand1>{number1.text}</operand1>
            <operand2>{number2.text}</operand2>
        </calc>
    </mx:Model>
    <mx:Panel>
        <mx:Form>
            <mx:FormItem label="Operand 1:">
                <mx:TextInput id="number1"/>
            </mx:FormItem>
            <mx:FormItem label="Operand 2:">
                <mx:TextInput id="number2"/>
            </mx:FormItem>
            <mx:Button label="Calculate" click="ro.calc(myCalcData.operand1,myCalcData.operand2)"/>
             <mx:Label text="Result: {ro.calc.lastResult} "  fontWeight="bold"/>
        </mx:Form>   
    </mx:Panel>       
</mx:Application>


Step 4: Create Grails HelloWorldService
Create grails new service from IDE. You can also create service by typing command
grails create-service HelloService

public class CalcService
{
  static expose = ['flex-remoting']
  def calc(int number1, int number2) { return number1 + number2 }
}

Step 5: Update configuration
Next step is to update configuration file, Config.groovy to enables web tier compiler for flex. Again, I am using similar file as in the example provided on grails website.

// enables the webtier compiler for all environments

Step 6: Run using command line

We are almost done, type in command line at project root to run the projectgrails run-app

Type in browser: http://localhost:8080/MyGrails/calc.mxml


You may need to change port, application context if you are using different configuration.



Sunday, May 25, 2008 | | 0 comments

Webservices with return type List<T>

I had written few web service which were returning List. Following are my findings when working with that.

Axis2 did not generate definition of T in wsdl. So to over come that problem I created a dummy method setT(T t) in order to get type definition of T generated in wsdl. Even though this is work around and it did not highlight or resolve the actual problem.

Axis2 generated List return type as anyType Object. Which created problem during client side code generation. The wsdl published the return type as anyType, Client had no way of determining what type of return object to be generated. So to be type safe I changed the implementation from List to T[]. Now while I believe type List will always be generated as anyType, I do think that List should not be generated as anyType. Tools should be smart enough to generate List as ArrayOfSpecificType in wsdl.
I think that is either due to two reasons,

  1. Tools have not caught upto the JDK5
  2. The specific way Java implements Generics, by using erasure, List becomes List which creates problem for the tool as tools have no way of identifying what type of list was that.

There are some other minor problems I faced while using array. I used method toArray(T[] t) to convert my List to Array. The method required me to pass a type T[] t. I created T[] t = new T[?]. I put number 3 - just randomly- to see if it all works. Service worked it generated response and it also appended two more elements named item1 and item2. I was clueless for a while where this sub elements are getting generated. Then it occurred to me the creation of array with three elements is putting it there. Finally, I settled with T[0]. But the problem with that is when result set was 0, client threw error stating unexpected element of type XXXXResponse. I believe this is a bug.

Monday, July 02, 2007 | | 0 comments

Struts: Creating Custom Validator

Creating Custom Validator

Idea was to create custom password validator which will not allow certain words to be password. This included common words like password, companyname, abc, 123 etc. Also this requirement will be extended in future to disallow password being same as first name, last name, phone numbers, email address etc.

As direct implementation one can always override validate method in the Action Form class for the form. But since password field existed in multiple forms it made more sense to make generic validator which can be added to validation-rules.xml file for the application.

Add following section in validation-rules.xml file. This describes validation rule.

<validator name="custompassword" classname="com.web.helper.CustomValidationUtility" method="validatePassword" methodparams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionMessages, org.apache.commons.validator.Validator, javax.servlet.http.HttpServletRequest" depends="" msg="errors.password"></validator>


Add the rule for the form that we need to validate.

<!-- change password -->
<form name="/change-password">
...
<field property="newPassword" depends="custompassword">
...
</field>
</form>




Create custom validator classes as

package com.web.helper;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.validator.Resources;

public class CustomValidationUtility implements Serializable
{

public static boolean validatePassword(
Object bean,
ValidatorAction va,
Field field,
ActionMessages messages,
Validator validator,
HttpServletRequest request) {

System.out.println("Here now");
String value = ValidatorUtils.getValueAsString(bean, field.getProperty());
List invalidPasswordList = new ArrayList();
invalidPasswordList.add("password");
invalidPasswordList.add("companyname");
invalidPasswordList.add("abc");
invalidPasswordList.add("123");

if (!GenericValidator.isBlankOrNull(value)) {
try {
if (invalidPasswordList.contains(value)) {
messages.add(field.getKey(),
Resources.getActionError(
request,
va,
field));

return false;
}
} catch (Exception e) {
messages.add(field.getKey(),
Resources.getActionError(
request,
va,
field));
return false;
}
}

return true;
}
}


That's it.
Roadblocks:

  • One of the problem I faced while working on this in eclipse + wtp that the class was not included in class path automatically. I have to stop server + Compile Clean + Publish + Restart the server for each change I made. I struggled with following exceptions. First was loadValidationActionClass method threw ValidatorException. This is because the my ValidationAction class i.e. CustomValidationUtility was not in classpath. I hoped that WTP will take care of this dynamically, but for some reason it didn't

  • Second exception was regarding the parameter of my validation method, they have to exactly match as given in validation-rules.xml.

  • Third problem faced was whether to use ActionErrors or ActionMessages as parameter. There are numerous example out on the web for custom validator to use ActionErrors. It gave me NullPointerException. On debugging I found out that errors parameter in the method was null. I switched it to ActionMessages (which is parent class of ActionError) and it worked. (Of course, WTP made me do entire trip of stopping, cleaning, compiling, publishing, and starting)

  • Some other problems other people faced working on this (which I found out while google-ing): You have to be absolutely sure which version of Struts and commons validator you are using. Struts 1.1 goes with commons-validator 1.1 and Struts 1.2 goes with Commons-validator 1.3. People spent frustrating amount of effort to resolve error only to find out they have mismatched library.


Usage and/or Extension of above idea
If developing commons validator based validation mechanism, above methods can be used to come up with specific validation for those fields which are going to used at multiple places. Advantages of such mechanism is it will save you from writing and debugging java script, can come up with architecture where you can validate it with databases - though approach needs to be well thought out and used sparingly. (e.g. list of password could have been stored in database tables), comparison with other fields on the page. In such case, I believe that validate method in form should be put to use rather than generic validations. Also above mechanism for validation can be used when your validation is interdependent on multiple fields on the same form.

Tuesday, July 18, 2006 | | 0 comments

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.