How to write a new driver wrapper for JOnAS

Introduction

The goal of this document is to describe how to write a new driver based on the work made for the new database manager. This document is divided into 3 parts. The first part describes how to write a driver with the defined framework. The second section describes how to extend the current driver wrapper for other databases. And the last section describes the current extensions made with the driver wrapper.

The contents of this guide are the following:

  1. Introduction
  2. Architecture
  3. A driver with the framework
  4. How to extend the driver wrapper
  5. Current extensions
  6. Jonas[newDriver]XADataSource
  7. Appendix A : Informix implementation

Architecture

There are many possibilities to use the new database manager from Jonas. All the possibilities work with a pool of connection which is an extension of the framework.

The first possibility is the default, the database manager uses the Standard class which uses the standard wrapper. In this case, we need to have a JDBC 1.0 driver. This way is the common way to work with the new database Manager. But you can use the second possibility to extend the driver wrapper for special works with some database. It is the case with Informix and Sybase, these databases need some hacks to work. In these cases, the database manager uses a special class (Informix or Sybase) which uses the standard wrapper.

The third possibility is to extend the driver wrapper for the InstantDB database. In this case, the database manager uses an InstantDB hat to extend the standard wrapper. We obtain, then, a JDBC 2.0 driver when we uses the InstantDB database.

The last possibility is to use a JDBC 2.0 driver. Oracle has a JDBC 2.0 driver and you can use it with the database manager from Jonas. In this case, the database manager needs to work with some special extensions (hats), the XADataSource, XAConnection, XAPreparedStatement and XAStatement are extended to do the glue between the database manager from Jonas and the JDBC 2.0 driver from Oracle.

The next section explains what is a framework.

The "How to extend the driver wrapper" section explains the cases of Informix and Sybase, and describes the Jonas hats, need to do the glue between the database manager from Jonas and the driver wrapper or a JDBC 2.0 driver.

A driver with the framework

The core package is defined as the framework of the Jdbc Service. The core virtual driver is made of a set of classes implementing jdbc2 classes. Most of those classes implement the interface by delegation. Other classes are abstract. It makes the foundation classes for all virtual drivers including virtual drivers implementing standard extensions, implementations of a pool, wrappers for instrumentation of existing drivers. There are no optimizations in this package. These classes have to be extented to write the corresponding driver.

How to extend the driver wrapper

Some classes have to be extended to support an extension of the wrapper : See Appendix A for the implementation of the Informix extension.

Current extensions

The current extensions of the driver wrapper are the following :

Informix

The driver wrapper is extended to add a method about transaction and query timeout.

Transaction and Query timeout are set with the same execute statement but we want to be able to set different values for query and transaction timeout.

This new method is integrated into the InformixConnectionHandle class, which extends StandardConnectionHandle class. The package is org.enhydra.jdbc.informix.

Sybase

The driver wrapper is extended to modify the setAutoCommit method in the StandardConnectionHandle. The process is the same. We extend this class in SybaseConnectionHandle to overwrite the method :
    package org.enhydra.jdbc.sybase;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;
    import java.util.*;

    public class SybaseConnectionHandle extends StandardConnectionHandle {

        public SybaseConnectionHandle (SybasePooledConnection pooledCon, Hashtable preparedStatementCache) {
          super (pooledCon, preparedStatementCache);
        } // constructor

        synchronized public void setAutoCommit(boolean autoCommit) throws SQLException {
          preInvoke();
          try {
            con.commit();
            con.setAutoCommit(autoCommit);
          } catch (SQLException e) {
            catchInvoke(e);
          }
        } // method setAutoCommit
    } // class SybaseConnectionHandle

Oracle (for Jonas)

The Oracle extension is made to set up some properties which can not be set by the *XAPoolDataSource and their factories. We have to define in this class, the getReference and getObjectInstance methods for JNDI lookup in the *XAPoolDataSource object. Here, we call the setURL method instead of the setUrl method defined in the Standard wrapper.

