Back to blog contents

Blog

Oracle's malicious OracleDataSource implementation and why it fails in Tomcat

Janurary 2008

Today I wanted to play around a bit with Oracle 11gR1 by adding support for it in the imageloop.com code.

Ok, I've downloaded the database from Oracle, installed it, setup a database instance and a schema for imageloop - everything fine. Next I changed the JDBC driver to use the 11g JDBC drivers. Ooops! Something new! Only four files in $ORACLE_HOME/jdbc/lib : ojdbc5.jar, ojdbc5_g.jar, ojdbc6.jar, ojdbc6_g.jar. Seems, that they dropped support for Java < 5. That's fine for me. But there must be a reason for it ... and there is a reason. A new bug ... aem sorry ... a new feature...

Next I just started my local Tomcat 6 web container containing the web application and it failed to start.
SEVERE: Error while registering Oracle JDBC Diagnosability MBean.
javax.management.MalformedObjectNameException: Invalid character '
' in value part of property
   at javax.management.ObjectName.construct(ObjectName.java:602)
   at javax.management.ObjectName.<init>(ObjectName.java:1394)
   at oracle.jdbc.driver.OracleDriver.registerMBeans(OracleDriver.java:303)
   at oracle.jdbc.driver.OracleDriver$1.run(OracleDriver.java:213)
   at java.security.AccessController.doPrivileged(Native Method)
   at oracle.jdbc.driver.OracleDriver.<clinit>(OracleDriver.java:209)
   at oracle.jdbc.pool.OracleDataSource.<clinit>(OracleDataSource.java:94)
Hmm...

I google'd and RTFM'd a bit ... no solution. But some investigation:

