Skip to main content

Role-Based Code Upgrade

March 8, 2007

{cs.r.title}



I often think software upgrades are invasive blunt instruments.
Bad upgrades can have a bad effect on productivity. I occasionally
see this when one of my scheduled antivirus software updates chokes
on scanning one of my local files. The next update generally
doesn't choke; i.e., the bug is quietly fixed! I recently upgraded
my browser application and when I couldn't get it to work, I then
had to revert to the old version, suffering a grumpiness-inducing
machine crash along the way. Clearly, this type of upgrade
mechanism carries a significant risk, which got me thinking ...

Is it really necessary to upgrade an entire application? Why not
just upgrade the parts you need in an effort to reduce the risk and
the seemingly inevitable disruption? Is it even possible to perform
a partial on-demand application upgrade? Can we put the "soft" back
in software?

In this article, I present a mechanism for what I call
targeted client upgrades. The upgrade code targets the needs
of a specific client user, rather than merely facilitating the IT
department or the vendor upgrade schedule.

On-demand upgrades fit into a broader category of software
deployment that is increasingly referred to as rules-based
IT
. Rules-based IT seeks to allow the definition of
business-driven rules for consuming IT services. In other words,
the IT rules facilitate the needs of the business and help its
users to do their jobs more effectively.

Traditionally, IT services are rolled out and maintained on an
enterprise-wide basis. This is the model used for Windows updates
and antivirus data file renewal. It's a clunky mechanism.

To illustrate a typical example, let's imagine I'm an HR director
and I need software features X, Y, and Z in a given application.
The IT folks roll out the software with my required features. The
features concern manipulation of private information, and hence are
password-protected so that only I can use them. In other words,
everyone gets the code, but only I can use it. Wouldn't it be nicer
if I could get the new software features with only a minimal
upgrade? Or better still, only my code gets the upgrade. Everyone
else is unaffected and unaware of the upgrade.

What I'm looking for here is a more flexible, lower-cost
upgrade mechanism. How might you go about creating such an
upgrade?

A Staff Management System

For my application domain, I'm going to stick with the idea of an
HR application that stores and maintains staff records. Many
organizations allow all staff to at least view a portion of such
data, while permitting only a select few to make modifications. I
occasionally wonder at the way some senior IT staff can view
confidential data, purely because they administer the HR systems. I
remember one company I worked in where a relatively junior IT
staffer remarked to me with pride how he knew what everyone's
salary was!

Figure 1 illustrates such an application where user privileges
are role-based. What this typically means is that users are grouped
by privilege levels. The privilege level dictates the allowed
access; e.g., certain menu options may be unavailable for users
below a specific privilege level. The role in Figure 1 could be
implemented using a password scheme, or by group membership or tying
in with some platform-specific mechanism.

HR application data accessed by role
Figure 1. HR application data accessed by role

The users on the left of Figure 1 can view all data and make
changes, whereas those on the right can only view their own specific
data. Figure 2 illustrates an example view for the privileged
users.

Privileged HR application data view
Figure 2. Privileged HR application data view

Figure 2 illustrates access to salary data. It might be argued
that Social Security numbers are also private, but for the purposes
of this article, let's assume they're not. Moving on, Figure 3
illustrates an example view for the non-privileged users. It's
likely that the view in Figure 3 would be even more restrictive,
just allowing a given user to view his/her own data.

Non-privileged HR application data view
Figure 3. Non-privileged HR application data view

Two Employee Types

The application consists of five main classes. Four of these
classes are application-centric and the fifth implements the
Commons
Digester
construction rules. I use the Commons Digester to
enable the easy creation of role-based upgrade components--more
on this later.

I'll describe the classes in the following sections, starting
with the base class Employee.java. The Employee
class is pretty self-explanatory, with a constructor, three private
data members, three setters, and a toString()
method.

[prettify]public class Employee
{
   private String personName;
   private String socialSecurityNumber;
   private String employeeType;

   public Employee(String type)
   {
      employeeType = type;
   }

   public void setPersonName(String name)
   {
      personName = name;
   }

   public void setSocialSecurityNumber(String ssn)
   {
      socialSecurityNumber = ssn;
   }

   public void setType(String type)
   {
      employeeType = type;
   }

   public String toString()
   {
      String newline = System.getProperty("line.separator");

      return employeeType + newline +
         "Name: " + personName + newline +
         "Social Security Number: " +
         socialSecurityNumber + newline;
   }
}
[/prettify]

Next up are the subclasses of Employee: ContractStaff and
PermanentStaff.

[prettify]public class ContractStaff extends Employee
{
   public ContractStaff()
   {
      super("Contractor");
   }
}

