Commits

Grace Batumbya committed 0c68cec

Starting 'connectionpool_support' branch

Comments (1)

Files changed (4)

src/main/java/org/sqlite/SQLiteConnectionPoolDataSource.java

+package org.sqlite;
+import java.sql.Connection;
+import java.sql.SQLException;
+import javax.sql.PooledConnection;
+import javax.sql.XAConnection;
+
+//******************************************************************************
+//**  SQLiteConnectionPoolDataSource
+//******************************************************************************
+/**
+ *   A data source for connection pools. It is a factory for XAConnection
+ *   and Connection objects. This class is usually registered in a JNDI naming
+ *   service.
+ * 
+ *   Partial port of H2 JdbcDataSource:
+ *   http://code.google.com/p/h2database/source/browse/trunk/h2/src/main/org/h2/jdbcx/JdbcDataSource.java?r=716
+ *
+ ******************************************************************************/
+
+public class SQLiteConnectionPoolDataSource extends SQLiteDataSource implements javax.sql.ConnectionPoolDataSource {
+
+    
+    //private static final long serialVersionUID = 1288136338451857771L;
+
+    private transient SQLiteDataSourceFactory factory;
+    //private transient PrintWriter logWriter;
+    //private int loginTimeout;
+    //private String user = "";
+    //private String password = "";
+    //private String url = "";
+
+
+  //**************************************************************************
+  //** Constructor
+  //**************************************************************************
+  /** Creates a new instance of ConnectionPoolDataSource. */
+
+    public SQLiteConnectionPoolDataSource() {
+        super();
+        initFactory();
+    }
+
+    public PooledConnection getPooledConnection() throws SQLException {
+        //return new SQLitePooledConnection();
+        return getXAConnection();
+    }
+
+    public PooledConnection getPooledConnection(String user, String password) throws SQLException {
+        //return new SQLitePooledConnection();
+        return getXAConnection(user, password);
+    }
+
+    public XAConnection getXAConnection() throws SQLException {
+        //int id = getNextId(XA_DATA_SOURCE);
+        return getXAConnection(null, null);
+    }
+
+
+    public XAConnection getXAConnection(String user, String password) throws SQLException {
+        //int id = getNextId(XA_DATA_SOURCE);
+        return new SQLitePooledConnection(factory, getJdbcConnection(user, password), this.getConfig().toProperties());
+    }
+
+    private void initFactory() {
+        factory = new SQLiteDataSourceFactory();
+    }
+
+
+    /**
+     * Open a new connection using the current URL, user name and password.
+     *
+     * @return the connection
+     */
+    public Connection getConnection() throws SQLException {
+        //debugCodeCall("getConnection");
+        return getJdbcConnection(null, null);
+    }
+
+    /**
+     * Open a new connection using the current URL and the specified user name
+     * and password.
+     *
+     * @param user the user name
+     * @param password the password
+     * @return the connection
+     */
+    public Connection getConnection(String user, String password) throws SQLException {
+        //if (isDebugEnabled()) {
+        //    debugCode("getConnection("+quote(user)+", "+quote(password)+");");
+        //}
+        return getJdbcConnection(user, password);
+    }
+
+    private Conn getJdbcConnection(String user, String password) throws SQLException {
+        //if (isDebugEnabled()) {
+        //    debugCode("getJdbcConnection("+quote(user)+", "+quote(password)+");");
+        //}
+        //Properties info = new Properties();
+        //info.setProperty("user", user);
+        //info.setProperty("password", password);
+
+        String url = this.getUrl();
+        String fileName = url.substring(JDBC.PREFIX.length());
+        if (fileName.startsWith("//")) fileName = fileName.substring(2);
+
+        
+        return new Conn(url, fileName, this.getConfig().toProperties());
+    }
+
+
+}

src/main/java/org/sqlite/SQLiteDataSourceFactory.java

