It's often very difficult to apply extensions or patches to an
existing application. Consider the steps required. First, a
software developer has to modify some source code. Then a platform
engineer rebuilds and repackages the system, creating a detailed
installation guideline. Finally, a deployment engineer redeploys
the software at customer site. Hopefully, it all works. Is there
any method to make these tasks simpler?
PatchExpert is a
simple tool to satisfy this requirement. It can insert software
extensions or patches through some predefined extension
points.
Problem
Let's look at an example first to see how difficult it is to
apply a patch to an existing application.
TestMain is a simple application used to print out
an onscreen triangle of asterisk ("*") characters. It
has a Calculator class to calculate the level of
triangle according to the given integer and asks
TrianglePrint to print out the triangle.
Here's the source code for a basic implementation.
Calculator uses the
Singleton
pattern, and is used to figure out how many asterisks to print out.
This implementation simply doubles the given argument.
public class Calculator {
static Calculator instance = new Calculator();
public static Calculator getInstance() {
return instance;
}
protected Calculator() {
}
public int calc(int num) {
return num * 2;
}
}
TrianglePrint prints out the triangle line by line,
according to the given level.
public class TrianglePrint {
int level;
public TrianglePrint(int level) {
this.level = level;
}
public void print() {
for (int i = 1; i <= level; i++)
System.out.println(getLine(i));
}
protected String getLine(int num) {
StringBuffer sb = new StringBuffer();
for(int i = 1; i <= num; i++)
sb.append("*");
return sb.toString();
}
public int getLevel() {
return level;
}
}
Finally, TestMain exercises the code by getting a
Calculator instance, calling calc(), and
sending the result on to TrianglePrint.
public class TestMain {
public static void main(String[] args) {
Calculator c = Calculator.getInstance();
int num = c.calc(4);
TrianglePrint printer = new TrianglePrint(num);
printer.print();
}
}
The application will print out an ASCII art triangle like
this:
*
**
***
****
*****
******
*******
********
Now imagine that after the application has been deployed on the
customer site, the requirements change: both the
Calculator algorithm and TrianglePrint
print behavior need to be changed. A patch should be applied to the
software. Below are the tasks typically taken to achieve that
goal.
- Software developer's task
The developer uses inheritance to minimize the changes applied
directly to the existing source code. He needs to add two new
classes and modify two of the original classes.
He creates ModernCalculator, a subclass of
Calculator. It overrides calc(int) to
provide a new algorithm that simply adds 1 to the given integer number
instead of doubling it.
public class ModernCalculator
extends Calculator {
public int calc(int num) {
return num + 1;
}
}
A new FancyTrianglePrint subclasses
TrianglePrint. It overrides getLine(int)
to generate a new string for the given line.
public class FancyTrianglePrint
extends TrianglePrint {
public FancyTrianglePrint(int level) {
super(level);
}
protected String getLine(int num) {
int level = getLevel();
StringBuffer sb = new StringBuffer();
for (int t = 1; t <= level - num; t++)
sb.append(" ");
for(int t = 1; t <= num; t++) {
sb.append("*");
if (t != num)
sb.append(" ");
}
for (int t = 1; t <= level - num; t++)
sb.append(" ");
return sb.toString();
}
}
Now the new implementation classes are ready. But how to
integrate them into the existing application? TestMain
needs to be modified to use FancyTrianglePrint instead
of TrianglePrint.
public class TestMain {
public static void main(String[] args) {
Calculator c = Calculator.getInstance();
int num = c.calc(4);
//TrianglePrint printer = new TrianglePrint(num);
TrianglePrint printer = new FancyTrianglePrint(num);
printer.print();
}
}
Since Calculator uses a Singleton pattern, it should
be modified to use a singleton instance of
ModernCalculator instead of Calculator.
public class Calculator {
//static Calculator instance = new Calculator();
static Calculator instance = new ModernCalculator();
...
}
- Platform engineer's task
With changes to the project source, the platform engineer needs
to add the new files to the project, rebuild the whole system,
re-package it (for a big project, it is most likely that some
modules need to be rebuilt and re-packaged) and ship it to the
deployment engineer with a detailed patch installation
guide.
- Deployment engineer's task
The deployment engineer needs to install the patch on the
customer site. The installation involves backing up old files and
deleting, adding, or replacing files according to the patch
installation guide.
Now the application with patch is ready to work. It prints out
the triangle like this:
*
* *
* * *
* * * *
* * * * *
Introducing PatchExpert
Framework
The java.net project
PatchExpert is a
simple tool to make extending and patching software easier. It
allows the developer to define some extension points in
the target application. Through these extension points, it can
insert implementation at runtime or by configuration. Figures 1 and
2 demonstrate the relationship among these elements.

