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 <xsl:message> 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);