You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2014/08/27 14:31:47 UTC

[2/5] git commit: 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.

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 <da...@apache.org>
Authored: Wed Aug 27 13:52:58 2014 +0200
Committer: Claus Ibsen <da...@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