+package org.sqlite;
+
+//******************************************************************************
+//**  SQLiteDataSourceFactory
+//******************************************************************************
+/**
+ *   This class is used to create new DataSource objects. Port from 
+ *   JdbcDataSourceFactory:
+ * 
+ *   http://code.google.com/p/h2database/source/browse/trunk/h2/src/main/org/h2/jdbcx/JdbcDataSourceFactory.java
+ *
+ ******************************************************************************/
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+//import org.h2.constant.SysProperties;
+//import org.h2.engine.Constants;
+//import org.h2.message.Trace;
+//import org.h2.message.TraceSystem;
+
+/**
+ * This class is used to create new DataSource objects.
+ * An application should not use this class directly.
+ */
+public class SQLiteDataSourceFactory implements ObjectFactory {
+
+    //private static TraceSystem cachedTraceSystem;
+    //private Trace trace;
+
+    static {
+        //org.h2.Driver.load();
+    }
+
+    /**
+     * The public constructor to create new factory objects.
+     */
+    public SQLiteDataSourceFactory() {
+        //trace = getTraceSystem().getTrace("JDBCX");
+    }
+
+    /**
+     * Creates a new object using the specified location or reference
+     * information.
+     *
+     * @param obj the reference (this factory only supports objects of type
+     *            javax.naming.Reference)
+     * @param name unused
+     * @param nameCtx unused
+     * @param environment unused
+     * @return the new JdbcDataSource, or null if the reference class name is
+     *         not JdbcDataSource.
+     */
+    public synchronized Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
+        //if (trace.isDebugEnabled()) {
+            //trace.debug("getObjectInstance obj={0} name={1} nameCtx={2} environment={3}", obj, name, nameCtx, environment);
+        //}
+        if (obj instanceof Reference) {
+            Reference ref = (Reference) obj;
+            if (ref.getClassName().equals(SQLiteDataSource.class.getName())) {
+                SQLiteDataSource dataSource = new SQLiteDataSource();
+
+
+                dataSource.setUrl((String) ref.get("url").getContent());
+
+                try{
+                String s = (String) ref.get("loginTimeout").getContent();
+                dataSource.setLoginTimeout(Integer.parseInt(s));
+                }
+                catch(Exception e){}
+                return dataSource;
+            }
+        }
+        return null;
+    }
+}

src/main/java/org/sqlite/SQLitePooledConnection.java