JDBC in 11g introduces diagnosability - cofigurable logging and such stuff. It works by registering an MBean (see javax.management) with the name pattern "com.oracle.jdbc:type=diagnosability,name="+loader, where loader is constructed from Thread.currentThread().getContextClassLoader().toString(). (Here's the Oracle code)

But if the string contains invalid characters, such as \r or \n the class javax.management.ObjectName (correctly) throws an exception. (OK, Oracle thought a little little bit in that direction and replace these characters with spaces: , = : " - but they did not think enough!)

So - where does this Oracle pattern fail? It fails in Tomcat when you programatically create the data source from in the web container! Oops - yeah - it fails in your favorite(?) web container - and probably in many more environments! Why? org.apache.catalina.loader.WebappClassLoader is the (context) class loader for web applications in Tomcat. org.apache.catalina.loader.WebappClassLoader.toString() returns a meaningful description in its implementation of toString() telling a lot of information ready to be dumped to console - INCLUDING NEW LINES:
WebappClassLoader\r\n
  delegate: false\r\n
  repositories:\r\n
    /WEB-INF/classes/\r\n
----------> Parent Classloader:\r\n
org.apache.catalina.loader.StandardClassLoader@12ad19e\r\n

And that's the reason why Oracle's OracleDataSource fails.

--flame off--
OK - now I code more than 20 years around bugs, that other people injected in their code... ;-)
(and there are people coding around the bugs (aem - "undocumented features") I added to my code...)
--flame on--

Workaround?!!

The following workaround is tricky - but works.

There is a workaround - but it requires you to code your own implementation of javax.sql.DataSource. It works around the issue, that the Oracle code performs a call to toString() on the context class loader. So - all we have to do is write our own class loader and override toString() and load the class oracle.jdbc.pool.OracleDataSource from our class loader. But it is important to remove all references to oracle.jdbc.pool.OracleDataSource and its derived classes from the code.

The workaround creates a new class loader, loads an init delegate through that class loader and calls the init delegate through a thread with the context class loader set to the new class loader.

  1. Your own implementation of javax.sql.DataSource (just as a delegate to the oracle data source)
  2. Move instantiation of oracle.jdbc.pool.OracleDataSource (and derived classes like oracle.jdbc.pool.OracleOCIConnectionPool, oracle.jdbc.pool.OracleConnectionPoolDataSource, oracle.jdbc.xa.OracleXADataSource and oracle.jdbc.xa.client.OracleXADataSource) to a new class.
  3. Instantiate the data source in your new class
  4. Maybe special permissions in your (web/J2EE) container to create your own class loader.

Let's assume you already have your implementation of javax.sql.DataSource called MyDataSource. All you need to do is to write a class, that implements javax.sql.DataSource and delegate all method calls to the "real" data source.

Next you write a new class MyDataSourceInitDelegate:

import oracle.jdbc.pool.OracleDataSource;

public final class MyDataSourceInitDelegate implements Runnable {
    private Object dataSource;

    private final IlDataSource ids;

    public IlDataSourceDelegateInit(MyDataSource ids) {
        this.ids = ids;
    }

    @SuppressWarnings("nls")
    public void run() {
        try {
            OracleDataSource ds = new OracleDataSource();
            // configure the oracle data source...
            ds.setXyx(abc);
            // ...
            this.dataSource = ds;
        } catch (Throwable t) {
            this.dataSource = t;
        }
    }

    public Object getDataSource() {
        return this.dataSource;
    }
}

In MyDataSource you replace the code new OracleDataSource() with a call to the delegate class above. It is important to mention that the class oracle.jdbc.pool.OracleDataSource and derived classes (see above) have not been loaded before.

public void initializeDataSource() throws Exception {
    Thread t = Thread.currentThread();
    ClassLoader ccl = t.getContextClassLoader();
    ClassLoader ccl2 = new ClassLoader(ccl) {
        @Override
        public String toString() {
            return "buggy-oracle-code-wrapper";
        }
    };

    Class<?> cls = ccl2.loadClass("com.imagelooop.tools.IlDataSourceDelegateInit");
    Constructor<?> ctor = cls.getConstructor(new Class[] { this.getClass() });
    Object dsInitDelegate = ctor.newInstance(new Object[] { this });
    Thread exec = new Thread((Runnable) dsInitDelegate);
    exec.setContextClassLoader(ccl2);
    exec.start();
    while (exec.isAlive()) {
        Thread.sleep(1);
    }

    Method m = cls.getMethod("getDataSource", new Class[0]);
    Object ds = m.invoke(dsInitDelegate, new Object[0]);

    if (ds instanceof Throwable)
        throw new Error((Throwable) ds);

    dataSource = (DataSource) ds;
}

What Oracle should do!

Oracle should think about their implementation to generate the name for the MBean. In general, it is a very good idea to add the id of the class loader to the name of the MBean - but the implementation is very bad - ClassLoader.toString() is not the way.

I would recommend the following implementation, which does not break existing code and works in almost any environment. It first tries to construct the name using the documented approach. If that fails, it uses a new approach using java.lang.System.identityHashCode()
    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    javax.management.ObjectName name;
    try {
        String loader = ccl.toString().replaceAll("[,=:\"]+", "");
        name = new javax.management.ObjectName("com.oracle.jdbc:type=diagnosability,name="+loader);
    }
    catch (javax.management.MalformedObjectNameException mone) {
        int hash = System.identityHashCode(ccl);
        String sName = "com.oracle.jdbc:type=diagnosability,name=contextClassLoader-"+cclHash;
        name = new javax.management.ObjectName(sName);
    }
    ...

Since the same code must be used by both the OracleDataSource and application code, I would recommend to add static methods to the Oracle JDBC driver code in a tool class that does not reference the OracleDataSource or one of its derived classes:

    public static final javax.management.ObjectName getDiagnosabilityMBeanName() {
        getDiagnosabilityMBeanName(Thread.currentThread().getContextClassLoader());
    }
    public static final javax.management.ObjectName getDiagnosabilityMBeanName(ClassLoader cl) {
        // (code as above but with ccl replaced with the passed in parameter)
    }