Updated Branches: refs/heads/camel-2.11.x 6333fe1f7 -> 690344774 refs/heads/camel-2.12.x ad6307503 -> c4cb3f5c3 refs/heads/master 4c70f54b5 -> a4c9617f0
CAMEL-6440 Fixed the issue of data loss on xpath after cxf(payload-mode) Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/c4cb3f5c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/c4cb3f5c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/c4cb3f5c Branch: refs/heads/camel-2.12.x Commit: c4cb3f5c3812a6807c0478b22f6a380e7eaf3c4f Parents: ad63075 Author: Willem Jiang <ningji...@apache.org> Authored: Wed Sep 18 11:32:17 2013 +0800 Committer: Willem Jiang <ningji...@apache.org> Committed: Wed Sep 18 11:48:31 2013 +0800 ---------------------------------------------------------------------- .../camel/converter/jaxp/DomConverter.java | 24 +- ...CxfConsumerPayloadXPathClientServerTest.java | 143 +++++++++++ .../cxf/CxfConsumerPayloadXPathTest.java | 250 +++++++++++++++++++ 3 files changed, 415 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/c4cb3f5c/camel-core/src/main/java/org/apache/camel/converter/jaxp/DomConverter.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/converter/jaxp/DomConverter.java b/camel-core/src/main/java/org/apache/camel/converter/jaxp/DomConverter.java index 725db0b..bbd7d7a 100644 --- a/camel-core/src/main/java/org/apache/camel/converter/jaxp/DomConverter.java +++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/DomConverter.java @@ -59,7 +59,7 @@ public final class DomConverter { boolean found = false; if (nodeList instanceof Node) { Node node = (Node) nodeList; - String s = xml.toString(node, exchange); + String s = toString(node, exchange); if (ObjectHelper.isNotEmpty(s)) { found = true; buffer.append(s); @@ -69,7 +69,7 @@ public final class DomConverter { int size = nodeList.getLength(); for (int i = 0; i < size; i++) { Node node = nodeList.item(i); - String s = xml.toString(node, exchange); + String s = toString(node, exchange); if (ObjectHelper.isNotEmpty(s)) { found = true; buffer.append(s); @@ -85,6 +85,26 @@ public final class DomConverter { return buffer.toString(); } + + private String toString(Node node, Exchange exchange) throws TransformerException { + String s; + if (node instanceof Text) { + Text textnode = (Text) node; + + StringBuilder b = new StringBuilder(); + b.append(textnode.getNodeValue()); + textnode = (Text) textnode.getNextSibling(); + while (textnode != null) { + b.append(textnode.getNodeValue()); + textnode = (Text) textnode.getNextSibling(); + } + s = b.toString(); + } else { + s = xml.toString(node, exchange); + + } + return s; + } @Converter public static Integer toInteger(NodeList nodeList) { http://git-wip-us.apache.org/repos/asf/camel/blob/c4cb3f5c/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathClientServerTest.java ---------------------------------------------------------------------- diff --git a/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathClientServerTest.java b/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathClientServerTest.java new file mode 100644 index 0000000..64980a9 --- /dev/null +++ b/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathClientServerTest.java @@ -0,0 +1,143 @@ +/** + * 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.cxf; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.builder.xml.XPathBuilder; +import org.apache.camel.converter.jaxp.XmlConverter; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.commons.lang.StringUtils; +import org.apache.cxf.BusFactory; +import org.apache.cxf.binding.soap.SoapHeader; +import org.apache.cxf.frontend.ClientFactoryBean; +import org.apache.cxf.frontend.ClientProxyFactoryBean; +import org.junit.Test; + + + +public class CxfConsumerPayloadXPathClientServerTest extends CamelTestSupport { + private static final String ECHO_RESPONSE = "<ns1:echoResponse xmlns:ns1=\"http://cxf.component.camel.apache.org/\">" + + "<return xmlns=\"http://cxf.component.camel.apache.org/\">echo Hello World!</return>" + + "</ns1:echoResponse>"; + + + protected final String simpleEndpointAddress = "http://localhost:" + + CXFTestSupport.getPort1() + "/" + getClass().getSimpleName() + "/test"; + protected final String simpleEndpointURI = "cxf://" + simpleEndpointAddress + + "?serviceClass=org.apache.camel.component.cxf.HelloService"; + + private String testMessage; + private String receivedMessageCxfPayloadApplyingXPath; + private String receivedMessageByDom; + private String receivedMessageStringApplyingXPath; + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from(simpleEndpointURI + "&dataFormat=PAYLOAD").to("log:info").process(new Processor() { + @SuppressWarnings("unchecked") + @Override + public void process(final Exchange exchange) throws Exception { + Object request = exchange.getIn().getBody(); + assertIsInstanceOf(CxfPayload.class, request); + + //attempt 1) applying XPath to exchange.getIn().getBody() + receivedMessageCxfPayloadApplyingXPath = XPathBuilder.xpath("//*[name()='arg0']/text()").evaluate(context, request, String.class); + + //attempt 2) in stead of XPATH, browse the DOM-tree + CxfPayload<SoapHeader> payload = (CxfPayload<SoapHeader>) request; + Element el = (Element) payload.getBody().get(0); + Element el2 = (Element) el.getFirstChild(); + Text textnode = (Text) el2.getFirstChild(); + receivedMessageByDom = textnode.getNodeValue(); + + textnode = (Text) textnode.getNextSibling(); + while (textnode != null) { + //the textnode appears to have siblings! + receivedMessageByDom = receivedMessageByDom + textnode.getNodeValue(); + textnode = (Text) textnode.getNextSibling(); + } + + //attempt 3) apply XPATH after converting CxfPayload to String + request = exchange.getIn().getBody(String.class); + assertIsInstanceOf(String.class, request); + receivedMessageStringApplyingXPath = XPathBuilder.xpath("//*[name()='arg0']/text()").evaluate(context, request, String.class); + + //build some dummy response + XmlConverter converter = new XmlConverter(); + Document outDocument = converter.toDOMDocument(ECHO_RESPONSE); + List<Source> outElements = new ArrayList<Source>(); + outElements.add(new DOMSource(outDocument.getDocumentElement())); + // set the payload header with null + CxfPayload<SoapHeader> responsePayload = new CxfPayload<SoapHeader>(null, outElements, null); + exchange.getOut().setBody(responsePayload); + } + }); + } + }; + } + + private void buildTestMessage(int size) { + testMessage = StringUtils.repeat("x", size); + } + + @Test + public void testMessageWithIncreasingSize() throws Exception { + + execTest(1); + execTest(10); + execTest(100); + execTest(1000); + execTest(10000); + execTest(100000); + + } + + private void execTest(int size) throws Exception { + buildTestMessage(size); + + ClientProxyFactoryBean proxyFactory = new ClientProxyFactoryBean(); + ClientFactoryBean clientBean = proxyFactory.getClientFactoryBean(); + clientBean.setAddress(simpleEndpointAddress); + clientBean.setServiceClass(HelloService.class); + clientBean.setBus(BusFactory.getDefaultBus()); + + HelloService client = (HelloService) proxyFactory.create(); + + String result = client.echo(testMessage); + assertEquals("We should get the echo string result from router", "echo Hello World!", result); + + //check received requests + assertEquals("Lengths of testMessage and receiveMessage should be equal (conversion body to String),", testMessage.length(), receivedMessageStringApplyingXPath.length()); + assertEquals("Lengths of receivedMessageByDom and receivedMessageCxfPayloadApplyingXPath should be equal", receivedMessageCxfPayloadApplyingXPath.length(), receivedMessageByDom.length()); + assertEquals("Lengths of testMessage and receiveMessage should be equal (body is CxfPayload),", testMessage.length(), receivedMessageCxfPayloadApplyingXPath.length()); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/c4cb3f5c/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathTest.java ---------------------------------------------------------------------- diff --git a/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathTest.java b/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathTest.java new file mode 100644 index 0000000..5ee5103 --- /dev/null +++ b/components/camel-cxf/src/test/java/org/apache/camel/component/cxf/CxfConsumerPayloadXPathTest.java @@ -0,0 +1,250 @@ +/** + * 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.cxf; + +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.builder.xml.XPathBuilder; +import org.apache.camel.impl.DefaultExchange; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.commons.lang.StringUtils; +import org.apache.cxf.binding.soap.SoapHeader; +import org.junit.ComparisonFailure; +import org.junit.Test; + +public class CxfConsumerPayloadXPathTest extends CamelTestSupport { + + public static final String HEADER_SIZE = "tstsize"; + + @Test + public void size1XPathStringResultTest() throws Exception { + simpleTest(1, new TestRouteWithXPathStringResultBuilder()); + } + + @Test + public void size100XPathStringResultTest() throws Exception { + simpleTest(100, new TestRouteWithXPathStringResultBuilder()); + } + + @Test + public void size1000XPathStringResultTest() throws Exception { + simpleTest(1000, new TestRouteWithXPathStringResultBuilder()); + } + + @Test + public void size10000XPathStringResultTest() throws Exception { + simpleTest(10000, new TestRouteWithXPathStringResultBuilder()); + } + + @Test + public void size1XPathTest() throws Exception { + simpleTest(1, new TestRouteWithXPathBuilder()); + } + + @Test + public void size100XPathTest() throws Exception { + simpleTest(100, new TestRouteWithXPathBuilder()); + } + + @Test + public void size1000XPathTest() throws Exception { + simpleTest(1000, new TestRouteWithXPathBuilder()); + } + + @Test + public void size10000XPathTest() throws Exception { + simpleTest(10000, new TestRouteWithXPathBuilder()); + } + + + + //the textnode appears to have siblings! + @Test + public void size10000DomTest() throws Exception { + simpleTest(10000, new TestRouteWithDomBuilder()); + } + + private class TestRouteWithXPathBuilder extends BaseRouteBuilder { + @Override + public void configure() { + from("cxf://" + testAddress + "?dataFormat=PAYLOAD") + .streamCaching() + .process(new XPathProcessor()) + .process(new ResponseProcessor()); + } + } + + private class TestRouteWithXPathStringResultBuilder extends BaseRouteBuilder { + @Override + public void configure() { + from("cxf://" + testAddress + "?dataFormat=PAYLOAD") + .streamCaching() + .process(new XPathStringResultProcessor()) + .process(new ResponseProcessor()); + } + } + + private class TestRouteWithDomFirstOneOnlyBuilder extends BaseRouteBuilder { + @Override + public void configure() { + from("cxf://" + testAddress + "?dataFormat=PAYLOAD") + .streamCaching() + .process(new DomFirstOneOnlyProcessor()) + .process(new ResponseProcessor()); + } + } + + private class TestRouteWithDomBuilder extends BaseRouteBuilder { + @Override + public void configure() { + from("cxf://" + testAddress + "?dataFormat=PAYLOAD") + .streamCaching() + .process(new DomProcessor()) + .process(new ResponseProcessor()); + } + } + + //implementation simular to xpath() in route: no data loss + private class XPathStringResultProcessor implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + Object obj = exchange.getIn().getBody(); + //xpath expression directly results in a: String + String content = (String) XPathBuilder.xpath("//xml/text()").stringResult().evaluate(context, obj, Object.class); + exchange.getOut().setBody(content); + exchange.getOut().setHeaders(exchange.getIn().getHeaders()); + } + } + + //this version leads to data loss + private class XPathProcessor implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + Object obj = exchange.getIn().getBody(); + //xpath expression results in a: net.sf.saxon.dom.DOMNodeList + //after which it is converted to a String + String content = XPathBuilder.xpath("//xml/text()").evaluate(context, obj, String.class); + exchange.getOut().setBody(content); + exchange.getOut().setHeaders(exchange.getIn().getHeaders()); + } + } + + //this version leads to data loss + private class DomFirstOneOnlyProcessor implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + Object obj = exchange.getIn().getBody(); + CxfPayload<SoapHeader> payload = (CxfPayload<SoapHeader>) obj; + Element el = (Element) payload.getBody().get(0); + Text textnode = (Text) el.getFirstChild(); + exchange.getOut().setBody(textnode.getNodeValue()); + exchange.getOut().setHeaders(exchange.getIn().getHeaders()); + } + } + + private class DomProcessor implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + Object obj = exchange.getIn().getBody(); + CxfPayload<SoapHeader> payload = (CxfPayload<SoapHeader>) obj; + Element el = (Element) payload.getBody().get(0); + Text textnode = (Text) el.getFirstChild(); + + StringBuilder b = new StringBuilder(); + b.append(textnode.getNodeValue()); + textnode = (Text) textnode.getNextSibling(); + while (textnode != null) { + //the textnode appears to have siblings! + b.append(textnode.getNodeValue()); + textnode = (Text) textnode.getNextSibling(); + } + + exchange.getOut().setBody(b.toString()); + exchange.getOut().setHeaders(exchange.getIn().getHeaders()); + } + } + + private class ResponseProcessor implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + Object obj = exchange.getIn().getBody(); + String content = (String) obj; + String msgOut = constructSoapMessage(content); + exchange.getOut().setBody(msgOut); + exchange.getOut().setHeaders(exchange.getIn().getHeaders()); + exchange.getOut().setHeader(HEADER_SIZE, "" + content.length()); + } + } + + private void simpleTest(int repeat, BaseRouteBuilder builder) throws Exception { + setUseRouteBuilder(false); + context.addRoutes(builder); + startCamelContext(); + + String content = StringUtils.repeat("x", repeat); + String msgIn = constructSoapMessage(content); + + Exchange exchgIn = new DefaultExchange(context); + exchgIn.setPattern(ExchangePattern.InOut); + exchgIn.getIn().setBody(msgIn); + + //Execute + Exchange exchgOut = template.send(builder.getTestAddress(), exchgIn); + + //Verify + String result = exchgOut.getOut().getBody(String.class); + assertNotNull("response on http call", result); + + //check for data loss in received input (after xpath) + String headerSize = exchgOut.getOut().getHeader(HEADER_SIZE, String.class); + assertEquals("" + repeat, headerSize); + + assertTrue("dataloss in output occurred", result.length() > repeat); + + stopCamelContext(); + } + + + private abstract class BaseRouteBuilder extends RouteBuilder { + protected final String testAddress = getAvailableUrl("test"); + + public String getTestAddress() { + return testAddress; + } + } + + private String getAvailableUrl(String pathEnd) { + int availablePort = AvailablePortFinder.getNextAvailable(); + String url = "http://localhost:" + availablePort + + "/" + getClass().getSimpleName(); + return url + "/" + pathEnd; + } + + private String constructSoapMessage(String content) { + return + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + + "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" + + "<soapenv:Body><xml>" + content + "</xml></soapenv:Body>" + + "</soapenv:Envelope>"; + } +} \ No newline at end of file