CAMEL-9534: XsltComponent: fix support for Saxon-B and Saxon >= 9.6

Using reflection and Saxon-specific APIs to configure Saxon's MessageWarner
class is flaky - the current implementation introduced with CAMEL-7753 only
works for Saxon-HE 9.3 - 9.5. Using XsltComponent with other Saxon versions
fails with IllegalStateException "Error pre-loading Saxon classes...".

Use TransformerFactory.setAttribute(FeatureKeys.MESSAGE_EMITTER_CLASS) to
install the MessageWarner class. Only log a warning on failure.

Make lazy-initialized factory variables volatile so that they only become
visible to other threads after the factory is fully initialized.

Note: The MessageWarner class in Saxon 9.6+ redirects <xsl:message> output
to the ErrorListener of the TransformerFactory rather than the Transformer
instance (see https://saxonica.plan.io/issues/2581). This cannot be easily
fixed on the Camel side, as setting a Transformer-specific ErrorListener on
the TransformerFactory would break multithreaded routes.

Signed-off-by: Karsten Blees <karsten.bl...@dcon.de>


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/3ac8d9fd
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/3ac8d9fd
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/3ac8d9fd

Branch: refs/heads/master
Commit: 3ac8d9fd74ec00fa5a969eb790e1df496073d92b
Parents: fe0e85a
Author: Karsten Blees <bl...@dcon.de>
Authored: Mon Jan 25 22:27:16 2016 +0100
Committer: Daniel Kulp <dk...@apache.org>
Committed: Mon Feb 1 16:06:24 2016 -0500

----------------------------------------------------------------------
 .../apache/camel/builder/xml/XsltBuilder.java   | 62 +-------------------
 .../camel/component/xslt/XsltEndpoint.java      |  5 --
 .../camel/converter/jaxp/XmlConverter.java      | 46 ++++++++++++++-
 .../util/toolbox/XsltAggregationStrategy.java   |  1 -
 .../camel/builder/xml/XsltBuilderTest.java      | 38 ------------
 5 files changed, 47 insertions(+), 105 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/3ac8d9fd/camel-core/src/main/java/org/apache/camel/builder/xml/XsltBuilder.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/builder/xml/XsltBuilder.java 
b/camel-core/src/main/java/org/apache/camel/builder/xml/XsltBuilder.java
index d9d65f4..d5e73da 100644
--- a/camel-core/src/main/java/org/apache/camel/builder/xml/XsltBuilder.java
+++ b/camel-core/src/main/java/org/apache/camel/builder/xml/XsltBuilder.java
@@ -19,13 +19,13 @@ package org.apache.camel.builder.xml;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
+
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.ErrorListener;
 import javax.xml.transform.Result;
@@ -42,8 +42,6 @@ import javax.xml.transform.stream.StreamSource;
 
 import org.w3c.dom.Node;
 
-import org.apache.camel.CamelContext;
-import org.apache.camel.CamelContextAware;
 import org.apache.camel.Exchange;
 import org.apache.camel.ExpectedBodyTypeException;
 import org.apache.camel.Message;
@@ -52,12 +50,10 @@ import org.apache.camel.RuntimeTransformException;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.converter.jaxp.StAX2SAXSource;
 import org.apache.camel.converter.jaxp.XmlConverter;
-import org.apache.camel.support.ServiceSupport;
 import org.apache.camel.support.SynchronizationAdapter;
 import org.apache.camel.util.ExchangeHelper;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
-import org.apache.camel.util.ObjectHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -76,9 +72,8 @@ import static org.apache.camel.util.ObjectHelper.notNull;
  *
  * @version 
  */
-public class XsltBuilder extends ServiceSupport implements Processor, 
CamelContextAware {
+public class XsltBuilder implements Processor {
     private static final Logger LOG = 
LoggerFactory.getLogger(XsltBuilder.class);
-    private CamelContext camelContext;
     private Map<String, Object> parameters = new HashMap<String, Object>();
     private XmlConverter converter = new XmlConverter();
     private Templates template;
@@ -89,9 +84,6 @@ public class XsltBuilder extends ServiceSupport implements 
Processor, CamelConte
     private boolean deleteOutputFile;
     private ErrorListener errorListener;
     private boolean allowStAX = true;
-    private volatile Method setMessageEmitterMethod;
-    private volatile Class<?> saxonReceiverClass;
-    private volatile Class<?> saxonWarnerClass;
 
     public XsltBuilder() {
     }
@@ -157,10 +149,6 @@ public class XsltBuilder extends ServiceSupport implements 
Processor, CamelConte
         }
     }
     
-    boolean isSaxonTransformer(Transformer transformer) {
-        return transformer.getClass().getName().startsWith("net.sf.saxon");
-    }
-
     // Builder methods
     // 
-------------------------------------------------------------------------
 
@@ -448,17 +436,7 @@ public class XsltBuilder extends ServiceSupport implements 
Processor, CamelConte
     }
 
     protected Transformer createTransformer() throws Exception {
-        Transformer t = getTemplate().newTransformer();
-
-        // special for saxon as we need to call setMessageEmitter on the 
transformer to hook from saxon to the JAXP errorListener
-        // so we can get notified if any errors happen during transformation
-        // see details at: 
https://stackoverflow.com/questions/4695489/capture-xslmessage-output-in-java
-        if (isSaxonTransformer(t) && setMessageEmitterMethod != null) {
-            Object warner = 
getCamelContext().getInjector().newInstance(saxonWarnerClass);
-            setMessageEmitterMethod.invoke(t, warner);
-        }
-
-        return t;
+        return getTemplate().newTransformer();
     }
 
     /**
@@ -589,40 +567,6 @@ public class XsltBuilder extends ServiceSupport implements 
Processor, CamelConte
         }
     }
 
-    public CamelContext getCamelContext() {
-        return camelContext;
-    }
-
-    public void setCamelContext(CamelContext camelContext) {
-        this.camelContext = camelContext;
-    }
-
-    @Override
-    protected void doStart() throws Exception {
-        ObjectHelper.notNull(camelContext, "camelContext", this);
-
-        // create a transformer to see if its saxon, as we then need to do 
some initial preparation
-        Transformer t = getTemplate().newTransformer();
-
-        if (isSaxonTransformer(t)) {
-            // pre-load saxon classes as we need to call the setMessageEmitter 
on the transformer to hook saxon to use the JAXP
-            // error listener, so we can capture errors and xsl:message 
outputs which end users may define in the xslt files
-            try {
-                saxonReceiverClass = 
getCamelContext().getClassResolver().resolveMandatoryClass("net.sf.saxon.event.Receiver");
-                saxonWarnerClass = 
getCamelContext().getClassResolver().resolveMandatoryClass("net.sf.saxon.serialize.MessageWarner");
-                setMessageEmitterMethod = 
t.getClass().getMethod("setMessageEmitter", saxonReceiverClass);
-            } catch (Exception e) {
-                throw new IllegalStateException("Error pre-loading Saxon 
classes. Make sure you have saxon on the classpath,"
-                        + " and the classloader can load the following two 
classes: net.sf.saxon.event.Receiver, net.sf.saxon.serialize.MessageWarner.", 
e);
-            }
-        }
-    }
-
-    @Override
-    protected void doStop() throws Exception {
-        // noop
-    }
-
     private static final class XsltBuilderOnCompletion extends 
SynchronizationAdapter {
         private final String fileName;
 

http://git-wip-us.apache.org/repos/asf/camel/blob/3ac8d9fd/camel-core/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java 
b/camel-core/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java
index fc0bad6..4588b5c 100644
--- a/camel-core/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java
+++ b/camel-core/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java
@@ -390,13 +390,8 @@ public class XsltEndpoint extends ProcessorEndpoint {
         // must load resource first which sets a template and do a stylesheet 
compilation to catch errors early
         loadResource(resourceUri);
 
-        // and then inject camel context and start service
-        xslt.setCamelContext(getCamelContext());
-
         // the processor is the xslt builder
         setProcessor(xslt);
-
-        ServiceHelper.startService(xslt);
     }
 
     protected void configureOutput(XsltBuilder xslt, String output) throws 
Exception {

http://git-wip-us.apache.org/repos/asf/camel/blob/3ac8d9fd/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java 
b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java
index 3079e7c..d29f643 100644
--- a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java
+++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XmlConverter.java
@@ -89,8 +89,8 @@ public class XmlConverter {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(XmlConverter.class);
 
-    private DocumentBuilderFactory documentBuilderFactory;
-    private TransformerFactory transformerFactory;
+    private volatile DocumentBuilderFactory documentBuilderFactory;
+    private volatile TransformerFactory transformerFactory;
 
     public XmlConverter() {
     }
@@ -1054,6 +1054,8 @@ public class XmlConverter {
     }
 
     public void setTransformerFactory(TransformerFactory transformerFactory) {
+        if (transformerFactory != null)
+            configureSaxonTransformerFactory(transformerFactory);
         this.transformerFactory = transformerFactory;
     }
 
@@ -1163,7 +1165,47 @@ public class XmlConverter {
             LOG.warn("TransformerFactory doesn't support the feature {} with 
value {}, due to {}.", new 
Object[]{javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, "true", e});
         }
         factory.setErrorListener(new XmlErrorListener());
+        configureSaxonTransformerFactory(factory);
         return factory;
     }
 
+    /**
+     * Make a Saxon TransformerFactory more JAXP compliant by configuring it to
+     * send &lt;xsl:message&gt; output to the ErrorListener.
+     *
+     * @param factory
+     *            the TransformerFactory
+     */
+    public void configureSaxonTransformerFactory(TransformerFactory factory) {
+        // check whether we have a Saxon TransformerFactory ("net.sf.saxon" 
for open source editions (HE / B)
+        // and "com.saxonica" for commercial editions (PE / EE / SA))
+        Class<?> factoryClass = factory.getClass();
+        if (factoryClass.getName().startsWith("net.sf.saxon")
+                || factoryClass.getName().startsWith("com.saxonica")) {
+
+            // just in case there are multiple class loaders with different 
Saxon versions, use the
+            // TransformerFactory's class loader to find Saxon support classes
+            ClassLoader loader = factoryClass.getClassLoader();
+
+            // try to find Saxon's MessageWarner class that redirects 
<xsl:message> to the ErrorListener
+            Class<?> messageWarner = null;
+            try {
+                // Saxon >= 9.3
+                messageWarner = 
loader.loadClass("net.sf.saxon.serialize.MessageWarner");
+            } catch (ClassNotFoundException cnfe) {
+                try {
+                    // Saxon < 9.3 (including Saxon-B / -SA)
+                    messageWarner = 
loader.loadClass("net.sf.saxon.event.MessageWarner");
+                } catch (ClassNotFoundException cnfe2) {
+                    LOG.warn("Error loading Saxon's 
net.sf.saxon.serialize.MessageWarner class from the classpath!"
+                            + " <xsl:message> output will not be redirected to 
the ErrorListener!");
+                }
+            }
+
+            if (messageWarner != null) {
+                // set net.sf.saxon.FeatureKeys.MESSAGE_EMITTER_CLASS
+                
factory.setAttribute("http://saxon.sf.net/feature/messageEmitterClass";, 
messageWarner.getName());
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/3ac8d9fd/camel-core/src/main/java/org/apache/camel/util/toolbox/XsltAggregationStrategy.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/util/toolbox/XsltAggregationStrategy.java
 
b/camel-core/src/main/java/org/apache/camel/util/toolbox/XsltAggregationStrategy.java
index fbadfae..31b4f7a 100644
--- 
a/camel-core/src/main/java/org/apache/camel/util/toolbox/XsltAggregationStrategy.java
+++ 
b/camel-core/src/main/java/org/apache/camel/util/toolbox/XsltAggregationStrategy.java
@@ -161,7 +161,6 @@ public class XsltAggregationStrategy implements 
AggregationStrategy {
         xslt.setFailOnNullBody(true);
         xslt.transformerCacheSize(0);
         xslt.setAllowStAX(true);
-        xslt.setCamelContext(context);
 
         configureOutput(xslt, output.name());
         loadResource(xslFile);

http://git-wip-us.apache.org/repos/asf/camel/blob/3ac8d9fd/camel-core/src/test/java/org/apache/camel/builder/xml/XsltBuilderTest.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/test/java/org/apache/camel/builder/xml/XsltBuilderTest.java 
b/camel-core/src/test/java/org/apache/camel/builder/xml/XsltBuilderTest.java
index 04468c7..aa02a90 100644
--- a/camel-core/src/test/java/org/apache/camel/builder/xml/XsltBuilderTest.java
+++ b/camel-core/src/test/java/org/apache/camel/builder/xml/XsltBuilderTest.java
@@ -51,8 +51,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -67,8 +65,6 @@ public class XsltBuilderTest extends ContextTestSupport {
 
         XsltBuilder builder = new XsltBuilder();
         builder.setTransformerURL(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -82,8 +78,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         File styleSheet = new 
File("src/test/resources/org/apache/camel/builder/xml/example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -98,8 +92,6 @@ public class XsltBuilderTest extends ContextTestSupport {
 
         XsltBuilder builder = new XsltBuilder();
         builder.setTransformerFile(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -113,8 +105,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         File styleSheet = new 
File("src/test/resources/org/apache/camel/builder/xml/example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(new 
FileInputStream(styleSheet));
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -129,8 +119,6 @@ public class XsltBuilderTest extends ContextTestSupport {
 
         XsltBuilder builder = new XsltBuilder();
         builder.setTransformerInputStream(new FileInputStream(styleSheet));
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -145,8 +133,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         Source styleSheet = new SAXSource(new InputSource(new 
FileInputStream(file)));
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -164,8 +150,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         Templates styleSheet = 
converter.getTransformerFactory().newTemplates(source);
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -179,8 +163,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet).outputString();
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -195,8 +177,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet).outputBytes();
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -211,8 +191,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet).outputDOM();
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -227,8 +205,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet).outputFile();
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -248,8 +224,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = 
XsltBuilder.xslt(styleSheet).outputFile().deleteOutputFile();
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
@@ -280,9 +254,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         builder.setConverter(converter);
         assertSame(converter, builder.getConverter());
 
-        builder.setCamelContext(context);
-        builder.start();
-
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
 
@@ -299,9 +270,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         builder.outputBytes();
         assertIsInstanceOf(StreamResultHandlerFactory.class, 
builder.getResultHandlerFactory());
 
-        builder.setCamelContext(context);
-        builder.start();
-
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody("<hello>world!</hello>");
 
@@ -314,8 +282,6 @@ public class XsltBuilderTest extends ContextTestSupport {
         URL styleSheet = getClass().getResource("example.xsl");
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody(null);
@@ -333,8 +299,6 @@ public class XsltBuilderTest extends ContextTestSupport {
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
         builder.setFailOnNullBody(true);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody(null);
@@ -352,8 +316,6 @@ public class XsltBuilderTest extends ContextTestSupport {
 
         XsltBuilder builder = XsltBuilder.xslt(styleSheet);
         builder.setFailOnNullBody(false);
-        builder.setCamelContext(context);
-        builder.start();
 
         Exchange exchange = new DefaultExchange(context);
         exchange.getIn().setBody(null);

Reply via email to