Friday, March 30, 2012

How to use JDBC driver in OSGi container

One frequently asked question on the FuseSource forum and several Apache projects like Apache servicemix/Karaf/Camel/CXF which I'm working on is that how to use JDBC driver in OSGi container? why I always encounter ClassNotFoundException?

Users usually use some third party libs like spring-jdbc or commons-dbcp to maintain the connection pool, so they configure it like
<bean id="databaseConnection" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/schema" />
<property name="username" value="***" />
<property name="password" value="***" />
</bean>

or

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver.class}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

Those beans always work in standalone  mode, but when users deploy it in OSGi container like FUSE ESB or Apache Servicemix/Karaf, they will see the JDBC driver ClassNotFoundException.

This error actually is from how OSGi classloader works, say, if bundleA need use some package, it need explicitly import it before hand.

However,  if a bundle(let's say it bundle A) using some code like classForName(jdbc_driver_classname) to init a jdbc driver class, this is a big issue in OSGi world, as if BundleA not import package for jdbc_driver_classname, you'll see the problem. Unfortunately BundleA actually can't know about package name for jdbc_driver_classname beforehand, as the jdbc_driver_classname generally is passed in through configuration during runtime, it can't be determined during build time, so you can't add correct package import when you generate bundle A.

Back to our original issue, spring-jdbc and  commons-dbcp is Bundle A here.

How to resolve it?

Well, we have several options

1.  Put all possible necessary packages in Import-Package OSGi metadata header, this is exactly how spring-jdbc do .  spring-jdbc optionally import packages like
    com.ibm.websphere.rsadapter;resolution:=optional;version=0,
    com.ibm.ws.rsadapter.jdbc;resolution:=optional;version=0,
    com.mchange.v2.c3p0;resolution:=optional;version="[0.9.1, 2.0.0)",
    oracle.jdbc;resolution:=optional;version=0,
    oracle.sql;resolution:=optional;version=0,
    org.apache.derby.impl.io;resolution:=optional;version="[10.5.0, 11.0.0)",
    org.apache.derby.jdbc;resolution:=optional;version="[10.5.0, 11.0.0)",
    org.h2;resolution:=optional;version="[1.0.0, 2.0.0)",
    org.hsqldb;resolution:=optional;version="[1.8.0, 2.0.0)",
    org.jboss.resource.adapter.jdbc;resolution:=optional;version=0,
    weblogic.jdbc.extensions;resolution:=optional;version=0   
    com.sun.rowset;resolution:=optional;version="[1.0.1, 2.0.0)",
pretty much all popular used JDBC drivers is available, but not include  mysql.
Of course we can hack the bundleA to add any Import-Package we want,  it's not a big deal as we always hack, but it's not very graceful, especially sometimes we can't hack  some bundles for license reason.

2. Make bundleA have DynamicImport-Package OSGi headers,  this is the way how Servicemix wrapped commons-dbcp do.It works, but we all know using DynamicImport-Package isn't the best practice in OSGi world. To support JDBC out of the box, we have to do some trade-off.

3. What I prefer to is the fragment bundle way, that said, you create JDBC driver as a fragment bundle, and attach it to bundle A(bundle A is host bundle now), so that all resource from fragment bundle is available for the host bundle. This way you needn't hack bundleA.

Finally, I'd say the fragment bundle is very useful when you try to get around some legacy libs in OSGi, it allow you pass the classname during runtime between bundle(like the JDBC driver we discuss here), it also can allow you to pass some resource files between bundles, I hope I can find some time to elaborate this issue later one.

4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hello,

    I was struggling with the same problem, and your article gave me some hope that it can be solved somehow. However, none of your three solutions helped. The only thing how I managed to fix it was by using SimpleDriverDataSource instead of DriverManagerDataSource. I have not investigated what is the difference among them, I am happy that is is working.

    Ivan

    ReplyDelete
  3. Thanks so much for the resolution.below is the Import-packages I used as suugested in the your article to solve the problem.

    oracle.jdbc;resolution:=optional;version=0,
    oracle.sql;resolution:=optional;version=0,
    oracle.jdbc;resolution:=optional;version=0,
    oracle.sql;resolution:=optional;version=0,

    ReplyDelete
  4. I read your article, I could understand the explanation you have given. It would be great if you could help me on understanding what is OSGi Compliance and how to achieve it for the java jdbc driver?

    I am in a project wherein, my driver to have the OSGi compliance and give it to customer. I appreciate any help on this.

    Attaching my problem statement and requirement for your reference.


    Problem Statement
    Currently the Java tamil drivers ( packaged as tamiljdbc.jar, tamiljdbc_licnese__3.63.56) are not OSGi compliant. They do not have OSGi headers in the manifest file. In order to reference these drivers from a Redhat Fuse application container (Service Mix) the jar files need to be manually installed into the container using the following steps:

    1. osgi:install -s wrap:mvn:com.ibm.tamil/tamiljdbc/9.7
    2. osgi:install -s wrap:mvn:com.ibm.tamil/tamiljdbc_license_/9.7 ( “wrap” is issued to modified the manifest file of each bundle to be OSGi compliant , this exports all classes within the bundle)
    3. dev:dynamic-import ( This setting allows OSGi to resolve dependencies using any of the bundles already installed in the container, effectively bypassing the usual dependency resolution mechanism (based on the Import-Package header). This is not recommended for normal deployment, because it bypasses version checks: you could easily pick up the wrong version of a package, causing your application to malfunction.)
    4. osgi:start

    Existing applications such as GIO have to run these steps manually for each Service Mix instance. GIO currently has more than 50 Service Mix instances.

    Requirement :

    Provide OSGi compliant tamil drivers. This would enable the following functionality:
    · OSGi management of the tamil drivers within a Service Mix container.
    · A more accurate dependency resolution of the drivers at install time.
    · Multiple versions of the driver can also be deployed into the same Service Mix instance.

    ReplyDelete