Adding Spice to Struts

by: Samudra Gupta

Last time when I published the article on Struts named "Strictly Struts", I had some requests to write something more on the topic. I am a big fan of Struts and find it so simple and elegant to use that it seems there is less of tricks in Struts to discuss. However, Ashish, one of my colleagues, presented me with a little problem, which provided the basis of this article. It is a problem with using dynamic form beans in Struts and we needed more than Struts normally provides.

The Problem

One afternoon Ashish and I were discussing a piece of work he was doing. All was coming along nicely but he was facing a problem of duplication of data in using DynaActionForm. He was trying to model an inheritance mechanism using DynaActionForm. For example, say, we are developing an online Vehicle registration system. Also let us assume that we want to register two types of Vehicle Car and Ship. The object model (Figure-1) of this problem domain inevitably looks something like the following:

Figure 1: The domain model for the Vehicle registration

We needed to replicate the similar relationship in the struts domain through ActionForms. As a strategy in our project, we use DynaActionForms. Listing –1 depicts what the initial form bean definitions looked like:


<form-bean name="CarForm" type="org.apache.struts.action.DynaActionForm">
      <form-property name="registrationNumber" type="java.lang.String"/>
      <form-property name="maker" type="java.lang.String"/><
      <form-property name="modelNumber" type="java.lang.Integer"/>

      <form-property name="engineSize" type="java.lang.Integer"/>
      <form-property name="noOfDoors" type="java.lang.Integer"/>
      <form-property name="insuranceGroup" type="java.lang.String"/>
  </form-bean>

  <form-bean name="ShipForm" type="org.apache.struts.action.DynaActionForm">
      <form-property name="registrationNumber" type="java.lang.String"/>
      <form-property name="maker" type="java.lang.String"/>
      <form-property name="modelNumber" type="java.lang.Integer"/>

      <form-property name="tonnage" type="java.lang.Integer"/>
      <form-property name="numberOfBoilers" type="java.lang.Integer"/>
      <form-property name="shipUsedAs" type="java.lang.String"/>
  </form-bean>

Listing-1: The initial form-bean definition

This solution will work fine without any problem in the real world. But rightly Ashish was not happy about the duplication of data in both the form beans (The bolded areas represent the duplicate data). Now we needed something more than what the default DynaActionForm offers in Struts. We both started thinking about a possible solution. Every solution to all problems requires a method in its approach. So we started from the basics of how Struts really handle the form-bean configurations internally.

The Possible Solutions…

Most of us are used to working in a strict time schedule where often it becomes a compromise between the best solution and the cheapest solution. More often, we end up doing the cheapest solution to keep people above us happy as the project gets delivered on time. So here we outlined the possible solutions we had:

  1. The cheapest solution would be to discard the DynaActionForm and use the normal ActionForm classes and create specific ActionForm classes to represent each domain object. In this approach, we could possibly create an ActionForm class for each of the classes in the domain model and even could cope with the inheritance very easily. In this solution, we could design a class structure as shown in Figure-2.

Figure 2: The ActionForm based solution

  1. The solution we would however like to achieve (dare we say the best!) is to modify the struts internals to cope with the inheritance in a declarative way via the configuration file. To a solution architect, this one is alluring.

We have carefully weighed both the solutions that we have at hand and reached the following conclusions.

  1. The solution 1 is by no means the ideal solution as we lose all the benefits of the DynaActionForm. The properties become pretty much hard coded within the ActionForm classes and the solution is less flexible.
  2. Adding new properties to the forms means changing and recompiling the Java code whereas by using DynaActionForm we can add or remove properties in the configuration files.
  3. More importantly, if we want to achieve dynamism in Model-View- Controller architecture where any change in the Model layer(Domain/Value objects) should only correspond a change in the View layer (JSPs), then it is pretty crucial that we refrain from such system where the Action classes and Form beans need to be changed to accommodate such features.

With these following conclusions in mind, we set out on the road to Solution 2.

The Road Map…

Our Solution 2 demands some manipulation of the Struts framework components. Hence, we decided to trace how the declarative form bean configuration is mapped to the form bean objects within the Struts layer. We have done a dissection of the Struts and the following facts were laid before us.

  • Struts uses the Commons Digester component of the configuration files.
  • The Commons Digester component relies on a set of rules to be passed to it in order to parse the configuration file. Struts passes a ConfigRuleSet object to the Digester component.
  • The ConfigRuleSet object contains all the parsing rules.
  • For the form bean configuration Struts uses a class called FormBeanConfig.
  • The framework initialises any particular form bean class by calling its initialize() method. Within the initialize() method each particular FormBean class gets hold of its own FormBeanConfig object, obtains all the properties and….
  • Dynamic Action Form classes(DynaActionForm etc.) gets all the properties from the FormBeanConfig object and puts them in a Map.

These facts again opened at least two possible solutions. But we soon realized the potential risks associated with each of them.

Working Out the Kinks

Now we pretty much have an idea of what to do. Our first reaction was to come up with a form-bean declaration like this:


  <form-bean name="someForm" type="org.apache.struts.action.DynaActionForm" includes=”anotherForm”>

