18 March, 2014

Passing parameters for XSL, using Builder Design Pattern

It is being a while since I posted something here. This is an older post that I found as a draft. :) Maybe someone will find it useful. 

Recently, new requirement came for the project that I was working on. In short: I had to pass another parameter to updated XSL transformation files. There were already certain parameter passing and I needed to add one more. Adding one more line can't hurt a bit, BUT passing value to method that calls XSL transformation could turn to a nightmare. Luckily, Builder Design pattern solved the problem like a magic :)
Here is the scenario:
Long time ago, when project was still in its birth phase, I wrote a method that handles the execution of XSL transformation from Java by passing XSL path, source XML and some parameters to it:

public static String XSLTransform(String xsltPath, String sourceXML, String arg1, String arg2){
  try {
        Document document;
        File stylesheet = new File(xsltPath);

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(false);
        factory.setFeature("http://xml.org/sax/features/namespaces", false);
        factory.setFeature("http://xml.org/sax/features/validation", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        
        DocumentBuilder builder = factory.newDocumentBuilder();
        ByteArrayInputStream ba = new ByteArrayInputStream(sourceXML.getBytes("UTF-8"));
        document = builder.parse(ba);

        // Use a Transformer for output
        TransformerFactory tFactory = TransformerFactory.newInstance();
        StreamSource stylesource = new StreamSource(stylesheet);
        Transformer transformer = tFactory.newTransformer(stylesource);
        transformer.setParameter("argument1", arg1);
        transformer.setParameter("argument2", arg2);
            
        DOMSource source = new DOMSource(document);
        StringWriter st = new StringWriter();
            
        StreamResult result = new StreamResult(st);
        transformer.transform(source, result);
        return st.toString(); 
        } catch (Exception e) {
            // For the sake of simplicity, I removed MANY catch clauses 
            e.printStackTrace();
            return "";
        }
    }
Above snippet is example how you should call Xalan XSL transformator against any XSL file and source XML. Notice that I pass file system path to XSL (not content of XSL) and raw XML content as string, but that is not important.
Year after, we revisited code for XSL once more to add another parameter to transformation. I got updated versions of XSL files that included another parameter that will be generated through Java and later, passed to above given code. Ok, no big deal. Another argument will be added to XSLTransform method and one more line of code inserted. Even better, I placed all arguments (except XSLPath and source XML) in hash map to handle further introduction of newer parameters:
static public String XSLTransform(String xsltPath, String sourceXML, Map<String, Object> parameters){
  try {
        Document document;
        File stylesheet = new File(xsltPath);

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(false);
        factory.setFeature("http://xml.org/sax/features/namespaces", false);
        factory.setFeature("http://xml.org/sax/features/validation", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        
        DocumentBuilder builder = factory.newDocumentBuilder();
        ByteArrayInputStream ba = new ByteArrayInputStream(sourceXML.getBytes("UTF-8"));
        document = builder.parse(ba);

        // Use a Transformer for output
        TransformerFactory tFactory = TransformerFactory.newInstance();
        StreamSource stylesource = new StreamSource(stylesheet);
        Transformer transformer = tFactory.newTransformer(stylesource);
        // Set parameters for transformer
        if (parameters != null)
         for (String item : parameters.keySet())
          if (parameters.containsKey(item))
           transformer.setParameter(item, parameters.get(item));
            
        DOMSource source = new DOMSource(document);
        StringWriter st = new StringWriter();
            
        StreamResult result = new StreamResult(st);
        transformer.transform(source, result);
        return st.toString(); 
        } catch (Exception e) {
            // For the sake of simplicity, I removed MANY catch clauses 
            e.printStackTrace();
            return "";
        }
    }

There goes the part about adding as much as you like input parameters to XSL transformation.
However, I faced very serious design crisis, as this change would cause project to enter a long refactoring phase, because all calls to the above modified method is exchanged. Not to mention the need to manually create map argument before EVERY call to the XSLTransform method.
First thought that crossed my mind are default method arguments. Unfortunately, Java is not mighty C, since it doesn't support that. Luckily I found two interesting threads (thread1, thread2) on Stackoverflow where they suggested using Builder Pattern. That was exactly what we needed!
import java.util.HashMap;
import java.util.Map;

public class XSLTransformBuilder {
 protected String _xsltPath;
 protected String _sourceXML;
 protected String _arg1 = null;
 protected String _arg2 = null;
 protected String _arg3 = null;
 
    // These two values are mandatory
 public XSLTransformBuilder(String xsltPath, String sourceXML){
  this._xsltPath = xsltPath;
  this._sourceXML = sourceXML;
 }
 
 public String buildXSLTransform(){
  Map<String, Object> parameters = new HashMap<String, Object>();
  
  if (this._arg1 != null)
   parameters.put("arg1", this._arg1);
  if (this._arg2 != null)
   parameters.put("arg2", this._arg2);
  if (this._arg3 != null)
   parameters.put("arg3", this._arg3);
  
  return XSLTransform.XSLTransform(this._xsltPath, this._sourceXML, parameters);
 }
 
 public XSLTransformBuilder arg1(String arg1){
  this._arg1 = arg1;
  return this;
 }

 public XSLTransformBuilder arg2(String arg2){
  this._arg2 = arg2;
  return this;
 }
 
 public XSLTransformBuilder arg3(String arg3){
  this._arg3 = arg3;
  return this;
 }
}
Very simple: You just create XSLTransformBuilder object by passing basic two arguments to its constructor. Then you use nested method technique to add parameters that you want in any order. Nesting is possible, since parameter "setters" return reference to XSLTransformBuilder. Finally, you finish nesting with buildXSLTransform that calls newer XSLTransform method and returns transformed XML. Here is an example:
String transformedXML = new XSLTransformBuilder()
        .arg1("demonstration")
        .arg2("of chained")
        .arg3("nested parameters")
        .buildXSLTransform();
With this solution, old XSLTransform can stay for a while until all calls to it are rewritten. Plus, if new parameters emerge, only XSLTransformBuilder will require change. Also, I placed @deprecated for old XSLTransform method to prevent other programmers from "accidentally" using the old code. Hopefully, they will adopt to robust Builder system or perish. :D

Note about Design Patterns: They are extremely useful design concept and can save you reinventing the wheel. After you get into Pattern world, you will realize that most of design problems are already solved, greatly reducing code design and earning you better salary and respect :) This is a "must know" for every software developer that has more than a year or two of working experience.
Design Patterns originated from this book: Design Patterns: Elements of Reusable Object-Oriented Software and became very popular in software engineering. Unfortunately, book is too straight-to-the-point type and is best used as reference.
If you want to discover Design Patterns easy-way then this book is perfect: Head First: Design Patterns. It is my first book about pattern world and, by most people, the best entry-level book about Design Patterns (some study says that it is sold in more copies then the original book mentioned above). Design Patterns might take some time to adopt to this kind of thinking, so don't rush it. The better you comprehend lessons in this book, the better software engineer you will be. ;)

No comments:

Post a Comment