Apache Camel and SOAP

To learn more about Apache Camel I have implemented a non-trivial integration scenario using freely available SOAP web services to create a service that can return the weather at an airport.

The webservices are:

My goals have been:

  • Use web services from different providers
  • Be able to test the routes
  • Include error handling
  • Try various methods for transformation

So far I’m quite happy with the results and impressed with the flexibility of Apache Camel. Have a look at the code available at AirportWeather project @ GitHub and let me know what you think.

Routes

The complete route looks like this:

from("direct:getMaximumTemperaturAtAirport")
	.to("direct:getAirportInformationByAirportCode")
	.to("direct:fromAirportInformationToLocation")
	.to("direct:fromLocationToNDFD")
	.to("direct:fromNDFDToTempInCelcius");

The flow is synchronous and each step is implemented in a separate route to be testable, for example I can stub out entire steps of the route.

A step can be further broken down into even smaller routes, for example separating the call to the web service and the error handling:

from("direct:getAirportInformationByAirportCode")
	.to("direct:invokeGetAirportInformationByAirportCode")
	.choice()
		.when().xpath("count(/NewDataSet/Table)=0").rollback("No Airport found")
		.otherwise();

Calling a SOAP web service

To call the web service I use Spring WS. The reason I didn’t use CXF is that the NDFD is using RPC encoding which is an old web standard not supported by the current version of CXF. The XML payload is generated using a Velocity template. This works well in this case, but later I want to compare this to JAXB generated objects from an XML schema.

from("direct:invokeGetAirportInformationByAirportCode")
    .to("velocity:getAirportInformationByAirportCode.vm")
    .to("spring-ws:http://www.webservicex.net/airport.asmx?soapAction=http://www.webserviceX.NET/

Transformation

Camel has very flexible support for transformation and un/marshalling. I have tried a couple of different variations. Registering a object and simply calling a method:

SimpleRegistry registry = new SimpleRegistry();
registry.put("temperatureTransformer", new TemperatureTransformer());
...
.transform().method("temperatureTransformer", "fromFahrenheitToCelsius");

Let Camel figure out which method to call:

registry.put("cdataTransformer", new CDataTransformer());
...
.transform().method("cdataTransformer");

XML unmarshalling using XStream:

XStreamDataFormat xmlToAirPortLocation() {
    XStream xstream = new MissingFieldIgnoringXStream();
    xstream.alias("Table", AirportLocation.class);
    XStreamDataFormat dataFormat = new XStreamDataFormat(xstream);
    return dataFormat;
}
...
.unmarshal(xmlToAirPortLocation())

XPath transformation:

.transform().xpath("/NewDataSet/Table[1]")

Let Camel figure out how to perform the conversion:

.convertBodyTo(Integer.class)

Stubbing routes

When testing I used AdviceWith to stub out a route in the test case. I’m not really sure if this is the best way, but it works.

context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
    @Override
    public void configure() throws Exception {
        interceptSendToEndpoint(someEndpoint)
            .skipSendToOriginalEndpoint().to("velocity:AirportResult-empty.xml");
    }
});

To make the stub actually override the endpoint it is important to call skipSendToOriginalEndpoint. I use Velocity to generate a static reply. See the AirportWeatherTest for more details.

Handling bad results

To verify the result of the invoked web service I have used a choice and rollback to signal the error, for example when the airport does not exist:

.choice()
    .when().xpath("count(/NewDataSet/Table)=0").rollback("No Airport found")
    .otherwise();

Future Camel rides

Things I will investigate further:

  • Other ways to stub out routes
  • Making use of camel-test
  • Use CXF and JAXB to avoid creating XML payloads using Velocity (perhaps for some other web service)

