The Mule 3.6 release brought a comprehensive and very welcome refresh of XML capabilities, including cutting-edge XSLT support via the Saxon 9.6 HE library. Unfortunately one feature lost in this overhaul was Saxon’s easy mechanism for using Java methods as XSLT extension functions. Here’s how we made it easier to use Java extension functions that work with Mule’s new XML stack.
Extension functions: The Old Way
Previously, the Saxon XSLT library used by Mule supported an extension mechanism called Reflexive Extension Functions. This mechanism used a combination of reflection, hard-wired conversion rules and naming conventions to map Java method signatures onto their XSLT equivalent.
This approach was beautifully simple. Say for example you wanted to calculate the square root of a nuumber. You could achieve this by directly invoking the
sqrt() method on the
java.lang.Math class. e.g. 1
<xsl:value-of select="math:sqrt($arg)" xmlns:math="java:java.lang.Math"/>
The key to the XSLT / Java interoperability is in declaring an XML namespace (in this case ‘math’) to a fully-qualified Java class name using the
java: prefix. Saxon would even nicely convert Java-friendly
camelCaseMethodNames into XSLT-friendly
dash-separated-function-names for you.
As convenient as this approach was, it had weaknesses when it came to clarity, performance and security. Reflexive extension functions are kept for legacy compatibility in the paid, proprietary Professional and Enterprise editions of Saxon but not in the Open Source ‘Home Edition’ used in Mule.
Extension functions: The New Way
Saxon’s new approach is called Integrated Extension Functions. Here developers have to write classes that explicitly implement Saxon extension APIs and then programmatically register each function with the XSLT engine before use. This approach resolves the weaknesses of the reflexive approach but at the expense of more code and requiring expert knowledge of Saxon internal classes (not just the standard JAXP APIs).
Here’s an example of a custom function for generating a random number:
Registering your extension functions
Once we have our Saxon extension function written, we need to register it with the XSLT transformer engine before executing any transformations. Proprietary versions of Saxon allow you to do this via an XML configuration file. In the free version used by Mule we are limited to programmatic configuration.
To hook in to the XSLT engine’s lifecycle we can sub-class
net.sf.saxon.jaxp.SaxonTransformerFactory (this is the Saxon library’s implementation of the standard JAXP Transformer interface). This subclass injects instances of extension function classes into the underlying Saxon
Processor object (which unfortunately due to being declared ‘private’ can only be accessed via Java reflection).
Discovery of extension classes to register can be achieved through the Java ServiceLoader mechanism. This plays nicely with Mule’s classloader behaviour and lets extension functions come from any library on the classpath.
Here’s an example of our customised TransformerFactory:
Configuring Mule’s XSLT transformer
The last step is to use configure Mule’s XSLT transformer to use our new JAXP TransformerFactory class. This is easily accomplished through the
transformerFactoryClass attribute on the
<mulexml:xslt-transformer> tag in the Mule flow:
<mulexml:xslt-transformer xsl-file="xslt/CreateUserAnalyticsRequest.xsl" transformerFactoryClass="com.sample.xslt.TransformerFactory"/>
The extension functions will get registered on each new XSLT Transformer instance. That means for pooled transforms (like XSLT) the same functions will get registered several times. This is a small drag on initialisation performance but good for thread safety as transformer instances in the pool get re-used.
Once again Mule’s Open Source core pays off. When a critical feature stopped working after an upgrade we were able to dive under the hood, find the issue and code a re-usable and extendible solution.