public class PermanentStaff extends Employee
{
   public PermanentStaff()
   {
      super("Permanent");
   }
}
[/prettify]

The last application-centric class is called
HRStaffDetails. This class stores the details of all
contractors and permanent staff.

[prettify]
public class HRStaffDetails
{
   private Vector contractors;
   private Vector permanentFolks;

   public HRStaffDetails()
   {
      contractors = new Vector();
      permanentFolks = new Vector();
   }

   public void addPermanentStaff(PermanentStaff person)
   {
      permanentFolks.addElement(person);
   }

   public void addContractor(ContractStaff person)
   {
      contractors.addElement(person);
   }

   public String toString()
   {
      String newline = System.getProperty("line.separator");
      StringBuffer buf = new StringBuffer();

      buf.append(newline);
      for(int i = 0; i < permanentFolks.size(); i++)
         buf.append(permanentFolks.elementAt(i)).append(newline);

      buf.append(newline);
      for(int i = 0; i < contractors.size(); i++)
      {
         buf.append(contractors.elementAt(i)).append(newline);
      }

      return buf.toString();
   }
}
[/prettify]

That's the basic code. How do I now build the
application?

The Apache Jakarta Commons Digester Code Deployment Model

Given that I want to illustrate role-based IT management and
dynamic upgrading, let's now take a look at a mechanism for
automatically creating the above Java classes from XML. This is
done by unmarshalling the following XML document. Notice that the
XML tags correspond with the above class and subclass names and the
associated data member names. For example, the tag
matches the name of the subclass
PermanentStaff. Once this tag is encountered during parsing, an object of the PermanentStaff class is instantiated with its data
members set to the values indicated in the tags below it; i.e.,
and . The XML file
is well-formed, so all tags consist of start and end elements;
i.e., and
.

[prettify]<?xml version="1.0"?>
<hrstaffdetails company="company">
   <permanentstaff>
   <personName>Francis Bacon</personName>
   <socialSecurityNumber>1111111</socialSecurityNumber>
   </permanentstaff>

   <permanentstaff>
   <personName>Francesca Bacon</personName>
   <socialSecurityNumber>2222222</socialSecurityNumber>
   </permanentstaff>

   <contractstaff>
   <personName>Francisco Bacon</personName>
   <socialSecurityNumber>3333333</socialSecurityNumber>
   </contractstaff>
</hrstaffdetails>
[/prettify]

This is the key to using the Commons Digester: you can parse the
XML files and create instances of arbitrarily complex class
structures. This is a powerful facility because it allows for the
flexible creation of objects in a location-independent fashion. In
other words, if I want to create an object of class A on machine X,
then I can simply define the XML file and run the Digester against
it. The result is a newly instantiated object where I want it, when
I want it. I'll be using this mechanism a little later to show how
to upgrade classes. Again, this is just another case of using the
flexibility of the Digester mechanism.

The Commons Digester class is used to process the above XML
document based on the following patterns and rules:

[prettify]
<?xml version="1.0"?>
<digester-rules>
   <object-create-rule pattern="hrstaffdetails" classname="HRStaffDetails" />
   <set-properties-rule pattern="hrstaffdetails" >
   <alias attr-name="company" prop-name="company" />
   </set-properties-rule>

   <pattern value="hrstaffdetails/permanentstaff">
      <object-create-rule classname="PermanentStaff" />
      <call-method-rule pattern="personName" methodname="setPersonName"
      paramcount="0" />
      <call-method-rule pattern="socialSecurityNumber" 
        methodname="setSocialSecurityNumber" paramcount="0" />
      <set-next-rule methodname="addPermanentStaff" />
   </pattern>

   <pattern value="hrstaffdetails/contractstaff">
      <object-create-rule classname="ContractStaff" />
      <call-method-rule pattern="personName"
        methodname="setPersonName" paramcount="0" />
      <call-method-rule pattern="socialSecurityNumber" 
        methodname="setSocialSecurityNumber" paramcount="0" />
      <set-next-rule methodname="addContractor" />
   </pattern>
</digester-rules>
[/prettify]

The above looks very complicated, but it's really not! Let's
break it down a bit. The object-create rule is invoked when the
pattern hrstaffdetails is encountered. When this occurs, an
object of the class HRStaffDetails is instantiated.
The next rule sets the properties of the class, in this case a
company name is set.

