Skip to main content

Using PatchExpert to Extend Your Code More Easily

{cs.r.title}









Contents
Problem
Introducing PatchExpert
   Framework
   PatchExpert API
   Configuration File
An Example of Using PatchExpert
Pros and Cons Compared to an AOP Solution
Conclusion
Reference

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.

[prettify]public class Calculator {
    static Calculator instance = new Calculator();

    public static Calculator getInstance() {
        return instance;
    }

    protected Calculator() {
    }

    public int calc(int num) {
        return num * 2;
    }
}
[/prettify]

TrianglePrint prints out the triangle line by line,
according to the given level.

[prettify]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;
    }
}
[/prettify]

Finally, TestMain exercises the code by getting a
Calculator instance, calling calc(), and
sending the result on to TrianglePrint.

[prettify]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();
    }
}
[/prettify]

The application will print out an ASCII art triangle like
this:

[prettify]*
**
***
****
*****
******
*******
********
[/prettify]

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.

    [prettify]public class ModernCalculator
                        extends Calculator {
       public int calc(int num) {
           return num + 1;
       }
    }
    
    [/prettify]

    A new FancyTrianglePrint subclasses
    TrianglePrint. It overrides getLine(int)
    to generate a new string for the given line.

    [prettify]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();
       }
    }
    
    [/prettify]

    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.

    [prettify]public class TestMain {
       public static void main(String[] args) {
           Calculator c = Calculator.getInstance();
           int num = c.calc(4);
    <b>//TrianglePrint printer = new TrianglePrint(num);
           TrianglePrint printer = new FancyTrianglePrint(num);</b>
           printer.print();
       }
    }
    
    </pre>
    
    Since <code>Calculator
    uses a Singleton pattern, it should be modified to use a singleton instance of ModernCalculator instead of Calculator.
    public class Calculator {
    <b>//static Calculator instance = new Calculator();
       static Calculator instance = new ModernCalculator();</b>
       ...
    }
    
    [/prettify]

  • 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.

The relationship among elements
Figure 1. The relationship among elements

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.

[prettify]&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;application&gt;
&lt;module name=&quot;moduleName&quot;&gt;
  &lt;extPoint className=&quot;class.full.name&quot; name=&quot;pointName&quot;/&gt;
  ...
  &lt;extPoint .../&gt;
&lt;/module&gt;
...
&lt;module ...&gt;
...
&lt;/module&gt;

&lt;patch name=&quot;patchfile.xml&quot;/&gt;
...
&lt;patch .../&gt;
&lt;/application&gt;
[/prettify]

  • 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.

[prettify]public class TestMain {
    public static void main(String[] args) {
        Calculator c = Calculator.getInstance();
        int num = c.calc(4);
<b>//TrianglePrint printer = new TrianglePrint(num);
        TrianglePrint printer = (TrianglePrint)
                ObjectFactory.getInstance().newObject(
                            &quot;triangle&quot;,
                            &quot;printer&quot;,
                            new Class[] {Integer.TYPE},
                            new Object[] {new Integer(num)});</b>
        printer.print();
    }
}
[/prettify]

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.

[prettify]public class Calculator {
<b>//static Calculator instance = new Calculator();
    static Calculator instance =
       (Calculator) ObjectFactory.getInstance().newObject(
                                      &quot;calculator&quot;, &quot;cal&quot;);</b>

    public static Calculator getInstance() {
        return instance;
    }

    protected Calculator() {
    }

    public int calc(int num) {
        return num * 2;
    }
}
[/prettify]

Now you need a classfactory.xml file, under the META-INF
directory and inside of the target sample.jar file.

[prettify] &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  &lt;application&gt;
    &lt;module name=&quot;triangle&quot;&gt;
      &lt;extPoint className=&quot;TrianglePrint&quot; name=&quot;printer&quot;/&gt;
    &lt;/module&gt;
    &lt;module name=&quot;calculator&quot;&gt;
      &lt;extPoint className=&quot;Calculator&quot; name=&quot;cal&quot;/&gt;
    &lt;/module&gt;
  &lt;/application&gt;
[/prettify]

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.

    [prettify]&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
    &lt;application&gt;
     &lt;module name=&quot;triangle&quot;&gt;
       &lt;extPoint className=&quot;FancyTrianglePrint&quot; name=&quot;printer&quot;/&gt;
     &lt;/module&gt;
     &lt;module name=&quot;calculator&quot;&gt;
       &lt;extPoint className=&quot;ModernCalculator&quot; name=&quot;cal&quot;/&gt;
     &lt;/module&gt;
    &lt;/application&gt;
    
    [/prettify]

    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

width="1" height="1" border="0" alt=" " />
Related Topics >> Programming   |   Testing   |