Problem with XPath splitters

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Problem with XPath splitters

Neil Thorne
Hi I've got a test case here where I can't figure out how to use the XPathBuilder to create Splitter Expressions.

I've got two tests in here. One suceeds. That's my custom splitter. It's all pure Java and suits my needs but I just spent so long on this XPath version it's just bugging me now.

You can see my expectations in the test but in summary -

For an xml snippet:

<a><b>Ginger</b><b>Mr Boots</b></a>


If I apply the xpath expression "a/b" in a splitter I expect to get a list of two Strings

<b>Ginger</b>
<b>Mr Boots</b>

but I only ever see the String 'Ginger' arrive at the destination endpoint.

What am I going wrong? Does the XPath expression just dump all the xml tags? And why am I only getting one result?

And for a long time I was getting a test failure for the first case also. It might be that I'm trying to run jdk1.6 with IntelliJ 4.5. Anyways the problem was with the org.apache.camel.converter.ObjectConverter.iterator(Object value) method.

I needed to change the camel code to explicitly cast the value to an Object[] when passing an array otherwise I was getting a list of size 1 with an Object[] in it, rather than a list of size Object[].length.

eg.

public static Iterator iterator(Object value) {
        if (value == null) {
            return Collections.EMPTY_LIST.iterator();
        } else if (value instanceof Collection) {
            Collection collection = (Collection)value;
            return collection.iterator();
        } else if (value.getClass().isArray()) {
            // TODO we should handle primitive array types?
            return Arrays.asList([Object]value).iterator();//see the explicit cast?
        } else {
            return Collections.singletonList(value).iterator();
        }
    }

Weird? I made the change once but then I was able to remove the change again so probably just an IntelliJ-ism.

See if the first test runs for you first time ;)

Here's my test case

package org.apache.camel.processor;

import org.apache.camel.*;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.xml.XPathBuilder;
import org.apache.camel.component.mock.MockEndpoint;

public class NeilSplitterTest extends ContextTestSupport {
    protected Endpoint<Exchange> startEndpoint;
    protected MockEndpoint resultEndpoint;

    class CatFight {

        String name;
        String[] cats;

        public String[] getCats() {
            return cats;
        }

        public void setCats(String[] cats) {
            this.cats = cats;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

    }

    public void testCustomExpression() throws Exception {

        resultEndpoint.expectedBodiesReceived("Ginger", "Mr Boots");

        template.send("direct:custom", new Processor() {
            public void process(Exchange exchange) {
                Message in = exchange.getIn();
                CatFight catFight = new CatFight();
                catFight.setName("blueydart");
                catFight.setCats(new String[]{"Ginger", "Mr Boots"});
                in.setBody(catFight);
                in.setHeader("foo", "bar");
            }
        });

        resultEndpoint.assertIsSatisfied();
    }

    public void testXPathExpression() throws Exception {

        resultEndpoint.expectedBodiesReceived("<b>Ginger</b>", "<b>Mr Boots</b>");

        template.send("direct:xpath", new Processor() {
            public void process(Exchange exchange) {
                Message in = exchange.getIn();
                in.setBody("<a><b>Ginger</b><b>Mr Boots</b></a>");
                in.setHeader("foo", "bar");
            }
        });

        resultEndpoint.assertIsSatisfied();
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        resultEndpoint = (MockEndpoint) resolveMandatoryEndpoint("mock:result");
    }

