Using PatchExpert to Extend Your Code More Easily Blog

Version 2

    {cs.r.title}



              
                                 

    Contents
    Problem
    Introducing Pat chExpert
       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 aCalculator class to calculate the level of triangle according to the given integer and asks TrianglePrintto print out the triangle.

    Here's the source code for a basic implementation.Calculator uses the Singletonpattern, 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 aCalculator 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 theCalculator algorithm and TrianglePrintprint 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 ofCalculator. 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 subclassesTrianglePrint. 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 ofTrianglePrint.

       
      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 ofModernCalculator instead ofCalculator.
       
      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.

    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 usescl 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 parameterparamTypes 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 parameterclassName 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 propertyorg.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 tagapplication.
    • There can be zero or more module tags andpatch tags under the applicationtag.
    • 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 theMETA-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 TrianglePrintinstance, we create an implementation instance for the extension point printer in the module triangle. The implementation class should have an accessible constructor with anint 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 theCalculator 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 theMETA-INF directory and inside of the targetsample.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 intopatch.jar with a classfactory.xml under theMETA-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 beforesample.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

      
    http://today.java.net/im/a.gif