This extension only works with the Oracle 8.1.6 version and the classes12.zip file which contains the JDBC 2.0 driver from Oracle.

    package org.objectweb.jonas.dbm;

    import oracle.jdbc.xa.client.OracleXADataSource;
    import org.objectweb.jonas.dbm.CommonDataSource;
    import java.sql.*;
    import javax.sql.*;
    import javax.naming.*;
    import javax.naming.spi.*;
    import java.util.Hashtable;

    public class JonasOracleXADataSource extends OracleXADataSource implements Referenceable, ObjectFactory,CommonDataSource {

        public JonasOracleXADataSource () throws SQLException{
            super();
        } // constructor
    
        public void setProperties(String url, String classname, String user, String password) throws SQLException {
            setURL(url);
            setUser(user);
            setPassword(password);
        } // method setProperties

        public Reference getReference() throws NamingException {
            // Note that we use getClass().getName() to provide the factory
            // class name. It is assumed that this class, and all of its
            // descendants are their own factories.

            Reference ref = new Reference(getClass().getName(), getClass().getName(), null);
            try {
                ref.add(new StringRefAddr("url", getURL()));
            
            } catch (SQLException ef) {
                throw new NamingException ("Exception JonasOracleXADataSource:getReference"); 
            }
            ref.add(new StringRefAddr("user", getUser()));
            ref.add(new StringRefAddr("password", getPassword()));

            return ref;
        } // method getReference
    
        /**
         * Methods inherited from ObjectFactory
         */
        public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env) throws Exception {
            Reference ref = (Reference)refObj;
            this.setURL((String)ref.get("url").getContent());
            this.setUser((String)ref.get("user").getContent());
            this.setPassword((String)ref.get("password").getContent());
 
            return this;
        } // method getObjectInstance

        public void setTransactionManager(TransactionManager tm) {}

        public void setDebug(boolean debug) {}

        public void setLogWriter(PrintWriter logWriter) {}
    } // class JonasOracleXADataSource
The JonasOracleXADataSource class is used in Jonas (Oracle1.properties) with the datasource.factory section :
 
 
    datasource.factory     org.objectweb.jonas.dbm.JonasOracleXADataSource

InstantDB

The InstantDB extension is needed to work with true XA driver for InstantDB database. 2 classes are extended. IdbXADataSource extends StandardXADataSource, and is going to work with IdbXAConnection object which is an extension of StandardXAConnection object. With these 2 classes, the wrapper becomes now a driver.

Jonas[newDriver]XADataSource

Common interface for XADataSource

This new object is used to implement an interface which defines a setProperties method to set up the following properties :
    package org.objectweb.jonas.dbm;
    import java.sql.*;

    public interface CommonDataSource {

        public void setProperties(String url, String classname, String user, String password) throws SQLException;
        public void setTransactionManager(TransactionManager tm);
        public void setDebug(boolean debug);
        public void setLogWriter(PrintWriter logWriter);
        public void setMinCon(int min);
        public void setMaxCon(int max);
    } // interface CommonDataSource
The setTransactionManager method is used to set up the transaction manager into the datasource. The setDebug method is used to set up if or not, the debug information will be displayed on the stream. The setLogWriter method is used to set up the logwriter object. The setMinCon method is used to set up the minimum of physical connection, and the setMaxCon method is used to set up the maximum of physical connection.

i.e. InstantDB

    package org.objectweb.jonas.dbm.instantdb;

    import org.objectweb.jonas.dbm.CommonDataSource;
    import org.enhydra.jdbc.instantdb.IdbXADataSource;
    import java.sql.*;

    public class JonasIdbXADataSource extends IdbXADataSource implements CommonDataSource {

        public void setProperties(String url, String classname, String user, String password) throws SQLException {
            setUrl(url);
            try {
                setDriverName(classname);
            } catch (Exception e) {
                log("Exception JonasIdbXADataSource:setProperties "+e);
                throw new SQLException("Exception JonasIdbXADataSource:setProperties ");
            }
            setUser(user);
            setPassword(password);

        } // method setProperties
    } // class JonasIdbXADataSource

