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:
<
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:
-
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
-
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.
-
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.
-
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.
-
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:
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 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
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 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
named "includes" specifying the form bean
name to be included. Thus, the new solution will look like this:
This new 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.
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.
|