Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java	(revision 573475)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleDbPersistenceManager.java	(working copy)
@@ -16,30 +16,6 @@
  */
 package org.apache.jackrabbit.core.persistence.bundle;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.util.Text;
-import org.apache.jackrabbit.core.state.ChangeLog;
-import org.apache.jackrabbit.core.state.ItemStateException;
-import org.apache.jackrabbit.core.state.NoSuchItemStateException;
-import org.apache.jackrabbit.core.state.NodeReferencesId;
-import org.apache.jackrabbit.core.state.NodeReferences;
-import org.apache.jackrabbit.core.persistence.PMContext;
-import org.apache.jackrabbit.core.persistence.bundle.util.DbNameIndex;
-import org.apache.jackrabbit.core.persistence.bundle.util.NodePropBundle;
-import org.apache.jackrabbit.core.persistence.bundle.util.BundleBinding;
-import org.apache.jackrabbit.core.persistence.bundle.util.ErrorHandling;
-import org.apache.jackrabbit.core.persistence.bundle.util.StringIndex;
-import org.apache.jackrabbit.core.persistence.util.Serializer;
-import org.apache.jackrabbit.core.persistence.util.BLOBStore;
-import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
-import org.apache.jackrabbit.core.fs.FileSystemResource;
-import org.apache.jackrabbit.core.fs.FileSystem;
-import org.apache.jackrabbit.core.fs.local.LocalFileSystem;
-import org.apache.jackrabbit.core.NodeId;
-import org.apache.jackrabbit.core.PropertyId;
-import org.apache.jackrabbit.uuid.UUID;
-
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -53,18 +29,44 @@
 import java.sql.Blob;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
-import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.sql.Driver;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
-import java.util.Collection;
-import java.util.ArrayList;
 
 import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.fs.local.LocalFileSystem;
+import org.apache.jackrabbit.core.persistence.AbstractPersistenceManager;
+import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.bundle.util.BundleBinding;
+import org.apache.jackrabbit.core.persistence.bundle.util.ConnectionRecoverable;
+import org.apache.jackrabbit.core.persistence.bundle.util.ConnectionRecoveryManager;
+import org.apache.jackrabbit.core.persistence.bundle.util.DbNameIndex;
+import org.apache.jackrabbit.core.persistence.bundle.util.ErrorHandling;
+import org.apache.jackrabbit.core.persistence.bundle.util.NodePropBundle;
+import org.apache.jackrabbit.core.persistence.bundle.util.StringIndex;
+import org.apache.jackrabbit.core.persistence.bundle.util.TrackingInputStream;
+import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
+import org.apache.jackrabbit.core.persistence.util.Serializer;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.NodeReferences;
+import org.apache.jackrabbit.core.state.NodeReferencesId;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.uuid.UUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * This is a generic persistence manager that stores the {@link NodePropBundle}s
  * in a database.
@@ -84,7 +86,8 @@
  * <li>&lt;param name="{@link #setErrorHandling(String) errorHandling}" value=""/>
  * </ul>
  */