Changes in Jonas

The [driver].properties file is used to specify the factory for the data source :
    datasource.factory     org.objectweb.jonas.dbm.Jonas[Driver]XADataSource
The previous class must implement the CommonDataSource interface. This interface is used in the DataBaseServiceImpl class to bind into JNDI the factory of the data source :
    try {
        CommonDataSource connect = (CommonDataSource)(Class.forName(xaFactory).newInstance());
        connect.setProperties(url, 
                              dsd.getProperty(CLASS_NAME),
                              dsd.getProperty(USER_NAME), 
                              dsd.getProperty(PASSWORD));
        connect.setTransactionManager(TransactionServiceImpl.getInstance().getTransactionManager());
        connect.setMinCon((new Integer(dsd.getProperty(MINCON, DEFAULT_MINCON))).intValue());
        connect.setMaxCon((new Integer(dsd.getProperty(MAXCON, DEFAULT_MAXCON))).intValue());
        connect.setDebug(debug);
    
        ds.setLogWriter(Trace.getLogWriter());
        connect.setLogWriter(Trace.getLogWriter());
        ds.std = (XADataSource)connect;
        ictx.rebind(xaName, connect);
        xadsList.put(xaName, connect);
    } catch ...

Appendix A : Informix implementation

InformixConnectionHandle.java

    package org.enhydra.jdbc.informix;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;
    import java.util.*;

    public class InformixConnectionHandle extends StandardConnectionHandle {

        private int lockModeWait = 0;   // The amound time to wait on a query or transaction

        public InformixConnectionHandle (InformixPooledConnection pooledCon, Hashtable preparedStatementCache) {
            super (pooledCon, preparedStatementCache);
        } // constructor

        public synchronized void setLockModeToWait(int seconds) throws SQLException {
            if (lockModeWait != seconds) {
                if (seconds >0) {
                    execute("SET LOCK MODE TO WAIT " + seconds);
                } else if (seconds == 0) {
                    execute("SET LOCK MODE TO NOT WAIT ");
                } else {
                    execute("SET LOCK MODE TO WAIT ");
                }
                lockModeWait = seconds;
            }
        } // method setLockModeToWait

        public synchronized void execute (String sql) throws SQLException {
            Statement stat = createStatement();
            stat.execute(sql);
        } // method execute

    } // class InformixConnectionHandle

InformixConnectionPoolDataSource.java

    package org.enhydra.jdbc.informix;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;
    import javax.sql.*;

    public class InformixConnectionPoolDataSource extends StandardConnectionPoolDataSource {
    
        /**
         * Create a pooled connection using the default username and password.
         */
        public PooledConnection getPooledConnection () throws SQLException {
            log("InformixConnectionPoolDataSource:getPooledConnection(0) return a pooled connection");
            return getPooledConnection (user, password);
        } // method getPooledConnection
    
        /**
         * Create a informix pooled connection using the supplied username and password.
         */
        public PooledConnection getPooledConnection (String user, String password) throws SQLException {
            log("InformixConnectionPoolDataSource:getPooledConnection(2) return a pooled connection");
            return new InformixPooledConnection ((ConnectionPoolDataSource)this, user, password);
        } // method getPooledConnection

    } // class InformixConnectionPoolDataSource

InformixPooledConnection.java

    package org.enhydra.jdbc.informix;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;
    import javax.sql.*;

    public class InformixPooledConnection extends StandardPooledConnection {
    
        public InformixPooledConnection (ConnectionPoolDataSource dataSource, String user, String password) throws SQLException {
            super((StandardConnectionPoolDataSource)dataSource, user, password);
        } // constructor

        protected void newConnectionHandle() {
            connectionHandle = new InformixConnectionHandle (this, preparedStatements);
        } // method newConnectionHandle

    } // class InformixPooledConnection