The next rule is called pattern value and serves to create an
instance of the class PermanentStaff. The contained
rule call-method-rule is used to invoke the base class
method setPersonName, using the
rule from the previous XML file.
The same mechanism occurs for the socialSecurityNumber pattern.
The last rule for the PermanentStaff class is called
set-next-rule, which in this case refers to the method
HRStaffDetails.addPermanentStaff. This completes the
first part of unmarshalling an instance of
HRStaffDetails. The rest of
HRStaffDetails is unmarshalled using the remainder of
the rules. The latter apply to the class ContractStaff
and operate in a manner identical to that for the class
PermanentStaff.

The last piece of the puzzle is the class that creates the
digester.

[prettify]
import org.apache.commons.digester.*;
import org.apache.commons.digester.xmlrules.*;

import java.io.*;
import java.util.*;

public class XmlRulesDriver
{
   public static void main(String[] args)
   {
      try
      {
         File input = new File(args[0]);
         File rules = new File(args[1]);
         Digester digester = DigesterLoader.createDigester(rules.toURL());
         HRStaffDetails staffDetails = (HRStaffDetails)digester.parse(input);
         System.out.println(staffDetails.toString());
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }
}
[/prettify]

The above code reads the two XML files described earlier, namely
hrstaffdetails.xml and rules.xml. Then, an instance
of Digester is created into which is passed the file
rules.xml. The Digester then parses the file
hrstaffdetails.xml in conjunction with the rules file to
produce an instance of the class HRStaffDetails. All
you need to get the code built is:

  • The above five Java classes.
  • A copy of the commons-digester JAR file in your classpath.

Specifically, you my find you'll need the following JAR files in
your CLASSPATH:

  • commons-digester-1.7.jar
  • commons-collections-3.2.jar
  • commons-logging-api-1.1.jar
  • commons-beanutils-core.jar

Then, to manually run the program, type the command:

[prettify]java XmlRulesDriver hrstaffdetails.xml rules.xml
[/prettify]

The above command should produce the output displayed earlier in
Figure 3. There is also a batch file (called ToRun.bat)
supplied with the code to help you run the program.

Let's now go back to Figure 3 and assume that the view in Figure
3 is that of all users, privileged and non-privileged. Our HR
director now decides that access is required to salary data. We
want to be able to display salary and hourly rate details for the
HR director as per Figure 2.

The HR Director Needs an Upgrade

You've just built the basic program that all users have.
Remember, we want to see the role-based upgrade in action. So I want
the HR director to get new classes that allow access to salary and
hourly rate details for staff and contractors, respectively. To
demonstrate this, the code is arranged into two folders called
old and upgrade. The files in the old folder are non-upgraded
versions as per Figure 3. The files in the upgrade folder
represent the modified files that produce the output illustrated in
Figure 2.

I now want to now upgrade the HR director client. To produce
the display in Figure 2, I make the following changes to the
files:

  • ContractStaff.java
  • PermanentStaff.java
  • rules.xml
  • hrstaffdetails.xml

The changes apply just to the addition of a new data member, an
associated setter method, and an overloaded toString() method in
each of the subclasses ContractStaff and
PermanentStaff.

[prettify]public class ContractStaff extends Employee
{
   private String hourlyRate;

   public ContractStaff()
   {
      super("Contractor");
   }

   public void setHourlyRate(String newHourlyRate)
   {
      hourlyRate = newHourlyRate;
   }

   public String toString()
   {
      String newline = System.getProperty("line.separator");

      return super.toString() +
              "Hourly rate: " + hourlyRate + newline;
   }
}

public class PermanentStaff extends Employee
{
   private String salary;

   public PermanentStaff()
   {
      super("Permanent");
   }

   public void setSalary(String newSalary)
   {
      salary = newSalary;
   }

   public String toString()
   {
      String newline = System.getProperty("line.separator");

      return super.toString() +
               "Salary: " + salary + newline;
   }
}
[/prettify]

The above class modifications are reflected in the following
changes to the two rules files.

[prettify]
<?xml version="1.0"?>

<digester-rules>
   <object-create-rule pattern="hrstaffdetails" classname="HRStaffDetails" />
   <set-properties-rule pattern="hrstaffdetails" <
   <alias attr-name="company" prop-name="company" />
   </set-properties-rule>

   <pattern value="hrstaffdetails/permanentstaff">
   <object-create-rule classname="PermanentStaff" />
   <call-method-rule pattern="personName" methodname="setPersonName"
   paramcount="0" />
   <call-method-rule pattern="socialSecurityNumber" 
     methodname="setSocialSecurityNumber" paramcount="0" />
   <call-method-rule pattern="salary" methodname="setSalary"
   paramcount="0" />
   <set-next-rule methodname="addPermanentStaff" />
   </pattern>