-public class BundleDbPersistenceManager extends AbstractBundlePersistenceManager {
+public class BundleDbPersistenceManager extends AbstractBundlePersistenceManager implements
+    ConnectionRecoverable {
 
     /** the cvs/svn id */
     static final String CVS_ID = "$URL$ $Rev$ $Date$";
@@ -135,22 +138,23 @@
     /** inidicates if uses (filesystem) blob store */
     protected boolean externalBLOBs;
 
+    /**
+     * The class that manages statement execution and recovery from connection loss.
+     */
+    protected ConnectionRecoveryManager connectionManager;
 
-    /** jdbc conection */
-    protected Connection con;
+    // SQL statements for bundle management
+    protected String bundleInsertSQL;
+    protected String bundleUpdateSQL;
+    protected String bundleSelectSQL;
+    protected String bundleDeleteSQL;
 
-    // shared prepared statements for bundle management
-    protected PreparedStatement bundleInsert;
-    protected PreparedStatement bundleUpdate;
-    protected PreparedStatement bundleSelect;
-    protected PreparedStatement bundleDelete;
+    // SQL statements for NodeReference management
+    protected String nodeReferenceInsertSQL;
+    protected String nodeReferenceUpdateSQL;
+    protected String nodeReferenceSelectSQL;
+    protected String nodeReferenceDeleteSQL;
 
-    // shared prepared statements for NodeReference management
-    protected PreparedStatement nodeReferenceInsert;
-    protected PreparedStatement nodeReferenceUpdate;
-    protected PreparedStatement nodeReferenceSelect;
-    protected PreparedStatement nodeReferenceDelete;
-
     /** file system where BLOB data is stored */
     protected CloseableBLOBStore blobStore;
 
@@ -387,6 +391,33 @@
         return externalBLOBs;
     }
 
+    /* (non-Javadoc)
+     * @see org.apache.jackrabbit.core.ConnectionRecoverable#recoverFromConnectionLoss()
+     */
+    public void recoverFromConnectionLoss(Connection connection) {
+
+        // Get a new StringIndex if the old one used the JDBC Connection
+        if (nameIndex instanceof DbNameIndex) {
+            ((DbNameIndex) nameIndex).close();
+            nameIndex = null;
+            getNameIndex();
+        }
+
+        // Get a new blobStore if the old one used the JDBC Connection
+        if (blobStore instanceof DbBlobStore) {
+            blobStore.close();
+            try {
+                blobStore = createBlobStore();
+            } catch (Exception e) {
+                log.error("failed to create blob store: ", e);
+            }
+        }
+
+        // Reload namespaces
+        binding = new BundleBinding(errorHandling, blobStore, getNsIndex(), getNameIndex(), context.getDataStore());
+        binding.setMinBlobSize(minBlobSize);
+    }
+
     /**
      * Checks if the required schema objects exist and creates them if they
      * don't exist yet.
@@ -404,7 +435,7 @@
                 throw new RepositoryException(msg);
             }
             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-            Statement stmt = con.createStatement();
+            Statement stmt = connectionManager.getConnection().createStatement();
             try {
                 String sql = reader.readLine();
                 while (sql != null) {
@@ -452,7 +483,7 @@
      * @throws SQLException if an SQL erro occurs.
      */
     protected boolean checkTablesExist() throws SQLException {
-        DatabaseMetaData metaData = con.getMetaData();
+        DatabaseMetaData metaData = connectionManager.getConnection().getMetaData();
         String tableName = schemaObjectPrefix + "BUNDLE";
         if (metaData.storesLowerCaseIdentifiers()) {
             tableName = tableName.toLowerCase();
@@ -486,40 +517,64 @@
      *
      * Basically wrapps a JDBC transaction around super.store().
      */
-    public synchronized void store(ChangeLog changeLog)
-            throws ItemStateException {
+    public synchronized void store(ChangeLog changeLog) throws ItemStateException {
 
-        try {
-            con.setAutoCommit(false);
-            super.store(changeLog);
-        } catch (SQLException e) {
-            String msg = "setting autocommit failed.";
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        } catch (ItemStateException e) {
-            // storing the changes failed, rollback changes
-            try {
-                con.rollback();
-            } catch (SQLException e1) {
-                String msg = "rollback of change log failed";
-                log.error(msg, e1);
-            }
-            // re-throw original exception
-            throw e;
+        // Disable the autoCommit and the autoReconnect of the
+        // connectionManager in order to do a transaction
+        // Note: order is important!
+        if (!connectionManager.setAutoCommit(false)) {
+            throw new ItemStateException("disabling autoCommit failed");
         }
+        connectionManager.setAutoReconnect(false);
 
-        // storing the changes succeeded, now commit the changes
+        // Try storing the changelog
         try {
-            con.commit();
-            con.setAutoCommit(true);
-        } catch (SQLException e) {
-            String msg = "committing change log failed";
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
+            store(changeLog, 1);
+        } finally {
+            // re-enable automatic reconnect feature and the autoCommit
+            connectionManager.setAutoReconnect(true);
+            connectionManager.setAutoCommit(true);
         }
     }
 
     /**
+     * @param changeLog
+     * @param tries
+     * @throws ItemStateException
+     */
+    private synchronized void store(ChangeLog changeLog, int tries)
+        throws ItemStateException {
+
+        // Recursion base
+        if (tries <= 0) {
+            return;
+        }
+
+        // Try storing the changelog and try to reconnect if this failed
+        try{
+            super.store(changeLog);
+            connectionManager.getConnection().commit();
+        } catch (Exception e) {
+            log.warn("storing and committing changes failed: " + e.getMessage());
+            // autoCommit = false and autoReconnect = false
+            Connection con = connectionManager.getConnection();
+            if (con != null) {
+                try {
+                    con.rollback();
+                } catch (SQLException e1) {
+                    log.error("rollback failed");
+                }
+            }
+            if (e instanceof SQLException || e.getCause() instanceof SQLException) {
+                connectionManager.reestablishConnection();
+                store(changeLog, tries - 1);
+            } else {
+                throw new ItemStateException(e.getMessage());
+            }
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     public void init(PMContext context) throws Exception {
@@ -530,23 +585,11 @@
 
         this.name = context.getHomeDir().getName();
 
-        // setup jdbc connection
-        // Note: Explicit creation of new instance of the driver is required
-        // in order to re-register the driver in the DriverManager after a
-        // repository shutdown.
-        Driver drv = (Driver) Class.forName(driver).newInstance();
-        log.info("JDBC driver created: {}", drv);
-        con = DriverManager.getConnection(url, user, password);
-        
-        DatabaseMetaData meta = con.getMetaData();
-        try {
-            log.info("Database: " + meta.getDatabaseProductName() + " / " + meta.getDatabaseProductVersion());
-            log.info("Driver: " + meta.getDriverName() + " / " + meta.getDriverVersion());
-        } catch (SQLException e) {
-            log.warn("Can not retrieve database and driver name / version", e);
-        }
-        
-        con.setAutoCommit(true);
+        // Create a ConnectionRecoveryManager that automatically tries to re-establish
+        // the database connection if it fails.
+        connectionManager = new ConnectionRecoveryManager(this);
+        connectionManager.setAutoReconnect(true);
+        connectionManager.setAutoCommit(true);
 
         // make sure schemaObjectPrefix consists of legal name characters only
         prepareSchemaObjectPrefix();
@@ -557,28 +600,10 @@
         // create correct blob store
         blobStore = createBlobStore();
 
-        // prepare statements
-        if (getStorageModel() == SM_BINARY_KEYS) {
-            bundleInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID) values (?, ?)");
-            bundleUpdate = con.prepareStatement("update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID = ?");
-            bundleSelect = con.prepareStatement("select BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE where NODE_ID = ?");
-            bundleDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "BUNDLE where NODE_ID = ?");
+        buildSQLStatements();
 
-            nodeReferenceInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "REFS (REFS_DATA, NODE_ID) values (?, ?)");
-            nodeReferenceUpdate = con.prepareStatement("update " + schemaObjectPrefix + "REFS set REFS_DATA = ? where NODE_ID = ?");
-            nodeReferenceSelect = con.prepareStatement("select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID = ?");
-            nodeReferenceDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "REFS where NODE_ID = ?");
-        } else {
-            bundleInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)");
-            bundleUpdate = con.prepareStatement("update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?");
-            bundleSelect = con.prepareStatement("select BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?");
-            bundleDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?");
+        initPreparedStatements();
 
-            nodeReferenceInsert = con.prepareStatement("insert into " + schemaObjectPrefix + "REFS (REFS_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)");
-            nodeReferenceUpdate = con.prepareStatement("update " + schemaObjectPrefix + "REFS set REFS_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?");
-            nodeReferenceSelect = con.prepareStatement("select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?");
-            nodeReferenceDelete = con.prepareStatement("delete from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?");
-        }
         // load namespaces
         binding = new BundleBinding(errorHandling, blobStore, getNsIndex(), getNameIndex(), context.getDataStore());
         binding.setMinBlobSize(minBlobSize);
@@ -631,7 +656,7 @@
      * @throws SQLException if an SQL error occurs.
      */
     protected DbNameIndex createDbNameIndex() throws SQLException {
-        return new DbNameIndex(con, schemaObjectPrefix);
+        return new DbNameIndex(connectionManager.getConnection(), schemaObjectPrefix);
     }
 
     /**
@@ -690,13 +715,9 @@
         DataInputStream din = null;
         try {
             if (getStorageModel() == SM_BINARY_KEYS) {
-                stmt = con.prepareStatement(
-                        "select NODE_ID, BUNDLE_DATA from "
-                        + schemaObjectPrefix + "BUNDLE");
+                stmt = connectionManager.getConnection().prepareStatement("select NODE_ID, BUNDLE_DATA from BUNDLE");
             } else {
-                stmt = con.prepareStatement(
-                        "select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from "
-                        + schemaObjectPrefix + "BUNDLE");
+                stmt = connectionManager.getConnection().prepareStatement("select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from BUNDLE");
             }
             stmt.execute();
             rs = stmt.getResultSet();
@@ -799,7 +820,7 @@
      * @throws Exception if an error occurs
      */
     protected void prepareSchemaObjectPrefix() throws Exception {
-        DatabaseMetaData metaData = con.getMetaData();
+        DatabaseMetaData metaData = connectionManager.getConnection().getMetaData();
         String legalChars = metaData.getExtraNameCharacters();
         legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
 
@@ -829,24 +850,10 @@
         }
 
         try {
-            // close shared prepared statements
-            closeStatement(bundleInsert);
-            closeStatement(bundleUpdate);
-            closeStatement(bundleSelect);
-            closeStatement(bundleDelete);
-
-            closeStatement(nodeReferenceInsert);
-            closeStatement(nodeReferenceUpdate);
-            closeStatement(nodeReferenceSelect);
-            closeStatement(nodeReferenceDelete);
-
             if (nameIndex instanceof DbNameIndex) {
                 ((DbNameIndex) nameIndex).close();
             }
-
-            // close jdbc connection
-            con.close();
-
+            connectionManager.close();
             // close blob store
             blobStore.close();
             blobStore = null;
@@ -877,16 +884,30 @@
     }
 
     /**
+     * Constructs a parameter list for a PreparedStatement
+     * for the given UUID.
+     *
+     * @param uuid the uuid
+     * @return a list of Objects
+     */
+    protected Object[] getKey(UUID uuid) {
+        if (getStorageModel() == SM_BINARY_KEYS) {
+            return new Object[]{uuid.getRawBytes()};
+        } else {
+            return new Object[]{new Long(uuid.getMostSignificantBits()),
+                    new Long(uuid.getLeastSignificantBits())};
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     protected synchronized NodePropBundle loadBundle(NodeId id)
             throws ItemStateException {
-        PreparedStatement stmt = bundleSelect;
         ResultSet rs = null;
         InputStream in = null;
         try {
-            setKey(stmt, id.getUUID(), 1);
-            stmt.execute();
+            Statement stmt = connectionManager.executeStmt(bundleSelectSQL, getKey(id.getUUID()));
             rs = stmt.getResultSet();
             if (!rs.next()) {
                 return null;
@@ -911,7 +932,6 @@
         } finally {
             closeStream(in);
             closeResultSet(rs);
-            resetStatement(stmt);
         }
     }
 
@@ -919,13 +939,10 @@
      * {@inheritDoc}
      */
     protected synchronized boolean existsBundle(NodeId id) throws ItemStateException {
-        PreparedStatement stmt = bundleSelect;
         ResultSet rs = null;
         try {
-            setKey(stmt, id.getUUID(), 1);
-            stmt.execute();
+            Statement stmt = connectionManager.executeStmt(bundleSelectSQL, getKey(id.getUUID()));
             rs = stmt.getResultSet();
-
             // a bundle exists, if the result has at least one entry
             return rs.next();
         } catch (Exception e) {
@@ -934,7 +951,6 @@
             throw new ItemStateException(msg, e);
         } finally {
             closeResultSet(rs);
-            resetStatement(stmt);
         }
     }
 
@@ -942,27 +958,24 @@
      * {@inheritDoc}
      */
     protected synchronized void storeBundle(NodePropBundle bundle) throws ItemStateException {
-        PreparedStatement stmt = null;
         try {
             ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
             DataOutputStream dout = new DataOutputStream(out);
             binding.writeBundle(dout, bundle);
             dout.close();
 
-            if (bundle.isNew()) {
-                stmt = bundleInsert;
-            } else {
-                stmt = bundleUpdate;
+            String sql = bundle.isNew() ? bundleInsertSQL : bundleUpdateSQL;
+            Object[] keys = getKey(bundle.getId().getUUID());
+            Object[] params = new Object[keys.length + 1];
+            params[0] = out.toByteArray();
+            for (int i = 1; i < params.length; i++) {
+                params[i] = keys[i-1];
             }
-            stmt.setBytes(1, out.toByteArray());
-            setKey(stmt, bundle.getId().getUUID(), 2);
-            stmt.execute();
+            connectionManager.executeStmt(sql, params);
         } catch (Exception e) {
             String msg = "failed to write bundle: " + bundle.getId();
             log.error(msg, e);
             throw new ItemStateException(msg, e);
-        } finally {
-            resetStatement(stmt);
         }
     }
 
@@ -970,10 +983,8 @@
      * {@inheritDoc}
      */
     protected synchronized void destroyBundle(NodePropBundle bundle) throws ItemStateException {
-        PreparedStatement stmt = bundleDelete;
         try {
-            setKey(stmt, bundle.getId().getUUID(), 1);
-            stmt.execute();
+            connectionManager.executeStmt(bundleDeleteSQL, getKey(bundle.getId().getUUID()));
             // also delete all
             bundle.removeAllProperties();
         } catch (Exception e) {
@@ -983,8 +994,6 @@
             String msg = "failed to delete bundle: " + bundle.getId();
             log.error(msg, e);
             throw new ItemStateException(msg, e);
-        } finally {
-            resetStatement(stmt);
         }
     }
 
@@ -997,12 +1006,11 @@
             throw new IllegalStateException("not initialized");
         }
 
-        PreparedStatement stmt = nodeReferenceSelect;
         ResultSet rs = null;
         InputStream in = null;
         try {
-            setKey(stmt, targetId.getTargetId().getUUID(), 1);
-            stmt.execute();
+            Statement stmt = connectionManager.executeStmt(
+                    nodeReferenceSelectSQL, getKey(targetId.getTargetId().getUUID()));
             rs = stmt.getResultSet();
             if (!rs.next()) {
                 throw new NoSuchItemStateException(targetId.toString());
@@ -1023,7 +1031,6 @@
         } finally {
             closeStream(in);
             closeResultSet(rs);
-            resetStatement(stmt);
         }
     }
 
@@ -1041,34 +1048,30 @@
             throw new IllegalStateException("not initialized");
         }
 
-        PreparedStatement stmt = null;
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+
         try {
-            // check if insert or update
-            if (exists(refs.getId())) {
-                stmt = nodeReferenceUpdate;
-            } else {
-                stmt = nodeReferenceInsert;
-            }
-
-            ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
             // serialize references
             Serializer.serialize(refs, out);
 
-            // we are synchronized on this instance, therefore we do not
-            // not have to additionally synchronize on the preparedStatement
+            Object[] keys = getKey(refs.getTargetId().getUUID());
+            Object[] params = new Object[keys.length + 1];
+            params[0] = out.toByteArray();
+            for (int i = 1; i < params.length; i++) {
+                params[i] = keys[i-1];
+            }
+            connectionManager.executeStmt(sql, params);
 
-            stmt.setBytes(1, out.toByteArray());
-            setKey(stmt, refs.getTargetId().getUUID(), 2);
-            stmt.execute();
-
             // there's no need to close a ByteArrayOutputStream
             //out.close();
         } catch (Exception e) {
-            String msg = "failed to write property state: " + refs.getTargetId();
+            String msg = "failed to write node references: " + refs.getId();
             log.error(msg, e);
             throw new ItemStateException(msg, e);
-        } finally {
-            resetStatement(stmt);
         }
     }
 
@@ -1080,10 +1083,9 @@
             throw new IllegalStateException("not initialized");
         }
 
-        PreparedStatement stmt = nodeReferenceDelete;
         try {
-            setKey(stmt, refs.getTargetId().getUUID(), 1);
-            stmt.execute();
+            connectionManager.executeStmt(nodeReferenceDeleteSQL,
+                    getKey(refs.getTargetId().getUUID()));
         } catch (Exception e) {
             if (e instanceof NoSuchItemStateException) {
                 throw (NoSuchItemStateException) e;
@@ -1091,8 +1093,6 @@
             String msg = "failed to delete references: " + refs.getTargetId();
             log.error(msg, e);
             throw new ItemStateException(msg, e);
-        } finally {
-            resetStatement(stmt);
         }
     }
 
@@ -1104,22 +1104,21 @@
             throw new IllegalStateException("not initialized");
         }
 
-        PreparedStatement stmt = nodeReferenceSelect;
         ResultSet rs = null;
         try {
-            setKey(stmt, targetId.getTargetId().getUUID(), 1);
-            stmt.execute();
+            Statement stmt = connectionManager.executeStmt(nodeReferenceSelectSQL,
+                    getKey(targetId.getTargetId().getUUID()));
             rs = stmt.getResultSet();
 
-            // a reference exists, if the result has at least one entry
+            // a reference exists if the result has at least one entry
             return rs.next();
         } catch (Exception e) {
-            String msg = "failed to check existence of node references: " + targetId;
+            String msg = "failed to check existence of node references: "
+                + targetId;
             log.error(msg, e);
             throw new ItemStateException(msg, e);
         } finally {
             closeResultSet(rs);
-            resetStatement(stmt);
         }
     }
 
@@ -1206,6 +1205,51 @@
     }
 
     /**
+     * Initializes the SQL strings.
+     */
+    protected void buildSQLStatements() {
+        // prepare statements
+        if (getStorageModel() == SM_BINARY_KEYS) {
+            bundleInsertSQL = "insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID) values (?, ?)";
+            bundleUpdateSQL = "update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID = ?";
+            bundleSelectSQL = "select BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE where NODE_ID = ?";
+            bundleDeleteSQL = "delete from " + schemaObjectPrefix + "BUNDLE where NODE_ID = ?";
+
+            nodeReferenceInsertSQL = "insert into " + schemaObjectPrefix + "REFS (REFS_DATA, NODE_ID) values (?, ?)";
+            nodeReferenceUpdateSQL = "update " + schemaObjectPrefix + "REFS set REFS_DATA = ? where NODE_ID = ?";
+            nodeReferenceSelectSQL = "select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID = ?";
+            nodeReferenceDeleteSQL = "delete from " + schemaObjectPrefix + "REFS where NODE_ID = ?";
+        } else {
+            bundleInsertSQL = "insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)";
+            bundleUpdateSQL = "update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?";
+            bundleSelectSQL = "select BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?";
+            bundleDeleteSQL = "delete from " + schemaObjectPrefix + "BUNDLE where NODE_ID_HI = ? and NODE_ID_LO = ?";
+
+            nodeReferenceInsertSQL = "insert into " + schemaObjectPrefix + "REFS (REFS_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)";
+            nodeReferenceUpdateSQL = "update " + schemaObjectPrefix + "REFS set REFS_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?";
+            nodeReferenceSelectSQL = "select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?";
+            nodeReferenceDeleteSQL = "delete from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?";
+        }
+    }
+
+    /**
+     * Adds the SQL strings that will be used to the {@link ConnectionRecoveryManager}.
+     *
+     * @throws SQLException
+     */
+    protected void initPreparedStatements() throws SQLException {
+        connectionManager.addStatement(bundleInsertSQL);
+        connectionManager.addStatement(bundleUpdateSQL);
+        connectionManager.addStatement(bundleSelectSQL);
+        connectionManager.addStatement(bundleDeleteSQL);
+        connectionManager.addStatement(nodeReferenceInsertSQL);
+        connectionManager.addStatement(nodeReferenceUpdateSQL);
+        connectionManager.addStatement(nodeReferenceSelectSQL);
+        connectionManager.addStatement(nodeReferenceDeleteSQL);
+    }
+
+
+    /**
      * Helper interface for closeable stores
      */
     protected static interface CloseableBLOBStore extends BLOBStore {
@@ -1252,19 +1296,19 @@
 
         public DbBlobStore() throws SQLException {
             blobInsert =
-                    con.prepareStatement("insert into "
+                connectionManager.getConnection().prepareStatement("insert into "
                     + schemaObjectPrefix + "BINVAL (BINVAL_DATA, BINVAL_ID) values (?, ?)");
             blobUpdate =
-                    con.prepareStatement("update "
+                connectionManager.getConnection().prepareStatement("update "
                     + schemaObjectPrefix + "BINVAL set BINVAL_DATA = ? where BINVAL_ID = ?");
             blobSelect =
-                    con.prepareStatement("select BINVAL_DATA from "
+                connectionManager.getConnection().prepareStatement("select BINVAL_DATA from "
                     + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?");
             blobSelectExist =
-                    con.prepareStatement("select 1 from "
+                connectionManager.getConnection().prepareStatement("select 1 from "
                     + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?");
             blobDelete =
-                    con.prepareStatement("delete from "
+                connectionManager.getConnection().prepareStatement("delete from "
                     + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?");
         }
 
@@ -1367,5 +1411,4 @@
             closeStatement(blobDelete);
         }
     }
-
 }
Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/DerbyPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/DerbyPersistenceManager.java	(revision 573475)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/DerbyPersistenceManager.java	(working copy)
@@ -58,7 +58,7 @@
 
     /** name of the embedded driver */
     public static final String DERBY_EMBEDDED_DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
-    
+
     /** the default logger */
     private static Logger log = LoggerFactory.getLogger(DerbyPersistenceManager.class);
 
@@ -271,7 +271,7 @@
     protected void checkSchema() throws SQLException, RepositoryException {
         // set properties
         if (DERBY_EMBEDDED_DRIVER.equals(getDriver())) {
-            Statement stmt = con.createStatement();
+            Statement stmt = connectionManager.getConnection().createStatement();
             try {
                 stmt.execute("CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY " +
                         "('derby.storage.initialPages', '" + derbyStorageInitialPages + "')");
@@ -317,7 +317,7 @@
         }
 
         // prepare connection url for issuing shutdown command
-        String url = con.getMetaData().getURL();
+        String url = connectionManager.getConnection().getMetaData().getURL();
         int pos = url.lastIndexOf(';');
         if (pos != -1) {
             // strip any attributes from connection url
@@ -329,7 +329,7 @@
         // otherwise Derby would mysteriously complain about some pending uncommitted
         // changes which can't possibly be true.
         // @todo further investigate
-        con.setAutoCommit(true);
+        connectionManager.getConnection().setAutoCommit(true);
 
         // now it's safe to shutdown the embedded Derby database
         try {
@@ -342,4 +342,4 @@
         super.close();
     }
 
-}
\ No newline at end of file
+}
Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/H2PersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/H2PersistenceManager.java	(revision 573475)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/H2PersistenceManager.java	(working copy)
@@ -103,7 +103,7 @@
      * {@inheritDoc}
      */
     protected void checkSchema() throws SQLException, RepositoryException {
-        Statement stmt = con.createStatement();
+        Statement stmt = connectionManager.getConnection().createStatement();
         try {
             stmt.execute("SET LOCK_TIMEOUT " + lockTimeout);
         } finally {
@@ -121,7 +121,7 @@
         }
         if (getUrl().startsWith("jdbc:h2:file:")) {
             // have to explicitly shutdown in-proc h2
-            Statement stmt = con.createStatement();
+            Statement stmt = connectionManager.getConnection().createStatement();
             stmt.execute("shutdown");
             stmt.close();
         }
@@ -129,4 +129,4 @@
         super.close();
     }
 
-}
\ No newline at end of file
+}
Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/Oracle9PersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/Oracle9PersistenceManager.java	(revision 573475)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/Oracle9PersistenceManager.java	(working copy)
@@ -92,7 +92,7 @@
 
         // use the Connection object for using the exact same
         // class loader that the Oracle driver was loaded with
-        blobClass = con.getClass().getClassLoader().loadClass("oracle.sql.BLOB");
+        blobClass = connectionManager.getConnection().getClass().getClassLoader().loadClass("oracle.sql.BLOB");
         DURATION_SESSION_CONSTANT =
                 new Integer(blobClass.getField("DURATION_SESSION").getInt(null));
         MODE_READWRITE_CONSTANT =
@@ -111,29 +111,32 @@
      */
     protected synchronized void storeBundle(NodePropBundle bundle)
             throws ItemStateException {
-        PreparedStatement stmt = null;
         Blob blob = null;
         try {
+            String sql;
             ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
             DataOutputStream dout = new DataOutputStream(out);
             binding.writeBundle(dout, bundle);
             dout.close();
 
             if (bundle.isNew()) {
-                stmt = bundleInsert;
+                sql = bundleInsertSQL;
             } else {
-                stmt = bundleUpdate;
+                sql = bundleUpdateSQL;
             }
             blob = createTemporaryBlob(new ByteArrayInputStream(out.toByteArray()));
-            stmt.setBlob(1, blob);
-            stmt.setBytes(2, bundle.getId().getUUID().getRawBytes());
-            stmt.execute();
+            Object[] key = getKey(bundle.getId().getUUID());
+            Object[] params = new Object[key.length + 1];
+            params[0] = blob;
+            for (int i = 0; i < params.length; i++) {
+                params[i + 1] = key[i];
+            }
+            connectionManager.executeStmt(sql, params);
         } catch (Exception e) {
             String msg = "failed to write bundle: " + bundle.getId();
             log.error(msg, e);
             throw new ItemStateException(msg, e);
         } finally {
-            resetStatement(stmt);
             if (blob != null) {
                 try {
                     freeTemporaryBlob(blob);
@@ -152,15 +155,11 @@
             throw new IllegalStateException("not initialized");
         }
 
-        PreparedStatement stmt = null;
         Blob blob = null;
         try {
             // check if insert or update
-            if (exists(refs.getId())) {
-                stmt = nodeReferenceUpdate;
-            } else {
-                stmt = nodeReferenceInsert;
-            }
+            boolean update = exists(refs.getId());
+            String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
 
             ByteArrayOutputStream out = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
             // serialize references
@@ -170,9 +169,13 @@
             // not have to additionally synchronize on the preparedStatement
 
             blob = createTemporaryBlob(new ByteArrayInputStream(out.toByteArray()));
-            stmt.setBlob(1, blob);
-            stmt.setBytes(2, refs.getTargetId().getUUID().getRawBytes());
-            stmt.execute();
+            Object[] key = getKey(refs.getTargetId().getUUID());
+            Object[] params = new Object[key.length + 1];
+            params[0] = blob;
+            for (int i = 0; i < params.length; i++) {
+                params[i + 1] = key[i];
+            }
+            connectionManager.executeStmt(sql, params);
 
             // there's no need to close a ByteArrayOutputStream
             //out.close();
@@ -181,7 +184,6 @@
             log.error(msg, e);
             throw new ItemStateException(msg, e);
         } finally {
-            resetStatement(stmt);
             if (blob != null) {
                 try {
                     freeTemporaryBlob(blob);
@@ -210,7 +212,7 @@
         Method createTemporary = blobClass.getMethod("createTemporary",
                 new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE});
         Object blob = createTemporary.invoke(null,
-                new Object[]{con, Boolean.FALSE, DURATION_SESSION_CONSTANT});
+                new Object[]{connectionManager.getConnection(), Boolean.FALSE, DURATION_SESSION_CONSTANT});
         Method open = blobClass.getMethod("open", new Class[]{Integer.TYPE});
         open.invoke(blob, new Object[]{MODE_READWRITE_CONSTANT});
         Method getBinaryOutputStream = blobClass.getMethod("getBinaryOutputStream", new Class[0]);
Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/OraclePersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/OraclePersistenceManager.java	(revision 573475)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/OraclePersistenceManager.java	(working copy)
@@ -16,12 +16,13 @@
  */
 package org.apache.jackrabbit.core.persistence.bundle;
 
+import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
 
 import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.bundle.util.DbNameIndex;
 import org.apache.jackrabbit.core.persistence.bundle.util.NGKDbNameIndex;
-import org.apache.jackrabbit.core.persistence.bundle.util.DbNameIndex;
 import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -111,16 +112,19 @@
             setSchemaObjectPrefix(context.getHomeDir().getName() + "_");
         }
         super.init(context);
-        
+
         // check driver version
         try {
-            DatabaseMetaData metaData = con.getMetaData();
-            if (metaData.getDriverMajorVersion() < 10) {
-                // Oracle drivers prior to version 10 only support
-                // writing BLOBs up to 32k in size...
-                log.warn("Unsupported driver version detected: "
-                        + metaData.getDriverName()
-                        + " v" + metaData.getDriverVersion());
+            Connection con = connectionManager.getConnection();
+            if (con != null) {
+                DatabaseMetaData metaData = con.getMetaData();
+                if (metaData.getDriverMajorVersion() < 10) {
+                    // Oracle drivers prior to version 10 only support
+                    // writing BLOBs up to 32k in size...
+                    log.warn("Unsupported driver version detected: "
+                            + metaData.getDriverName()
+                            + " v" + metaData.getDriverVersion());
+                }
             }
         } catch (SQLException e) {
             log.warn("Can not retrieve driver version", e);
@@ -133,7 +137,7 @@
      * @throws SQLException if an SQL error occurs.
      */
     protected DbNameIndex createDbNameIndex() throws SQLException {
-        return new NGKDbNameIndex(con, schemaObjectPrefix);
+        return new NGKDbNameIndex(connectionManager.getConnection(), schemaObjectPrefix);
     }
 
     /**
@@ -168,7 +172,7 @@
      * @inheritDoc
      */
     protected void prepareSchemaObjectPrefix() throws Exception {
-        DatabaseMetaData metaData = con.getMetaData();
+        DatabaseMetaData metaData = connectionManager.getConnection().getMetaData();
         String legalChars = metaData.getExtraNameCharacters();
         legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
 
Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/PostgreSQLPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/PostgreSQLPersistenceManager.java	(revision 573475)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/PostgreSQLPersistenceManager.java	(working copy)
@@ -28,9 +28,9 @@
 
 import java.io.DataInputStream;
 import java.io.InputStream;
-import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 
 /**
  * Extends the {@link BundleDbPersistenceManager} by PostgreSQL specific code.
@@ -77,7 +77,7 @@
      * @throws java.sql.SQLException if an SQL error occurs.
      */
     protected DbNameIndex createDbNameIndex() throws SQLException {
-        return new PostgreSQLNameIndex(con, schemaObjectPrefix);
+        return new PostgreSQLNameIndex(connectionManager.getConnection(), schemaObjectPrefix);
     }
 
     /**
@@ -90,10 +90,9 @@
 
     protected synchronized NodePropBundle loadBundle(NodeId id)
             throws ItemStateException {
-        PreparedStatement stmt = bundleSelect;
         try {
-            setKey(stmt, id.getUUID(), 1);
-            ResultSet rs = stmt.executeQuery();
+            Statement stmt = connectionManager.executeStmt(bundleSelectSQL, getKey(id.getUUID()));
+            ResultSet rs = stmt.getResultSet();
             try {
                 if (rs.next()) {
                     InputStream input = rs.getBinaryStream(1);
@@ -116,8 +115,6 @@
             String msg = "failed to read bundle: " + id + ": " + e;
             log.error(msg);
             throw new ItemStateException(msg, e);
-        } finally {
-            resetStatement(stmt);
         }
     }
 
Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ConnectionRecoverable.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ConnectionRecoverable.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ConnectionRecoverable.java	(revision 0)
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.bundle.util;
+
+import java.sql.Connection;
+
+/**
+ * This interface should be implemented by classes that want to use the
+ * {@link ConnectionRecoveryManager} class. It provides methods to setup
+ * a database connection, and a callback method that should be invoked
+ * whenever the database connection has been lost and consequently
+ * re-established.
+ *
+ */
+public interface ConnectionRecoverable {
+
+    /**
+     * A callback to this {@link ConnectionRecoverable} that is invoked
+     * by a {@link ConnectionRecoveryManager} whenever it has recovered from a
+     * connection loss.
+     *
+     * @param connection the new database connection
+     */
+    void recoverFromConnectionLoss(Connection connection);
+
+    /**
+     * @return the database driver
+     */
+    String getDriver();
+
+    /**
+     * @return the database url
+     */
+    String getUrl();
+
+    /**
+     * @return the user for the database connection
+     */
+    String getUser();
+
+    /**
+     * @return the password for the database connection
+     */
+    String getPassword();
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\bundle\util\ConnectionRecoverable.java
___________________________________________________________________
Name: svn:keywords
   + LastChangedBy LastChangedDate LastChangedRevision HeadURL
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ConnectionRecoveryManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ConnectionRecoveryManager.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/bundle/util/ConnectionRecoveryManager.java	(revision 0)
@@ -0,0 +1,387 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.bundle.util;
+
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class manages a database connection in close cooperation with the user
+ * of this {@link ConnectionRecoveryManager} which must implement the
+ * {@link ConnectionRecoverable} interface.
+ *
+ */
+public class ConnectionRecoveryManager {
+
+    /**
+     * The default logger.
+     */
+    private static Logger log = LoggerFactory.getLogger(ConnectionRecoveryManager.class);
+
+    /**
+     * The database connection that is managed by this {@link ConnectionRecoveryManager}.
+     */
+    private Connection connection;
+
+    /**
+     * An internal flag governing whether an automatic reconnect should be
+     * attempted after a SQLException had been encountered in
+     * {@link #executeStmt(String, Object[])}.
+     */
+    private boolean autoReconnect = true;
+
+    /**
+     * Time to sleep in ms before a reconnect is attempted.
+     */
+    private static final int SLEEP_BEFORE_RECONNECT = 10000;
+
+    /**
+     * The set of SQL statements which this class manages.
+     */
+    private Set sqlStatements = new HashSet();
+
+    /**
+     * The map of prepared statements (key: SQL stmt, value: prepared stmt).
+     */
+    private HashMap preparedStatements = new HashMap();
+
+    /**
+     * The class that needs this RecoverableConnectionManager.
+     */
+    private ConnectionRecoverable recoverable;
+
+    /**
+     * Indicates whether the managed connection is open or closed.
+     */
+    private boolean isClosed;
+
+    /**
+     * Number of reconnection attempts per method call.
+     */
+    private static final int TRIALS = 1;
+
+    /**
+     * Creates a {@link ConnectionRecoveryManager} and establishes
+     * a database Connection using the driver, user, password and url
+     * parameters provided by the {@link ConnectionRecoverable}.
+     *
+     * @param recoverable the {@link ConnectionRecoverable} that uses the
+     * connection managed by this class
+     * @throws Exception on error
+     */
+    public ConnectionRecoveryManager(ConnectionRecoverable recoverable) {
+        this.recoverable = recoverable;
+        try {
+            setupConnection();
+            isClosed = false;
+        } catch (Exception e) {
+            close();
+        }
+    }
+
+    /**
+     * @return the database connection that is managed, possibly null
+     */
+    public synchronized Connection getConnection() {
+        int trials = TRIALS;
+        while (trials-- > 0) {
+
+            // First, try to reconnect if needed
+            if (isClosed && autoReconnect) {
+                reestablishConnection();
+            }
+
+            // Then, try to return the connection
+            if (!isClosed) {
+                return connection;
+            }
+        }
+        log.warn("failed to get connection");
+        return null;
+    }
+
+    /**
+     * @param autoReconnect sets the autoReconnect feature
+     */
+    public synchronized void setAutoReconnect(boolean autoReconnect) {
+        this.autoReconnect = autoReconnect;
+    }
+
+    /**
+     * Sets the autoCommit feature of the managed database
+     * Connection.
+     *
+     * @param autoCommit sets the autoCommit feature
+     * @return true iff it succeeded
+     */
+    public synchronized boolean setAutoCommit(boolean autoCommit) {
+        int trials = TRIALS;
+        while (trials-- > 0) {
+
+            // First, try to reconnect if needed
+            if (isClosed && autoReconnect) {
+                reestablishConnection();
+            }
+
+            // Then, try to set the autocommit
+            if (!isClosed) {
+                try {
+                    connection.setAutoCommit(autoCommit);
+                    return true;
+                } catch (SQLException e) {
+                    logException("could not set autoCommit", e);
+                    close();
+                }
+            }
+        }
+        log.warn("failed to set autoCommit");
+        return false;
+    }
+
+    /**
+     * Creates a {@link PreparedStatement} from the given SQl String.
+     * This must be done first before {@link #executeStmt(String, Object[])}
+     * for the given SQL string can be called.
+     *
+     * @param sql the String to create a {@link PreparedStatement} for
+     * @return true iff succeeded
+     */
+    public synchronized boolean addStatement(String sql) {
+        int trials = TRIALS;
+        while (trials-- > 0) {
+
+            // First, try to reconnect if needed
+            if (isClosed && autoReconnect) {
+                reestablishConnection();
+            }
+
+            // Try to prepare the statement
+            if (!isClosed) {
+                sqlStatements.add(sql);
+                try {
+                    preparedStatements.put(sql, connection.prepareStatement(sql));
+                    return true;
+                } catch (SQLException e) {
+                    logException("could not prepare statement", e);
+                    sqlStatements.remove(sql);
+                    close();
+                }
+            }
+        }
+        log.warn("failed to add statement");
+        return false;
+    }
+
+    /**
+     * Executes the given SQL statement with the specified parameters.
+     * If a <code>SQLException</code> is encountered and
+     * <code>autoReconnect==true</code> <i>one</i> attempt is made to re-establish
+     * the database connection and re-execute the statement.
+     *
+     * @param sql    statement to execute
+     * @param params parameters to set
+     * @return the <code>Statement</code> object that had been executed
+     * @throws Exception if an error occurs
+     */
+    public synchronized Statement executeStmt(String sql, Object[] params)
+        throws Exception {
+
+        int trials = TRIALS;
+        while (trials-- > 0) {
+
+            // First, try to reconnect if needed
+            if (isClosed && autoReconnect) {
+                reestablishConnection();
+            }
+
+            // Then, try to execute the statement if the connection is back
+            if (!isClosed) {
+                PreparedStatement stmt = (PreparedStatement) preparedStatements.get(sql);
+                if (stmt == null) {
+                    throw new Exception("unknown SQL statement: " + sql);
+                }
+                try {
+                    for (int i = 0; i < params.length; i++) {
+                        if (params[i] instanceof Long) {
+                            stmt.setLong(i + 1, ((Long) params[i]).longValue());
+                        } else if (params[i] instanceof byte[]) {
+                            stmt.setBytes(i + 1, (byte[]) params[i]);
+                        } else if (params[i] instanceof Blob) {
+                            stmt.setBlob(i + 1, (Blob) params[i]);
+                        } else {
+                            stmt.setObject(i + 1, params[i]);
+                        }
+                    }
+                    stmt.execute();
+                    resetStatement(stmt);
+                    return stmt;
+                } catch (SQLException e) {
+                    logException("failed to execute statement", e);
+                    close();
+                }
+            }
+        }
+        // Failed to execute the stament
+        throw new Exception("failed to execute statement");
+    }
+
+    /**
+     * Re-establishes the database connection. This method is called by
+     * {@link #executeStmt(String, Object[])} after a <code>SQLException</code>
+     * had been encountered.
+     *
+     * @return true if the connection could be successfully re-established,
+     *         false otherwise.
+     */
+    public synchronized boolean reestablishConnection() {
+
+        // Try to shut down current connection gracefully
+        // in order to avoid potential memory leaks
+        close();
+
+        // Sleep for a while to give database a chance
+        // to restart before a reconnect is attempted.
+        try {
+            Thread.sleep(SLEEP_BEFORE_RECONNECT);
+        } catch (InterruptedException ignore) {
+        }
+
+        // now try to re-establish connection
+        try {
+            setupConnection();
+            rePrepareStatements();
+        } catch (Exception e) {
+            log.error("failed to re-establish connection: " + e.getMessage());
+            close();
+            return false;
+        }
+
+        // Order is important: set isClosed to false before calling
+        // recoverFromConnectionLoss as this callback might use methods
+        // of this class such as getConnection. Also disable
+        // autoReconnect before the callback to prevent infinite recursion.
+        boolean oldAutoReconnect = autoReconnect;
+        autoReconnect = false;
+        isClosed = false;
+        recoverable.recoverFromConnectionLoss(connection);
+        autoReconnect = oldAutoReconnect;
+        return true;
+    }
+
+    /**
+     * Closes all resources held by this {@link ConnectionRecoveryManager}.
+     */
+    public synchronized void close() {
+        // close shared prepared statements
+        for (Iterator it = preparedStatements.values().iterator(); it.hasNext(); ) {
+            PreparedStatement stmt = ((PreparedStatement) it.next());
+            if (stmt != null) {
+                try {
+                    stmt.close();
+                } catch (SQLException se) {
+                    // ignored, see JCR-765
+                }
+            }
+        }
+        preparedStatements.clear();
+        try {
+            if (connection != null) {
+                if (!connection.getAutoCommit()) {
+                    connection.rollback();
+                }
+                connection.close();
+            }
+        } catch (SQLException ignore) {
+        }
+        connection = null;
+        isClosed = true;
+    }
+
+    /**
+     * Creates the database connection.
+     *
+     * @throws Exception on error
+     */
+    private void setupConnection() throws Exception {
+        Driver drv = (Driver) Class.forName(recoverable.getDriver()).newInstance();
+        connection = DriverManager.getConnection(recoverable.getUrl(),
+                recoverable.getUser(), recoverable.getPassword());
+        connection.setAutoCommit(true);
+        DatabaseMetaData meta = connection.getMetaData();
+        try {
+            log.info("Database: " + meta.getDatabaseProductName() + " / " + meta.getDatabaseProductVersion());
+            log.info("Driver: " + meta.getDriverName() + " / " + meta.getDriverVersion());
+        } catch (SQLException e) {
+            log.warn("Can not retrieve database and driver name / version", e);
+        }
+    }
+
+    /**
+     * Re-prepares the statements that have been added by the
+     * {@link #addStatement(String)} method.
+     */
+    private void rePrepareStatements() throws SQLException {
+        for (Iterator it = sqlStatements.iterator(); it.hasNext(); ) {
+            String sqlString = (String) it.next();
+            preparedStatements.put(sqlString, connection.prepareStatement(sqlString));
+        }
+    }
+
+    /**
+     * Resets the given <code>PreparedStatement</code> by clearing the
+     * parameters and warnings contained.
+     *
+     * @param stmt The <code>PreparedStatement</code> to reset. If
+     *             <code>null</code> this method does nothing.
+     */
+    private void resetStatement(PreparedStatement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.clearParameters();
+                stmt.clearWarnings();
+            } catch (SQLException se) {
+                logException("Failed resetting PreparedStatement", se);
+            }
+        }
+    }
+
+    /**
+     * Logs an sql exception.
+     *
+     * @param message the message
+     * @param se the exception
+     */
+    private void logException(String message, SQLException se) {
+        message = message == null ? "" : message;
+        log.error(message + ", reason: " + se.getMessage() + ", state/code: "
+                + se.getSQLState() + "/" + se.getErrorCode());
+        log.debug("   dump:", se);
+    }
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\bundle\util\ConnectionRecoveryManager.java
___________________________________________________________________
Name: svn:keywords
   + LastChangedBy LastChangedDate LastChangedRevision HeadURL
Name: svn:eol-style
   + native

