CAMEL-7753: xslt component - Store warning/errors etc as exchange properties so end users can get hold of those. Now works with camel-saxon also.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/1b38aad3 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/1b38aad3 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/1b38aad3 Branch: refs/heads/master Commit: 1b38aad342529e98b5f76e11029ec50b1768949f Parents: 68d0f14 Author: Claus Ibsen <davscl...@apache.org> Authored: Wed Aug 27 13:52:58 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Wed Aug 27 13:52:58 2014 +0200 ---------------------------------------------------------------------- camel-core/pom.xml | 2 + .../apache/camel/builder/xml/XsltBuilder.java | 92 ++++++++++++++++---- .../camel/component/xslt/XsltEndpoint.java | 12 ++- .../camel-saxon/src/test/data/terminate.xml | 36 ++++++++ .../xslt/SaxonXsltMessageTerminateTest.java | 60 +++++++++++++ .../apache/camel/component/xslt/terminate.xsl | 34 ++++++++ 6 files changed, 217 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/1b38aad3/camel-core/pom.xml ---------------------------------------------------------------------- diff --git a/camel-core/pom.xml b/camel-core/pom.xml index 98f6ad8..4c13e0b 100644 --- a/camel-core/pom.xml +++ b/camel-core/pom.xml @@ -49,6 +49,8 @@ javax.xml.bind.annotation.adapters;resolution:=optional, javax.xml.stream;resolution:=optional, javax.xml.transform.stax;resolution:=optional, + net.sf.saxon.event;resolution:=optional, + net.sf.saxon.serialize;resolution:=optional, * </camel.osgi.import> <camel.osgi.export.service> http://git-wip-us.apache.org/repos/asf/camel/blob/1b38aad3/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 3803def..8ccc9b6 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.stream.XMLStreamReader; import javax.xml.transform.ErrorListener; @@ -41,8 +41,8 @@ import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stax.StAXSource; 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; @@ -51,13 +51,15 @@ import org.apache.camel.RuntimeTransformException; import org.apache.camel.TypeConverter; import org.apache.camel.converter.jaxp.StaxSource; import org.apache.camel.converter.jaxp.XmlConverter; -import org.apache.camel.converter.jaxp.XmlErrorListener; +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; +import org.w3c.dom.Node; import static org.apache.camel.util.ObjectHelper.notNull; @@ -70,8 +72,9 @@ import static org.apache.camel.util.ObjectHelper.notNull; * * @version */ -public class XsltBuilder implements Processor { +public class XsltBuilder extends ServiceSupport implements Processor, CamelContextAware { 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; @@ -80,8 +83,11 @@ public class XsltBuilder implements Processor { private boolean failOnNullBody = true; private URIResolver uriResolver; private boolean deleteOutputFile; - private ErrorListener errorListener = new XsltErrorListener(); + private ErrorListener errorListener; private boolean allowStAX = true; + private volatile Method setMessageEmitterMethod; + private volatile Class<?> saxonReceiverClass; + private volatile Class<?> saxonWarnerClass; public XsltBuilder() { } @@ -106,7 +112,7 @@ public class XsltBuilder implements Processor { Transformer transformer = getTransformer(); configureTransformer(transformer, exchange); - transformer.setErrorListener(new DefaultTransformErrorHandler(exchange)); + ResultHandler resultHandler = resultHandlerFactory.createResult(exchange); Result result = resultHandler.getResult(); exchange.setProperty("isXalanTransformer", isXalanTransformer(transformer)); @@ -141,6 +147,10 @@ public class XsltBuilder implements Processor { return transformer.getClass().getName().startsWith("org.apache.xalan.transformer"); } + boolean isSaxonTransformer(Transformer transformer) { + return transformer.getClass().getName().startsWith("net.sf.saxon"); + } + // Builder methods // ------------------------------------------------------------------------- @@ -329,7 +339,8 @@ public class XsltBuilder implements Processor { */ public void setTransformerSource(Source source) throws TransformerConfigurationException { TransformerFactory factory = converter.getTransformerFactory(); - factory.setErrorListener(errorListener); + // use a logger error listener so users can see from the logs what the error may be + factory.setErrorListener(new XsltErrorListener()); if (getUriResolver() != null) { factory.setURIResolver(getUriResolver()); } @@ -407,22 +418,35 @@ public class XsltBuilder implements Processor { private void releaseTransformer(Transformer transformer) { if (transformers != null) { transformer.reset(); - transformer.setErrorListener(errorListener); transformers.offer(transformer); } } - private Transformer getTransformer() throws TransformerConfigurationException { + private Transformer getTransformer() throws Exception { Transformer t = null; if (transformers != null) { t = transformers.poll(); } if (t == null) { - t = getTemplate().newTransformer(); + t = createTransformer(); } return t; } + 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; + } + /** * Checks whether we need an {@link InputStream} to access the message body. * <p/> @@ -525,19 +549,23 @@ public class XsltBuilder implements Processor { /** * Configures the transformer with exchange specific parameters */ - protected void configureTransformer(Transformer transformer, Exchange exchange) { + protected void configureTransformer(Transformer transformer, Exchange exchange) throws Exception { if (uriResolver == null) { uriResolver = new XsltUriResolver(exchange.getContext().getClassResolver(), null); } transformer.setURIResolver(uriResolver); - transformer.setErrorListener(new XmlErrorListener()); + if (errorListener == null) { + // set our error listener so we can capture errors and report them back on the exchange + transformer.setErrorListener(new DefaultTransformErrorHandler(exchange)); + } else { + // use custom error listener + transformer.setErrorListener(errorListener); + } transformer.clearParameters(); - addParameters(transformer, exchange.getProperties()); addParameters(transformer, exchange.getIn().getHeaders()); addParameters(transformer, getParameters()); - transformer.setParameter("exchange", exchange); transformer.setParameter("in", exchange.getIn()); transformer.setParameter("out", exchange.getOut()); @@ -555,6 +583,40 @@ public class XsltBuilder implements Processor { } } + 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/1b38aad3/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 240118a..ea0fa26 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 @@ -17,7 +17,6 @@ package org.apache.camel.component.xslt; import java.io.IOException; - import javax.xml.transform.Source; import javax.xml.transform.TransformerException; @@ -30,6 +29,7 @@ import org.apache.camel.builder.xml.XsltBuilder; import org.apache.camel.impl.ProcessorEndpoint; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; +import org.apache.camel.util.ServiceHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,19 +87,16 @@ public class XsltEndpoint extends ProcessorEndpoint { @Override protected void onExchange(Exchange exchange) throws Exception { - if (!cacheStylesheet || cacheCleared) { loadResource(resourceUri); } super.onExchange(exchange); - } /** * Loads the resource. * * @param resourceUri the resource to load - * * @throws TransformerException is thrown if error loading resource * @throws IOException is thrown if error loading resource */ @@ -118,11 +115,18 @@ public class XsltEndpoint extends ProcessorEndpoint { @Override protected void doStart() throws Exception { super.doStart(); + + // 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()); + ServiceHelper.startService(xslt); } @Override protected void doStop() throws Exception { super.doStop(); + ServiceHelper.stopService(xslt); } } http://git-wip-us.apache.org/repos/asf/camel/blob/1b38aad3/components/camel-saxon/src/test/data/terminate.xml ---------------------------------------------------------------------- diff --git a/components/camel-saxon/src/test/data/terminate.xml b/components/camel-saxon/src/test/data/terminate.xml new file mode 100644 index 0000000..6a9b5cb --- /dev/null +++ b/components/camel-saxon/src/test/data/terminate.xml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<staff> + + <programmer> + <name>Bugs Bunny</name> + <dob>03/21/1970</dob> + <age>31</age> + <address>4895 Wabbit Hole Road</address> + <phone>865-111-1111</phone> + </programmer> + + <programmer> + <name>Daisy Duck</name> + <dob></dob> + <age></age> + <address>748 Golden Pond</address> + <phone>865-222-2222</phone> + </programmer> + +</staff> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/1b38aad3/components/camel-saxon/src/test/java/org/apache/camel/component/xslt/SaxonXsltMessageTerminateTest.java ---------------------------------------------------------------------- diff --git a/components/camel-saxon/src/test/java/org/apache/camel/component/xslt/SaxonXsltMessageTerminateTest.java b/components/camel-saxon/src/test/java/org/apache/camel/component/xslt/SaxonXsltMessageTerminateTest.java new file mode 100644 index 0000000..62d5d15 --- /dev/null +++ b/components/camel-saxon/src/test/java/org/apache/camel/component/xslt/SaxonXsltMessageTerminateTest.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.xslt; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class SaxonXsltMessageTerminateTest extends CamelTestSupport { + + @Test + public void testXsltTerminate() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(0); + getMockEndpoint("mock:dead").expectedMessageCount(1); + + assertMockEndpointsSatisfied(); + + Exchange out = getMockEndpoint("mock:dead").getReceivedExchanges().get(0); + assertNotNull(out); + // this exception is just a generic xslt error + Exception cause = out.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class); + assertNotNull(cause); + + // we have the xsl termination message as a error property on the exchange as we set terminate=true + Exception error = out.getProperty(Exchange.XSLT_ERROR, Exception.class); + assertNotNull(error); + assertEquals("Error: DOB is an empty string!", error.getMessage()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(deadLetterChannel("mock:dead")); + + from("file:src/test/data/?fileName=terminate.xml&noop=true") + .to("xslt:org/apache/camel/component/xslt/terminate.xsl?saxon=true") + .to("log:foo") + .to("mock:result"); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/1b38aad3/components/camel-saxon/src/test/resources/org/apache/camel/component/xslt/terminate.xsl ---------------------------------------------------------------------- diff --git a/components/camel-saxon/src/test/resources/org/apache/camel/component/xslt/terminate.xsl b/components/camel-saxon/src/test/resources/org/apache/camel/component/xslt/terminate.xsl new file mode 100644 index 0000000..d4d9860 --- /dev/null +++ b/components/camel-saxon/src/test/resources/org/apache/camel/component/xslt/terminate.xsl @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:template match="/"> + <html> + <body> + <xsl:for-each select="staff/programmer"> + <p>Name: <xsl:value-of select="name"/><br /> + <xsl:if test="dob=''"> + <xsl:message terminate="yes">Error: DOB is an empty string!</xsl:message> + </xsl:if> + </p> + </xsl:for-each> + </body> + </html> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file