InformixXAConnection.java

    package org.enhydra.jdbc.informix;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;

    public class InformixXAConnection extends StandardXAConnection {
    
        /**
         * Creates the first free connection.
         */
        public InformixXAConnection (InformixXADataSource dataSource, String user, String password) throws SQLException {
            super (dataSource, user, password); // creates the first Connection object
        
            // Save the constructor parameters.
            this.dataSource = dataSource;
            curCon = new InformixXAStatefulConnection (dataSource, con);// wrap connection as a stateful connection
        
            // NOTE - the current connection is not made known to the data source
            // so it is not eligible for re-use. It only goes on the data source list
            // if it ever becomes associated with a global transaction.
            timerThread = new Thread (this);    // create the backgroup thread to check for timeouts

            timerThread.start();                        // start the timer thread
            timerThread.suspend();                      // and suspend until some timeouts get set up
            dataSource.log("InformixXAConnection created");
        } // constructor

    } // class InformixXAConnection

InformixXADataSource.java

    package org.enhydra.jdbc.informix;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;
    import javax.sql.*;

    public class InformixXADataSource extends StandardXADataSource {

        /**
         * Creates an XA connection using the default username and password.
         */
        public XAConnection getXAConnection () throws SQLException {
            log("InformixXADataSource:getXAConnection(0) XA connection returned");
            return getXAConnection (user, password);
        } // method getPooledConnection
    
        /**
         * Creates an XA connection using the supplied username and password.
         */
        public XAConnection getXAConnection (String user, String password) throws SQLException {
            InformixXAConnection xac = new InformixXAConnection (this, user, password);
            connectionCount++;
            log("InformixXADataSource:getXAConnection(2) XA connection returned");
            return xac;
        } // method getPooledConnection
    
        /**
         * Create a pooled connection using the default username and password.
         */
        public PooledConnection getPooledConnection () throws SQLException {
            log("InformixConnectionPoolDataSource:getPooledConnection(0) return a pooled connection");
            return getPooledConnection (user, password);
        } // method getPooledConnection
    
        /**
         * Create a informix pooled connection using the supplied username and password.
         */
        public PooledConnection getPooledConnection (String user, String password) throws SQLException {
            log("InformixConnectionPoolDataSource:getPooledConnection(2) return a pooled connection");
            return new InformixPooledConnection (this, user, password);
        } // method getPooledConnection

    } // class InformixXADataSource

InformixXAStatefulConnection.java

    package org.enhydra.jdbc.informix;

    import org.enhydra.jdbc.standard.*;
    import java.sql.*;
    import javax.transaction.Status;

    public class InformixXAStatefulConnection extends StandardXAStatefulConnection {

        /**
         * Creates a new stateful connection in the FREE state (NO_TRANSACTION)
         */
        InformixXAStatefulConnection (InformixXADataSource dataSource, Connection con) {
            super((StandardXADataSource)dataSource, con);
        } // method InformixXAStatefulConnection

    } // class InformixXAStatefulConnection

JonasInformixXADataSource.java

    package org.objectweb.jonas.dbm;

    import org.objectweb.jonas.dbm.CommonDataSource;
    import org.enhydra.jdbc.standard.StandardXADataSource;
    import java.sql.*;
    import javax.sql.*;
    import javax.naming.*;
    import javax.naming.spi.*;
    import java.util.Hashtable;

    public class JonasInformixXADataSource extends StandardXADataSource implements CommonDataSource {

        public StandardInformixXADataSource () throws SQLException {
            super();
        } // constructor
    
        public void setProperties(String url, String classname, String user, String password) throws SQLException {
            setUrl(url);
            setUser(user);
            setPassword(password);
            try {
                setDriverName(classname);
            } catch (SQLException e) {
                log("Exception JonasInformixXADataSource:setProperties "+e);
                throw new SQLException("Exception JonasInformixXADataSource:setProperties ");
            }
        } // method setProperties

    } // class JonasInformixXADataSource