+package org.sqlite;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.sql.XAConnection;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.sql.StatementEventListener;
+
+//******************************************************************************
+//**  SQLitePooledConnection
+//******************************************************************************
+/**
+ *   This class provides support for distributed transactions. An application
+ *   developer usually does not use this interface. It is used by the
+ *   transaction manager internally.
+ *
+ *   Port from H2 JdbcXAConnection:
+ *   http://code.google.com/p/h2database/source/browse/trunk/h2/src/main/org/h2/jdbcx/JdbcXAConnection.java
+ *
+ ******************************************************************************/
+
+public class SQLitePooledConnection implements XAConnection, XAResource {
+
+
+    private SQLiteDataSourceFactory factory;
+
+    // this connection is kept open as long as the XAConnection is alive
+    private Conn physicalConn;
+
+    // this connection is replaced whenever getConnection is called
+    private volatile Connection handleConn;
+    private ArrayList<ConnectionEventListener> listeners = arrayList();
+    private Xid currentTransaction;
+
+
+    java.util.Properties config;
+
+
+    //Port from TraceObject.java
+    protected static final int XID = 15;
+    protected static final int ARRAY = 16;
+    private static final int LAST = ARRAY + 1;
+    private static final int[] ID = new int[LAST];
+    protected static int getNextId(int type) {
+        return ID[type]++;
+    }
+    
+
+    //Port from New.java
+    public static <T> ArrayList<T> arrayList() {
+        return new ArrayList<T>(4);
+    }
+
+
+  //**************************************************************************
+  //** Constructor
+  //**************************************************************************
+  /** Creates a new instance of SQLitePooledConnection. */
+
+    public SQLitePooledConnection(SQLiteDataSourceFactory factory, Conn physicalConn, java.util.Properties config) {
+        this.factory = factory;
+        //setTrace(factory.getTrace(), TraceObject.XA_DATA_SOURCE, id);
+        this.physicalConn = physicalConn;
+        this.config = config;
+    }
+
+    public XAResource getXAResource() {
+        debugCodeCall("getXAResource");
+        return this;
+    }
+
+    /**
+     * Close the physical connection.
+     * This method is usually called by the connection pool.
+     *
+     * @throws SQLException
+     */
+    public void close() throws SQLException {
+        Connection lastHandle = handleConn;
+        if (lastHandle != null) {
+            listeners.clear();
+            lastHandle.close();
+        }
+        if (physicalConn != null) {
+            try {
+                physicalConn.close();
+            } finally {
+                physicalConn = null;
+            }
+        }
+    }
+
+
+    /**
+     * Get a connection that is a handle to the physical connection. This method
+     * is usually called by the connection pool. This method closes the last
+     * connection handle if one exists.
+     *
+     * @return the connection
+     */
+    public Connection getConnection() throws SQLException {
+        debug("getConnection()");
+        Connection lastHandle = handleConn;
+        if (lastHandle != null) {
+            lastHandle.close();
+        }
+        // this will ensure the rollback command is cached
+        //physicalConn.rollback();
+        handleConn = new PooledJdbcConnection(physicalConn, config);
+        return handleConn;
+    }
+    /**
+     * Get the list of prepared transaction branches.
+     * This method is called by the transaction manager during recovery.
+     *
+     * @param flag TMSTARTRSCAN, TMENDRSCAN, or TMNOFLAGS. If no other flags are set,
+     *  TMNOFLAGS must be used.
+     *  @return zero or more Xid objects
+     * @throws XAException
+     */
+    public Xid[] recover(int flag) throws XAException {
+        //debugCodeCall("recover", quoteFlags(flag));
+        checkOpen();
+        Statement stat = null;
+        try {
+            stat = physicalConn.createStatement();
+            ResultSet rs = stat.executeQuery("SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT ORDER BY TRANSACTION");
+            ArrayList<Xid> list = arrayList();
+            while (rs.next()) {
+                String tid = rs.getString("TRANSACTION");
+                int id = getNextId(XID);
+                Xid xid = new SQLiteXid(factory, id, tid);
+                list.add(xid);
+            }
+            rs.close();
+            Xid[] result = new Xid[list.size()];
+            list.toArray(result);
+            return result;
+        } catch (SQLException e) {
+            XAException xa = new XAException(XAException.XAER_RMERR);
+            xa.initCause(e);
+            throw xa;
+        } finally {
+             closeSilently(stat);
+        }
+    }
+
+    /**
+     * Prepare a transaction.
+     *
+     * @param xid the transaction id
+     * @return XA_OK
+     * @throws XAException
+     */
+    public int prepare(Xid xid) throws XAException {
+        //if (isDebugEnabled()) {
+        //    debugCode("prepare("+JdbcXid.toString(xid)+");");
+        //}
+        checkOpen();
+        if (!currentTransaction.equals(xid)) {
+            throw new XAException(XAException.XAER_INVAL);
+        }
+        Statement stat = null;
+        try {
+            stat = physicalConn.createStatement();
+            stat.execute("PREPARE COMMIT " + SQLiteXid.toString(xid));
+        } catch (SQLException e) {
+            throw convertException(e);
+        } finally {
+             closeSilently(stat);
+        }
+        return XA_OK;
+    }
+
+    /**
+     * Forget a transaction.
+     * This method does not have an effect for this database.
+     *
+     * @param xid the transaction id
+     */
+    public void forget(Xid xid) {
+        //if (isDebugEnabled()) {
+        //    debugCode("forget("+JdbcXid.toString(xid)+");");
+        //}
+    }
+
+    /**
+     * Roll back a transaction.
+     *
+     * @param xid the transaction id
+     * @throws XAException
+     */
+    public void rollback(Xid xid) throws XAException {
+        //if (isDebugEnabled()) {
+        //    debugCode("rollback("+JdbcXid.toString(xid)+");");
+        //}
+        try {
+            physicalConn.rollback();
+            physicalConn.setAutoCommit(true);
+            Statement stat = null;
+            try {
+                stat = physicalConn.createStatement();
+                stat.execute("ROLLBACK TRANSACTION " + SQLiteXid.toString(xid));
+            } catch (SQLException e) {
+                // ignore (not a two phase commit)
+            } finally {
+                 closeSilently(stat);
+            }
+        } catch (SQLException e) {
+            throw convertException(e);
+        }
+        currentTransaction = null;
+    }
+
+
+    /**
+     * End a transaction.
+     *
+     * @param xid the transaction id
+     * @param flags TMSUCCESS, TMFAIL, or TMSUSPEND
+     * @throws XAException
+     */
+    public void end(Xid xid, int flags) throws XAException {
+        //if (isDebugEnabled()) {
+        //    debugCode("end("+JdbcXid.toString(xid)+", "+quoteFlags(flags)+");");
+        //}
+        // TODO transaction end: implement this method
+        if (flags == TMSUSPEND) {
+            return;
+        }
+        if (!currentTransaction.equals(xid)) {
+            throw new XAException(XAException.XAER_OUTSIDE);
+        }
+    }
+
+    /**
+     * Start or continue to work on a transaction.
+     *
+     * @param xid the transaction id
+     * @param flags TMNOFLAGS, TMJOIN, or TMRESUME
+     * @throws XAException
+     */
+    public void start(Xid xid, int flags) throws XAException {
+        //if (isDebugEnabled()) {
+        //    debugCode("start("+JdbcXid.toString(xid)+", "+quoteFlags(flags)+");");
+        //}
+        if (flags == TMRESUME) {
+            return;
+        }
+        if (flags == TMJOIN) {
+            if (currentTransaction != null && !currentTransaction.equals(xid)) {
+                throw new XAException(XAException.XAER_RMERR);
+            }
+        } else if (currentTransaction != null) {
+            throw new XAException(XAException.XAER_NOTA);
+        }
+        try {
+            physicalConn.setAutoCommit(false);
+        } catch (SQLException e) {
+            throw convertException(e);
+        }
+        currentTransaction = xid;
+    }
+
+    /**
+     * Commit a transaction.
+     *
+     * @param xid the transaction id
+     * @param onePhase use a one-phase protocol if true
+     * @throws XAException
+     */
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+        //if (isDebugEnabled()) {
+        //    debugCode("commit("+JdbcXid.toString(xid)+", "+onePhase+");");
+        //}
+        Statement stat = null;
+        try {
+            if (onePhase) {
+                physicalConn.commit();
+            } else {
+                stat = physicalConn.createStatement();
+                stat.execute("COMMIT TRANSACTION " + SQLiteXid.toString(xid));
+            }
+            physicalConn.setAutoCommit(true);
+        } catch (SQLException e) {
+            throw convertException(e);
+        } finally {
+             closeSilently(stat);
+        }
+        currentTransaction = null;
+    }
+
+    /**
+     * Register a new listener for the connection.
+     *
+     * @param listener the event listener
+     */
+    public void addConnectionEventListener(ConnectionEventListener listener) {
+        debugCode("addConnectionEventListener(listener);");
+        listeners.add(listener);
+    }
+
+    /**
+     * Remove the event listener.
+     *
+     * @param listener the event listener
+     */
+    public void removeConnectionEventListener(ConnectionEventListener listener) {
+        debugCode("removeConnectionEventListener(listener);");
+        listeners.remove(listener);
+    }
+
+    /**
+     * INTERNAL
+     */
+    void closedHandle() {
+        debugCode("closedHandle();");
+        ConnectionEvent event = new ConnectionEvent(this);
+        // go backward so that a listener can remove itself
+        // (otherwise we need to clone the list)
+        for (int i = listeners.size() - 1; i >= 0; i--) {
+            ConnectionEventListener listener = listeners.get(i);
+            listener.connectionClosed(event);
+        }
+        handleConn = null;
+    }
+
+    /**
+     * Get the transaction timeout.
+     *
+     * @return 0
+     */
+    public int getTransactionTimeout() {
+        debugCodeCall("getTransactionTimeout");
+        return 0;
+    }
+
+    /**
+     * Set the transaction timeout.
+     *
+     * @param seconds ignored
+     * @return false
+     */
+    public boolean setTransactionTimeout(int seconds) {
+        debugCodeCall("setTransactionTimeout", seconds);
+        return false;
+    }
+
+    /**
+     * Checks if this is the same XAResource.
+     *
+     * @param xares the other object
+     * @return true if this is the same object
+     */
+    public boolean isSameRM(XAResource xares) {
+        debugCode("isSameRM(xares);");
+        return xares == this;
+    }
+
+    public void addStatementEventListener(StatementEventListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+
+    public void removeStatementEventListener(StatementEventListener listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    
+    private static XAException convertException(SQLException e) {
+        XAException xa = new XAException(e.getMessage());
+        xa.initCause(e);
+        return xa;
+    }
+
+
+    private void checkOpen() throws XAException {
+        if (physicalConn == null) {
+            throw new XAException(XAException.XAER_RMERR);
+        }
+    }
+
+
+
+    private static String getFileName(String url){
+        String fileName = url.substring(JDBC.PREFIX.length());
+        if (fileName.startsWith("//")) fileName = fileName.substring(2);
+
+        return fileName;
+
+    }
+    
+    /**
+     * A pooled connection.
+     */
+    class PooledJdbcConnection extends Conn {
+
+        private boolean isClosed;
+
+        public PooledJdbcConnection(Conn conn, java.util.Properties config) throws SQLException {
+            super(conn.url(), getFileName(conn.url()), config);
+        }
+
+
+
+        public synchronized void close() throws SQLException {
+            if (!isClosed) {
+                if (getAutoCommit()==false) rollback();
+                //setAutoCommit(true); //<--Not sure why we need this line
+                closedHandle();
+                super.close(); //<--I had to add this line to explicitly close the connection...
+                isClosed = true;
+            }
+        }
+
+        public synchronized boolean isClosed() throws SQLException {
+            return isClosed || super.isClosed();
+        }
+
+        protected synchronized void checkClosed(boolean write) {
+            if (isClosed) {
+                //throw DbException.get(ErrorCode.OBJECT_CLOSED);
+            }
+
+
+
+            //super.checkClosed(write);
+        }
+
+    } //PooledJdbcConnection
+
+
+    /**
+     * Close a statement without throwing an exception.
+     *
+     * @param stat the statement or null
+     */
+    public static void closeSilently(Statement stat) {
+        if (stat != null) {
+            try {
+                stat.close();
+            } catch (SQLException e) {
+                // ignore
+            }
+        }
+    }
+
+
+    private static boolean debug = false;
+    private static void debugCodeCall(String str, int seconds){
+        if (debug) System.err.println(str);
+    }
+    private static void debugCodeCall(String str){
+        if (debug) System.err.println(str);
+    }
+    private static void debugCode(String str){
+        if (debug) System.err.println(str);
+    }
+    private static void debug(String str){
+        if (debug) System.err.println(str);
+    }
+    private static boolean isDebugEnabled(){ return debug; }
+
+}

src/main/java/org/sqlite/SQLiteXid.java

+package org.sqlite;
+import java.util.StringTokenizer;
+import javax.transaction.xa.Xid;
+
+//******************************************************************************
+//**  SQLiteXid
+//******************************************************************************
+/**
+ *   An object of this class represents a transaction id.
+ *
+ *   Port from H2 JdbcXid:
+ *   http://code.google.com/p/h2database/source/browse/trunk/h2/src/main/org/h2/jdbcx/JdbcXid.java
+ *
+ ******************************************************************************/
+
+public class SQLiteXid implements Xid {
+
+    private static final String PREFIX = "XID";
+
+    private int formatId;
+    private byte[] branchQualifier;
+    private byte[] globalTransactionId;
+
+    SQLiteXid(SQLiteDataSourceFactory factory, int id, String tid) {
+        //setTrace(factory.getTrace(), TraceObject.XID, id);
+        try {
+            StringTokenizer tokenizer = new StringTokenizer(tid, "_");
+            String prefix = tokenizer.nextToken();
+            if (!PREFIX.equals(prefix)) {
+                //throw DbException.get(ErrorCode.WRONG_XID_FORMAT_1, tid);
+            }
+            formatId = Integer.parseInt(tokenizer.nextToken());
+            branchQualifier = convertHexToBytes(tokenizer.nextToken());
+            globalTransactionId = convertHexToBytes(tokenizer.nextToken());
+        } catch (RuntimeException e) {
+            //throw DbException.get(ErrorCode.WRONG_XID_FORMAT_1, tid);
+        }
+    }
+
+    /**
+     * INTERNAL
+     */
+    public static String toString(Xid xid) {
+        StringBuilder buff = new StringBuilder(PREFIX);
+        buff.append('_').
+            append(xid.getFormatId()).
+            append('_').
+            append(convertBytesToHex(xid.getBranchQualifier())).
+            append('_').
+            append(convertBytesToHex(xid.getGlobalTransactionId()));
+        return buff.toString();
+    }
+
+    /**
+     * Get the format id.
+     *
+     * @return the format id
+     */
+    public int getFormatId() {
+        //debugCodeCall("getFormatId");
+        return formatId;
+    }
+
+    /**
+     * The transaction branch identifier.
+     *
+     * @return the identifier
+     */
+    public byte[] getBranchQualifier() {
+        //debugCodeCall("getBranchQualifier");
+        return branchQualifier;
+    }
+
+    /**
+     * The global transaction identifier.
+     *
+     * @return the transaction id
+     */
+    public byte[] getGlobalTransactionId() {
+        //debugCodeCall("getGlobalTransactionId");
+        return globalTransactionId;
+    }
+
+
+
+
+
+    /*
+     The following methods were ported from org.h2.util.StringUtils.java
+
+     */
+
+    private static final char[] HEX = "0123456789abcdef".toCharArray();
+
+
+
+    /**
+     * Convert a byte array to a hex encoded string.
+     *
+     * @param value the byte array
+     * @return the hex encoded string
+     */
+    private static String convertBytesToHex(byte[] value) {
+        return convertBytesToHex(value, value.length);
+    }
+
+    /**
+     * Convert a byte array to a hex encoded string.
+     *
+     * @param value the byte array
+     * @param len the number of bytes to encode
+     * @return the hex encoded string
+     */
+    private static String convertBytesToHex(byte[] value, int len) {
+        char[] buff = new char[len + len];
+        char[] hex = HEX;
+        for (int i = 0; i < len; i++) {
+            int c = value[i] & 0xff;
+            buff[i + i] = hex[c >> 4];
+            buff[i + i + 1] = hex[c & 0xf];
+        }
+        return new String(buff);
+    }
+    /**
+     * Convert a hex encoded string to a byte array.
+     *
+     * @param s the hex encoded string
+     * @return the byte array
+     */
+    private static byte[] convertHexToBytes(String s) {
+        int len = s.length();
+        if (len % 2 != 0) {
+            //throw DbException.get(ErrorCode.HEX_STRING_ODD_1, s);
+        }
+        len /= 2;
+        byte[] buff = new byte[len];
+        for (int i = 0; i < len; i++) {
+            buff[i] = (byte) ((getHexDigit(s, i + i) << 4) | getHexDigit(s, i + i + 1));
+        }
+        return buff;
+    }
+
+    private static int getHexDigit(String s, int i) {
+        char c = s.charAt(i);
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        } else if (c >= 'a' && c <= 'f') {
+            return c - 'a' + 0xa;
+        } else if (c >= 'A' && c <= 'F') {
+            return c - 'A' + 0xa;
+        } else {
+            return -99999;
+            //throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s);
+        }
+    }
+
+}