    protected RouteBuilder createRouteBuilder() {
        return new RouteBuilder() {
            public void configure() {
                Expression catFightCats = new Expression(){
                    public Object evaluate(Exchange exchange) {
                        CatFight catFight = (CatFight) exchange.getIn().getBody();
                        String[] cats = catFight.getCats();
                        return cats;
                    }
                };
                from("direct:custom").splitter(catFightCats).to("mock:result");
                XPathBuilder xPathBuilder = new XPathBuilder("a/b");
                from("direct:xpath").splitter(xPathBuilder).to("mock:result");
            }
        };
    }
}
Reply | Threaded
Open this post in threaded view
|

Re: Problem with XPath splitters

jstrachan
On 8/31/07, Neil Thorne <[hidden email]> wrote:

>
> Hi I've got a test case here where I can't figure out how to use the
> XPathBuilder to create Splitter Expressions.
>
> I've got two tests in here. One suceeds. That's my custom splitter. It's all
> pure Java and suits my needs but I just spent so long on this XPath version
> it's just bugging me now.
>
> You can see my expectations in the test but in summary -
>
> For an xml snippet:
>
> <a><b>Ginger</b><b>Mr Boots</b></a>
>
>
> If I apply the xpath expression "a/b" in a splitter I expect to get a list
> of two Strings
>
> <b>Ginger</b>
> <b>Mr Boots</b>
>
> but I only ever see the String 'Ginger' arrive at the destination endpoint.
>
> What am I going wrong? Does the XPath expression just dump all the xml tags?
> And why am I only getting one result?
>
> And for a long time I was getting a test failure for the first case also. It
> might be that I'm trying to run jdk1.6 with IntelliJ 4.5. Anyways the
> problem was with the
> org.apache.camel.converter.ObjectConverter.iterator(Object value) method.
>
> I needed to change the camel code to explicitly cast the value to an
> Object[] when passing an array otherwise I was getting a list of size 1 with
> an Object[] in it, rather than a list of size Object[].length.
>
> eg.
>
> public static Iterator iterator(Object value) {
>         if (value == null) {
>             return Collections.EMPTY_LIST.iterator();
>         } else if (value instanceof Collection) {
>             Collection collection = (Collection)value;
>             return collection.iterator();
>         } else if (value.getClass().isArray()) {
>             // TODO we should handle primitive array types?
>             return Arrays.asList([Object]value).iterator();//see the
> explicit cast?
>         } else {
>             return Collections.singletonList(value).iterator();
>         }
>     }
>
> Weird? I made the change once but then I was able to remove the change again
> so probably just an IntelliJ-ism.
>
> See if the first test runs for you first time ;)
>
> Here's my test case
>
> package org.apache.camel.processor;
>
> import org.apache.camel.*;
> import org.apache.camel.builder.RouteBuilder;
> import org.apache.camel.builder.xml.XPathBuilder;
> import org.apache.camel.component.mock.MockEndpoint;
>
> public class NeilSplitterTest extends ContextTestSupport {
>     protected Endpoint<Exchange> startEndpoint;
>     protected MockEndpoint resultEndpoint;
>
>     class CatFight {
>
>         String name;
>         String[] cats;
>
>         public String[] getCats() {
>             return cats;
>         }
>
>         public void setCats(String[] cats) {
>             this.cats = cats;
>         }
>
>         public String getName() {
>             return name;
>         }
>
>         public void setName(String name) {
>             this.name = name;
>         }
>
>     }
>
>     public void testCustomExpression() throws Exception {
>
>         resultEndpoint.expectedBodiesReceived("Ginger", "Mr Boots");
>
>         template.send("direct:custom", new Processor() {
>             public void process(Exchange exchange) {
>                 Message in = exchange.getIn();
>                 CatFight catFight = new CatFight();
>                 catFight.setName("blueydart");
>                 catFight.setCats(new String[]{"Ginger", "Mr Boots"});
>                 in.setBody(catFight);
>                 in.setHeader("foo", "bar");
>             }
>         });
>
>         resultEndpoint.assertIsSatisfied();
>     }
>
>     public void testXPathExpression() throws Exception {
>
>         resultEndpoint.expectedBodiesReceived("<b>Ginger</b>",
> "<b>Mr Boots</b>");
>
>         template.send("direct:xpath", new Processor() {
>             public void process(Exchange exchange) {
>                 Message in = exchange.getIn();
>                 in.setBody("<a><b>Ginger</b><b>Mr
> Boots</b></a>");
>                 in.setHeader("foo", "bar");
>             }
>         });
>
>         resultEndpoint.assertIsSatisfied();
>     }
>
>     @Override
>     protected void setUp() throws Exception {
>         super.setUp();
>
>         resultEndpoint = (MockEndpoint)
> resolveMandatoryEndpoint("mock:result");
>     }
>
>     protected RouteBuilder createRouteBuilder() {
>         return new RouteBuilder() {
>             public void configure() {
>                 Expression catFightCats = new Expression(){
>                     public Object evaluate(Exchange exchange) {
>                         CatFight catFight = (CatFight)
> exchange.getIn().getBody();
>                         String[] cats = catFight.getCats();
>                         return cats;
>                     }
>                 };
>
> from("direct:custom").splitter(catFightCats).to("mock:result");
>                 XPathBuilder xPathBuilder = new XPathBuilder("a/b");
>
> from("direct:xpath").splitter(xPathBuilder).to("mock:result");
>             }
>         };
>     }
> }

Firstly thanks for this awesome patch!

It turned up numerous problems. Firstly array expressions were not
being iterated over by default in the splitter (which I've now fixed).

Secondly JAXP is kinda wierd; by default if you don't specify an
explicit result type, the "a/b" evaluates to the string value of the
first node found! Truly strange.

So I've defaulted the type of XPath expressions to be NODESET, which
basically returns the correct XML fragment (so it splits into <b>
documents now). I did feel a bit dirty making this change; as any
string type XPath expression will barf now unless you explicitly call
the XPathBuilder.stringResult - e.g.

  XPathBuilder.xpath("concat('hey', /foo.bar)").stringResult()

but I don't see a better way around this as yet. Anyone else any better ideas?

I've just commit your patch - see the NeilSplitterTest in the
camel.issues package. Thanks again!

--
James
-------
http://macstrac.blogspot.com/
Reply | Threaded
Open this post in threaded view
|

Re: Problem with XPath splitters

Arjan Moraal
With this simple example the test is passing indeed. But when I feed a slightly more complicated xml it is failing. Maybe it's just something simple I'm overlooking...

    public void testXPathWithRUFixtures() throws Exception {
        log.debug("testXPathWithRUFixtures()");
        context.addRoutes(
            new RouteBuilder() {
                @Override
                public void configure() {
                    from("direct:xpathrufix").splitter(xpath("/rugbyFixtures/rugbyFixture")).to("mock:result");
                }
            }
        );

        final String fixture1 =
            "<rugbyFixture id=\"2909509\" venue=\"Parc des Princes\" "+
            "date=\"2007-10-19\" time=\"20:00:00\">"+
            "<homeTeam id=\"33176\" name=\"France\" />"+
            "<awayTeam id=\"33085\" name=\"Argentina\" />"+
            "<competition id=\"700\" name=\"World Cup\" />"+
            "</rugbyFixture>";
        final String fixture2 =
            "<rugbyFixture id=\"2866397\" venue=\"Kingston Park\" "+
            "date=\"2007-10-21\" time=\"14:00:00\">"+
            "<homeTeam id=\"34698\" name=\"Newcastle\" />"+
            "<awayTeam id=\"33094\" name=\"Bath\" />"+
            "<competition id=\"100\" name=\"Guinness Premiership\" />"+
            "</rugbyFixture>";
       
        resultEndpoint.expectedBodiesReceived(fixture1, fixture2);

        template.send("direct:xpathrufix", new Processor() {
            public void process(Exchange exchange) {
                Message in = exchange.getIn();
                in.setBody(//"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+
                        "<rugbyFixtures>"+fixture1+fixture2+"</rugbyFixtures>");
            }
        });
       
        resultEndpoint.assertIsSatisfied();
    }

The error is:

java.lang.AssertionError: mock:result Body of message: 0. Expected: <<rugbyFixture id="2909509" venue="Parc des Princes" date="2007-10-19" time="20:00:00"><homeTeam id="33176" name="France" /><awayTeam id="33085" name="Argentina" /><competition id="700" name="World Cup" /></rugbyFixture>> but was: <<rugbyFixture date="2007-10-19" id="2909509" time="20:00:00" venue="Parc des Princes"><homeTeam id="33176" name="France"/><awayTeam id="33085" name="Argentina"/><competition id="700" name="World Cup"/></rugbyFixture>>
        at org.apache.camel.component.mock.MockEndpoint.fail(MockEndpoint.java:531)
        at org.apache.camel.component.mock.MockEndpoint.assertEquals(MockEndpoint.java:513)
        at org.apache.camel.component.mock.MockEndpoint$2.run(MockEndpoint.java:252)
        at org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied(MockEndpoint.java:179)
        at org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied(MockEndpoint.java:144)
        at com.bbc.newsi.feeds.common.dslroutes.CamelTest.testXPathWithRUFixtures(CamelTest.java:101)
Reply | Threaded
Open this post in threaded view
|

Re: Problem with XPath splitters

gertv
L.S.,


At first glance, it seems to me that the correct result is returned.  
Only the order of the attributes within the XML element has changed, but
everything is there.  I don't think that there is a way to enforce this
order of attributes, it's probably being rearranged while parsing the
XML document for the XPath splitter.

One possible solution is to check the id attribute in your test
afterwards using the DOM API instead of using the expectedBodies(...):
       
        resultEndpoint.assertIsSatisfied();
        assertEquals("2909509",
resultEndpoint.assertExchangeReceived(0).getIn().getBody(Element.class).getAttribute("id"));
        assertEquals("2866397",
resultEndpoint.assertExchangeReceived(1).getIn().getBody(Element.class).getAttribute("id"));


Regards,

Gert


arjanm wrote:

> With this simple example the test is passing indeed. But when I feed a
> slightly more complicated xml it is failing. Maybe it's just something
> simple I'm overlooking...
>
>     public void testXPathWithRUFixtures() throws Exception {
>         log.debug("testXPathWithRUFixtures()");
>         context.addRoutes(
>             new RouteBuilder() {
>                 @Override
>                 public void configure() {
>                    
> from("direct:xpathrufix").splitter(xpath("/rugbyFixtures/rugbyFixture")).to("mock:result");
>                 }
>             }
>         );
>
>         final String fixture1 =
>             "<rugbyFixture id=\"2909509\" venue=\"Parc des Princes\" "+
>             "date=\"2007-10-19\" time=\"20:00:00\">"+
>             "<homeTeam id=\"33176\" name=\"France\" />"+
>             "<awayTeam id=\"33085\" name=\"Argentina\" />"+
>             "<competition id=\"700\" name=\"World Cup\" />"+
>             "</rugbyFixture>";
>         final String fixture2 =
>             "<rugbyFixture id=\"2866397\" venue=\"Kingston Park\" "+
>             "date=\"2007-10-21\" time=\"14:00:00\">"+
>             "<homeTeam id=\"34698\" name=\"Newcastle\" />"+
>             "<awayTeam id=\"33094\" name=\"Bath\" />"+
>             "<competition id=\"100\" name=\"Guinness Premiership\" />"+
>             "</rugbyFixture>";
>        
>         resultEndpoint.expectedBodiesReceived(fixture1, fixture2);
>
>         template.send("direct:xpathrufix", new Processor() {
>             public void process(Exchange exchange) {
>                 Message in = exchange.getIn();
>                 in.setBody(//"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+
>                        
> "<rugbyFixtures>"+fixture1+fixture2+"</rugbyFixtures>");
>             }
>         });
>        
>         resultEndpoint.assertIsSatisfied();
>     }
>
> The error is:
>
> java.lang.AssertionError: mock:result Body of message: 0. Expected:
> <<rugbyFixture id="2909509" venue="Parc des Princes" date="2007-10-19"
> time="20:00:00"><homeTeam id="33176" name="France" /><awayTeam id="33085"
> name="Argentina" /><competition id="700" name="World Cup" /></rugbyFixture>>
> but was: <<rugbyFixture date="2007-10-19" id="2909509" time="20:00:00"
> venue="Parc des Princes"><homeTeam id="33176" name="France"/><awayTeam
> id="33085" name="Argentina"/><competition id="700" name="World
> Cup"/></rugbyFixture>>
> at org.apache.camel.component.mock.MockEndpoint.fail(MockEndpoint.java:531)
> at
> org.apache.camel.component.mock.MockEndpoint.assertEquals(MockEndpoint.java:513)
> at
> org.apache.camel.component.mock.MockEndpoint$2.run(MockEndpoint.java:252)
> at
> org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied(MockEndpoint.java:179)
> at
> org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied(MockEndpoint.java:144)
> at
> com.bbc.newsi.feeds.common.dslroutes.CamelTest.testXPathWithRUFixtures(CamelTest.java:101)
>
>  

Reply | Threaded
Open this post in threaded view
|

Re: Problem with XPath splitters

Arjan Moraal
Hi Gert,

You are right, the test passes when just checking the id's.

I was a bit confused by the output of the failure:
java.lang.AssertionError: mock:result Body of message: 0. Expected:
<<rugbyFixture id=...
It looked like the body of the message was just the string "0" instead of an XML snippet.

Thanks,
Arjan


<quote author="Gert Vanthienen">
L.S.,


At first glance, it seems to me that the correct result is returned.  
Only the order of the attributes within the XML element has changed, but
everything is there.  I don't think that there is a way to enforce this
order of attributes, it's probably being rearranged while parsing the
XML document for the XPath splitter.

One possible solution is to check the id attribute in your test
afterwards using the DOM API instead of using the expectedBodies(...):
       
        resultEndpoint.assertIsSatisfied();
        assertEquals("2909509",
resultEndpoint.assertExchangeReceived(0).getIn().getBody(Element.class).getAttribute("id"));
        assertEquals("2866397",
resultEndpoint.assertExchangeReceived(1).getIn().getBody(Element.class).getAttribute("id"));


Regards,

Gert