   <pattern value="hrstaffdetails/contractstaff">
   <object-create-rule classname="ContractStaff" />
   <call-method-rule pattern="personName" methodname="setPersonName"
   paramcount="0" />
   <call-method-rule pattern="socialSecurityNumber" 
     methodname="setSocialSecurityNumber" paramcount="0" />
   <call-method-rule pattern="hourlyRate" methodname="setHourlyRate"
   paramcount="0" />
   <set-next-rule methodname="addContractor" />
   </pattern>
</digester-rules>

<?xml version="1.0"?>

<hrstaffdetails company="company">

   <permanentstaff>
   personName>Francis Bacon</personName>
   <socialSecurityNumber>1111111</socialSecurityNumber>
   <salary>30000</salary>
   </permanentstaff>

   <permanentstaff>
   <personName>Francesca Bacon</personName>
   <socialSecurityNumber>2222222</socialSecurityNumber>
   <salary>30000</salary>
   </permanentstaff>

   <contractstaff>
   <personName>Francisco Bacon</personName>
   <socialSecurityNumber>3333333</socialSecurityNumber>
   <hourlyRate>300</hourlyRate>
   </contractstaff>
</hrstaffdetails>
[/prettify]

With these changes in place, the Java files can be recompiled
and the program can be rerun. This will produce the program output
displayed in Figure 2. If you don't want to recompile the Java
classes on the client machine, the compiled Java files can be
copied, overwriting the old versions. Then, when the digester is
created, the new code is used. The rules in this case are
represented by the two XML files.

The easiest way to test the supplied code is to copy it into a
folder. Within this folder you will see two subfolders called
upgrade and old. From the main folder, compile and run the
program as described earlier. This will give you the Figure 3
program output. To see the upgraded code, copy the contents from
the upgrade folder into the main folder. Then, recompile and run
the program to see the upgraded view.

Upgrade Deployment

I've described the old code and how it is upgraded. How
might this be used in practice? One way could be for each
application user to be provided with classes generated by the
Digester mechanism. The Digester might run on a central server,
distributing classes (or a complete application) to the different
end users. The upgrade mechanism is put in place to generate the
required classes and distribute them based on user roles. If
required, the role-based classes could be distributed as applets.
In other words, different users need not be issued with the same
classes. Instead, the different users can receive their own
required version of the application classes.

A More Generalized Upgrade Approach

In Figure 4, I present a more general approach to the upgrade
problem. You can see that I've added a new element in the center of
the network, called a software factory. This is an entity that could
be designed to generate upgrades on demand. The software factory
could be designated as the location where the digester runs. So
the output of the factory could be complete upgrades such as the
one required by our old friend the HR director.

The key point is that the Commons Digester framework provides a
really useful scheme for unmarshalling Java classes based on XML
rules.

A software factory to dispense upgrades
Figure 4. A software factory to dispense upgrades

Are Upgrade Mechanisms a Thing of the Past?

One can argue that clients are becoming increasingly thin: just
bare browsers. Given the massive popularity of web containers (such
as Tomcat) and application servers (such as Geronimo), maybe the
only place where upgrades will be relevant is at the server? Do we
really need to worry about client-side upgrades? My thinking is
that there is a growing need for dynamic, event-driven client-side
code such as that provided by Ajax. If this trend accelerates, then
client code will fragment beyond the basic browser. Once this
happens, upgrades will figure on both the client and server
sides.

Conclusions

Software upgrades are an unavoidable fact of life for developers
and users alike. They carry with them a significant element of
risk: the new software version may simply not work! This is as true
for an antivirus signature file update as it is for a Windows
update; and the same applies for a full application upgrade.

Telecom systems have long used the model of partial upgrades.
The same approach is also seen in high-performance,
primary-secondary network processors. In addition, database engines
also use the same approach. Such applications as these have hard
service level agreements that must be met.

Another aspect of these high-performance applications is their
distributed nature. If you have two running instances of a database
server with one backing the other up, then you can have two
different versions. In fact, the mechanism for upgrading such
instances is often to initiate a failover from the primary to the
secondary. The primary instance can then be upgraded and then a
failover is executed from the secondary back to the primary again.
At this point, the secondary can be upgraded. All without affecting
service.

With the advent of dual-core desktop machines, it's likely that
desktop software upgrade mechanisms will become more complex. This
should, in turn, help facilitate ongoing service. The Commons
Digester provides an elegant means of upgrading code. The code
produced can be targeted at all users or just a select few. This
obviates the need for a single upgrade of all users, thereby
reducing the risk and facilitating the end users' needs.

Resources

width="1" height="1" border="0" alt=" " />
Stephen B. Morris is an independent writer/consultant based in Ireland.
Related Topics >> Programming   |