Figure 1. The relationship among elements

Figure 2. The relationship among elements
The software architect decides where the software can be
extended. At these extension points, the developer makes a call to
PatchExpert, providing a name for the extension point.
There can be many different implementations to be inserted into
the extension point. Of course, these implementations should have
something in common. They often have the same superclass or
implement the same interface.
An external XML configuration file is used to map the named
extension point to a specific implementation. Of course, this means
it is possible to change implementations without touching the
original code.
PatchExpert API
ObjectFactory is the main class of PatchExpert.
Let's check the methods in this class one by one.
public static ObjectFactory getInstance()
This is the factory method. It always returns the singleton
instance of ObjectFactory.
public Object newObject(String module, String
name)
This method creates an implementation instance for a specific
extension point. The parameter module is the module name
of the extension point. It acts like a namespace to avoid name
collision. The parameter name specifies the extension
point in the module. This method is suitable for an implementation
class with an accessible default constructor.
public Object newObject(String module, String name,
ClassLoader cl)
This method is almost the same as newObject(String module,
String name). The only difference is that it uses
cl to specify which class loader is used to load the
implementation class.
public Object newObject(String module, String name,
Class[] paramTypes, Object[] paramValues)
This method is used when the implementation class has a
non-default accessible constructor. The parameter
paramTypes is used to specify the constructor
signature, while paramValues specifies the parameters
for the constructor.
public Object newObject(String module, String name,
Class[] paramTypes, Object[] paramValues, ClassLoader cl)
This method is almost the same as newObject(String module,
String name, Class[] paramTypes, Object[] paramValues),
except that it uses the given class loader cl to load
the implementation class.
These newObject() methods only specify the
extension point and how to initialize the implementation instance.
They have no knowledge about which implementation class will be
used for that extension point. It is the external configuration
file's responsibility to combine a specific implementation class
with the extension point.
public void putClassConfig(String moduleName, String
name, String className)
This method is used to change the combination of implementation
class and extension point at runtime. The parameter
className gives the full name of the implementation
class. The effect of this method will overwrite the setting in
the external configuration file.
public void refresh()
This method is used to refresh the combination of implementation
class and extension point at runtime. It can be called after the
external configuration file has changed.
Configuration File
The configuration file is where you specify implementation
classes for extension points. The default configuration file name
is classfactory.xml. If needed, this name can be changed
through the system property
org.jingle.patchexpert.configname. The configuration
file should be placed in the META-INF directory, which can
be accessed by class path or .jar file.
Below is a template of the configuration file.
<?xml version="1.0" encoding="UTF-8"?>
<application>
<module name="moduleName">
<extPoint className="class.full.name" name="pointName"/>
...
<extPoint .../>
</module>
...
<module ...>
...
</module>
<patch name="patchfile.xml"/>
...
<patch .../>
</application>
- The configuration file should have a root tag
application.
- There can be zero or more
module tags and
patch tags under the application
tag.
- The
module tag acts like a namespace. It contains zero
or more extPoint sub-elements.
- The
extPoint tag combines the extension point with
an implementation class name to define an extension point.
- The
patch tag is used to specify the patch
configuration file location, which is relative to the META-INF
directory. The patch configuration file has the same format and its
settings will overwrite the original settings.
There may be more than one accessible configuration file in an
application. When the ObjectFactory is initialized, it
loads all of the accessible configuration files, according to the
opposite order of the class path. Configuration file settings loaded
later will overwrite previous ones.
An Example of Using PatchExpert
Now let's apply PatchExpert to the previous example. The code
has only a few changes.
The TrianglePrint instance creation is an extension
point. Instead of creating an explicit TrianglePrint
instance, we create an implementation instance for the extension point
printer in the module triangle. The
implementation class should have an accessible constructor with an
int parameter.
public class TestMain {
public static void main(String[] args) {
Calculator c = Calculator.getInstance();
int num = c.calc(4);
//TrianglePrint printer = new TrianglePrint(num);
TrianglePrint printer = (TrianglePrint)
ObjectFactory.getInstance().newObject(
"triangle",
"printer",
new Class[] {Integer.TYPE},
new Object[] {new Integer(num)});
printer.print();
}
}
There's another extension point where the
Calculator singleton instance is created. We create an
implementation instance for the extension point cal in
the module calculator here. The implementation class
should have an accessible default constructor.
public class Calculator {
//static Calculator instance = new Calculator();
static Calculator instance =
(Calculator) ObjectFactory.getInstance().newObject(
"calculator", "cal");
public static Calculator getInstance() {
return instance;
}
protected Calculator() {
}
public int calc(int num) {
return num * 2;
}
}
Now you need a classfactory.xml file, under the META-INF
directory and inside of the target sample.jar file.
<?xml version="1.0" encoding="UTF-8"?>
<application>
<module name="triangle">
<extPoint className="TrianglePrint" name="printer"/>
</module>
<module name="calculator">
<extPoint className="Calculator" name="cal"/>
</module>
</application>
This configuration file connects the actual implementation
classes to the extension points.
The target sample.jar file is deployed on the customer
site. Now look at how the requirements change is handled when
PatchExpert is used.
- Software developer's task
The developer provides two new classes,
ModernCalculator and FancyTrianglePrint,
as before. There is no modification applied to the old source code.
This can reduce the risk of introducing new bugs when fixing old
ones.
- Platform engineer's task
The platform engineer builds the new Java files with the
original sample.jar and packages the new Java classes into
patch.jar with a classfactory.xml under the
META-INF directory.
<?xml version="1.0" encoding="UTF-8"?>
<application>
<module name="triangle">
<extPoint className="FancyTrianglePrint" name="printer"/>
</module>
<module name="calculator">
<extPoint className="ModernCalculator" name="cal"/>
</module>
</application>
This is a much lighter workload compared to rebuilding the whole
system. And there is no need to re-package the system. Only the
newly created patch.jar is shipped to the deployment
engineer.
- Deployment engineer's task
The deployment engineer just drops the patch.jar file into the
library directory and makes sure that it appears before
sample.jar in the class path.
That's all. Now the patch works. It is much easier than before,
isn't it?
Pros and Cons Compared to an AOP Solution
Aspect-Oriented
Programming (AOP) is another commonly used technique to provide
extensions and patches to an existing application. The AOP approach
and PatchExpert both try to avoid touching the old system in order
to reduce the upgrade risk. But at the byte-code level, the AOP
compiler will insert some byte code into old class files while
compiling the new aspect. Compared to the AOP approach,
PatchExpert has the following pros and cons:
Pros:
- AOP's byte-code-level change may require the rebuilding and
repackaging of a whole module or system. And the deployment
engineer still has many things to do: backing up old files and
deleting, adding, or replacing files according to the patch
installation guide. A PatchExpert approach is free of this
workload.
- With the help of PatchExpert, the target application can switch
implementations via an edit of the external configuration file. In some
cases, it can even switch the implementation at runtime.
- With PatchExpert, it is very easy to discard the extension or
patch and roll back to the original version. No build is required;
just delete the add-on patch package.
Cons:
- For PatchExpert to work, it has to be part of the application
framework. This means it should be integrated at the beginning of
the software design process. You cannot use PatchExpert to apply a
patch to an application that does not already use PatchExpert. In
this case, an AOP solution is much more flexible, since an AOP
approach can be used regardless of whether the original application
uses AOP or not.
- PatchExpert asks the architect to think carefully in the design
phase about where extension points should go. AOP has no such
requirement; it can define arbitrary "point cuts" as its extension
points.
Conclusion
PatchExpert can simplify the task of applying extensions or patches
to an existing application. It just adds the patch package without
touching the original system. This reduces the risk of introducing
new bugs when fixing old ones. In the meantime, it makes the
rolling back easier: it's no more complicated than deleting the add-on
patch package.
Of course, PatchExpert can be used in other domains, such as
software integration testing. For example, when the integration
test depends on external services that are not currently
available, it is very easy to continue the test by adding mock
services into the test environment without touching the source
code. When the external services are available, just remove the
mock services and test again.
Reference
Lu Jian is a senior Java architect/developer with four years of Java development experience. |