This Post Has 9 Comments

  1. Pranay

    HI Jan,
    Nice blog and helped us so much..
    can u please give us full example of calling any existing (external) webservice using camel-cxf and java dsl?

    thanks
    Pranay

  2. ramana polaka

    hi jan
    i am new learner of apache camel
    when i ran this app i got exception

    Exception in thread “main” org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[Message:
    MAA
    MADRAS MENMBARKAM
    India
    IN
    733
    -5.5
    10050
    50
    13
    5
    0
    N
    80
    17
    0
    E
    ]
    at org.apache.camel.util.ObjectHelper.wrapCamelExecutionException(ObjectHelper.java:1368)
    at org.apache.camel.util.ExchangeHelper.extractResultBody(ExchangeHelper.java:622)
    at org.apache.camel.impl.DefaultProducerTemplate.extractResultBody(DefaultProducerTemplate.java:467)
    at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:133)
    at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:149)
    at org.apache.camel.impl.DefaultProducerTemplate.requestBody(DefaultProducerTemplate.java:297)
    at com.jayway.airportweather.Main.main(Main.java:16)
    Caused by: com.thoughtworks.xstream.converters.ConversionException: RunwayLengthFeet : RunwayLengthFeet
    —- Debugging information —-
    message : RunwayLengthFeet : RunwayLengthFeet
    line number : 8
    path : /Table/RunwayLengthFeet
    cause-message : RunwayLengthFeet : RunwayLengthFeet
    class : com.jayway.airportweather.model.AirportLocation
    cause-exception : com.thoughtworks.xstream.alias.CannotResolveClassException
    required-type : com.jayway.airportweather.model.AirportLocation
    ——————————-
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:45)
    at com.thoughtworks.xstream.core.ReferenceByXPathUnmarshaller.convertAnother(ReferenceByXPathUnmarshaller.java:39)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.start(TreeUnmarshaller.java:99)
    at com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy.unmarshal(ReferenceByXPathMarshallingStrategy.java:12)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:552)
    at com.thoughtworks.xstream.XStream.unmarshal(XStream.java:531)
    at org.apache.camel.dataformat.xstream.AbstractXStreamWrapper.unmarshal(AbstractXStreamWrapper.java:213)
    at org.apache.camel.processor.UnmarshalProcessor.process(UnmarshalProcessor.java:65)
    at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:72)
    at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:398)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:118)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:80)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
    at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:51)
    at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:110)
    at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:72)
    at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:398)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:118)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:80)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
    at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:51)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
    at org.apache.camel.processor.UnitOfWorkProducer.process(UnitOfWorkProducer.java:73)
    at org.apache.camel.impl.ProducerCache$2.doInProducer(ProducerCache.java:378)
    at org.apache.camel.impl.ProducerCache$2.doInProducer(ProducerCache.java:346)
    at org.apache.camel.impl.ProducerCache.doInProducer(ProducerCache.java:242)
    at org.apache.camel.impl.ProducerCache.sendExchange(ProducerCache.java:346)
    at org.apache.camel.impl.ProducerCache.send(ProducerCache.java:201)
    at org.apache.camel.impl.DefaultProducerTemplate.send(DefaultProducerTemplate.java:128)
    at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:132)
    … 3 more
    Caused by: com.thoughtworks.xstream.alias.CannotResolveClassException: RunwayLengthFeet : RunwayLengthFeet
    at com.thoughtworks.xstream.mapper.DefaultMapper.realClass(DefaultMapper.java:35)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.XmlFriendlyMapper.realClass(XmlFriendlyMapper.java:44)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.ClassAliasingMapper.realClass(ClassAliasingMapper.java:49)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.DynamicProxyMapper.realClass(DynamicProxyMapper.java:46)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.ArrayMapper.realClass(ArrayMapper.java:70)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:18)
    at com.thoughtworks.xstream.mapper.CachingMapper.realClass(CachingMapper.java:27)
    at com.thoughtworks.xstream.converters.reflection.ReflectionConverter.determineType(ReflectionConverter.java:179)
    at com.thoughtworks.xstream.converters.reflection.ReflectionConverter.unmarshal(ReflectionConverter.java:102)
    at com.thoughtworks.xstream.core.TreeUnmarshaller.convertAnother(TreeUnmarshaller.java:38)
    … 34 more
    pls help me……….

    1. Jan Kronquist

      Hmmm.. maybe the APIs have changed since I published this code. I haven’t run the code in quite a while :-)

  3. Frank Walter

    Hello Jan,

    How do I invoke the same SOAP service to get a JSON response instead of xml?
    I have a Rest Route defined, and I want it to invoke a SOAP service to send a JSON payload and receive a JSON response.
    Which component shall I be using? Does Spring WS also solves this? Any example will be highly useful.

    Thanks & Warm Regards,
    Frank Walter

  4. Frank Walter

    Hello Jan,
    Which component we can use to invoke a SOAP service from a camel route and to get a JSON response?
    I have a requirement to write a Camel route that calls a SOAP service and sends a parameter, but the response needs to be JSON?
    Any example would be much helpful.

    Thanks,
    Frank Walter

Leave a Reply