Transparently Enriching Services with CXF Interceptors in OSGi


Most Enterprise Architects will want to apply common cross-cutting concerns to the Services in their corporation. Imagine logging, security, reliability, QoS policies, etc. [By the way, let's drop the word "Web" from Web Services when we speak about Apache CXF, as it's capable of doing so much more than just plain HTTP].

With most frameworks you'd typically end up creating lots of boilerplate code and documenting heavy guidelines so that developers know what rules to adhere to. The results are hefty deployment artefacts, developer productivity loss and a bunch problems whenever you need to change the common logic (because you may have several spots in code where to replicate that change!).

If you are using Apache CXF (or the enterprise version of Apache CXF) and OSGi, there's very good news for you! You have a powerful combination in your hands to apply magic behind the curtains on-the-fly every time a service is provisioned and de-provisioned in your OSGi environment!

In this post I will show you how to uncover this magic.

The use case


To illustrate the example, I'll take the Logging Feature from Apache CXF and establish a policy in my OSGi container so that the feature is automatically applied to all CXF Buses that get registered. Some background concepts:

  • CXF Feature: A feature in CXF is simply a grouping of CXF Interceptors. The LoggingFeature adds the LoggingInInterceptor and LoggingOutInterceptor to the interceptor chains in the bus.
  • CXF Bus: according to the CXF docs: "The Bus is the backbone of CXF architecture. It manages extensions and acts as an interceptor provider. The interceptors for the bus will be added to the respective inbound and outbound message and fault interceptor chains for all client and server endpoints created on the bus (in its context)."
  • CXF Bus Registration: Every time a CXF bus is registered, by default CXF exports it as an OSGi Service to the OSGi Service Registry.
  • OSGi Service: an object that a bundle makes available to any other bundle in the OSGi container.

The solution


The solution is very simple and requires no advanced knowledge about OSGi nor digging into internals at all. All we need to do is create a new OSGi bundle that "listens" to new service registrations matching the org.apache.cxf.Bus interface.
With this solution, whenever a new CXF Bus comes to life via Apache CXF in the OSGi container, it will immediately get enriched to log all incoming requests and outgoing responses.
The "enricher" will be a standard Java class with a single method that takes a CXF Bus as a parameter. As you can see, this class is completely ignorant of OSGi:

public class CXFBusListener {
    private final static Logger LOG = LoggerFactory.getLogger(CXFBusListener.class);
    public void busRegistered(Bus bus) {
        LOG.info("Adding LoggingFeature interceptor on bus: " + bus);
        LoggingFeature lf = new LoggingFeature();
        // initialise the feature on the bus, which will add the interceptors
        lf.initialize(bus);
        LOG.info("Successfully added LoggingFeature interceptor on bus: " + bus);
    }
}

Now we'll ask OSGi to call us whenever a new service with interface org.apache.cxf.Bus appears in the container. I'll use OSGi Blueprint for my IoC needs, so I create a file in OSGI-INF/blueprint with the following definition:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">

 <reference-list id="busListener" interface="org.apache.cxf.Bus" availability="optional">
  <reference-listener bind-method="busRegistered">
   <bean class="org.fusesource.examples.CXFBusListener" />
  </reference-listener>
 </reference-list>

</blueprint>

And we're good to go!

Now install the bundle (make sure that the start level is lower than the CXF Services you will register later) and watch the log file for the log statements, as soon as you register new services (or if you have any existing ones already):

        14:53:31,393 | INFO  | ExtenderThread-6 | CXFBusListener | 167 - cxf-transparent-interceptors - 0.0.1.SNAPSHOT | Adding LoggingFeature interceptor on bus: org.apache.cxf.bus.spring.SpringBus@2bf35e0a
        14:53:31,397 | INFO  | ExtenderThread-6 | CXFBusListener | 167 - cxf-transparent-interceptors - 0.0.1.SNAPSHOT | Successfully added LoggingFeature
        interceptor on bus: org.apache.cxf.bus.spring.SpringBus@2bf35e0a 
When you send a request to the Service, you will see the logging interceptors being "magically" applied even though you did not define them explicitly in the service:
--------------------------------------
14:56:59,732 | INFO  | tp1597033892-218 | LoggingInInterceptor             |  -  -  | Inbound Message
----------------------------
ID: 2
Address: http://localhost:8181/cxf/HelloWorld
Encoding: ISO-8859-1
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], Content-Length=[236], content-type=[text/xml], Host=[localhost:8181], User-Agent=[curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5]}
Payload: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cxf="http://cxf.examples.servicemix.apache.org/"><soapenv:Header/><soapenv:Body><cxf:sayHi><arg0>Raul</arg0></cxf:sayHi></soapenv:Body></soapenv:Envelope>
--------------------------------------
14:56:59,733 | INFO  | tp1597033892-218 | LoggingOutInterceptor            |  -  -  | Outbound Message
---------------------------
ID: 2
Encoding: ISO-8859-1
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:sayHiResponse xmlns:ns2="http://cxf.examples.servicemix.apache.org/"><return>Hello Raul</return></ns2:sayHiResponse></soap:Body></soap:Envelope>
--------------------------------------
        

And voilà!

Source code available here: https://github.com/raulk/cxf-transparent-interceptors

[NOTE - As Dan Kulp indicated: "If using CXF 2.6.x, it can be even easier. By default with CXF 2.6.x, when a Bus is created by OSGi, it looks in the Service registry for any service that implements org.apache.cxf.feature.Feature and will automatically apply those to the bus. Thus, you don't need the CXFBusListener at all. Just register your feature as a service."]

2 Responses so far.

  1. If using CXF 2.6.x, it can be even easier. By default with CXF 2.6.x, when a Bus is created by OSGi, it looks in the Service registry for any service that implements org.apache.cxf.feature.Feature and will automatically apply those to the bus. Thus, you don't need the CXFBusListener at all. Just register your feature as a service.

  2. Thanks, Dan! I updated the post with this new method. Is it documented somewhere? Can you define some criteria/filters - via OSGi service properties or so - to choose which CXF buses a feature will be applied to? If not, I can easily see this functionality being a bit dangerous otherwise.

Leave a Reply

Category

Category

Category

Category