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:
- The Airport Information Webservice to find the location of an airport
- The National Digital Forecast Database Webservice provided by http://www.weather.gov/ to find the weather at a location
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)
Hi Jan
This is a nice article. I took the liberty to add a link to it from the Camel articles web page: http://camel.apache.org/articles
It takes hours for it to sync the update.
Thanks!
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
Hi Jan
Excellent article, made a fork of your project and used xsl transformations instead together with xspec for unit-testing the xsl.
Wrote an article about it here: http://pokerjocke.blogspot.com/2011/09/unit-testing-xsl-transformations-with.html
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……….
Hmmm.. maybe the APIs have changed since I published this code. I haven’t run the code in quite a while :-)
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
You should probably go to the camel support page instead: http://camel.apache.org/support.html
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