In this mode of declaration, the form-bean "someForm" will include all the properties declared in the form-bean "anotherForm". This is exactly what we wanted. However, we raised that in order to implement this solution we need to do the followings:

  • The ConfigRuleSet class already declares rules by which we can capture any attribute defined for the <form-bean> tag. This is good news.
  • However, The Ruleset Also Defines That The Defined Attributes Must Correspond To The Instance Variables Of The Formbeanconfig Class. For Example, There Are Two Attributes "Name" And Type" In The ≪Form-Bean≫ Tag And The Formbeanconfig Class Also Defines Two Instance Variables Called "Name" And "Type". The Digester Component Sets The Values Of These Instance Variables To The Attribute Value Specified In The Configuration File By Invoking The Corresponding Set() Methods In The Formbeanconfig Class.
  • Thus, to accommodate another attribute for the <form-bean> tag, we need to extend the FormBeanConfig class.
  • Moreover, there is no easy way to attach a custom FormBeanConfig class to the framework. The only alternative is to create a new RuleSet class, which defines the rules for parsing the <form- bean> definition by specifying the alternative FormBeanConfig class.
  • Thus, we need to create a new RuleSet class.
  • Additionally, we raised that in the initialization process of the form bean, we need to read all the properties of another form bean defined in the "includes" attribute and put them into the map of the original form bean. For example, the initialization process should put all the properties of the "anotherForm" form bean in the Map of the form bean "someForm".
  • In order to achieve this, we need to extend the existing DynaActionForm and override the initialize() method to do the job.

By carefully weighing the work involved, we decided that it is certainly an overkill to create a new FormBeanConfig class and a new RuleSet class. But in all the cases, we need to come up with a new FormBean class to override the initialize() method.

The Final Solution…

The final solution we came up with will include an extra <form-property> named "includes" specifying the form bean name to be included. Thus, the new solution will look like this:


<form-bean
name="ShipForm" type="our own form bean">

<form-property name="includes"
type="java.lang.String" initial="anotherForm"/>

</form-bean>

This new <form-property> must have the name "includes" and the initial value of this property must hold the name of the form bean that we want to include. With this idea in mind, we ventured to write a new ActionForm class.

The CustomDynaForm class

We decided to create a class called CustomDynaForm, which will extend the original Struts DynaActionForm class. We will override the initialize() method of the DynaActionForm class to include all the properties from another form bean. Listing –2 is the source code for the new CustomDynaForm class.


package custom.struts;

import org.apache.struts.action.DynaActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.FormPropertyConfig;
public class CustomDynaForm extends DynaActionForm
{
     /**
      * override the initialize() method
     */
    public void initialize(ActionMapping mapping)
    {
        super.initialize(mapping);

        //form bean name

        String name = mapping.getName();

        //form bean config

        FormBeanConfig config = mapping.getModuleConfig().findFormBeanConfig(name);

        //now check if there is "includes" property

        FormPropertyConfig propConfig = config.findFormPropertyConfig("includes");
        if (propConfig != null)
        {
              //get the initial values
            String formBeanToInclude = propConfig.getInitial();
            FormBeanConfig formBeanConfig =  mapping.getModuleConfig().findFormBeanConfig(formBeanToInclude);
            FormPropertyConfig properties[] = formBeanConfig.findFormPropertyConfigs();
            for (int i = 0; i < properties.length; i++)
            {
                //set(properties[i].getName(), properties[i].initial());

                this.getMap().put(properties[i].getName(), properties[i].getInitial());
            }
        }
    }
}

Listing 2: The CustomDyaForm source code

The Vehicle Registration Redone…

With this new framework component in place, our Vehicle registration configuration file will have a new look. Listing 3 presents the new form bean configurations.


<form-bean name=" vehicleCommonForm " type="org.apache.struts.action.DynaActionForm">
      <form-property name="registrationNumber" type="java.lang.String"/>
      <form-property name="maker" type="java.lang.String"/>
      <form-property name="modelNumber" type="java.lang.Integer"/>
</form-bean>

<form-bean name="CarForm" type="org.apache.struts.action.DynaActionForm">
      <form-property name="engineSize" type="java.lang.Integer"/>
      <form-property name="noOfDoors" type="java.lang.Integer"/>
      <form-property name="insuranceGroup" type="java.lang.String"/>
      <form-property name="includes" type="java.lang.String" initial=”vehicleCommonForm"/>

  </form-bean>

  <form-bean name="ShipForm" type="org.apache.struts.action.DynaActionForm">
      <form-property name="tonnage" type="java.lang.Integer"/>
      <form-property name="numberOfBoilers" type="java.lang.Integer"/>
      <form-property name="shipUsedAs" type="java.lang.String"/>
      <form-property name="includes" type="java.lang.String" initial=”vehicleCommonForm"/>

  </form-bean>

Listing 3: The changed form bean configuration file

In this new model, we have reduced the duplication of the data across form beans. Next we tested this new model against our application and voila it worked fine!

Conclusion…

This new model helped us model the inheritance relationship that exists in the domain model of the Struts layer. This definitely is more manageable and a more flexible solution. If the super class in the domain model needs to hold some additional data, it is only in one place that we need to add it to. For a large scale system where we are defining hundreds of different types of Vehicles, this could be a serious maintenance gain. Also to us the solution looked cleaner. To achieve the best out of it, it will be a wise idea to also use composite pattern in declaring the JSP. One can think of writing a single vehicleCommon.jsp and include it in carForm.jsp and shipForm.jsp. Hope this article will help you, if you are facing similar problems. Please feel free to write to me with your ideas. Happy Struts !


Samudra Gupta has six years of Java related application development experience. He has been involved in various research based projects in Java including e-commerece based and application design and development projects. He is based in the United Kingdom. In his free time, he is a columnist in different Java Magazines and Journals and loves to play contract bridge.