CXF and Blueprint
Before diving into the main content I’ll just give some short snippets about the technology used here.
Camel is a framework which implements all the (widely-used) enterprise integration patterns and allows for communication between multiple transports (JMS, HTTP and others) through routing.
Apache CXF implements the JAX-RS specification and Camel provides support for it through the CXFRS component. When using this combination to develop REST services there’s been a number of things I’ve wanted to do which haven’t been immediately obvious. This blog will be looking at the parts of developing and testing a REST service, and explaining some of the gotchas involved.
The second interesting aspect of this is using Blueprint as the dependency injection mechanism for deploying this OSGi ready service. Blueprint can be difficult to test but hopefully that can be made easier with the tips that follow.
Create your resources
My resource is a simple blog post which allows one operation on it: getting a single post by id using a path parameter. Define your resources as follows:
The first media type defined in your produces tag will be the default. If a client does not specify an Accept header on its HTTP request then the service will default to returning the first MediaType in the Produces tag.
Camel Route and Blueprint
I use the fact that the CXFRS component puts the method name of the resource in the operationName header to route each request via a recipient list to a direct endpoint to process it. The restEndpoint field is set by blueprint, utilising it’s DI and default property capabilities. The inline processor simply returns a Post object. For anyone not familiar with Camel, the route is by default a request-reply (or in-out in Camel’s language) route which means as long as you don’t send it somewhere else like an activemq queue then the exchange will be routed back to where it came from i.e. the CXF server, Camel takes care of all the http responding and marshalling for you which is excellent.
Finally, wire it all up in the blueprint. I have given my property placeholder element a persistent-id and an update strategy of none. Using these default properties is what makes the service testable because I can swap the service url to a local url in my test. The cxf-server needs to know about it’s resources in the serviceBeans element and it’s providers which automatically give you marshalling to JSON or XML for free. (As long as an annotated object is in the body when it reaches the end of the Camel route).
Using camel-blueprint-test is the hardest part of this as it requires a lot of hidden dependencies. However, it is a useful test to have and whilst it is slow I would recommend having at least one test in your suite to ensure your blueprint file deploys. It’s main drawback above the flakiness is it’s speed, which is a lot slower than CamelTestSupport based tests. So use CamelTestSupport for most tests and CamelBlueprintTestSupport for a single test.
The key to being able to test it is substituting an absolute local URL in for the cxf-server’s address in the blueprint, so I have used a property to be able to switch it at test time. This can be achieved with a simple CamelTestSupport based test as well by setting the restEndpoint field on the route builder to an absolute local URL like so: cxfrs:http://localhost:10001/api/1/blog.
- The persistent id returned from useOverridePropertiesWithConfigAdmin must be the same as the persistent-id attribute in the property-placeholder element in the blueprint file
- If your properties being substituted in from useOverridePropertiesWithConfigAdmin aren’t being deployed when the Camel context starts then specify update-strategy=”reload”
- Make sure asm-all:4.1 is the first dependency in your pom file’s dependency list.
- If you aren’t receiving a message through your route then check that the route you expect is actually being started. Blueprint testing takes only the first Camel context it finds and loads that. To make sure it always loads your route use the bundle filter method. The below example will load any Camel context it finds except bundles whose names start with bundle.name.unwanted and bundle.name.unwanted2:
- If you are still receiving exceptions the best thing may be to update the version of your blueprint-test dependency to the latest version to avoid exceptions such as felix complaining about a null pointer in the main thread and “NoSuchComponentException: No component with id ‘blueprintBundle’ could be found” appearing in your logs.
- It’s very important to have logging enabled to diagnose issues if your blueprint test is not starting correctly. Issues such as blueprint giving up waiting for service is indicative of a wrong blueprint file with missing ids or incorrectly specified beans.
The Full Test
This test allows you to use a real HTTP client to test that your service implementation will work when deployed. This is a much better test than overriding the endpoints with directs and simply testing the processing in the route because it gives you confidence in your specification of the CXF server.
The end result is a real CXF service being run at test time enabling you to test your full implementation before it’s even been deployed!
The full project can be found here: https://github.com/PhilHardwick/cxf-blueprint-camel-example