Thursday, December 18, 2008

Sharing an Oracle JDBC pool in SMX4

In a previous post, I talked about how to share a JDBC pool using ServiceMix4; in that example, I used a Postgres connection pool. I wanted to the same with Oracle as the base driver and the commons-dbcp pooling library and went on my merry way. But then, disaster struck: I got a dreaded ClassNotFound exception. "Can this be?" I thought... isn't OSGi supposed to shield me from class-loading hell and put some colour back into my graying hair?

Delving in, I figured out what the issue was, and, I'm glad to say, the ServiceMix OSGi kernel was behaving exactly as it should. I had wrapped the commons-dbcp JAR without much thought: it turns out that code in the commons org.apache.commons.dbcp.BasicDataSource class (see below) is doing a Class.forName() call to load the oracle.jdbc.driver.OracleDriver class.

<bean id="oracle-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:BLAH"/>
<property name="username" value="blah"/>
<property name="password" value="blah"/>
<property name="maxActive" value="10"/>
<property name="poolPreparedStatements" value="true"/>
</bean>

Now, OSGi bundles have to specify the packages they import using the Import-Package tag. That's all well and good if you know what these packages are in advance. Poor commons-dbcp can't know in advance what kind of driver it should load, so Import-Package just doesn't work here. Instead, you've got to allow the commons-dbcp bundle to import anything: you can do this using the DynamicImport-Package tag, setting it to DynamicImport-Package: *.

We're currently patching the commons-dbcp bundle on the ServiceMix bundle repository so that it includes the DynamicImport-Package: * tag by default.

Wednesday, December 17, 2008

How to share a single JDBC Pool across SMX4 bundles

In the last week I have been working on a solution involving a number of Camel routes deployed into FUSE ESB 4 (ServiceMix4). The new OSGi archetypes for ServiceMix4 (servicemix-osgi-camel-archetype, servicemix-osgi-cxf-code-first-archetype and servicemix-osgi-cxf-wsdl-first-archetype) are really handy in that regard - nice work Ashwin Karpe! Those new archetypes have been checked in to SVN, but may not be in the release build yet so if you want them you may need to check them out and build them locally.

Because SMX4 is built on OSGi, you can deploy any Java artifact, not just JBI components. I put this to the test: my customer wanted to have a single, shared JDBC pool across all of his bundles. You can do this by creating the pool in a simple Spring file, and registering it as a service for all the other bundles to use. To do this, I installed the driver for PostgreSQL into my SMX container, using the nifty "wrap:" feature that will take a jar and osgi-ify it. Then, I created a Spring context file to intantiate the bean, like this:


<bean id="postgresPoolingDS" class="org.postgresql.ds.PGPoolingDataSource">
<property name="serverName" value="localhost"/>
<property name="databaseName" value="play"/>
<property name="portNumber" value="0"/>
<property name="user" value="play"/>
<property name="password" value="pa55w0rd"/>
<property name="dataSourceName" value="postgres"/>
<property name="initialConnections" value="1"/>
<property name="maxConnections" value="100"/>
</bean>

<osgi:service id="postgresPoolingDSService" ref="postgresPoolingDS"
auto-export="all-classes"/>


Here's the really neat bit: when you drop this spring file into the deploy directory of ServiceMix 4, the ServiceMix kernel will osgi-ify the file and deploy it as a bundle. Hey-presto: the pool is created and registered as a service in the OSGi service registry.

To access this service from one of my camel routes, I used the <osgi:reference> to get a reference to the dataSource in my bundle's spring file. You can then inject the reference into your route and away you go.


<osgi:reference id="dataSource" interface="javax.sql.DataSource" />


The "look-up" is done by interface type: in this case I'm OK as there's only one javax.sql.DataSource registered. I should really do a more explicit query, but I guess I'll leave that as an exercise for the interested reader.

For me, this kind of thing is OSGi at its best: allowing sharing and reuse of Java classes and Java objects in a really nice, modular way.

Monday, December 1, 2008

Take care when propagating transport headers through Camel!

Last week I worked with a fellow FUSE enthusiast on a very cool proof-of-concept showing how to route XML messages containing partially encrypted message payload. We used FUSE Services Framework (Apache CXF) to create a SOAP service and consumer, and used the nifty WSS4J interceptors (thanks to Glen Mazza's excellent blog entry on the subject) to encrypt selected elements of the SOAP message. CXF is such a great project - we got our service running really quickly without any trouble at all.

Feeling boisterous and buoyant with the taste of success, we forged ahead to put an intermediary Camel route between the service and the consumer, to do some content-based routing based on the non-encrypted part of the payload. This kind of X-Path routing is really easy to do with FUSE Medation Router (Apache Camel). Thinking that our coffee break was only minutes away we ran a quick test only to witness an exception on the SOAP consumer, deep in some HTTP-commons code: "bad chunk charachter '60'". Very strange, we thought: we knew that the unencrypted version worked fine. And, we know that it worked fine when the consumer talked directly with the service.

We investigated. A cursory look at the ASCII table shows that '60' is the code for '<'. Hmmm. Instantly this stank of something going on with the XML payload. But what? Our hopes for fresh coffee were now overrun by a burning desire to figure this out. We looked at all the angles, and eventually after an hour or so of scatching our heads, saw the light. The CXF consumer (the client!) was transmitting the payload using HTTP chunking: this has two effects at a payload level. First, a transport attribute, Transfer-Encoding, is set as "chunked"; second, the payload is then transmitted "chunked" by breaking it up into smaller chunks and transmitting each chunk preceded with a line containing a number indicating the size of the chunk.

CXF provides support for chunking, as do the Camel HTTP and Jetty components; so, the problem was not the chunking per se. Here's what was going on: the Camel route was receiving the payload and reassembling (or "unchunking") the content; however, our route was passing on the Transfer-Encoding header intact. So, when the payload arrived at the target service, the payload was unchunked, but the header suggested the opposite. The server read the first line of the payload to get the chunk size, got a '<', and correctly argued that this is an invalid character to describe the chunk size.

We disabled chunking on the client side in CXF, and then everything worked fine - effectively this just removes the problem by cutting it off at source. In general though, I think it raises a word of caution: be mindful of the headers you propagate through integration flows.