Index: src/main/java/org/apache/jackrabbit/core/persistence/AbstractPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/AbstractPersistenceManager.java	(revision 586792)
+++ src/main/java/org/apache/jackrabbit/core/persistence/AbstractPersistenceManager.java	(working copy)
@@ -56,7 +56,7 @@
      *
      * {@inheritDoc}
      */
-    public synchronized void store(ChangeLog changeLog) throws ItemStateException {
+    public void store(ChangeLog changeLog) throws ItemStateException {
         Iterator iter = changeLog.deletedStates();
         while (iter.hasNext()) {
             ItemState state = (ItemState) iter.next();
Index: src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java	(revision 586792)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java	(working copy)
@@ -109,7 +109,7 @@
  * </pre>
  * See also {@link DerbyPersistenceManager}, {@link OraclePersistenceManager}.
  */
-public class SimpleDbPersistenceManager extends DatabasePersistenceManager {
+public class SimpleDbPersistenceManager extends JDBCPersistenceManager {
 
     protected String driver;
     protected String url;
Index: src/main/java/org/apache/jackrabbit/core/persistence/db/OracleDatasourcePersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/OracleDatasourcePersistenceManager.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/OracleDatasourcePersistenceManager.java	(revision 0)
@@ -0,0 +1,468 @@
+/*
+ * 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.db;
+
+import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.util.Serializer;
+import org.apache.jackrabbit.core.state.*;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import javax.transaction.UserTransaction;
+import java.io.*;
+import java.lang.reflect.Method;
+import java.sql.*;
+
+/**
+ * <code>OraclePersistenceManager</code> is a JDBC-based
+ * <code>PersistenceManager</code> for Jackrabbit that persists
+ * <code>ItemState</code> and <code>NodeReferences</code> objects in Oracle
+ * database using a simple custom serialization format and a
+ * very basic non-normalized database schema (in essence tables with one 'key'
+ * and one 'data' column).
+ * <p/>
+ * It is configured through the following properties:
+ * <ul>
+ * <li><code>driver</code>: the FQN name of the JDBC driver class
+ * (default: <code>"oracle.jdbc.OracleDriver"</code>)</li>
+ * <li><code>schema</code>: type of schema to be used
+ * (default: <code>"oracle"</code>)</li>
+ * <li><code>url</code>: the database url (e.g.
+ * <code>"jdbc:oracle:thin:@[host]:[port]:[sid]"</code>)</li>
+ * <li><code>user</code>: the database user</li>
+ * <li><code>password</code>: the user's password</li>
+ * <li><code>schemaObjectPrefix</code>: prefix to be prepended to schema objects</li>
+ * <li><code>tableSpace</code>: the tablespace to use</li>
+ * <li><code>externalBLOBs</code>: if <code>true</code> (the default) BINARY
+ * values (BLOBs) are stored in the local file system;
+ * if <code>false</code> BLOBs are stored in the database</li>
+ * </ul>
+ * See also {@link org.apache.jackrabbit.core.persistence.db.SimpleDbPersistenceManager}.
+ * <p/>
+ * The following is a fragment from a sample configuration:
+ * <pre>
+ *   &lt;PersistenceManager class="org.apache.jackrabbit.core.persistence.db.OraclePersistenceManager"&gt;
+ *       &lt;param name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/&gt;
+ *       &lt;param name="user" value="scott"/&gt;
+ *       &lt;param name="password" value="tiger"/&gt;
+ *       &lt;param name="schemaObjectPrefix" value="${wsp.name}_"/&gt;
+ *       &lt;param name="tableSpace" value=""/&gt;
+ *       &lt;param name="externalBLOBs" value="false"/&gt;
+ *  &lt;/PersistenceManager&gt;
+ * </pre>
+ */
+public class OracleDatasourcePersistenceManager extends DatasourcePersistenceManager {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(OraclePersistenceManager.class);
+
+    private Class blobClass;
+    private Integer DURATION_SESSION_CONSTANT;
+    private Integer MODE_READWRITE_CONSTANT;
+
+    /** the variable for the Oracle table space */
+    public static final String TABLE_SPACE_VARIABLE =
+        "${tableSpace}";
+
+    /** the Oracle table space to use */
+    protected String tableSpace;
+
+    /**
+     * Creates a new <code>OraclePersistenceManager</code> instance.
+     */
+    public OracleDatasourcePersistenceManager() {
+        // preset some attributes to reasonable defaults
+        schema = "oracle";
+        schemaObjectPrefix = "";
+        setDataSourceLocation("oracleDS");
+        initialized = false;
+    }
+
+    /**
+     * Returns the configured Oracle table space.
+     * @return the configured Oracle table space.
+     */
+    public String getTableSpace() {
+        return tableSpace;
+    }
+
+    /**
+     * Sets the Oracle table space.
+     * @param tableSpace the Oracle table space.
+     */
+    public void setTableSpace(String tableSpace) {
+        if (tableSpace != null) {
+            this.tableSpace = tableSpace.trim();
+        } else {
+            this.tableSpace = null;
+        }
+    }
+
+    //---------------------------------< SimpleDbPersistenceManager overrides >
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Retrieve the <code>oracle.sql.BLOB</code> class via reflection, and
+     * initialize the values for the <code>DURATION_SESSION</code> and
+     * <code>MODE_READWRITE</code> constants defined there.
+     * @see oracle.sql.BLOB#DURATION_SESSION
+     * @see oracle.sql.BLOB#MODE_READWRITE
+     * @noinspection JavadocReference
+     */
+    public void init(PMContext context) throws Exception {
+        super.init(context);
+
+        if (!externalBLOBs) {
+            blobStore = new OracleBLOBStore();
+        }
+
+        // initialize oracle.sql.BLOB class & constants
+
+        // use the Connection object for using the exact same
+        // class loader that the Oracle driver was loaded with
+        blobClass = getConnection().getClass().getClassLoader().loadClass("oracle.sql.BLOB");
+        DURATION_SESSION_CONSTANT =
+                new Integer(blobClass.getField("DURATION_SESSION").getInt(null));
+        MODE_READWRITE_CONSTANT =
+                new Integer(blobClass.getField("MODE_READWRITE").getInt(null));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists((NodeId) state.getId());
+        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
+
+        Blob blob = null;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize node state
+            Serializer.serialize(state, out);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            blob = createTemporaryBlob(con, new ByteArrayInputStream(out.toByteArray()));
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBlob(1, blob);
+            stmt.setString(2, state.getNodeId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + state.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            if (blob != null) {
+                try {
+                    freeTemporaryBlob(blob);
+                } catch (Exception ignore) {
+                }
+            }
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void store(PropertyState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists((PropertyId) state.getId());
+        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
+
+        Blob blob = null;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize property state
+            Serializer.serialize(state, out, blobStore);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            blob = createTemporaryBlob(con, new ByteArrayInputStream(out.toByteArray()));
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBlob(1, blob);
+            stmt.setString(2, state.getPropertyId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + state.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            if (blob != null) {
+                try {
+                    freeTemporaryBlob(blob);
+                } catch (Exception ignore) {
+                }
+            }
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void store(NodeReferences refs) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+
+        Blob blob = null;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            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 sql statement
+            blob = createTemporaryBlob(con, new ByteArrayInputStream(out.toByteArray()));
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBlob(1, blob);
+            stmt.setString(2, refs.getId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node references: " + refs.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            if (blob != null) {
+                try {
+                    freeTemporaryBlob(blob);
+                } catch (Exception ignore) {
+                }
+            }
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Overridden in order to support multiple oracle schemas. Note that
+     * schema names in Oracle correspond to the username of the connection.
+     * See http://issues.apache.org/jira/browse/JCR-582
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void checkSchema() throws Exception {
+        Connection con = null;
+        try {
+            con = getConnection();
+            DatabaseMetaData metaData = con.getMetaData();
+            String tableName = schemaObjectPrefix + "NODE";
+            if (metaData.storesLowerCaseIdentifiers()) {
+                tableName = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                tableName = tableName.toUpperCase();
+            }
+            String userName = metaData.getUserName();
+
+            ResultSet rs = metaData.getTables(null, userName, tableName, null);
+            boolean schemaExists;
+            try {
+                schemaExists = rs.next();
+            } finally {
+                rs.close();
+            }
+
+            if (!schemaExists) {
+                // read ddl from resources
+                InputStream in = getSchemaDDL();
+                if (in == null) {
+                    String msg = "Configuration error: unknown schema '" + schema + "'";
+                    log.debug(msg);
+                    throw new RepositoryException(msg);
+                }
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                Statement stmt = con.createStatement();
+                UserTransaction trans = super.getUserTransaction();
+                try {
+                    trans.begin();
+                    String sql = reader.readLine();
+                    while (sql != null) {
+                        // Skip comments and empty lines
+                        if (!sql.startsWith("#") && sql.length() > 0) {
+                            // replace prefix variable
+                            sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
+
+                            // set the tablespace if it is defined
+                            String tspace;
+                            if (tableSpace == null || "".equals(tableSpace)) {
+                                tspace = "";
+                            } else {
+                                tspace = "tablespace " + tableSpace;
+                            }
+                            sql = Text.replace(sql, TABLE_SPACE_VARIABLE, tspace).trim();
+
+                            // execute sql stmt
+                            stmt.executeUpdate(sql);
+                        }
+                        // read next sql stmt
+                        sql = reader.readLine();
+                    }
+                    // commit the changes
+                    trans.commit();
+                } catch (Exception e) {
+                    trans.rollback();
+                    throw e;
+                } finally {
+                    closeStream(in);
+                    closeStatement(stmt);
+                }
+            }
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    //----------------------------------------< oracle-specific blob handling >
+    /**
+     * Creates a temporary oracle.sql.BLOB instance via reflection and spools
+     * the contents of the specified stream.
+     */
+    protected Blob createTemporaryBlob(Connection con, InputStream in) throws Exception {
+        /*
+        BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
+        blob.open(BLOB.MODE_READWRITE);
+        OutputStream out = blob.getBinaryOutputStream();
+        ...
+        out.flush();
+        out.close();
+        blob.close();
+        return blob;
+        */
+        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});
+        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]);
+        OutputStream out = (OutputStream) getBinaryOutputStream.invoke(blob, null);
+        try {
+            int read;
+            byte[] buf = new byte[8192];
+            while ((read = in.read(buf, 0, buf.length)) > -1) {
+                out.write(buf, 0, read);
+            }
+        } finally {
+            try {
+                out.flush();
+            } catch (IOException ioe) {
+            }
+            out.close();
+        }
+        Method close = blobClass.getMethod("close", new Class[0]);
+        close.invoke(blob, null);
+        return (Blob) blob;
+    }
+
+    /**
+     * Frees a temporary oracle.sql.BLOB instance via reflection.
+     */
+    protected void freeTemporaryBlob(Object blob) throws Exception {
+        // blob.freeTemporary();
+        Method freeTemporary = blobClass.getMethod("freeTemporary", new Class[0]);
+        freeTemporary.invoke(blob, null);
+    }
+
+    //--------------------------------------------------------< inner classes >
+    class OracleBLOBStore extends DatasourceBLOBStore {
+        /**
+         * {@inheritDoc}
+         */
+        public synchronized void put(String blobId, InputStream in, long size)
+                throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            ResultSet rs = null;
+            boolean exists = false;
+            try {
+                con = getConnection();
+                stmt = con.prepareStatement(blobSelectExistSQL);
+                stmt.setString(1, blobId);
+                rs = stmt.executeQuery();
+                exists = rs.next();
+            } finally {
+                closeResultSet(rs);
+                closeStatement(stmt);
+            }
+
+            Blob blob = null;
+            try {
+                String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
+                blob = createTemporaryBlob(con, in);
+                stmt = con.prepareStatement(sql);
+                stmt.setBlob(1, blob);
+                stmt.setString(2, blobId);
+                stmt.execute();
+            } finally {
+                if (blob != null) {
+                    try {
+                        freeTemporaryBlob(blob);
+                    } catch (Exception ignore) {
+                    }
+                }
+                closeConnection(con);
+                closeStatement(stmt);
+            }
+        }
+    }
+}
\ No newline at end of file

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\db\OracleDatasourcePersistenceManager.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/persistence/db/JDBCPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/JDBCPersistenceManager.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/JDBCPersistenceManager.java	(revision 0)
@@ -0,0 +1,825 @@
+/*
+ * 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.db;
+
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.Serializer;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemState;
+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.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.value.BLOBFileValue;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PropertyType;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Abstract base class for JDBC based database persistence managers. This class
+ * contains common functionality for database persistence manager subclasses
+ * that normally differ only in the way the database connection is acquired.
+ * Subclasses should override the {@link #getConnection()} method to return
+ * the configured database connection.
+ * <p>
+ * See the {@link org.apache.jackrabbit.core.persistence.db.SimpleDbPersistenceManager} for a detailed description
+ * of the available configuration options and database behaviour.
+ */
+public abstract class JDBCPersistenceManager extends DatabasePersistenceManager {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(DatabasePersistenceManager.class);
+
+    // jdbc connection
+    protected Connection con;
+
+    // internal flag governing whether an automatic reconnect should be
+    // attempted after a SQLException had been encountered
+    protected boolean autoReconnect = true;
+    // time to sleep in ms before a reconnect is attempted
+    protected static final int SLEEP_BEFORE_RECONNECT = 10000;
+
+    // the map of prepared statements (key: sql stmt, value: prepared stmt)
+    private HashMap preparedStatements = new HashMap();
+
+    /**
+     * Creates a new <code>DatabasePersistenceManager</code> instance.
+     */
+    public JDBCPersistenceManager() {
+        super();
+    }
+
+    public BLOBStore getBLOBStore() {
+        return new JDBCBLOBStore();
+    }
+
+    //---------------------------------------------------< PersistenceManager >
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(PMContext context) throws Exception {
+        if (initialized) {
+            throw new IllegalStateException("already initialized");
+        }
+
+        // setup jdbc connection
+        initConnection();
+
+        super.init(context);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void close() throws Exception {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        try {
+            // close shared prepared statements
+            for (Iterator it = preparedStatements.values().iterator(); it.hasNext();) {
+                closeStatement((PreparedStatement) it.next());
+            }
+            preparedStatements.clear();
+
+            if (externalBLOBs) {
+                // close BLOB file system
+                blobFS.close();
+                blobFS = null;
+            }
+            blobStore = null;
+
+            // close jdbc connection
+            closeConnection(con);
+
+        } finally {
+            initialized = false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void store(ChangeLog changeLog)
+            throws ItemStateException {
+        // temporarily disable automatic reconnect feature
+        // since the changes need to be persisted atomically
+        autoReconnect = false;
+        try {
+            ItemStateException ise = null;
+            // number of attempts to store the changes
+            int trials = 2;
+            while (trials > 0) {
+                try {
+                    super.store(changeLog);
+                    break;
+                } catch (ItemStateException e) {
+                    // catch exception and fall through...
+                    ise = e;
+                }
+
+                if (ise != null && ise.getCause() instanceof SQLException
+                        && --trials > 0) {
+                    // a SQLException has been thrown, try to reconnect
+                    log.warn("storing changes failed, about to reconnect...", ise.getCause());
+
+                    // try to reconnect
+                    if (reestablishConnection()) {
+                        // now let's give it another try
+                        ise = null;
+                        continue;
+                    } else {
+                        // reconnect failed, proceed with error processing
+                        break;
+                    }
+                }
+            }
+
+            if (ise == null) {
+                // storing the changes succeeded, now commit the changes
+                try {
+                    con.commit();
+                } catch (SQLException e) {
+                    String msg = "committing change log failed";
+                    log.error(msg, e);
+                    throw new ItemStateException(msg, e);
+                }
+            } else {
+                // storing the changes failed, rollback changes
+                try {
+                    con.rollback();
+                } catch (SQLException e) {
+                    String msg = "rollback of change log failed";
+                    log.error(msg, e);
+                }
+                // re-throw original exception
+                throw ise;
+            }
+        } finally {
+            // re-enable automatic reconnect feature
+            autoReconnect = true;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeState load(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        synchronized (nodeStateSelectSQL) {
+            ResultSet rs = null;
+            InputStream in = null;
+            try {
+                Statement stmt = executeStmt(nodeStateSelectSQL, new Object[]{id.toString()});
+                rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    throw new NoSuchItemStateException(id.toString());
+                }
+
+                in = rs.getBinaryStream(1);
+                NodeState state = createNew(id);
+                Serializer.deserialize(state, in);
+
+                return state;
+            } catch (Exception e) {
+                if (e instanceof NoSuchItemStateException) {
+                    throw (NoSuchItemStateException) e;
+                }
+                String msg = "failed to read node state: " + id;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                closeStream(in);
+                closeResultSet(rs);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PropertyState load(PropertyId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        synchronized (propertyStateSelectSQL) {
+            ResultSet rs = null;
+            InputStream in = null;
+            try {
+                Statement stmt = executeStmt(propertyStateSelectSQL, new Object[]{id.toString()});
+                rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    throw new NoSuchItemStateException(id.toString());
+                }
+
+                in = rs.getBinaryStream(1);
+                PropertyState state = createNew(id);
+                Serializer.deserialize(state, in, blobStore);
+
+                return state;
+            } catch (Exception e) {
+                if (e instanceof NoSuchItemStateException) {
+                    throw (NoSuchItemStateException) e;
+                }
+                String msg = "failed to read property state: " + id;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                closeStream(in);
+                closeResultSet(rs);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * This method uses shared <code>PreparedStatement</code>s which must
+     * be executed strictly sequentially. Because this method synchronizes on
+     * the persistence manager instance there is no need to synchronize on the
+     * shared statement. If the method would not be sychronized the shared
+     * statements would have to be synchronized.
+     */
+    public synchronized void store(NodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
+
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize node state
+            Serializer.serialize(state, out);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            executeStmt(sql, new Object[]{out.toByteArray(), state.getNodeId().toString()});
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + state.getNodeId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * This method uses shared <code>PreparedStatement</code>s which must
+     * be executed strictly sequentially. Because this method synchronizes on
+     * the persistence manager instance there is no need to synchronize on the
+     * shared statement. If the method would not be sychronized the shared
+     * statements would have to be synchronized.
+     */
+    public synchronized void store(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
+
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize property state
+            Serializer.serialize(state, out, blobStore);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            executeStmt(sql, new Object[]{out.toByteArray(), state.getPropertyId().toString()});
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + state.getPropertyId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void destroy(NodeState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        try {
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            executeStmt(nodeStateDeleteSQL, new Object[]{state.getNodeId().toString()});
+        } catch (Exception e) {
+            String msg = "failed to delete node state: " + state.getNodeId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void destroy(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // make sure binary values (BLOBs) are properly removed
+        InternalValue[] values = state.getValues();
+        if (values != null) {
+            for (int i = 0; i < values.length; i++) {
+                InternalValue val = values[i];
+                if (val != null) {
+                    if (val.getType() == PropertyType.BINARY) {
+                        BLOBFileValue blobVal = val.getBLOBFileValue();
+                        // delete internal resource representation of BLOB value
+                        blobVal.delete(true);
+                        // also remove from BLOBStore
+                        String blobId = blobStore.createId(state.getPropertyId(), i);
+                        try {
+                            blobStore.remove(blobId);
+                        } catch (Exception e) {
+                            log.warn("failed to remove from BLOBStore: " + blobId, e);
+                        }
+                    }
+                }
+            }
+        }
+
+        try {
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            executeStmt(propertyStateDeleteSQL, new Object[]{state.getPropertyId().toString()});
+        } catch (Exception e) {
+            String msg = "failed to delete property state: " + state.getPropertyId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeReferences load(NodeReferencesId targetId)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        synchronized (nodeReferenceSelectSQL) {
+            ResultSet rs = null;
+            InputStream in = null;
+            try {
+                Statement stmt = executeStmt(
+                        nodeReferenceSelectSQL, new Object[]{targetId.toString()});
+                rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    throw new NoSuchItemStateException(targetId.toString());
+                }
+
+                in = rs.getBinaryStream(1);
+                NodeReferences refs = new NodeReferences(targetId);
+                Serializer.deserialize(refs, in);
+
+                return refs;
+            } catch (Exception e) {
+                if (e instanceof NoSuchItemStateException) {
+                    throw (NoSuchItemStateException) e;
+                }
+                String msg = "failed to read node references: " + targetId;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                closeStream(in);
+                closeResultSet(rs);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * This method uses shared <code>PreparedStatement</code>s which must
+     * be executed strictly sequentially. Because this method synchronizes on
+     * the persistence manager instance there is no need to synchronize on the
+     * shared statement. If the method would not be sychronized the shared
+     * statements would have to be synchronized.
+     */
+    public synchronized void store(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+
+        try {
+            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 sql statement
+            executeStmt(sql, new Object[]{out.toByteArray(), refs.getId().toString()});
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node references: " + refs.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void destroy(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        try {
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            executeStmt(nodeReferenceDeleteSQL, new Object[]{refs.getId().toString()});
+        } catch (Exception e) {
+            String msg = "failed to delete node references: " + refs.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        synchronized (nodeStateSelectExistSQL) {
+            ResultSet rs = null;
+            try {
+                Statement stmt = executeStmt(nodeStateSelectExistSQL, new Object[]{id.toString()});
+                rs = stmt.getResultSet();
+
+                // a node state exists if the result has at least one entry
+                return rs.next();
+            } catch (Exception e) {
+                String msg = "failed to check existence of node state: " + id;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                closeResultSet(rs);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(PropertyId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        synchronized (propertyStateSelectExistSQL) {
+            ResultSet rs = null;
+            try {
+                Statement stmt = executeStmt(
+                        propertyStateSelectExistSQL, new Object[]{id.toString()});
+                rs = stmt.getResultSet();
+
+                // a property state exists if the result has at least one entry
+                return rs.next();
+            } catch (Exception e) {
+                String msg = "failed to check existence of property state: " + id;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                closeResultSet(rs);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeReferencesId targetId) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        synchronized (nodeReferenceSelectExistSQL) {
+            ResultSet rs = null;
+            try {
+                Statement stmt = executeStmt(
+                        nodeReferenceSelectExistSQL, new Object[]{targetId.toString()});
+                rs = stmt.getResultSet();
+
+                // 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;
+                log.error(msg, e);
+                throw new ItemStateException(msg, e);
+            } finally {
+                closeResultSet(rs);
+            }
+        }
+    }
+
+    //----------------------------------< misc. helper methods & overridables >
+
+    /**
+     * Initializes the database connection used by this persistence manager.
+     * <p>
+     * Subclasses should normally override the {@link #getConnection()}
+     * method instead of this one. The default implementation calls
+     * {@link #getConnection()} to get the database connection and disables
+     * the autocommit feature.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void initConnection() throws Exception {
+        con = getConnection();
+        con.setAutoCommit(false);
+    }
+
+    /**
+     * Re-establishes the database connection. This method is called by
+     * {@link #store(ChangeLog)} and {@link #executeStmt(String, Object[])}
+     * after a <code>SQLException</code> had been encountered.
+     * @return true if the connection could be successfully re-established,
+     *         false otherwise.
+     */
+    protected synchronized boolean reestablishConnection() {
+        // in any case try to shut down current connection
+        // gracefully in order to avoid potential memory leaks
+
+        // 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
+                }
+            }
+        }
+        closeConnection(con);
+
+        // 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 {
+            initConnection();
+            initPreparedStatements();
+            return true;
+        } catch (Exception e) {
+            log.error("failed to re-establish connection", e);
+            // reconnect failed
+            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 SQLException if an error occurs
+     */
+    protected Statement executeStmt(String sql, Object[] params)
+            throws SQLException {
+        int trials = autoReconnect ? 2 : 1;
+        while (true) {
+            PreparedStatement stmt = (PreparedStatement) preparedStatements.get(sql);
+            try {
+                for (int i = 0; i < params.length; i++) {
+                    if (params[i] instanceof SizedInputStream) {
+                        SizedInputStream in = (SizedInputStream) params[i];
+                        stmt.setBinaryStream(i + 1, in, (int) in.getSize());
+                    } else {
+                        stmt.setObject(i + 1, params[i]);
+                    }
+                }
+                stmt.execute();
+                resetStatement(stmt);
+                return stmt;
+            } catch (SQLException se) {
+                if (--trials == 0) {
+                    // no more trials, re-throw
+                    throw se;
+                }
+                log.warn("execute failed, about to reconnect...", se.getMessage());
+
+                // try to reconnect
+                if (reestablishConnection()) {
+                    // reconnect succeeded; check whether it's possible to
+                    // re-execute the prepared stmt with the given parameters
+                    for (int i = 0; i < params.length; i++) {
+                        if (params[i] instanceof SizedInputStream) {
+                            SizedInputStream in = (SizedInputStream) params[i];
+                            if (in.isConsumed()) {
+                                // we're unable to re-execute the prepared stmt
+                                // since an InputStream paramater has already
+                                // been 'consumed';
+                                // re-throw previous SQLException
+                                throw se;
+                            }
+                        }
+                    }
+
+                    // try again to execute the statement
+                    continue;
+                } else {
+                    // reconnect failed, re-throw previous SQLException
+                    throw se;
+                }
+            }
+        }
+    }
+
+    /**
+     * Initializes the map of prepared statements.
+     *
+     * @throws SQLException if an error occurs
+     */
+    protected void initPreparedStatements() throws SQLException {
+        preparedStatements.put(
+                nodeStateInsertSQL, con.prepareStatement(nodeStateInsertSQL));
+        preparedStatements.put(
+                nodeStateUpdateSQL, con.prepareStatement(nodeStateUpdateSQL));
+        preparedStatements.put(
+                nodeStateSelectSQL, con.prepareStatement(nodeStateSelectSQL));
+        preparedStatements.put(
+                nodeStateSelectExistSQL, con.prepareStatement(nodeStateSelectExistSQL));
+        preparedStatements.put(
+                nodeStateDeleteSQL, con.prepareStatement(nodeStateDeleteSQL));
+
+        preparedStatements.put(
+                propertyStateInsertSQL, con.prepareStatement(propertyStateInsertSQL));
+        preparedStatements.put(
+                propertyStateUpdateSQL, con.prepareStatement(propertyStateUpdateSQL));
+        preparedStatements.put(
+                propertyStateSelectSQL, con.prepareStatement(propertyStateSelectSQL));
+        preparedStatements.put(
+                propertyStateSelectExistSQL, con.prepareStatement(propertyStateSelectExistSQL));
+        preparedStatements.put(
+                propertyStateDeleteSQL, con.prepareStatement(propertyStateDeleteSQL));
+
+        preparedStatements.put(
+                nodeReferenceInsertSQL, con.prepareStatement(nodeReferenceInsertSQL));
+        preparedStatements.put(
+                nodeReferenceUpdateSQL, con.prepareStatement(nodeReferenceUpdateSQL));
+        preparedStatements.put(
+                nodeReferenceSelectSQL, con.prepareStatement(nodeReferenceSelectSQL));
+        preparedStatements.put(
+                nodeReferenceSelectExistSQL, con.prepareStatement(nodeReferenceSelectExistSQL));
+        preparedStatements.put(
+                nodeReferenceDeleteSQL, con.prepareStatement(nodeReferenceDeleteSQL));
+
+        if (!externalBLOBs) {
+            preparedStatements.put(blobInsertSQL, con.prepareStatement(blobInsertSQL));
+            preparedStatements.put(blobUpdateSQL, con.prepareStatement(blobUpdateSQL));
+            preparedStatements.put(blobSelectSQL, con.prepareStatement(blobSelectSQL));
+            preparedStatements.put(blobSelectExistSQL, con.prepareStatement(blobSelectExistSQL));
+            preparedStatements.put(blobDeleteSQL, con.prepareStatement(blobDeleteSQL));
+        }
+    }
+
+    //--------------------------------------------------------< inner classes >
+
+    class JDBCBLOBStore extends DbBLOBStore {
+        /**
+         * {@inheritDoc}
+         */
+        public InputStream get(String blobId) throws Exception {
+            synchronized (blobSelectSQL) {
+                Statement stmt = executeStmt(blobSelectSQL, new Object[]{blobId});
+                final ResultSet rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    closeResultSet(rs);
+                    throw new Exception("no such BLOB: " + blobId);
+                }
+                InputStream in = rs.getBinaryStream(1);
+                if (in == null) {
+                    // some databases treat zero-length values as NULL;
+                    // return empty InputStream in such a case
+                    closeResultSet(rs);
+                    return new ByteArrayInputStream(new byte[0]);
+                }
+
+                /**
+                 * return an InputStream wrapper in order to
+                 * close the ResultSet when the stream is closed
+                 */
+                return new FilterInputStream(in) {
+                    public void close() throws IOException {
+                        in.close();
+                        // now it's safe to close ResultSet
+                        closeResultSet(rs);
+                    }
+                };
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public synchronized void put(String blobId, InputStream in, long size)
+                throws Exception {
+            Statement stmt = executeStmt(blobSelectExistSQL, new Object[]{blobId});
+            ResultSet rs = stmt.getResultSet();
+            // a BLOB exists if the result has at least one entry
+            boolean exists = rs.next();
+            closeResultSet(rs);
+
+            String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
+            executeStmt(sql, new Object[]{new SizedInputStream(in, size), blobId});
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public synchronized boolean remove(String blobId) throws Exception {
+            Statement stmt = executeStmt(blobDeleteSQL, new Object[]{blobId});
+            return stmt.getUpdateCount() == 1;
+        }
+    }
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\db\JDBCPersistenceManager.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java	(revision 586792)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java	(working copy)
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.core.persistence.db;
 
-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.local.LocalFileSystem;
@@ -24,26 +23,12 @@
 import org.apache.jackrabbit.core.persistence.PMContext;
 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.ItemState;
-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.core.state.NodeState;
-import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.core.value.BLOBFileValue;
-import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FilterInputStream;
 import java.io.IOException;
@@ -55,8 +40,6 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.util.HashMap;
-import java.util.Iterator;
 
 /**
  * Abstract base class for database persistence managers. This class
@@ -88,18 +71,6 @@
     // initial size of buffer used to serialize objects
     protected static final int INITIAL_BUFFER_SIZE = 1024;
 
-    // jdbc connection
-    protected Connection con;
-
-    // internal flag governing whether an automatic reconnect should be
-    // attempted after a SQLException had been encountered
-    protected boolean autoReconnect = true;
-    // time to sleep in ms before a reconnect is attempted
-    protected static final int SLEEP_BEFORE_RECONNECT = 10000;
-
-    // the map of prepared statements (key: sql stmt, value: prepared stmt)
-    private HashMap preparedStatements = new HashMap();
-
     // SQL statements for NodeState management
     protected String nodeStateInsertSQL;
     protected String nodeStateUpdateSQL;
@@ -183,6 +154,8 @@
         this.externalBLOBs = Boolean.valueOf(externalBLOBs).booleanValue();
     }
 
+    public abstract BLOBStore getBLOBStore();
+
     //---------------------------------------------------< PersistenceManager >
     /**
      * {@inheritDoc}
@@ -192,17 +165,6 @@
             throw new IllegalStateException("already initialized");
         }
 
-        // setup jdbc connection
-        initConnection();
-
-        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);
-        }
-
         // make sure schemaObjectPrefix consists of legal name characters only
         prepareSchemaObjectPrefix();
 
@@ -212,9 +174,6 @@
         // build sql statements
         buildSQLStatements();
 
-        // prepare statements
-        initPreparedStatements();
-
         if (externalBLOBs) {
             /**
              * store BLOBs in local file system in a sub directory
@@ -229,517 +188,15 @@
             /**
              * store BLOBs in db
              */
-            blobStore = new DbBLOBStore();
+            blobStore = getBLOBStore();
         }
 
         initialized = true;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public synchronized void close() throws Exception {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        try {
-            // close shared prepared statements
-            for (Iterator it = preparedStatements.values().iterator(); it.hasNext();) {
-                closeStatement((PreparedStatement) it.next());
-            }
-            preparedStatements.clear();
-
-            if (externalBLOBs) {
-                // close BLOB file system
-                blobFS.close();
-                blobFS = null;
-            }
-            blobStore = null;
-
-            // close jdbc connection
-            closeConnection(con);
-
-        } finally {
-            initialized = false;
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public synchronized void store(ChangeLog changeLog)
-            throws ItemStateException {
-        // temporarily disable automatic reconnect feature
-        // since the changes need to be persisted atomically
-        autoReconnect = false;
-        try {
-            ItemStateException ise = null;
-            // number of attempts to store the changes
-            int trials = 2;
-            while (trials > 0) {
-                try {
-                    super.store(changeLog);
-                    break;
-                } catch (ItemStateException e) {
-                    // catch exception and fall through...
-                    ise = e;
-                }
-
-                if (ise != null && ise.getCause() instanceof SQLException
-                        && --trials > 0) {
-                    // a SQLException has been thrown, try to reconnect
-                    log.warn("storing changes failed, about to reconnect...", ise.getCause());
-
-                    // try to reconnect
-                    if (reestablishConnection()) {
-                        // now let's give it another try
-                        ise = null;
-                        continue;
-                    } else {
-                        // reconnect failed, proceed with error processing
-                        break;
-                    }
-                }
-            }
-
-            if (ise == null) {
-                // storing the changes succeeded, now commit the changes
-                try {
-                    con.commit();
-                } catch (SQLException e) {
-                    String msg = "committing change log failed";
-                    log.error(msg, e);
-                    throw new ItemStateException(msg, e);
-                }
-            } else {
-                // storing the changes failed, rollback changes
-                try {
-                    con.rollback();
-                } catch (SQLException e) {
-                    String msg = "rollback of change log failed";
-                    log.error(msg, e);
-                }
-                // re-throw original exception
-                throw ise;
-            }
-        } finally {
-            // re-enable automatic reconnect feature
-            autoReconnect = true;
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public NodeState load(NodeId id)
-            throws NoSuchItemStateException, ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        synchronized (nodeStateSelectSQL) {
-            ResultSet rs = null;
-            InputStream in = null;
-            try {
-                Statement stmt = executeStmt(nodeStateSelectSQL, new Object[]{id.toString()});
-                rs = stmt.getResultSet();
-                if (!rs.next()) {
-                    throw new NoSuchItemStateException(id.toString());
-                }
-
-                in = rs.getBinaryStream(1);
-                NodeState state = createNew(id);
-                Serializer.deserialize(state, in);
-
-                return state;
-            } catch (Exception e) {
-                if (e instanceof NoSuchItemStateException) {
-                    throw (NoSuchItemStateException) e;
-                }
-                String msg = "failed to read node state: " + id;
-                log.error(msg, e);
-                throw new ItemStateException(msg, e);
-            } finally {
-                closeStream(in);
-                closeResultSet(rs);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public PropertyState load(PropertyId id)
-            throws NoSuchItemStateException, ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        synchronized (propertyStateSelectSQL) {
-            ResultSet rs = null;
-            InputStream in = null;
-            try {
-                Statement stmt = executeStmt(propertyStateSelectSQL, new Object[]{id.toString()});
-                rs = stmt.getResultSet();
-                if (!rs.next()) {
-                    throw new NoSuchItemStateException(id.toString());
-                }
-
-                in = rs.getBinaryStream(1);
-                PropertyState state = createNew(id);
-                Serializer.deserialize(state, in, blobStore);
-
-                return state;
-            } catch (Exception e) {
-                if (e instanceof NoSuchItemStateException) {
-                    throw (NoSuchItemStateException) e;
-                }
-                String msg = "failed to read property state: " + id;
-                log.error(msg, e);
-                throw new ItemStateException(msg, e);
-            } finally {
-                closeStream(in);
-                closeResultSet(rs);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * This method uses shared <code>PreparedStatement</code>s which must
-     * be executed strictly sequentially. Because this method synchronizes on
-     * the persistence manager instance there is no need to synchronize on the
-     * shared statement. If the method would not be sychronized the shared
-     * statements would have to be synchronized.
-     */
-    public synchronized void store(NodeState state) throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        // check if insert or update
-        boolean update = state.getStatus() != ItemState.STATUS_NEW;
-        //boolean update = exists(state.getId());
-        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
-
-        try {
-            ByteArrayOutputStream out =
-                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
-            // serialize node state
-            Serializer.serialize(state, out);
-
-            // we are synchronized on this instance, therefore we do not
-            // not have to additionally synchronize on the sql statement
-            executeStmt(sql, new Object[]{out.toByteArray(), state.getNodeId().toString()});
-
-            // there's no need to close a ByteArrayOutputStream
-            //out.close();
-        } catch (Exception e) {
-            String msg = "failed to write node state: " + state.getNodeId();
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * This method uses shared <code>PreparedStatement</code>s which must
-     * be executed strictly sequentially. Because this method synchronizes on
-     * the persistence manager instance there is no need to synchronize on the
-     * shared statement. If the method would not be sychronized the shared
-     * statements would have to be synchronized.
-     */
-    public synchronized void store(PropertyState state)
-            throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        // check if insert or update
-        boolean update = state.getStatus() != ItemState.STATUS_NEW;
-        //boolean update = exists(state.getId());
-        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
-
-        try {
-            ByteArrayOutputStream out =
-                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
-            // serialize property state
-            Serializer.serialize(state, out, blobStore);
-
-            // we are synchronized on this instance, therefore we do not
-            // not have to additionally synchronize on the sql statement
-            executeStmt(sql, new Object[]{out.toByteArray(), state.getPropertyId().toString()});
-
-            // there's no need to close a ByteArrayOutputStream
-            //out.close();
-        } catch (Exception e) {
-            String msg = "failed to write property state: " + state.getPropertyId();
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public synchronized void destroy(NodeState state)
-            throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        try {
-            // we are synchronized on this instance, therefore we do not
-            // not have to additionally synchronize on the sql statement
-            executeStmt(nodeStateDeleteSQL, new Object[]{state.getNodeId().toString()});
-        } catch (Exception e) {
-            String msg = "failed to delete node state: " + state.getNodeId();
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public synchronized void destroy(PropertyState state)
-            throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        // make sure binary values (BLOBs) are properly removed
-        InternalValue[] values = state.getValues();
-        if (values != null) {
-            for (int i = 0; i < values.length; i++) {
-                InternalValue val = values[i];
-                if (val != null) {
-                    if (val.getType() == PropertyType.BINARY) {
-                        BLOBFileValue blobVal = val.getBLOBFileValue();
-                        // delete internal resource representation of BLOB value
-                        blobVal.delete(true);
-                        // also remove from BLOBStore
-                        String blobId = blobStore.createId(state.getPropertyId(), i);
-                        try {
-                            blobStore.remove(blobId);
-                        } catch (Exception e) {
-                            log.warn("failed to remove from BLOBStore: " + blobId, e);
-                        }
-                    }
-                }
-            }
-        }
-
-        try {
-            // we are synchronized on this instance, therefore we do not
-            // not have to additionally synchronize on the sql statement
-            executeStmt(propertyStateDeleteSQL, new Object[]{state.getPropertyId().toString()});
-        } catch (Exception e) {
-            String msg = "failed to delete property state: " + state.getPropertyId();
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public NodeReferences load(NodeReferencesId targetId)
-            throws NoSuchItemStateException, ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        synchronized (nodeReferenceSelectSQL) {
-            ResultSet rs = null;
-            InputStream in = null;
-            try {
-                Statement stmt = executeStmt(
-                        nodeReferenceSelectSQL, new Object[]{targetId.toString()});
-                rs = stmt.getResultSet();
-                if (!rs.next()) {
-                    throw new NoSuchItemStateException(targetId.toString());
-                }
-
-                in = rs.getBinaryStream(1);
-                NodeReferences refs = new NodeReferences(targetId);
-                Serializer.deserialize(refs, in);
-
-                return refs;
-            } catch (Exception e) {
-                if (e instanceof NoSuchItemStateException) {
-                    throw (NoSuchItemStateException) e;
-                }
-                String msg = "failed to read node references: " + targetId;
-                log.error(msg, e);
-                throw new ItemStateException(msg, e);
-            } finally {
-                closeStream(in);
-                closeResultSet(rs);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * This method uses shared <code>PreparedStatement</code>s which must
-     * be executed strictly sequentially. Because this method synchronizes on
-     * the persistence manager instance there is no need to synchronize on the
-     * shared statement. If the method would not be sychronized the shared
-     * statements would have to be synchronized.
-     */
-    public synchronized void store(NodeReferences refs)
-            throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        // check if insert or update
-        boolean update = exists(refs.getId());
-        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
-
-        try {
-            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 sql statement
-            executeStmt(sql, new Object[]{out.toByteArray(), refs.getId().toString()});
-
-            // there's no need to close a ByteArrayOutputStream
-            //out.close();
-        } catch (Exception e) {
-            String msg = "failed to write node references: " + refs.getId();
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public synchronized void destroy(NodeReferences refs)
-            throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        try {
-            // we are synchronized on this instance, therefore we do not
-            // not have to additionally synchronize on the sql statement
-            executeStmt(nodeReferenceDeleteSQL, new Object[]{refs.getId().toString()});
-        } catch (Exception e) {
-            String msg = "failed to delete node references: " + refs.getId();
-            log.error(msg, e);
-            throw new ItemStateException(msg, e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean exists(NodeId id) throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        synchronized (nodeStateSelectExistSQL) {
-            ResultSet rs = null;
-            try {
-                Statement stmt = executeStmt(nodeStateSelectExistSQL, new Object[]{id.toString()});
-                rs = stmt.getResultSet();
-
-                // a node state exists if the result has at least one entry
-                return rs.next();
-            } catch (Exception e) {
-                String msg = "failed to check existence of node state: " + id;
-                log.error(msg, e);
-                throw new ItemStateException(msg, e);
-            } finally {
-                closeResultSet(rs);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean exists(PropertyId id) throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        synchronized (propertyStateSelectExistSQL) {
-            ResultSet rs = null;
-            try {
-                Statement stmt = executeStmt(
-                        propertyStateSelectExistSQL, new Object[]{id.toString()});
-                rs = stmt.getResultSet();
-
-                // a property state exists if the result has at least one entry
-                return rs.next();
-            } catch (Exception e) {
-                String msg = "failed to check existence of property state: " + id;
-                log.error(msg, e);
-                throw new ItemStateException(msg, e);
-            } finally {
-                closeResultSet(rs);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean exists(NodeReferencesId targetId) throws ItemStateException {
-        if (!initialized) {
-            throw new IllegalStateException("not initialized");
-        }
-
-        synchronized (nodeReferenceSelectExistSQL) {
-            ResultSet rs = null;
-            try {
-                Statement stmt = executeStmt(
-                        nodeReferenceSelectExistSQL, new Object[]{targetId.toString()});
-                rs = stmt.getResultSet();
-
-                // 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;
-                log.error(msg, e);
-                throw new ItemStateException(msg, e);
-            } finally {
-                closeResultSet(rs);
-            }
-        }
-    }
-
     //----------------------------------< misc. helper methods & overridables >
 
     /**
-     * Initializes the database connection used by this persistence manager.
-     * <p>
-     * Subclasses should normally override the {@link #getConnection()}
-     * method instead of this one. The default implementation calls
-     * {@link #getConnection()} to get the database connection and disables
-     * the autocommit feature.
-     *
-     * @throws Exception if an error occurs
-     */
-    protected void initConnection() throws Exception {
-        con = getConnection();
-        con.setAutoCommit(false);
-    }
-
-    /**
      * Abstract factory method for creating a new database connection. This
      * method is called by {@link #init(PMContext)} when the persistence
      * manager is started. The returned connection should come with the default
@@ -769,116 +226,12 @@
      * @param connection database connection
      * @throws Exception if an error occurs
      */
-    protected void closeConnection(Connection connection) throws Exception {
-        connection.close();
-    }
-
-    /**
-     * Re-establishes the database connection. This method is called by
-     * {@link #store(ChangeLog)} and {@link #executeStmt(String, Object[])}
-     * after a <code>SQLException</code> had been encountered.
-     * @return true if the connection could be successfully re-established,
-     *         false otherwise.
-     */
-    protected synchronized boolean reestablishConnection() {
-        // in any case try to shut down current connection
-        // gracefully in order to avoid potential memory leaks
-
-        // 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
-                }
-            }
-        }
-        try {
-            closeConnection(con);
-        } catch (Exception ignore) {
-        }
-
-        // 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 {
-            initConnection();
-            initPreparedStatements();
-            return true;
-        } catch (Exception e) {
-            log.error("failed to re-establish connection", e);
-            // reconnect failed
-            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 SQLException if an error occurs
-     */
-    protected Statement executeStmt(String sql, Object[] params)
-            throws SQLException {
-        int trials = autoReconnect ? 2 : 1;
-        while (true) {
-            PreparedStatement stmt = (PreparedStatement) preparedStatements.get(sql);
+    protected void closeConnection(Connection connection) {
+        if (connection != null) {
             try {
-                for (int i = 0; i < params.length; i++) {
-                    if (params[i] instanceof SizedInputStream) {
-                        SizedInputStream in = (SizedInputStream) params[i];
-                        stmt.setBinaryStream(i + 1, in, (int) in.getSize());
-                    } else {
-                        stmt.setObject(i + 1, params[i]);
-                    }
-                }
-                stmt.execute();
-                resetStatement(stmt);
-                return stmt;
+                connection.close();
             } catch (SQLException se) {
-                if (--trials == 0) {
-                    // no more trials, re-throw
-                    throw se;
-                }
-                log.warn("execute failed, about to reconnect...", se.getMessage());
-
-                // try to reconnect
-                if (reestablishConnection()) {
-                    // reconnect succeeded; check whether it's possible to
-                    // re-execute the prepared stmt with the given parameters
-                    for (int i = 0; i < params.length; i++) {
-                        if (params[i] instanceof SizedInputStream) {
-                            SizedInputStream in = (SizedInputStream) params[i];
-                            if (in.isConsumed()) {
-                                // we're unable to re-execute the prepared stmt
-                                // since an InputStream paramater has already
-                                // been 'consumed';
-                                // re-throw previous SQLException
-                                throw se;
-                            }
-                        }
-                    }
-
-                    // try again to execute the statement
-                    continue;
-                } else {
-                    // reconnect failed, re-throw previous SQLException
-                    throw se;
-                }
+                this.logException("Error closing connection", se);
             }
         }
     }
@@ -934,13 +287,16 @@
         }
     }
 
-    protected void logException(String message, SQLException se) {
+    protected void logException(String message, Exception e) {
         if (message != null) {
             log.error(message);
         }
-        log.error("    reason: " + se.getMessage());
-        log.error("state/code: " + se.getSQLState() + "/" + se.getErrorCode());
-        log.debug("      dump:", se);
+        log.error("    reason: " + e.getMessage());
+        if (e instanceof SQLException) {
+            SQLException se = (SQLException)e;
+            log.error("state/code: " + se.getSQLState() + "/" + se.getErrorCode());
+        }
+        log.debug("      dump:", e);
     }
 
     /**
@@ -951,25 +307,31 @@
      * @throws Exception if an error occurs
      */
     protected void prepareSchemaObjectPrefix() throws Exception {
-        DatabaseMetaData metaData = con.getMetaData();
-        String legalChars = metaData.getExtraNameCharacters();
-        legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
+        Connection con = null;
+        try {
+            con = getConnection();
+            DatabaseMetaData metaData = con.getMetaData();
+            String legalChars = metaData.getExtraNameCharacters();
+            legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
 
-        String prefix = schemaObjectPrefix.toUpperCase();
-        StringBuffer escaped = new StringBuffer();
-        for (int i = 0; i < prefix.length(); i++) {
-            char c = prefix.charAt(i);
-            if (legalChars.indexOf(c) == -1) {
-                escaped.append("_x");
-                String hex = Integer.toHexString(c);
-                escaped.append("0000".toCharArray(), 0, 4 - hex.length());
-                escaped.append(hex);
-                escaped.append("_");
-            } else {
-                escaped.append(c);
+            String prefix = schemaObjectPrefix.toUpperCase();
+            StringBuffer escaped = new StringBuffer();
+            for (int i = 0; i < prefix.length(); i++) {
+                char c = prefix.charAt(i);
+                if (legalChars.indexOf(c) == -1) {
+                    escaped.append("_x");
+                    String hex = Integer.toHexString(c);
+                    escaped.append("0000".toCharArray(), 0, 4 - hex.length());
+                    escaped.append(hex);
+                    escaped.append("_");
+                } else {
+                    escaped.append(c);
+                }
             }
+            schemaObjectPrefix = escaped.toString();
+        } finally {
+            closeConnection(con);
         }
-        schemaObjectPrefix = escaped.toString();
     }
 
     /**
@@ -979,51 +341,57 @@
      * @throws Exception if an error occurs
      */
     protected void checkSchema() throws Exception {
-        DatabaseMetaData metaData = con.getMetaData();
-        String tableName = schemaObjectPrefix + "NODE";
-        if (metaData.storesLowerCaseIdentifiers()) {
-            tableName = tableName.toLowerCase();
-        } else if (metaData.storesUpperCaseIdentifiers()) {
-            tableName = tableName.toUpperCase();
-        }
-
-        ResultSet rs = metaData.getTables(null, null, tableName, null);
-        boolean schemaExists;
+        Connection con = null;
         try {
-            schemaExists = rs.next();
-        } finally {
-            rs.close();
-        }
-
-        if (!schemaExists) {
-            // read ddl from resources
-            InputStream in = getSchemaDDL();
-            if (in == null) {
-                String msg = "Configuration error: unknown schema '" + schema + "'";
-                log.debug(msg);
-                throw new RepositoryException(msg);
+            con = getConnection();
+            DatabaseMetaData metaData = con.getMetaData();
+            String tableName = schemaObjectPrefix + "NODE";
+            if (metaData.storesLowerCaseIdentifiers()) {
+                tableName = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                tableName = tableName.toUpperCase();
             }
-            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-            Statement stmt = con.createStatement();
+
+            ResultSet rs = metaData.getTables(null, null, tableName, null);
+            boolean schemaExists;
             try {
-                String sql = reader.readLine();
-                while (sql != null) {
-                    // Skip comments and empty lines
-                    if (!sql.startsWith("#") && sql.length() > 0) {
-                        // replace prefix variable
-                        sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
-                        // execute sql stmt
-                        stmt.executeUpdate(sql);
+                schemaExists = rs.next();
+            } finally {
+                rs.close();
+            }
+
+            if (!schemaExists) {
+                // read ddl from resources
+                InputStream in = getSchemaDDL();
+                if (in == null) {
+                    String msg = "Configuration error: unknown schema '" + schema + "'";
+                    log.debug(msg);
+                    throw new RepositoryException(msg);
+                }
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                Statement stmt = con.createStatement();
+                try {
+                    String sql = reader.readLine();
+                    while (sql != null) {
+                        // Skip comments and empty lines
+                        if (!sql.startsWith("#") && sql.length() > 0) {
+                            // replace prefix variable
+                            sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
+                            // execute sql stmt
+                            stmt.executeUpdate(sql);
+                        }
+                        // read next sql stmt
+                        sql = reader.readLine();
                     }
-                    // read next sql stmt
-                    sql = reader.readLine();
+                    // commit the changes
+                    con.commit();
+                } finally {
+                    closeStream(in);
+                    closeStatement(stmt);
                 }
-                // commit the changes
-                con.commit();
-            } finally {
-                closeStream(in);
-                closeStatement(stmt);
             }
+        } finally {
+            closeConnection(con);
         }
     }
 
@@ -1091,54 +459,6 @@
         }
     }
 
-    /**
-     * Initializes the map of prepared statements.
-     *
-     * @throws SQLException if an error occurs
-     */
-    protected void initPreparedStatements() throws SQLException {
-        preparedStatements.put(
-                nodeStateInsertSQL, con.prepareStatement(nodeStateInsertSQL));
-        preparedStatements.put(
-                nodeStateUpdateSQL, con.prepareStatement(nodeStateUpdateSQL));
-        preparedStatements.put(
-                nodeStateSelectSQL, con.prepareStatement(nodeStateSelectSQL));
-        preparedStatements.put(
-                nodeStateSelectExistSQL, con.prepareStatement(nodeStateSelectExistSQL));
-        preparedStatements.put(
-                nodeStateDeleteSQL, con.prepareStatement(nodeStateDeleteSQL));
-
-        preparedStatements.put(
-                propertyStateInsertSQL, con.prepareStatement(propertyStateInsertSQL));
-        preparedStatements.put(
-                propertyStateUpdateSQL, con.prepareStatement(propertyStateUpdateSQL));
-        preparedStatements.put(
-                propertyStateSelectSQL, con.prepareStatement(propertyStateSelectSQL));
-        preparedStatements.put(
-                propertyStateSelectExistSQL, con.prepareStatement(propertyStateSelectExistSQL));
-        preparedStatements.put(
-                propertyStateDeleteSQL, con.prepareStatement(propertyStateDeleteSQL));
-
-        preparedStatements.put(
-                nodeReferenceInsertSQL, con.prepareStatement(nodeReferenceInsertSQL));
-        preparedStatements.put(
-                nodeReferenceUpdateSQL, con.prepareStatement(nodeReferenceUpdateSQL));
-        preparedStatements.put(
-                nodeReferenceSelectSQL, con.prepareStatement(nodeReferenceSelectSQL));
-        preparedStatements.put(
-                nodeReferenceSelectExistSQL, con.prepareStatement(nodeReferenceSelectExistSQL));
-        preparedStatements.put(
-                nodeReferenceDeleteSQL, con.prepareStatement(nodeReferenceDeleteSQL));
-
-        if (!externalBLOBs) {
-            preparedStatements.put(blobInsertSQL, con.prepareStatement(blobInsertSQL));
-            preparedStatements.put(blobUpdateSQL, con.prepareStatement(blobUpdateSQL));
-            preparedStatements.put(blobSelectSQL, con.prepareStatement(blobSelectSQL));
-            preparedStatements.put(blobSelectExistSQL, con.prepareStatement(blobSelectExistSQL));
-            preparedStatements.put(blobDeleteSQL, con.prepareStatement(blobDeleteSQL));
-        }
-    }
-
     //--------------------------------------------------------< inner classes >
 
     class SizedInputStream extends FilterInputStream {
@@ -1179,7 +499,7 @@
         }
     }
 
-    class DbBLOBStore implements BLOBStore {
+    abstract class DbBLOBStore implements BLOBStore {
         /**
          * {@inheritDoc}
          */
@@ -1192,61 +512,5 @@
             sb.append(']');
             return sb.toString();
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        public InputStream get(String blobId) throws Exception {
-            synchronized (blobSelectSQL) {
-                Statement stmt = executeStmt(blobSelectSQL, new Object[]{blobId});
-                final ResultSet rs = stmt.getResultSet();
-                if (!rs.next()) {
-                    closeResultSet(rs);
-                    throw new Exception("no such BLOB: " + blobId);
-                }
-                InputStream in = rs.getBinaryStream(1);
-                if (in == null) {
-                    // some databases treat zero-length values as NULL;
-                    // return empty InputStream in such a case
-                    closeResultSet(rs);
-                    return new ByteArrayInputStream(new byte[0]);
-                }
-
-                /**
-                 * return an InputStream wrapper in order to
-                 * close the ResultSet when the stream is closed
-                 */
-                return new FilterInputStream(in) {
-                    public void close() throws IOException {
-                        in.close();
-                        // now it's safe to close ResultSet
-                        closeResultSet(rs);
-                    }
-                };
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public synchronized void put(String blobId, InputStream in, long size)
-                throws Exception {
-            Statement stmt = executeStmt(blobSelectExistSQL, new Object[]{blobId});
-            ResultSet rs = stmt.getResultSet();
-            // a BLOB exists if the result has at least one entry
-            boolean exists = rs.next();
-            closeResultSet(rs);
-
-            String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
-            executeStmt(sql, new Object[]{new SizedInputStream(in, size), blobId});
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public synchronized boolean remove(String blobId) throws Exception {
-            Statement stmt = executeStmt(blobDeleteSQL, new Object[]{blobId});
-            return stmt.getUpdateCount() == 1;
-        }
     }
 }
Index: src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java	(revision 586792)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java	(working copy)
@@ -404,7 +404,7 @@
     }
 
     //--------------------------------------------------------< inner classes >
-    class OracleBLOBStore extends DbBLOBStore {
+    class OracleBLOBStore extends JDBCBLOBStore {
         /**
          * {@inheritDoc}
          */
Index: src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java	(revision 586792)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java	(working copy)
@@ -16,9 +16,6 @@
  */
 package org.apache.jackrabbit.core.persistence.db;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
@@ -64,11 +61,6 @@
 public class DerbyPersistenceManager extends SimpleDbPersistenceManager {
 
     /**
-     * Logger instance
-     */
-    private static Logger log = LoggerFactory.getLogger(DerbyPersistenceManager.class);
-
-    /**
      * Flag indicating whether this derby database should be shutdown on close.
      */
     protected boolean shutdownOnClose;
@@ -104,31 +96,31 @@
      * @throws SQLException if an error occurs
      * @see DatabasePersistenceManager#closeConnection(Connection)
      */
-    protected void closeConnection(Connection connection) throws SQLException {
-        // prepare connection url for issuing shutdown command
-        String url = connection.getMetaData().getURL();
-        int pos = url.lastIndexOf(';');
-        if (pos != -1) {
-            // strip any attributes from connection url
-            url = url.substring(0, pos);
-        }
-        url += ";shutdown=true";
+    protected void closeConnection(Connection connection) {
+        try {
+            // prepare connection url for issuing shutdown command
+            String url = connection.getMetaData().getURL();
+            int pos = url.lastIndexOf(';');
+            if (pos != -1) {
+                // strip any attributes from connection url
+                url = url.substring(0, pos);
+            }
+            url += ";shutdown=true";
 
-        // we have to reset the connection to 'autoCommit=true' before closing it;
-        // otherwise Derby would mysteriously complain about some pending uncommitted
-        // changes which can't possibly be true.
-        // @todo further investigate
-        connection.setAutoCommit(true);
-        connection.close();
+            // we have to reset the connection to 'autoCommit=true' before closing it;
+            // otherwise Derby would mysteriously complain about some pending uncommitted
+            // changes which can't possibly be true.
+            // @todo further investigate
+            connection.setAutoCommit(true);
+            connection.close();
 
-        if (shutdownOnClose) {
-            // now it's safe to shutdown the embedded Derby database
-            try {
+            if (shutdownOnClose) {
+                // now it's safe to shutdown the embedded Derby database
                 DriverManager.getConnection(url);
-            } catch (SQLException e) {
-                // a shutdown command always raises a SQLException
-                log.info(e.getMessage());
+
             }
+        } catch (SQLException e) {
+            logException("Error closing connection", e);
         }
     }
 }
Index: src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPooledPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPooledPersistenceManager.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPooledPersistenceManager.java	(revision 0)
@@ -0,0 +1,1247 @@
+/*
+ * 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.db;
+
+import org.apache.derby.jdbc.EmbeddedDataSource;
+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.local.LocalFileSystem;
+import org.apache.jackrabbit.core.persistence.AbstractPersistenceManager;
+import org.apache.jackrabbit.core.persistence.PMContext;
+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.*;
+import org.apache.jackrabbit.core.value.BLOBFileValue;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import java.io.*;
+import java.sql.*;
+import java.util.Iterator;
+
+public class DerbyPooledPersistenceManager extends AbstractPersistenceManager {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(DerbyPooledPersistenceManager.class);
+
+    protected static final String SCHEMA_OBJECT_PREFIX_VARIABLE =
+            "${schemaObjectPrefix}";
+
+    protected boolean initialized;
+
+    protected String schema;
+    protected String schemaObjectPrefix;
+
+    protected boolean externalBLOBs;
+
+    // initial size of buffer used to serialize objects
+    protected static final int INITIAL_BUFFER_SIZE = 1024;
+
+    // SQL statements for NodeState management
+    protected String nodeStateInsertSQL;
+    protected String nodeStateUpdateSQL;
+    protected String nodeStateSelectSQL;
+    protected String nodeStateSelectExistSQL;
+    protected String nodeStateDeleteSQL;
+
+    // SQL statements for PropertyState management
+    protected String propertyStateInsertSQL;
+    protected String propertyStateUpdateSQL;
+    protected String propertyStateSelectSQL;
+    protected String propertyStateSelectExistSQL;
+    protected String propertyStateDeleteSQL;
+
+    // SQL statements for NodeReference management
+    protected String nodeReferenceInsertSQL;
+    protected String nodeReferenceUpdateSQL;
+    protected String nodeReferenceSelectSQL;
+    protected String nodeReferenceSelectExistSQL;
+    protected String nodeReferenceDeleteSQL;
+
+    // SQL statements for BLOB management
+    // (if <code>externalBLOBs==false</code>)
+    protected String blobInsertSQL;
+    protected String blobUpdateSQL;
+    protected String blobSelectSQL;
+    protected String blobSelectExistSQL;
+    protected String blobDeleteSQL;
+
+
+    /**
+     * file system where BLOB data is stored
+     * (if <code>externalBLOBs==true</code>)
+     */
+    protected FileSystem blobFS;
+    /**
+     * BLOBStore that manages BLOB data in the file system
+     * (if <code>externalBLOBs==true</code>)
+     */
+    protected BLOBStore blobStore;
+
+    private static EmbeddedDataSource datasource;
+
+    protected boolean shutdownOnClose;
+    protected String dbName;
+
+    /**
+     * Creates a new <code>DatabasePersistenceManager</code> instance.
+     */
+    public DerbyPooledPersistenceManager() {
+        schema = "derby";
+        schemaObjectPrefix = "";
+        dbName = "";
+        shutdownOnClose = true;
+        externalBLOBs = true;
+        initialized = false;
+    }
+
+    //----------------------------------------------------< setters & getters >   
+    public String getSchemaObjectPrefix() {
+        return schemaObjectPrefix;
+    }
+
+    public void setSchemaObjectPrefix(String schemaObjectPrefix) {
+        // make sure prefix is all uppercase
+        this.schemaObjectPrefix = schemaObjectPrefix.toUpperCase();
+    }
+
+    public String getSchema() {
+        return schema;
+    }
+
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
+    public boolean isExternalBLOBs() {
+        return externalBLOBs;
+    }
+
+    public void setExternalBLOBs(boolean externalBLOBs) {
+        this.externalBLOBs = externalBLOBs;
+    }
+
+    public void setExternalBLOBs(String externalBLOBs) {
+        this.externalBLOBs = Boolean.valueOf(externalBLOBs).booleanValue();
+    }
+
+    public BLOBStore getBLOBStore() {
+        return new DatasourceBLOBStore();
+    }
+
+    public boolean isShutdownOnClose() {
+        return shutdownOnClose;
+    }
+
+    public void setShutdownOnClose(boolean shutdownOnClose) {
+        this.shutdownOnClose = shutdownOnClose;
+    }
+
+    public String getDbName() {
+        return dbName;
+    }
+
+    public void setDbName(String dbName) {
+        this.dbName = dbName;
+    }
+
+    //---------------------------------------------------< PersistenceManager >
+    /**
+     * Returns a JDBC connection from a {@link javax.sql.DataSource} acquired from Derby
+     *
+     * @return new database connection
+     * @throws SQLException if a database access error occurs
+     * @see DatabasePersistenceManager#getConnection()
+     */
+    protected Connection getConnection() throws SQLException {
+        return datasource.getConnection();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized void init(PMContext context) throws Exception {
+        if (initialized) {
+            throw new IllegalStateException("already initialized");
+        }
+        if (datasource == null) {
+            datasource = new EmbeddedDataSource();
+            datasource.setDatabaseName(dbName);
+            datasource.setCreateDatabase("create");
+            datasource.getConnection().close();
+        }
+        // make sure schemaObjectPrefix consists of legal name characters only
+        prepareSchemaObjectPrefix();
+
+        // check if schema objects exist and create them if necessary
+        checkSchema();
+
+        // build sql statements
+        buildSQLStatements();
+
+        if (externalBLOBs) {
+            /**
+             * store BLOBs in local file system in a sub directory
+             * of the workspace home directory
+             */
+            LocalFileSystem blobFS = new LocalFileSystem();
+            blobFS.setRoot(new File(context.getHomeDir(), "blobs"));
+            blobFS.init();
+            this.blobFS = blobFS;
+            blobStore = new FileSystemBLOBStore(blobFS);
+        } else {
+            /**
+             * store BLOBs in db
+             */
+            blobStore = getBLOBStore();
+        }
+
+        initialized = true;
+    }
+
+    //----------------------------------< misc. helper methods & overridables >
+
+    /**
+     * Closes the given database connection. This method is called by
+     * {@link #close()} to close the connection acquired using
+     * {@link #getConnection()} when the persistence manager was started.
+     * <p/>
+     * The default implementation just calls the {@link Connection#close()}
+     * method of the given connection, but subclasses can override this
+     * method to provide more extensive database and connection cleanup.
+     *
+     * @param connection database connection
+     * @throws Exception if an error occurs
+     */
+    protected void closeConnection(Connection connection) {
+        if (connection != null) {
+            try {
+                connection.setAutoCommit(true);
+                connection.close();
+            } catch (SQLException se) {
+                this.logException("Error closing connection", se);
+            }
+        }
+    }
+
+    /**
+     * Resets the given <code>PreparedStatement</code> by clearing the parameters
+     * and warnings contained.
+     * <p/>
+     * NOTE: This method MUST be called in a synchronized context as neither
+     * this method nor the <code>PreparedStatement</code> instance on which it
+     * operates are thread safe.
+     *
+     * @param stmt The <code>PreparedStatement</code> to reset. If
+     *             <code>null</code> this method does nothing.
+     */
+    protected void resetStatement(PreparedStatement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.clearParameters();
+                stmt.clearWarnings();
+            } catch (SQLException se) {
+                logException("failed resetting PreparedStatement", se);
+            }
+        }
+    }
+
+    protected void closeResultSet(ResultSet rs) {
+        if (rs != null) {
+            try {
+                rs.close();
+            } catch (SQLException se) {
+                logException("failed closing ResultSet", se);
+            }
+        }
+    }
+
+    protected void closeStream(InputStream in) {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException ignore) {
+            }
+        }
+    }
+
+    protected void closeStatement(Statement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.close();
+            } catch (SQLException se) {
+                logException("failed closing Statement", se);
+            }
+        }
+    }
+
+    protected void logException(String message, Exception e) {
+        if (message != null) {
+            log.error(message);
+        }
+        log.error("    reason: " + e.getMessage());
+        if (e instanceof SQLException) {
+            SQLException se = (SQLException) e;
+            log.error("state/code: " + se.getSQLState() + "/" + se.getErrorCode());
+        }
+        log.debug("      dump:", e);
+    }
+
+    /**
+     * Makes sure that <code>schemaObjectPrefix</code> does only consist of
+     * characters that are allowed in names on the target database. Illegal
+     * characters will be escaped as necessary.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void prepareSchemaObjectPrefix() throws Exception {
+        Connection con = null;
+        try {
+            con = getConnection();
+            DatabaseMetaData metaData = getConnection().getMetaData();
+            String legalChars = metaData.getExtraNameCharacters();
+            legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
+
+            String prefix = schemaObjectPrefix.toUpperCase();
+            StringBuffer escaped = new StringBuffer();
+            for (int i = 0; i < prefix.length(); i++) {
+                char c = prefix.charAt(i);
+                if (legalChars.indexOf(c) == -1) {
+                    escaped.append("_x");
+                    String hex = Integer.toHexString(c);
+                    escaped.append("0000".toCharArray(), 0, 4 - hex.length());
+                    escaped.append(hex);
+                    escaped.append("_");
+                } else {
+                    escaped.append(c);
+                }
+            }
+            schemaObjectPrefix = escaped.toString();
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * Checks if the required schema objects exist and creates them if they
+     * don't exist yet.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void checkSchema() throws Exception {
+        Connection con = null;
+        try {
+            con = getConnection();
+            con.setAutoCommit(false);
+            DatabaseMetaData metaData = con.getMetaData();
+            String tableName = schemaObjectPrefix + "NODE";
+            if (metaData.storesLowerCaseIdentifiers()) {
+                tableName = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                tableName = tableName.toUpperCase();
+            }
+
+            ResultSet rs = metaData.getTables(null, null, tableName, null);
+            boolean schemaExists;
+            try {
+                schemaExists = rs.next();
+            } finally {
+                closeResultSet(rs);
+            }
+
+            if (!schemaExists) {
+                // read ddl from resources
+                InputStream in = getSchemaDDL();
+                if (in == null) {
+                    String msg = "Configuration error: unknown schema '" + schema + "'";
+                    log.debug(msg);
+                    throw new RepositoryException(msg);
+                }
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                Statement stmt = con.createStatement();
+                //UserTransaction trans = getUserTransaction();
+                try {
+                    //trans.begin();
+                    String sql = reader.readLine();
+                    while (sql != null) {
+                        // Skip comments and empty lines
+                        if (!sql.startsWith("#") && sql.length() > 0) {
+                            // replace prefix variable
+                            sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
+                            // execute sql stmt
+                            stmt.executeUpdate(sql);
+                        }
+                        // read next sql stmt
+                        sql = reader.readLine();
+                    }
+                    con.commit();
+                    // commit the changes
+                    // trans.commit();
+                } catch (Exception e) {
+                    //trans.rollback();
+                    con.rollback();
+                    throw e;
+                } finally {
+                    closeStream(in);
+                    closeStatement(stmt);
+                }
+            }
+        } finally {
+            con.setAutoCommit(true);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * Returns an input stream to the schema DDL resource.
+     *
+     * @return an input stream to the schema DDL resource.
+     */
+    protected InputStream getSchemaDDL() {
+        // JCR-595: Use the class explicitly instead of using getClass()
+        // to avoid problems when subclassed in a different package
+        return DatabasePersistenceManager.class.getResourceAsStream(schema + ".ddl");
+    }
+
+    /**
+     * Builds the SQL statements
+     */
+    protected void buildSQLStatements() {
+        nodeStateInsertSQL = "insert into "
+                + schemaObjectPrefix + "NODE (NODE_DATA, NODE_ID) values (?, ?)";
+
+        nodeStateUpdateSQL = "update "
+                + schemaObjectPrefix + "NODE set NODE_DATA = ? where NODE_ID = ?";
+        nodeStateSelectSQL = "select NODE_DATA from "
+                + schemaObjectPrefix + "NODE where NODE_ID = ?";
+        nodeStateSelectExistSQL = "select 1 from "
+                + schemaObjectPrefix + "NODE where NODE_ID = ?";
+        nodeStateDeleteSQL = "delete from "
+                + schemaObjectPrefix + "NODE where NODE_ID = ?";
+
+        propertyStateInsertSQL = "insert into "
+                + schemaObjectPrefix + "PROP (PROP_DATA, PROP_ID) values (?, ?)";
+        propertyStateUpdateSQL = "update "
+                + schemaObjectPrefix + "PROP set PROP_DATA = ? where PROP_ID = ?";
+        propertyStateSelectSQL = "select PROP_DATA from "
+                + schemaObjectPrefix + "PROP where PROP_ID = ?";
+        propertyStateSelectExistSQL = "select 1 from "
+                + schemaObjectPrefix + "PROP where PROP_ID = ?";
+        propertyStateDeleteSQL = "delete from "
+                + schemaObjectPrefix + "PROP where PROP_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 = ?";
+        nodeReferenceSelectExistSQL = "select 1 from "
+                + schemaObjectPrefix + "REFS where NODE_ID = ?";
+        nodeReferenceDeleteSQL = "delete from "
+                + schemaObjectPrefix + "REFS where NODE_ID = ?";
+
+        if (!externalBLOBs) {
+            blobInsertSQL = "insert into "
+                    + schemaObjectPrefix + "BINVAL (BINVAL_DATA, BINVAL_ID) values (?, ?)";
+            blobUpdateSQL = "update "
+                    + schemaObjectPrefix + "BINVAL set BINVAL_DATA = ? where BINVAL_ID = ?";
+            blobSelectSQL =
+                    "select BINVAL_DATA from "
+                            + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?";
+            blobSelectExistSQL =
+                    "select 1 from "
+                            + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?";
+            blobDeleteSQL = "delete from "
+                    + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?";
+        }
+    }
+
+    /**
+     * Right now, this iterates over all items in the changelog and
+     * calls the individual methods that handle single item states
+     * or node references objects. Properly implemented, this method
+     * should ensure that changes are either written completely to
+     * the underlying persistence layer, or not at all.
+     * <p/>
+     * {@inheritDoc}
+     */
+    public void store(ChangeLog changeLog) throws ItemStateException {
+        Connection con = null;
+        try {
+            con = getConnection();
+            con.setAutoCommit(false);
+            ItemStateException ise = null;
+            try {
+                Iterator iter = changeLog.deletedStates();
+                while (iter.hasNext()) {
+                    ItemState state = (ItemState) iter.next();
+                    if (state.isNode()) {
+                        destroy((NodeState) state, con);
+                    } else {
+                        destroy((PropertyState) state, con);
+                    }
+                }
+                iter = changeLog.addedStates();
+                while (iter.hasNext()) {
+                    ItemState state = (ItemState) iter.next();
+                    if (state.isNode()) {
+                        store((NodeState) state, con);
+                    } else {
+                        store((PropertyState) state, con);
+                    }
+                }
+                iter = changeLog.modifiedStates();
+                while (iter.hasNext()) {
+                    ItemState state = (ItemState) iter.next();
+                    if (state.isNode()) {
+                        store((NodeState) state, con);
+                    } else {
+                        store((PropertyState) state, con);
+                    }
+                }
+                iter = changeLog.modifiedRefs();
+                while (iter.hasNext()) {
+                    NodeReferences refs = (NodeReferences) iter.next();
+                    if (refs.hasReferences()) {
+                        store(refs, con);
+                    } else {
+                        if (exists(refs.getId())) {
+                            destroy(refs, con);
+                        }
+                    }
+                }
+            } catch (ItemStateException ie) {
+                ise = ie;
+            }
+
+            if (ise == null) {
+                try {
+                    con.commit();
+                } catch (SQLException se) {
+                    String msg = "committing change log failed";
+                    log.error(msg, se);
+                    throw new ItemStateException(msg, se);
+                }
+            } else {
+                try {
+                    con.rollback();
+                } catch (SQLException se) {
+                    String msg = "rollback of change log failed";
+                    log.error(msg, se);
+                }
+            }
+        } catch (SQLException se) {
+            String msg = "storing change log failed";
+            log.error(msg, se);
+            throw new ItemStateException(msg, se);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeState load(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateSelectSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeState state = createNew(id);
+            Serializer.deserialize(state, in);
+
+            return state;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read node state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PropertyState load(PropertyId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        ResultSet rs = null;
+        PreparedStatement stmt = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateSelectSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            PropertyState state = createNew(id);
+            Serializer.deserialize(state, in, blobStore);
+
+            return state;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read property state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        try {
+            con = getConnection();
+            store(state, con);
+        } catch (SQLException e) {
+            String msg = "failed to write node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void store(NodeState state, Connection con) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize node state
+            Serializer.serialize(state, out);
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, state.getNodeId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        try {
+            con = getConnection();
+            store(state, con);
+        } catch (SQLException e) {
+            String msg = "failed to write property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void store(PropertyState state, Connection con)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize property state
+            Serializer.serialize(state, out, blobStore);
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, state.getPropertyId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(NodeState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        try {
+            con = getConnection();
+            destroy(state, con);
+        } catch (SQLException e) {
+            String msg = "failed to delete node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void destroy(NodeState state, Connection con)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        PreparedStatement stmt = null;
+        try {
+            stmt = con.prepareStatement(nodeStateDeleteSQL);
+            stmt.setString(1, state.getNodeId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        try {
+            con = getConnection();
+            destroy(state, con);
+        } catch (SQLException e) {
+            String msg = "failed to delete property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void destroy(PropertyState state, Connection con)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // make sure binary values (BLOBs) are properly removed
+        InternalValue[] values = state.getValues();
+        if (values != null) {
+            for (int i = 0; i < values.length; i++) {
+                InternalValue val = values[i];
+                if (val != null) {
+                    if (val.getType() == PropertyType.BINARY) {
+                        BLOBFileValue blobVal = val.getBLOBFileValue();
+                        // delete internal resource representation of BLOB value
+                        blobVal.delete(true);
+                        // also remove from BLOBStore
+                        String blobId = blobStore.createId(state.getPropertyId(), i);
+                        try {
+                            blobStore.remove(blobId);
+                        } catch (Exception e) {
+                            logException("failed to remove from BLOBStore: " + blobId, e);
+                        }
+                    }
+                }
+            }
+        }
+        PreparedStatement stmt = null;
+        try {
+            stmt = con.prepareStatement(propertyStateDeleteSQL);
+            stmt.setString(1, state.getPropertyId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeReferences load(NodeReferencesId targetId)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceSelectSQL);
+            stmt.setString(1, targetId.toString());
+            rs = stmt.executeQuery();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(targetId.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeReferences refs = new NodeReferences(targetId);
+            Serializer.deserialize(refs, in);
+
+            return refs;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read node references: " + targetId;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        try {
+            con = getConnection();
+            store(refs, con);
+        } catch (SQLException e) {
+            String msg = "failed to write node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void store(NodeReferences refs, Connection con)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize references
+            Serializer.serialize(refs, out);
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, refs.getId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        try {
+            con = getConnection();
+            destroy(refs, con);
+        } catch (SQLException e) {
+            String msg = "failed to delete node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void destroy(NodeReferences refs, Connection con)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceDeleteSQL);
+            stmt.setString(1, refs.getId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateSelectExistSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            // a node state exists if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of node state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(PropertyId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateSelectExistSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            // a property state exists if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of property state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeReferencesId targetId) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceSelectExistSQL);
+            stmt.setString(1, targetId.toString());
+            rs = stmt.executeQuery();
+
+            // a property state 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;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    public void close() throws Exception {
+        if (shutdownOnClose) {
+            datasource.setShutdownDatabase("shutdown");
+            Connection con = datasource.getConnection();
+            con.close();
+        }
+    }
+
+    //--------------------------------------------------------< inner classes >
+
+    class SizedInputStream extends FilterInputStream {
+        private final long size;
+        private boolean consumed = false;
+
+        SizedInputStream(InputStream in, long size) {
+            super(in);
+            this.size = size;
+        }
+
+        long getSize() {
+            return size;
+        }
+
+        boolean isConsumed() {
+            return consumed;
+        }
+
+        public int read() throws IOException {
+            consumed = true;
+            return super.read();
+        }
+
+        public long skip(long n) throws IOException {
+            consumed = true;
+            return super.skip(n);
+        }
+
+        public int read(byte b[]) throws IOException {
+            consumed = true;
+            return super.read(b);
+        }
+
+        public int read(byte b[], int off, int len) throws IOException {
+            consumed = true;
+            return super.read(b, off, len);
+        }
+    }
+
+    class DatasourceBLOBStore implements BLOBStore {
+
+        public String createId(PropertyId id, int index) {
+            // the blobId is a simple string concatenation of id plus index
+            StringBuffer sb = new StringBuffer();
+            sb.append(id.toString());
+            sb.append('[');
+            sb.append(index);
+            sb.append(']');
+            return sb.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public InputStream get(String blobId) throws Exception {
+            final Connection con = getConnection();
+            final PreparedStatement stmt = con.prepareStatement(blobSelectSQL);
+            stmt.setString(1, blobId);
+            final ResultSet rs = stmt.executeQuery();
+            if (!rs.next()) {
+                closeResultSet(rs);
+                closeStatement(stmt);
+                closeConnection(con);
+                throw new Exception("no such BLOB: " + blobId);
+            }
+            InputStream in = rs.getBinaryStream(1);
+            if (in == null) {
+                // some databases treat zero-length values as NULL;
+                // return empty InputStream in such a case
+                closeResultSet(rs);
+                closeStatement(stmt);
+                closeConnection(con);
+                return new ByteArrayInputStream(new byte[0]);
+            }
+
+            /**
+             * return an InputStream wrapper in order to
+             * close the ResultSet when the stream is closed
+             */
+            return new FilterInputStream(in) {
+                public void close() throws IOException {
+                    in.close();
+                    // now it's safe to close ResultSet
+                    closeResultSet(rs);
+                    closeStatement(stmt);
+                    closeConnection(con);
+                }
+            };
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void put(String blobId, InputStream in, long size)
+                throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            try {
+                con = getConnection();
+                ResultSet rs = null;
+                boolean exists = false;
+                try {
+                    stmt = con.prepareStatement(blobSelectExistSQL);
+                    stmt.setString(1, blobId);
+                    rs = stmt.executeQuery();
+
+                    // a BLOB exists if the result has at least one entry
+                    exists = rs.next();
+
+                } finally {
+                    closeResultSet(rs);
+
+                }
+                String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
+                stmt = con.prepareStatement(sql);
+                stmt.setBinaryStream(1, in, (int) size);
+                stmt.setString(2, blobId);
+                stmt.execute();
+            } finally {
+                closeStatement(stmt);
+                closeConnection(con);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean remove(String blobId) throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            try {
+                con = getConnection();
+                stmt = con.prepareStatement(blobDeleteSQL);
+                stmt.setString(1, blobId);
+                stmt.execute();
+                return stmt.getUpdateCount() == 1;
+            } finally {
+                closeStatement(stmt);
+                closeConnection(con);
+            }
+        }
+    }
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\db\DerbyPooledPersistenceManager.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/persistence/db/JNDIDatabasePersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/JNDIDatabasePersistenceManager.java	(revision 586792)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/JNDIDatabasePersistenceManager.java	(working copy)
@@ -16,10 +16,27 @@
  */
 package org.apache.jackrabbit.core.persistence.db;
 
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.Serializer;
+import org.apache.jackrabbit.core.state.*;
+import org.apache.jackrabbit.core.value.BLOBFileValue;
+import org.apache.jackrabbit.core.value.InternalValue;
+
+import javax.jcr.PropertyType;
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
 import javax.sql.DataSource;
+import javax.transaction.*;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.FilterInputStream;
 import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 
 /**
@@ -62,6 +79,14 @@
 
     //-------------------------------------------< DatabasePersistenceManager >
 
+    public BLOBStore getBLOBStore() {
+        return new JNDIBLOBStore(); 
+    }
+
+    public void close() {
+        ; // do nothing
+    }
+
     /**
      * Returns a JDBC connection from a {@link DataSource} acquired from JNDI
      * with the configured data source location.
@@ -77,4 +102,567 @@
         return dataSource.getConnection();
     }
 
+    protected UserTransaction getUserTransaction() throws NamingException {
+        InitialContext ctx = new InitialContext();
+        UserTransaction ut = (UserTransaction)ctx.lookup("java:comp/UserTransaction");
+        // try a few other ways
+        if (ut == null) {
+            ut = (UserTransaction)ctx.lookup("UserTransaction");
+        }
+        if (ut == null) {
+            ut = (UserTransaction)ctx.lookup("javax.transaction.UserTransaction");
+        }
+        return ut;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(ChangeLog changeLog)
+            throws ItemStateException {
+        try {
+            UserTransaction trans = getUserTransaction();
+            try {
+                super.store(changeLog);
+                trans.commit();
+            } catch (ItemStateException e) {
+                trans.rollback();
+                throw e;
+            }
+        } catch (NamingException e) {
+           logException("Error retrieving UserTransaction", e);
+            throw new ItemStateException("Error retrieving UserTransaction", e);
+        } catch (RollbackException e) {
+            logException("Error rolling back transaction", e);
+            throw new ItemStateException("Error rolling back transaction", e);
+        } catch (HeuristicMixedException e) {
+            logException("Transaction exception", e);
+            throw new ItemStateException("Transaction exception", e);
+        } catch (HeuristicRollbackException e) {
+            logException("Transaction exception", e);
+            throw new ItemStateException("Transaction exception", e);
+        } catch (SystemException e) {
+            logException("Transaction exception", e);                        
+            throw new ItemStateException("Transaction exception", e);
+        }
+    }
+
+    /**
+      * {@inheritDoc}
+      */
+    public NodeState load(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateSelectSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeState state = createNew(id);
+            Serializer.deserialize(state, in);
+
+            return state;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read node state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PropertyState load(PropertyId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        ResultSet rs = null;
+        PreparedStatement stmt = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateSelectSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            PropertyState state = createNew(id);
+            Serializer.deserialize(state, in, blobStore);
+
+            return state;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read property state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize node state
+            Serializer.serialize(state, out);
+
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, state.getNodeId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize property state
+            Serializer.serialize(state, out, blobStore);
+
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, state.getPropertyId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(NodeState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateDeleteSQL);
+            stmt.setString(1, state.getNodeId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // make sure binary values (BLOBs) are properly removed
+        InternalValue[] values = state.getValues();
+        if (values != null) {
+            for (int i = 0; i < values.length; i++) {
+                InternalValue val = values[i];
+                if (val != null) {
+                    if (val.getType() == PropertyType.BINARY) {
+                        BLOBFileValue blobVal = val.getBLOBFileValue();
+                        // delete internal resource representation of BLOB value
+                        blobVal.delete(true);
+                        // also remove from BLOBStore
+                        String blobId = blobStore.createId(state.getPropertyId(), i);
+                        try {
+                            blobStore.remove(blobId);
+                        } catch (Exception e) {
+                            logException("failed to remove from BLOBStore: " + blobId, e);
+                        }
+                    }
+                }
+            }
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateDeleteSQL);
+            stmt.setString(1, state.getPropertyId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeReferences load(NodeReferencesId targetId)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceSelectSQL);
+            stmt.setString(1, targetId.toString());
+            rs = stmt.executeQuery();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(targetId.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeReferences refs = new NodeReferences(targetId);
+            Serializer.deserialize(refs, in);
+
+            return refs;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read node references: " + targetId;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize references
+            Serializer.serialize(refs, out);
+
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, refs.getId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceDeleteSQL);
+            stmt.setString(1, refs.getId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateSelectExistSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            // a node state exists if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of node state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(PropertyId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateSelectExistSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            // a property state exists if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of property state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeReferencesId targetId) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceSelectExistSQL);
+            stmt.setString(1, targetId.toString());
+            rs = stmt.executeQuery();
+
+            // a property state 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;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+        //--------------------------------------------------------< inner classes >
+    class JNDIBLOBStore extends DbBLOBStore {
+            /**
+             * {@inheritDoc}
+             */
+            public InputStream get(String blobId) throws Exception {
+                final Connection con = getConnection();
+                final PreparedStatement stmt = con.prepareStatement(blobSelectSQL);
+                stmt.setString(1, blobId);
+                final ResultSet rs = stmt.executeQuery();
+                if (!rs.next()) {
+                    closeResultSet(rs);
+                    closeStatement(stmt);
+                    closeConnection(con);
+                    throw new Exception("no such BLOB: " + blobId);
+                }
+                InputStream in = rs.getBinaryStream(1);
+                if (in == null) {
+                    // some databases treat zero-length values as NULL;
+                    // return empty InputStream in such a case
+                    closeResultSet(rs);
+                    closeStatement(stmt);
+                    closeConnection(con);
+                    return new ByteArrayInputStream(new byte[0]);
+                }
+
+                /**
+                 * return an InputStream wrapper in order to
+                 * close the ResultSet when the stream is closed
+                 */
+                return new FilterInputStream(in) {
+                    public void close() throws IOException {
+                        in.close();
+                        // now it's safe to close ResultSet
+                        closeResultSet(rs);
+                        closeStatement(stmt);
+                        closeConnection(con);
+                    }
+                };
+            }
+
+            /**
+             * {@inheritDoc}
+             */
+            public void put(String blobId, InputStream in, long size)
+                    throws Exception {
+                Connection con = null;
+                PreparedStatement stmt = null;
+                try {
+                    con = getConnection();
+                    ResultSet rs = null;
+                    boolean exists = false;
+                    try {
+                        stmt = con.prepareStatement(blobSelectExistSQL);
+                        stmt.setString(1, blobId);
+                        rs = stmt.executeQuery();
+
+                        // a BLOB exists if the result has at least one entry
+                        exists = rs.next();
+
+                    } finally {
+                        closeResultSet(rs);
+
+                    }
+                    String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
+                    stmt = con.prepareStatement(sql);
+                    stmt.setBinaryStream(1, in, (int) size);
+                    stmt.setString(2, blobId);
+                    stmt.execute();
+                } finally {
+                    closeStatement(stmt);
+                    closeConnection(con);
+                }
+            }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean remove(String blobId) throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            try {
+                con = getConnection();
+                stmt = con.prepareStatement(blobDeleteSQL);
+                stmt.setString(1, blobId);
+                stmt.execute();
+                return stmt.getUpdateCount() == 1;                       
+            } finally {
+                closeStatement(stmt);
+                closeConnection(con);
+            }
+        }
+    }
 }
Index: src/main/java/org/apache/jackrabbit/core/persistence/db/DatasourcePersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/DatasourcePersistenceManager.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/DatasourcePersistenceManager.java	(revision 0)
@@ -0,0 +1,1092 @@
+/*
+ * 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.db;
+
+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.local.LocalFileSystem;
+import org.apache.jackrabbit.core.persistence.AbstractPersistenceManager;
+import org.apache.jackrabbit.core.persistence.PMContext;
+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.*;
+import org.apache.jackrabbit.core.value.BLOBFileValue;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+import javax.transaction.*;
+import java.io.*;
+import java.sql.*;
+
+public class DatasourcePersistenceManager extends AbstractPersistenceManager {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(DatasourcePersistenceManager.class);
+
+    protected static final String SCHEMA_OBJECT_PREFIX_VARIABLE =
+            "${schemaObjectPrefix}";
+
+    protected boolean initialized;
+
+    protected String schema;
+    protected String schemaObjectPrefix;
+
+    protected boolean externalBLOBs;
+
+    // initial size of buffer used to serialize objects
+    protected static final int INITIAL_BUFFER_SIZE = 1024;
+
+    // SQL statements for NodeState management
+    protected String nodeStateInsertSQL;
+    protected String nodeStateUpdateSQL;
+    protected String nodeStateSelectSQL;
+    protected String nodeStateSelectExistSQL;
+    protected String nodeStateDeleteSQL;
+
+    // SQL statements for PropertyState management
+    protected String propertyStateInsertSQL;
+    protected String propertyStateUpdateSQL;
+    protected String propertyStateSelectSQL;
+    protected String propertyStateSelectExistSQL;
+    protected String propertyStateDeleteSQL;
+
+    // SQL statements for NodeReference management
+    protected String nodeReferenceInsertSQL;
+    protected String nodeReferenceUpdateSQL;
+    protected String nodeReferenceSelectSQL;
+    protected String nodeReferenceSelectExistSQL;
+    protected String nodeReferenceDeleteSQL;
+
+    // SQL statements for BLOB management
+    // (if <code>externalBLOBs==false</code>)
+    protected String blobInsertSQL;
+    protected String blobUpdateSQL;
+    protected String blobSelectSQL;
+    protected String blobSelectExistSQL;
+    protected String blobDeleteSQL;
+
+
+    /**
+     * file system where BLOB data is stored
+     * (if <code>externalBLOBs==true</code>)
+     */
+    protected FileSystem blobFS;
+    /**
+     * BLOBStore that manages BLOB data in the file system
+     * (if <code>externalBLOBs==true</code>)
+     */
+    protected BLOBStore blobStore;
+
+    private String dataSourceLocation;
+
+    /**
+     * Creates a new <code>DatabasePersistenceManager</code> instance.
+     */
+    public DatasourcePersistenceManager() {
+        schema = "default";
+        schemaObjectPrefix = "";
+        externalBLOBs = true;
+        initialized = false;
+    }
+
+    //----------------------------------------------------< setters & getters >
+    /**
+     * Returns the JNDI location of the data source.
+     *
+     * @return data source location
+     */
+    public String getDataSourceLocation() {
+        return dataSourceLocation;
+    }
+
+    /**
+     * Sets the JNDI location of the data source.
+     *
+     * @param dataSourceLocation data source location
+     */
+    public void setDataSourceLocation(String dataSourceLocation) {
+        this.dataSourceLocation = dataSourceLocation;
+    }
+
+    public String getSchemaObjectPrefix() {
+        return schemaObjectPrefix;
+    }
+
+    public void setSchemaObjectPrefix(String schemaObjectPrefix) {
+        // make sure prefix is all uppercase
+        this.schemaObjectPrefix = schemaObjectPrefix.toUpperCase();
+    }
+
+    public String getSchema() {
+        return schema;
+    }
+
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
+    public boolean isExternalBLOBs() {
+        return externalBLOBs;
+    }
+
+    public void setExternalBLOBs(boolean externalBLOBs) {
+        this.externalBLOBs = externalBLOBs;
+    }
+
+    public void setExternalBLOBs(String externalBLOBs) {
+        this.externalBLOBs = Boolean.valueOf(externalBLOBs).booleanValue();
+    }
+
+    public BLOBStore getBLOBStore() {
+        return new DatasourceBLOBStore();
+    }
+
+    //---------------------------------------------------< PersistenceManager >
+    /**
+     * Returns a JDBC connection from a {@link javax.sql.DataSource} acquired from JNDI
+     * with the configured data source location.
+     *
+     * @return new database connection
+     * @throws javax.naming.NamingException if the given data source location does not exist
+     * @throws SQLException                 if a database access error occurs
+     * @see DatabasePersistenceManager#getConnection()
+     */
+    protected Connection getConnection() throws NamingException, SQLException {
+        InitialContext ic = new InitialContext();
+        DataSource dataSource = (DataSource) ic.lookup(dataSourceLocation);
+        return dataSource.getConnection();
+    }
+
+    protected UserTransaction getUserTransaction() throws NamingException {
+        InitialContext ctx = new InitialContext();
+        UserTransaction ut = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
+        // try a few other ways
+        if (ut == null) {
+            ut = (UserTransaction) ctx.lookup("UserTransaction");
+        }
+        if (ut == null) {
+            ut = (UserTransaction) ctx.lookup("javax.transaction.UserTransaction");
+        }
+        return ut;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(PMContext context) throws Exception {
+        if (initialized) {
+            throw new IllegalStateException("already initialized");
+        }
+
+        // make sure schemaObjectPrefix consists of legal name characters only
+        prepareSchemaObjectPrefix();
+
+        // check if schema objects exist and create them if necessary
+        checkSchema();
+
+        // build sql statements
+        buildSQLStatements();
+
+        if (externalBLOBs) {
+            /**
+             * store BLOBs in local file system in a sub directory
+             * of the workspace home directory
+             */
+            LocalFileSystem blobFS = new LocalFileSystem();
+            blobFS.setRoot(new File(context.getHomeDir(), "blobs"));
+            blobFS.init();
+            this.blobFS = blobFS;
+            blobStore = new FileSystemBLOBStore(blobFS);
+        } else {
+            /**
+             * store BLOBs in db
+             */
+            blobStore = getBLOBStore();
+        }
+
+        initialized = true;
+    }
+
+    //----------------------------------< misc. helper methods & overridables >
+
+    /**
+     * Closes the given database connection. This method is called by
+     * {@link #close()} to close the connection acquired using
+     * {@link #getConnection()} when the persistence manager was started.
+     * <p/>
+     * The default implementation just calls the {@link Connection#close()}
+     * method of the given connection, but subclasses can override this
+     * method to provide more extensive database and connection cleanup.
+     *
+     * @param connection database connection
+     * @throws Exception if an error occurs
+     */
+    protected void closeConnection(Connection connection) {
+        if (connection != null) {
+            try {
+                connection.close();
+            } catch (SQLException se) {
+                this.logException("Error closing connection", se);
+            }
+        }
+    }
+
+    /**
+     * Resets the given <code>PreparedStatement</code> by clearing the parameters
+     * and warnings contained.
+     * <p/>
+     * NOTE: This method MUST be called in a synchronized context as neither
+     * this method nor the <code>PreparedStatement</code> instance on which it
+     * operates are thread safe.
+     *
+     * @param stmt The <code>PreparedStatement</code> to reset. If
+     *             <code>null</code> this method does nothing.
+     */
+    protected void resetStatement(PreparedStatement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.clearParameters();
+                stmt.clearWarnings();
+            } catch (SQLException se) {
+                logException("failed resetting PreparedStatement", se);
+            }
+        }
+    }
+
+    protected void closeResultSet(ResultSet rs) {
+        if (rs != null) {
+            try {
+                rs.close();
+            } catch (SQLException se) {
+                logException("failed closing ResultSet", se);
+            }
+        }
+    }
+
+    protected void closeStream(InputStream in) {
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException ignore) {
+            }
+        }
+    }
+
+    protected void closeStatement(Statement stmt) {
+        if (stmt != null) {
+            try {
+                stmt.close();
+            } catch (SQLException se) {
+                logException("failed closing Statement", se);
+            }
+        }
+    }
+
+    protected void logException(String message, Exception e) {
+        if (message != null) {
+            log.error(message);
+        }
+        log.error("    reason: " + e.getMessage());
+        if (e instanceof SQLException) {
+            SQLException se = (SQLException) e;
+            log.error("state/code: " + se.getSQLState() + "/" + se.getErrorCode());
+        }
+        log.debug("      dump:", e);
+    }
+
+    /**
+     * Makes sure that <code>schemaObjectPrefix</code> does only consist of
+     * characters that are allowed in names on the target database. Illegal
+     * characters will be escaped as necessary.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void prepareSchemaObjectPrefix() throws Exception {
+        Connection con = null;
+        try {
+            con = getConnection();
+            DatabaseMetaData metaData = getConnection().getMetaData();
+            String legalChars = metaData.getExtraNameCharacters();
+            legalChars += "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
+
+            String prefix = schemaObjectPrefix.toUpperCase();
+            StringBuffer escaped = new StringBuffer();
+            for (int i = 0; i < prefix.length(); i++) {
+                char c = prefix.charAt(i);
+                if (legalChars.indexOf(c) == -1) {
+                    escaped.append("_x");
+                    String hex = Integer.toHexString(c);
+                    escaped.append("0000".toCharArray(), 0, 4 - hex.length());
+                    escaped.append(hex);
+                    escaped.append("_");
+                } else {
+                    escaped.append(c);
+                }
+            }
+            schemaObjectPrefix = escaped.toString();
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * Checks if the required schema objects exist and creates them if they
+     * don't exist yet.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void checkSchema() throws Exception {
+        Connection con = null;
+        try {
+            con = getConnection();
+            DatabaseMetaData metaData = con.getMetaData();
+            String tableName = schemaObjectPrefix + "NODE";
+            if (metaData.storesLowerCaseIdentifiers()) {
+                tableName = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                tableName = tableName.toUpperCase();
+            }
+
+            ResultSet rs = metaData.getTables(null, null, tableName, null);
+            boolean schemaExists;
+            try {
+                schemaExists = rs.next();
+            } finally {
+                closeResultSet(rs);
+            }
+
+            if (!schemaExists) {
+                // read ddl from resources
+                InputStream in = getSchemaDDL();
+                if (in == null) {
+                    String msg = "Configuration error: unknown schema '" + schema + "'";
+                    log.debug(msg);
+                    throw new RepositoryException(msg);
+                }
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                Statement stmt = con.createStatement();
+                UserTransaction trans = getUserTransaction();
+                try {
+                    trans.begin();
+                    String sql = reader.readLine();
+                    while (sql != null) {
+                        // Skip comments and empty lines
+                        if (!sql.startsWith("#") && sql.length() > 0) {
+                            // replace prefix variable
+                            sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
+                            // execute sql stmt
+                            stmt.executeUpdate(sql);
+                        }
+                        // read next sql stmt
+                        sql = reader.readLine();
+                    }
+                    // commit the changes
+                    trans.commit();
+                } catch (Exception e) {
+                    trans.rollback();
+                    throw e;
+                } finally {
+                    closeStream(in);
+                    closeStatement(stmt);
+                }
+            }
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * Returns an input stream to the schema DDL resource.
+     *
+     * @return an input stream to the schema DDL resource.
+     */
+    protected InputStream getSchemaDDL() {
+        // JCR-595: Use the class explicitly instead of using getClass()
+        // to avoid problems when subclassed in a different package
+        return DatabasePersistenceManager.class.getResourceAsStream(schema + ".ddl");
+    }
+
+    /**
+     * Builds the SQL statements
+     */
+    protected void buildSQLStatements() {
+        nodeStateInsertSQL = "insert into "
+                + schemaObjectPrefix + "NODE (NODE_DATA, NODE_ID) values (?, ?)";
+
+        nodeStateUpdateSQL = "update "
+                + schemaObjectPrefix + "NODE set NODE_DATA = ? where NODE_ID = ?";
+        nodeStateSelectSQL = "select NODE_DATA from "
+                + schemaObjectPrefix + "NODE where NODE_ID = ?";
+        nodeStateSelectExistSQL = "select 1 from "
+                + schemaObjectPrefix + "NODE where NODE_ID = ?";
+        nodeStateDeleteSQL = "delete from "
+                + schemaObjectPrefix + "NODE where NODE_ID = ?";
+
+        propertyStateInsertSQL = "insert into "
+                + schemaObjectPrefix + "PROP (PROP_DATA, PROP_ID) values (?, ?)";
+        propertyStateUpdateSQL = "update "
+                + schemaObjectPrefix + "PROP set PROP_DATA = ? where PROP_ID = ?";
+        propertyStateSelectSQL = "select PROP_DATA from "
+                + schemaObjectPrefix + "PROP where PROP_ID = ?";
+        propertyStateSelectExistSQL = "select 1 from "
+                + schemaObjectPrefix + "PROP where PROP_ID = ?";
+        propertyStateDeleteSQL = "delete from "
+                + schemaObjectPrefix + "PROP where PROP_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 = ?";
+        nodeReferenceSelectExistSQL = "select 1 from "
+                + schemaObjectPrefix + "REFS where NODE_ID = ?";
+        nodeReferenceDeleteSQL = "delete from "
+                + schemaObjectPrefix + "REFS where NODE_ID = ?";
+
+        if (!externalBLOBs) {
+            blobInsertSQL = "insert into "
+                    + schemaObjectPrefix + "BINVAL (BINVAL_DATA, BINVAL_ID) values (?, ?)";
+            blobUpdateSQL = "update "
+                    + schemaObjectPrefix + "BINVAL set BINVAL_DATA = ? where BINVAL_ID = ?";
+            blobSelectSQL =
+                    "select BINVAL_DATA from "
+                            + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?";
+            blobSelectExistSQL =
+                    "select 1 from "
+                            + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?";
+            blobDeleteSQL = "delete from "
+                    + schemaObjectPrefix + "BINVAL where BINVAL_ID = ?";
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(ChangeLog changeLog)
+            throws ItemStateException {
+        try {
+            UserTransaction trans = getUserTransaction();
+            try {
+                super.store(changeLog);
+                trans.commit();
+            } catch (ItemStateException e) {
+                trans.rollback();
+                throw e;
+            }
+        } catch (NamingException e) {
+           logException("Error retrieving UserTransaction", e);
+            throw new ItemStateException("Error retrieving UserTransaction", e);
+        } catch (RollbackException e) {
+            logException("Error rolling back transaction", e);
+            throw new ItemStateException("Error rolling back transaction", e);
+        } catch (HeuristicMixedException e) {
+            logException("Transaction exception", e);
+            throw new ItemStateException("Transaction exception", e);
+        } catch (HeuristicRollbackException e) {
+            logException("Transaction exception", e);
+            throw new ItemStateException("Transaction exception", e);
+        } catch (SystemException e) {
+            logException("Transaction exception", e);
+            throw new ItemStateException("Transaction exception", e);
+        }
+    }
+
+    /**
+      * {@inheritDoc}
+      */
+    public NodeState load(NodeId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateSelectSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeState state = createNew(id);
+            Serializer.deserialize(state, in);
+
+            return state;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read node state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PropertyState load(PropertyId id)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        ResultSet rs = null;
+        PreparedStatement stmt = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateSelectSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            PropertyState state = createNew(id);
+            Serializer.deserialize(state, in, blobStore);
+
+            return state;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read property state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize node state
+            Serializer.serialize(state, out);
+
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, state.getNodeId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists(state.getId());
+        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize property state
+            Serializer.serialize(state, out, blobStore);
+
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, state.getPropertyId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(NodeState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateDeleteSQL);
+            stmt.setString(1, state.getNodeId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete node state: " + state.getNodeId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(PropertyState state)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // make sure binary values (BLOBs) are properly removed
+        InternalValue[] values = state.getValues();
+        if (values != null) {
+            for (int i = 0; i < values.length; i++) {
+                InternalValue val = values[i];
+                if (val != null) {
+                    if (val.getType() == PropertyType.BINARY) {
+                        BLOBFileValue blobVal = val.getBLOBFileValue();
+                        // delete internal resource representation of BLOB value
+                        blobVal.delete(true);
+                        // also remove from BLOBStore
+                        String blobId = blobStore.createId(state.getPropertyId(), i);
+                        try {
+                            blobStore.remove(blobId);
+                        } catch (Exception e) {
+                            logException("failed to remove from BLOBStore: " + blobId, e);
+                        }
+                    }
+                }
+            }
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateDeleteSQL);
+            stmt.setString(1, state.getPropertyId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete property state: " + state.getPropertyId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public NodeReferences load(NodeReferencesId targetId)
+            throws NoSuchItemStateException, ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        InputStream in = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceSelectSQL);
+            stmt.setString(1, targetId.toString());
+            rs = stmt.executeQuery();
+            if (!rs.next()) {
+                throw new NoSuchItemStateException(targetId.toString());
+            }
+
+            in = rs.getBinaryStream(1);
+            NodeReferences refs = new NodeReferences(targetId);
+            Serializer.deserialize(refs, in);
+
+            return refs;
+        } catch (Exception e) {
+            if (e instanceof NoSuchItemStateException) {
+                throw (NoSuchItemStateException) e;
+            }
+            String msg = "failed to read node references: " + targetId;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStream(in);
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize references
+            Serializer.serialize(refs, out);
+
+            con = getConnection();
+            stmt = con.prepareStatement(sql);
+            stmt.setBytes(1, out.toByteArray());
+            stmt.setString(2, refs.getId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy(NodeReferences refs)
+            throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceDeleteSQL);
+            stmt.setString(1, refs.getId().toString());
+            stmt.execute();
+        } catch (Exception e) {
+            String msg = "failed to delete node references: " + refs.getId();
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeStateSelectExistSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            // a node state exists if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of node state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(PropertyId id) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(propertyStateSelectExistSQL);
+            stmt.setString(1, id.toString());
+            rs = stmt.executeQuery();
+
+            // a property state exists if the result has at least one entry
+            return rs.next();
+        } catch (Exception e) {
+            String msg = "failed to check existence of property state: " + id;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(NodeReferencesId targetId) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        try {
+            con = getConnection();
+            stmt = con.prepareStatement(nodeReferenceSelectExistSQL);
+            stmt.setString(1, targetId.toString());
+            rs = stmt.executeQuery();
+
+            // a property state 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;
+            logException(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            closeResultSet(rs);
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    public void close() throws Exception {
+        ; // do nothing
+    }
+
+    //--------------------------------------------------------< inner classes >
+
+    class SizedInputStream extends FilterInputStream {
+        private final long size;
+        private boolean consumed = false;
+
+        SizedInputStream(InputStream in, long size) {
+            super(in);
+            this.size = size;
+        }
+
+        long getSize() {
+            return size;
+        }
+
+        boolean isConsumed() {
+            return consumed;
+        }
+
+        public int read() throws IOException {
+            consumed = true;
+            return super.read();
+        }
+
+        public long skip(long n) throws IOException {
+            consumed = true;
+            return super.skip(n);
+        }
+
+        public int read(byte b[]) throws IOException {
+            consumed = true;
+            return super.read(b);
+        }
+
+        public int read(byte b[], int off, int len) throws IOException {
+            consumed = true;
+            return super.read(b, off, len);
+        }
+    }
+
+    class DatasourceBLOBStore implements BLOBStore {
+
+        public String createId(PropertyId id, int index) {
+            // the blobId is a simple string concatenation of id plus index
+            StringBuffer sb = new StringBuffer();
+            sb.append(id.toString());
+            sb.append('[');
+            sb.append(index);
+            sb.append(']');
+            return sb.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public InputStream get(String blobId) throws Exception {
+            final Connection con = getConnection();
+            final PreparedStatement stmt = con.prepareStatement(blobSelectSQL);
+            stmt.setString(1, blobId);
+            final ResultSet rs = stmt.executeQuery();
+            if (!rs.next()) {
+                closeResultSet(rs);
+                closeStatement(stmt);
+                closeConnection(con);
+                throw new Exception("no such BLOB: " + blobId);
+            }
+            InputStream in = rs.getBinaryStream(1);
+            if (in == null) {
+                // some databases treat zero-length values as NULL;
+                // return empty InputStream in such a case
+                closeResultSet(rs);
+                closeStatement(stmt);
+                closeConnection(con);
+                return new ByteArrayInputStream(new byte[0]);
+            }
+
+            /**
+             * return an InputStream wrapper in order to
+             * close the ResultSet when the stream is closed
+             */
+            return new FilterInputStream(in) {
+                public void close() throws IOException {
+                    in.close();
+                    // now it's safe to close ResultSet
+                    closeResultSet(rs);
+                    closeStatement(stmt);
+                    closeConnection(con);
+                }
+            };
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void put(String blobId, InputStream in, long size)
+                throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            try {
+                con = getConnection();
+                ResultSet rs = null;
+                boolean exists = false;
+                try {
+                    stmt = con.prepareStatement(blobSelectExistSQL);
+                    stmt.setString(1, blobId);
+                    rs = stmt.executeQuery();
+
+                    // a BLOB exists if the result has at least one entry
+                    exists = rs.next();
+
+                } finally {
+                    closeResultSet(rs);
+
+                }
+                String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
+                stmt = con.prepareStatement(sql);
+                stmt.setBinaryStream(1, in, (int) size);
+                stmt.setString(2, blobId);
+                stmt.execute();
+            } finally {
+                closeStatement(stmt);
+                closeConnection(con);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean remove(String blobId) throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            try {
+                con = getConnection();
+                stmt = con.prepareStatement(blobDeleteSQL);
+                stmt.setString(1, blobId);
+                stmt.execute();
+                return stmt.getUpdateCount() == 1;
+            } finally {
+                closeStatement(stmt);
+                closeConnection(con);
+            }
+        }
+    }
+}

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\db\DatasourcePersistenceManager.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: src/main/java/org/apache/jackrabbit/core/persistence/db/OracleJNDIPersistenceManager.java
===================================================================
--- src/main/java/org/apache/jackrabbit/core/persistence/db/OracleJNDIPersistenceManager.java	(revision 0)
+++ src/main/java/org/apache/jackrabbit/core/persistence/db/OracleJNDIPersistenceManager.java	(revision 0)
@@ -0,0 +1,465 @@
+/*
+ * 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.db;
+
+import org.apache.jackrabbit.core.persistence.PMContext;
+import org.apache.jackrabbit.core.persistence.util.Serializer;
+import org.apache.jackrabbit.core.state.NodeReferences;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.sql.*;
+
+/**
+ * <code>OraclePersistenceManager</code> is a JDBC-based
+ * <code>PersistenceManager</code> for Jackrabbit that persists
+ * <code>ItemState</code> and <code>NodeReferences</code> objects in Oracle
+ * database using a simple custom serialization format and a
+ * very basic non-normalized database schema (in essence tables with one 'key'
+ * and one 'data' column).
+ * <p/>
+ * It is configured through the following properties:
+ * <ul>
+ * <li><code>dataSourceLocation</code>: the jndi datasource name</li>
+ * <li><code>schemaObjectPrefix</code>: prefix to be prepended to schema objects</li>
+ * <li><code>tableSpace</code>: the tablespace to use</li>
+ * <li><code>externalBLOBs</code>: if <code>true</code> (the default) BINARY
+ * values (BLOBs) are stored in the local file system;
+ * if <code>false</code> BLOBs are stored in the database</li>
+ * </ul>
+ * See also {@link org.apache.jackrabbit.core.persistence.db.SimpleDbPersistenceManager}.
+ * <p/>
+ * The following is a fragment from a sample configuration:
+ * <pre>
+ *   &lt;PersistenceManager class="org.apache.jackrabbit.core.persistence.db.OraclePersistenceManager"&gt;
+ *       &lt;param name="dataSouceLocation" value="OracleDS"/&gt;
+ *       &lt;param name="schemaObjectPrefix" value="${wsp.name}_"/&gt;
+ *       &lt;param name="tableSpace" value=""/&gt;
+ *       &lt;param name="externalBLOBs" value="false"/&gt;
+ *  &lt;/PersistenceManager&gt;
+ * </pre>
+ */
+public class OracleJNDIPersistenceManager extends JNDIDatabasePersistenceManager {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(OraclePersistenceManager.class);
+
+    private Class blobClass;
+    private Integer DURATION_SESSION_CONSTANT;
+    private Integer MODE_READWRITE_CONSTANT;
+
+    /** the variable for the Oracle table space */
+    public static final String TABLE_SPACE_VARIABLE =
+        "${tableSpace}";
+
+    /** the Oracle table space to use */
+    protected String tableSpace;
+
+    /**
+     * Creates a new <code>OraclePersistenceManager</code> instance.
+     */
+    public OracleJNDIPersistenceManager() {
+        // preset some attributes to reasonable defaults
+        schema = "oracle";
+        schemaObjectPrefix = "";
+        initialized = false;
+    }
+
+    /**
+     * Returns the configured Oracle table space.
+     * @return the configured Oracle table space.
+     */
+    public String getTableSpace() {
+        return tableSpace;
+    }
+
+    /**
+     * Sets the Oracle table space.
+     * @param tableSpace the Oracle table space.
+     */
+    public void setTableSpace(String tableSpace) {
+        if (tableSpace != null) {
+            this.tableSpace = tableSpace.trim();
+        } else {
+            this.tableSpace = null;
+        }
+    }
+
+    //---------------------------------< SimpleDbPersistenceManager overrides >
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Retrieve the <code>oracle.sql.BLOB</code> class via reflection, and
+     * initialize the values for the <code>DURATION_SESSION</code> and
+     * <code>MODE_READWRITE</code> constants defined there.
+     * @see oracle.sql.BLOB#DURATION_SESSION
+     * @see oracle.sql.BLOB#MODE_READWRITE
+     * @noinspection JavadocReference
+     */
+    public void init(PMContext context) throws Exception {
+        super.init(context);
+
+        if (!externalBLOBs) {
+            blobStore = new OracleBLOBStore();
+        }
+
+        // initialize oracle.sql.BLOB class & constants
+
+        // use the Connection object for using the exact same
+        // class loader that the Oracle driver was loaded with
+        Connection con = null;
+        try {
+            con = getConnection();
+            blobClass = con.getClass().getClassLoader().loadClass("oracle.sql.BLOB");
+        } finally {
+            closeConnection(con);
+        }
+        DURATION_SESSION_CONSTANT =
+                new Integer(blobClass.getField("DURATION_SESSION").getInt(null));
+        MODE_READWRITE_CONSTANT =
+                new Integer(blobClass.getField("MODE_READWRITE").getInt(null));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists((NodeId) state.getId());
+        String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
+        Connection con = null;
+        PreparedStatement stmt = null;
+        Blob blob = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize node state
+            Serializer.serialize(state, out);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            con = getConnection();
+            blob = createTemporaryBlob(con, new ByteArrayInputStream(out.toByteArray()));
+            stmt = con.prepareStatement(sql);
+            stmt.setBlob(1, blob);
+            stmt.setString(2, state.getNodeId().toString());            
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node state: " + state.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            if (blob != null) {
+                try {
+                    freeTemporaryBlob(blob);
+                } catch (Exception ignore) {
+                }
+            }
+            closeStatement(stmt);
+            closeConnection(con);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(PropertyState state) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = state.getStatus() != ItemState.STATUS_NEW;
+        //boolean update = exists((PropertyId) state.getId());
+        String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
+
+        Connection con = null;
+        PreparedStatement stmt = null;
+        Blob blob = null;
+        try {
+            ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
+            // serialize property state
+            Serializer.serialize(state, out, blobStore);
+
+            // we are synchronized on this instance, therefore we do not
+            // not have to additionally synchronize on the sql statement
+            con = getConnection();
+            blob = createTemporaryBlob(con, new ByteArrayInputStream(out.toByteArray()));
+            stmt = con.prepareStatement(sql);
+            stmt.setBlob(1, blob);
+            stmt.setString(2, state.getPropertyId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write property state: " + state.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            if (blob != null) {
+                try {
+                    freeTemporaryBlob(blob);
+                } catch (Exception ignore) {
+                }
+            }
+            closeConnection(con);
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void store(NodeReferences refs) throws ItemStateException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // check if insert or update
+        boolean update = exists(refs.getId());
+        String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
+        
+        Connection con = null;
+        PreparedStatement stmt = null;
+        Blob blob = null;
+        try {
+            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 sql statement
+            con = getConnection();
+            blob = createTemporaryBlob(con, new ByteArrayInputStream(out.toByteArray()));
+            stmt = con.prepareStatement(sql);
+            stmt.setBlob(1, blob);
+            stmt.setString(2, refs.getId().toString());
+            stmt.execute();
+
+            // there's no need to close a ByteArrayOutputStream
+            //out.close();
+        } catch (Exception e) {
+            String msg = "failed to write node references: " + refs.getId();
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            if (blob != null) {
+                try {
+                    freeTemporaryBlob(blob);
+                } catch (Exception ignore) {
+                }
+            }
+            closeConnection(con);
+            closeStatement(stmt);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * Overridden in order to support multiple oracle schemas. Note that
+     * schema names in Oracle correspond to the username of the connection.
+     * See http://issues.apache.org/jira/browse/JCR-582
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void checkSchema() throws Exception {
+        Connection con = null;
+        try {
+            con = getConnection();
+            DatabaseMetaData metaData = con.getMetaData();
+            String tableName = schemaObjectPrefix + "NODE";
+            if (metaData.storesLowerCaseIdentifiers()) {
+                tableName = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                tableName = tableName.toUpperCase();
+            }
+            String userName = metaData.getUserName();
+
+            ResultSet rs = metaData.getTables(null, userName, tableName, null);
+            boolean schemaExists;
+            try {
+                schemaExists = rs.next();
+            } finally {
+                closeResultSet(rs);
+            }
+
+            if (!schemaExists) {
+                // read ddl from resources
+                InputStream in = getSchemaDDL();
+                if (in == null) {
+                    String msg = "Configuration error: unknown schema '" + schema + "'";
+                    log.debug(msg);
+                    throw new RepositoryException(msg);
+                }
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                Statement stmt = con.createStatement();
+                try {
+                    String sql = reader.readLine();
+                    while (sql != null) {
+                        // Skip comments and empty lines
+                        if (!sql.startsWith("#") && sql.length() > 0) {
+                            // replace prefix variable
+                            sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
+
+                            // set the tablespace if it is defined
+                            String tspace;
+                            if (tableSpace == null || "".equals(tableSpace)) {
+                                tspace = "";
+                            } else {
+                                tspace = "tablespace " + tableSpace;
+                            }
+                            sql = Text.replace(sql, TABLE_SPACE_VARIABLE, tspace).trim();
+
+                            // execute sql stmt
+                            stmt.executeUpdate(sql);
+                        }
+                        // read next sql stmt
+                        sql = reader.readLine();
+                    }
+                    // commit the changes
+                    con.commit();
+                } finally {
+                    closeStream(in);
+                    closeStatement(stmt);
+                }
+            }
+        } finally {
+            closeConnection(con);
+        }
+    }
+
+    //----------------------------------------< oracle-specific blob handling >
+    /**
+     * Creates a temporary oracle.sql.BLOB instance via reflection and spools
+     * the contents of the specified stream.
+     */
+    protected Blob createTemporaryBlob(Connection con, InputStream in) throws Exception {
+        /*
+        BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
+        blob.open(BLOB.MODE_READWRITE);
+        OutputStream out = blob.getBinaryOutputStream();
+        ...
+        out.flush();
+        out.close();
+        blob.close();
+        return blob;
+        */
+        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});
+        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]);
+        OutputStream out = (OutputStream) getBinaryOutputStream.invoke(blob, null);
+        try {
+            int read;
+            byte[] buf = new byte[8192];
+            while ((read = in.read(buf, 0, buf.length)) > -1) {
+                out.write(buf, 0, read);
+            }
+        } finally {
+            try {
+                out.flush();
+            } catch (IOException ioe) {
+            }
+            out.close();
+        }
+        Method close = blobClass.getMethod("close", new Class[0]);
+        close.invoke(blob, null);
+        return (Blob) blob;
+    }
+
+    /**
+     * Frees a temporary oracle.sql.BLOB instance via reflection.
+     */
+    protected void freeTemporaryBlob(Object blob) throws Exception {
+        // blob.freeTemporary();        
+        Method freeTemporary = blobClass.getMethod("freeTemporary", new Class[0]);
+        freeTemporary.invoke(blob, null);
+    }
+
+    //--------------------------------------------------------< inner classes >
+    class OracleBLOBStore extends JNDIBLOBStore {
+        /**
+         * {@inheritDoc}
+         */
+        public synchronized void put(String blobId, InputStream in, long size)
+                throws Exception {
+            Connection con = null;
+            PreparedStatement stmt = null;
+            ResultSet rs = null;
+            boolean exists = false;
+            try {
+                con = getConnection();
+                stmt = con.prepareStatement(blobSelectExistSQL);
+                stmt.setString(1, blobId);
+                rs = stmt.executeQuery();
+                exists = rs.next();
+            } finally {
+                closeResultSet(rs);
+                closeStatement(stmt);
+            }
+
+            Blob blob = null;
+            try {
+                String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
+                blob = createTemporaryBlob(con, in);
+                stmt = con.prepareStatement(sql);
+                stmt.setBlob(1, blob);
+                stmt.setString(2, blobId);
+                stmt.execute();                
+            } finally {
+                if (blob != null) {
+                    try {
+                        freeTemporaryBlob(blob);
+                    } catch (Exception ignore) {
+                    }
+                }
+                closeConnection(con);
+                closeStatement(stmt);
+            }
+        }
+    }
+}
\ No newline at end of file

Property changes on: src\main\java\org\apache\jackrabbit\core\persistence\db\OracleJNDIPersistenceManager.java
___________________________________________________________________
Name: svn:eol-style
   + native

