You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by st...@apache.org on 2005/11/21 18:46:30 UTC

svn commit: r345937 - in /incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit: core/fs/ core/fs/db/ core/state/db/ value/

Author: stefan
Date: Mon Nov 21 09:46:10 2005
New Revision: 345937

URL: http://svn.apache.org/viewcvs?rev=345937&view=rev
Log:
- jdbc-based FileSystem implementation
- some minor fixes/cleanups

Added:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/daffodil.ddl
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/default.ddl
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/derby.ddl
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mssql.ddl
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mysql.ddl
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/package.html   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/postgresql.ddl
Modified:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/DerbyPersistenceManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/SimpleDbPersistenceManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueHelper.java

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java?rev=345937&r1=345936&r2=345937&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java Mon Nov 21 09:46:10 2005
@@ -58,7 +58,11 @@
 
     protected String buildBasePath(String path) {
         if (path.startsWith(SEPARATOR)) {
-            return basePath + path;
+            if (path.length() == 1) {
+                return basePath;
+            } else {
+                return basePath + path;
+            }
         } else {
             return basePath + SEPARATOR + path;
         }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java?rev=345937&r1=345936&r2=345937&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java Mon Nov 21 09:46:10 2005
@@ -137,6 +137,51 @@
     }
 
     /**
+     * Tests whether the specified path represents the root path, i.e. "/".
+     *
+     * @param path path to test
+     * @return true if the specified path represents the root path; false otherwise.
+     */
+    public static boolean denotesRoot(String path) {
+        return path.equals(FileSystem.SEPARATOR);
+    }
+
+    /**
+     * Checks if <code>path</code> is a valid path.
+     *
+     * @param path the path to be checked
+     * @throws FileSystemException If <code>path</code> is not a valid path
+     */
+    public static void checkFormat(String path) throws FileSystemException {
+        if (path == null) {
+            throw new FileSystemException("null path");
+        }
+
+        // path must be absolute, i.e. starting with '/'
+        if (!path.startsWith(FileSystem.SEPARATOR)) {
+            throw new FileSystemException("not an absolute path: " + path);
+        }
+
+        // trailing '/' is not allowed (except for root path)
+        if (path.endsWith(FileSystem.SEPARATOR) && path.length() > 1) {
+            throw new FileSystemException("malformed path: " + path);
+        }
+
+        String[] names = path.split(FileSystem.SEPARATOR);
+        for (int i = 1; i < names.length; i++) {
+            // name must not be empty
+            if (names[i].length() == 0) {
+                throw new FileSystemException("empty name: " + path);
+            }
+            // leading/trailing whitespace is not allowed
+            String trimmed = names[i].trim();
+            if (!trimmed.equals(names[i])) {
+                throw new FileSystemException("illegal leading or trailing whitespace in name: " + path);
+            }
+        }
+    }
+
+    /**
      * Returns the parent directory of the specified <code>path</code>.
      *
      * @param path a file system path denoting a directory or a file.

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java Mon Nov 21 09:46:10 2005
@@ -0,0 +1,1398 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed 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.fs.db;
+
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.FileSystemPathUtil;
+import org.apache.jackrabbit.core.fs.RandomAccessOutputStream;
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.util.TransientFileFactory;
+import org.apache.log4j.Logger;
+
+import javax.jcr.RepositoryException;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.FileInputStream;
+import java.io.RandomAccessFile;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+
+/**
+ * <code>DbFileSystem</code> is a generic JDBC-based <code>FileSystem</code>
+ * implementation for Jackrabbit that persists file system entries in a
+ * database table.
+ * <p/>
+ * It is configured through the following properties:
+ * <ul>
+ * <li><code>driver</code>: the FQN name of the JDBC driver class</li>
+ * <li><code>url</code>: the database url of the form <code>jdbc:subprotocol:subname</code></li>
+ * <li><code>user</code>: the database user</li>
+ * <li><code>password</code>: the user's password</li>
+ * <li><code>schema</code>: type of schema to be used
+ * (e.g. <code>mysql</code>, <code>mssql</code>, etc.); </li>
+ * <li><code>schemaObjectPrefix</code>: prefix to be prepended to schema objects</li>
+ * </ul>
+ * The required schema objects are automatically created by executing the DDL
+ * statements read from the [schema].ddl file. The .ddl file is read from the
+ * resources by calling <code>getClass().getResourceAsStream(schema + ".ddl")</code>.
+ * Every line in the specified .ddl file is executed separatly by calling
+ * <code>java.sql.Statement.execute(String)</code> where every occurence of the
+ * the string <code>"${schemaObjectPrefix}"</code> has been replaced with the
+ * value of the property <code>schemaObjectPrefix</code>.
+ * <p/>
+ * The following is a fragment from a sample configuration using MySQL:
+ * <pre>
+ *   &lt;FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem"&gt;
+ *       &lt;param name="driver" value="com.mysql.jdbc.Driver"/&gt;
+ *       &lt;param name="url" value="jdbc:mysql:///test"/&gt;
+ *       &lt;param name="schema" value="mysql"/&gt;
+ *       &lt;param name="schemaObjectPrefix" value="rep_"/&gt;
+ *   &lt;/FileSystem&gt;
+ * </pre>
+ * The following is a fragment from a sample configuration using Daffodil One$DB Embedded:
+ * <pre>
+ *   &lt;FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem"&gt;
+ *       &lt;param name="driver" value="in.co.daffodil.db.jdbc.DaffodilDBDriver"/&gt;
+ *       &lt;param name="url" value="jdbc:daffodilDB_embedded:rep;path=${rep.home}/databases;create=true"/&gt;
+ *       &lt;param name="user" value="daffodil"/&gt;
+ *       &lt;param name="password" value="daffodil"/&gt;
+ *       &lt;param name="schema" value="daffodil"/&gt;
+ *       &lt;param name="schemaObjectPrefix" value="rep_"/&gt;
+ *   &lt;/FileSystem&gt;
+ * </pre>
+ * See also {@link DerbyFileSystem}.
+ */
+public class DbFileSystem implements FileSystem {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = Logger.getLogger(DbFileSystem.class);
+
+    protected static final String SCHEMA_OBJECT_PREFIX_VARIABLE =
+            "${schemaObjectPrefix}";
+
+    protected boolean initialized;
+
+    protected String driver;
+    protected String url;
+    protected String user;
+    protected String password;
+    protected String schema;
+    protected String schemaObjectPrefix;
+
+    // initial size of buffer used to serialize objects
+    protected static final int INITIAL_BUFFER_SIZE = 8192;
+
+    // jdbc connection
+    protected Connection con;
+
+    // shared prepared statements
+    protected PreparedStatement selectExistStmt;
+    protected PreparedStatement selectFileExistStmt;
+    protected PreparedStatement selectFolderExistStmt;
+    protected PreparedStatement selectChildCountStmt;
+    protected PreparedStatement selectDataStmt;
+    protected PreparedStatement selectLastModifiedStmt;
+    protected PreparedStatement selectLengthStmt;
+    protected PreparedStatement selectFileNamesStmt;
+    protected PreparedStatement selectFolderNamesStmt;
+    protected PreparedStatement selectFileAndFolderNamesStmt;
+    protected PreparedStatement deleteFileStmt;
+    protected PreparedStatement deleteFolderStmt;
+    protected PreparedStatement insertFileStmt;
+    protected PreparedStatement insertFolderStmt;
+    protected PreparedStatement updateDataStmt;
+    protected PreparedStatement updateLastModifiedStmt;
+    protected PreparedStatement copyFileStmt;
+    protected PreparedStatement copyFilesStmt;
+
+    /**
+     * Default constructor
+     */
+    public DbFileSystem() {
+        schema = "default";
+        schemaObjectPrefix = "";
+        initialized = false;
+    }
+
+    //----------------------------------------------------< setters & getters >
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getDriver() {
+        return driver;
+    }
+
+    public void setDriver(String driver) {
+        this.driver = driver;
+    }
+
+    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;
+    }
+
+    //-------------------------------------------< java.lang.Object overrides >
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DbFileSystem) {
+            DbFileSystem other = (DbFileSystem) obj;
+            if (((driver != null) ? driver.equals(other.driver) : other.driver == null)
+                    && ((url != null) ? url.equals(other.url) : other.url == null)
+                    && ((user != null) ? user.equals(other.user) : other.user == null)
+                    && ((password != null) ? password.equals(other.password) : other.password == null)
+                    && ((schema != null) ? schema.equals(other.schema) : other.schema == null)
+                    && ((schemaObjectPrefix != null) ? schemaObjectPrefix.equals(other.schemaObjectPrefix) : other.schemaObjectPrefix == null)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns zero to satisfy the Object equals/hashCode contract.
+     * This class is mutable and not meant to be used as a hash key.
+     *
+     * @return always zero
+     * @see Object#hashCode()
+     */
+    public int hashCode() {
+        return 0;
+    }
+
+    //-----------------------------------------------------------< FileSystem >
+    /**
+     * {@inheritDoc}
+     */
+    public void init() throws FileSystemException {
+        if (initialized) {
+            throw new IllegalStateException("already initialized");
+        }
+
+        try {
+            // setup jdbc connection
+            Class.forName(driver);
+            con = DriverManager.getConnection(url, user, password);
+            con.setAutoCommit(true);
+
+            // check if schema objects exist and create them if necessary
+            checkSchema();
+
+            // prepare statements
+            insertFileStmt = con.prepareStatement("insert into "
+                    + schemaObjectPrefix + "FSENTRY "
+                    + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, "
+                    + "FSENTRY_LASTMOD, FSENTRY_LENGTH) "
+                    + "values (?, ?, ?, ?, ?)");
+
+            insertFolderStmt = con.prepareStatement("insert into "
+                    + schemaObjectPrefix + "FSENTRY "
+                    + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_LASTMOD, FSENTRY_LENGTH) "
+                    + "values (?, ?, ?, 0)");
+
+            updateDataStmt = con.prepareStatement("update "
+                    + schemaObjectPrefix + "FSENTRY "
+                    + "set FSENTRY_DATA = ?, FSENTRY_LASTMOD = ?, FSENTRY_LENGTH = ? "
+                    + "where FSENTRY_PATH = ? and FSENTRY_NAME = ? "
+                    + "and FSENTRY_DATA is not null");
+
+            updateLastModifiedStmt = con.prepareStatement("update "
+                    + schemaObjectPrefix + "FSENTRY set FSENTRY_LASTMOD = ? "
+                    + "where FSENTRY_PATH = ? and FSENTRY_NAME = ? "
+                    + "and FSENTRY_DATA is not null");
+
+            selectExistStmt = con.prepareStatement("select 1 from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ?");
+
+            selectFileExistStmt = con.prepareStatement("select 1 from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ? and FSENTRY_DATA is not null");
+
+            selectFolderExistStmt = con.prepareStatement("select 1 from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ? and FSENTRY_DATA is null");
+
+            selectFileNamesStmt = con.prepareStatement("select FSENTRY_NAME from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_DATA is not null");
+
+            selectFolderNamesStmt = con.prepareStatement("select FSENTRY_NAME from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_DATA is null");
+
+            selectFileAndFolderNamesStmt = con.prepareStatement("select FSENTRY_NAME from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ?");
+
+            selectChildCountStmt = con.prepareStatement("select count(FSENTRY_NAME) from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ?  ");
+
+            selectDataStmt = con.prepareStatement("select FSENTRY_DATA from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ? and FSENTRY_DATA is not null");
+
+            selectLastModifiedStmt = con.prepareStatement("select FSENTRY_LASTMOD from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ?");
+
+            selectLengthStmt = con.prepareStatement("select FSENTRY_LENGTH from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ? and FSENTRY_DATA is not null");
+
+            deleteFileStmt = con.prepareStatement("delete from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ? and FSENTRY_DATA is not null");
+
+            deleteFolderStmt = con.prepareStatement("delete from "
+                    + schemaObjectPrefix + "FSENTRY where "
+                    + "(FSENTRY_PATH = ? and FSENTRY_NAME = ? and FSENTRY_DATA is null) "
+                    + "or (FSENTRY_PATH = ?) "
+                    + "or (FSENTRY_PATH like ?) ");
+
+            copyFileStmt = con.prepareStatement("insert into "
+                    + schemaObjectPrefix + "FSENTRY "
+                    + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, "
+                    + "FSENTRY_LASTMOD, FSENTRY_LENGTH) "
+                    + "select ?, ?, FSENTRY_DATA, "
+                    + "FSENTRY_LASTMOD, FSENTRY_LENGTH from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_NAME = ? and FSENTRY_DATA is not null");
+
+            copyFilesStmt = con.prepareStatement("insert into "
+                    + schemaObjectPrefix + "FSENTRY "
+                    + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, "
+                    + "FSENTRY_LASTMOD, FSENTRY_LENGTH) "
+                    + "select ?, FSENTRY_NAME, FSENTRY_DATA, "
+                    + "FSENTRY_LASTMOD, FSENTRY_LENGTH from "
+                    + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? "
+                    + "and FSENTRY_DATA is not null");
+
+            // finally verify that there's a file system root entry
+            verifyRoodExists();
+
+            initialized = true;
+        } catch (Exception e) {
+            String msg = "failed to initialize file system";
+            log.error(msg, e);
+            throw new FileSystemException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        try {
+            // close shared prepared statements
+            closeStatement(insertFileStmt);
+            closeStatement(insertFolderStmt);
+            closeStatement(updateDataStmt);
+            closeStatement(updateLastModifiedStmt);
+            closeStatement(selectExistStmt);
+            closeStatement(selectFileExistStmt);
+            closeStatement(selectFolderExistStmt);
+            closeStatement(selectFileNamesStmt);
+            closeStatement(selectFolderNamesStmt);
+            closeStatement(selectFileAndFolderNamesStmt);
+            closeStatement(selectChildCountStmt);
+            closeStatement(selectDataStmt);
+            closeStatement(selectLastModifiedStmt);
+            closeStatement(selectLengthStmt);
+            closeStatement(deleteFolderStmt);
+            closeStatement(deleteFileStmt);
+            closeStatement(copyFileStmt);
+            closeStatement(copyFilesStmt);
+
+            // close jdbc connection
+            con.close();
+        } catch (Exception e) {
+            String msg = "error closing file system";
+            log.error(msg, e);
+            throw new FileSystemException(msg, e);
+        } finally {
+            initialized = false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void createFolder(String folderPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(folderPath);
+
+        if (!exists(folderPath)) {
+            createDeepFolder(folderPath);
+        } else {
+            throw new FileSystemException("file system entry already exists: " + folderPath);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFile(String filePath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(filePath);
+
+        String parentDir = FileSystemPathUtil.getParentDir(filePath);
+        String name = FileSystemPathUtil.getName(filePath);
+
+        int count = 0;
+        PreparedStatement stmt = deleteFileStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                count = stmt.executeUpdate();
+            } catch (Exception e) {
+                String msg = "failed to delete file: " + filePath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+
+        if (count == 0) {
+            throw new FileSystemException("no such file: " + filePath);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFolder(String folderPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(folderPath);
+
+        if (folderPath.equals(FileSystem.SEPARATOR)) {
+            throw new FileSystemException("cannot delete root");
+        }
+
+        String parentDir = FileSystemPathUtil.getParentDir(folderPath);
+        String name = FileSystemPathUtil.getName(folderPath);
+
+        int count = 0;
+        PreparedStatement stmt = deleteFolderStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.setString(3, folderPath);
+                stmt.setString(4, folderPath + FileSystem.SEPARATOR + "%");
+                count = stmt.executeUpdate();
+            } catch (Exception e) {
+                String msg = "failed to delete folder: " + folderPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+
+        if (count == 0) {
+            throw new FileSystemException("no such folder: " + folderPath);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(String path) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(path);
+
+        String parentDir = FileSystemPathUtil.getParentDir(path);
+        String name = FileSystemPathUtil.getName(path);
+
+        PreparedStatement stmt = selectExistStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.execute();
+                rs = stmt.getResultSet();
+
+                // a file system entry exists if the result set
+                // has at least one entry
+                return rs.next();
+            } catch (Exception e) {
+                String msg = "failed to check existence of file system entry: " + path;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFile(String path) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(path);
+
+        String parentDir = FileSystemPathUtil.getParentDir(path);
+        String name = FileSystemPathUtil.getName(path);
+
+        PreparedStatement stmt = selectFileExistStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.execute();
+                rs = stmt.getResultSet();
+
+                // a file exists if the result set has at least one entry
+                return rs.next();
+            } catch (Exception e) {
+                String msg = "failed to check existence of file: " + path;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFolder(String path) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(path);
+
+        String parentDir = FileSystemPathUtil.getParentDir(path);
+        String name = FileSystemPathUtil.getName(path);
+
+        PreparedStatement stmt = selectFolderExistStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.execute();
+                rs = stmt.getResultSet();
+
+                // a folder exists if the result set has at least one entry
+                return rs.next();
+            } catch (Exception e) {
+                String msg = "failed to check existence of folder: " + path;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long lastModified(String path) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(path);
+
+        String parentDir = FileSystemPathUtil.getParentDir(path);
+        String name = FileSystemPathUtil.getName(path);
+
+        PreparedStatement stmt = selectLastModifiedStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.execute();
+                rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    throw new FileSystemException("no such file system entry: " + path);
+                }
+                return rs.getLong(1);
+            } catch (Exception e) {
+                String msg = "failed to determine lastModified of file system entry: " + path;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long length(String filePath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(filePath);
+
+        String parentDir = FileSystemPathUtil.getParentDir(filePath);
+        String name = FileSystemPathUtil.getName(filePath);
+
+        PreparedStatement stmt = selectLengthStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.execute();
+                rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    throw new FileSystemException("no such file: " + filePath);
+                }
+                return rs.getLong(1);
+            } catch (Exception e) {
+                String msg = "failed to determine length of file: " + filePath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasChildren(String path) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(path);
+
+        if (!exists(path)) {
+            throw new FileSystemException("no such file system entry: " + path);
+        }
+
+        PreparedStatement stmt = selectChildCountStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, path);
+                stmt.execute();
+                rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    return false;
+                }
+                int count = rs.getInt(1);
+                if (FileSystemPathUtil.denotesRoot(path)) {
+                    // ingore file system root entry
+                    count--;
+                }
+                return (count > 0);
+            } catch (Exception e) {
+                String msg = "failed to determine child count of file system entry: " + path;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] list(String folderPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(folderPath);
+
+        if (!isFolder(folderPath)) {
+            throw new FileSystemException("no such folder: " + folderPath);
+        }
+
+        PreparedStatement stmt = selectFileAndFolderNamesStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, folderPath);
+                stmt.execute();
+                rs = stmt.getResultSet();
+                ArrayList names = new ArrayList();
+                while (rs.next()) {
+                    String name = rs.getString(1);
+                    if (name.length() == 0
+                            && FileSystemPathUtil.denotesRoot(folderPath)) {
+                        // this is the file system root entry, skip...
+                        continue;
+                    }
+                    names.add(name);
+                }
+                return (String[]) names.toArray(new String[names.size()]);
+            } catch (Exception e) {
+                String msg = "failed to list child entries of folder: " + folderPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFiles(String folderPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(folderPath);
+
+        if (!isFolder(folderPath)) {
+            throw new FileSystemException("no such folder: " + folderPath);
+        }
+
+        PreparedStatement stmt = selectFileNamesStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, folderPath);
+                stmt.execute();
+                rs = stmt.getResultSet();
+                ArrayList names = new ArrayList();
+                while (rs.next()) {
+                    names.add(rs.getString(1));
+                }
+                return (String[]) names.toArray(new String[names.size()]);
+            } catch (Exception e) {
+                String msg = "failed to list file entries of folder: " + folderPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFolders(String folderPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(folderPath);
+
+        if (!isFolder(folderPath)) {
+            throw new FileSystemException("no such folder: " + folderPath);
+        }
+
+        PreparedStatement stmt = selectFolderNamesStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, folderPath);
+                stmt.execute();
+                rs = stmt.getResultSet();
+                ArrayList names = new ArrayList();
+                while (rs.next()) {
+                    String name = rs.getString(1);
+                    if (name.length() == 0
+                            && FileSystemPathUtil.denotesRoot(folderPath)) {
+                        // this is the file system root entry, skip...
+                        continue;
+                    }
+                    names.add(name);
+                }
+                return (String[]) names.toArray(new String[names.size()]);
+            } catch (Exception e) {
+                String msg = "failed to list folder entries of folder: " + folderPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void touch(String filePath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(filePath);
+
+        String parentDir = FileSystemPathUtil.getParentDir(filePath);
+        String name = FileSystemPathUtil.getName(filePath);
+
+        int count = 0;
+        PreparedStatement stmt = updateLastModifiedStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setLong(1, System.currentTimeMillis());
+                stmt.setString(2, parentDir);
+                stmt.setString(3, name);
+                count = stmt.executeUpdate();
+            } catch (Exception e) {
+                String msg = "failed to touch file: " + filePath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+
+        if (count == 0) {
+            throw new FileSystemException("no such file: " + filePath);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getInputStream(String filePath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(filePath);
+
+        String parentDir = FileSystemPathUtil.getParentDir(filePath);
+        String name = FileSystemPathUtil.getName(filePath);
+
+        PreparedStatement stmt = selectDataStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.execute();
+                final ResultSet rs = stmt.getResultSet();
+                if (!rs.next()) {
+                    throw new FileSystemException("no such file: " + filePath);
+                }
+                InputStream in = rs.getBinaryStream(1);
+                /**
+                 * return an InputStream wrapper in order to
+                 * close the ResultSet when the stream is closed
+                 */
+                return new FilterInputStream(in) {
+                    public void close() throws IOException {
+                        super.close();
+                        // close ResultSet
+                        closeResultSet(rs);
+                    }
+                };
+            } catch (Exception e) {
+                String msg = "failed to retrieve data of file: " + filePath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public OutputStream getOutputStream(final String filePath)
+            throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(filePath);
+
+        final String parentDir = FileSystemPathUtil.getParentDir(filePath);
+        final String name = FileSystemPathUtil.getName(filePath);
+
+        if (!isFolder(parentDir)) {
+            throw new FileSystemException("path not found: " + parentDir);
+        }
+
+        if (isFolder(filePath)) {
+            throw new FileSystemException("path denotes folder: " + filePath);
+        }
+
+        try {
+            TransientFileFactory fileFactory = TransientFileFactory.getInstance();
+            final File tmpFile = fileFactory.createTransientFile("bin", null, null);
+
+            return new FilterOutputStream(new FileOutputStream(tmpFile)) {
+                File f = tmpFile;
+
+                public void close() throws IOException {
+                    super.close();
+
+                    PreparedStatement stmt = null;
+                    InputStream in = null;
+                    try {
+                        if (isFile(filePath)) {
+                            stmt = updateDataStmt;
+                            synchronized (stmt) {
+                                long length = f.length();
+                                in = new FileInputStream(f);
+                                stmt.setBinaryStream(1, in, (int) length);
+                                stmt.setLong(2, System.currentTimeMillis());
+                                stmt.setLong(3, length);
+                                stmt.setString(4, parentDir);
+                                stmt.setString(5, name);
+                                stmt.executeUpdate();
+                            }
+                        } else {
+                            stmt = insertFileStmt;
+                            stmt.setString(1, parentDir);
+                            stmt.setString(2, name);
+                            long length = f.length();
+                            in = new FileInputStream(f);
+                            stmt.setBinaryStream(3, in, (int) length);
+                            stmt.setLong(4, System.currentTimeMillis());
+                            stmt.setLong(5, length);
+                            stmt.executeUpdate();
+                        }
+
+                    } catch (Exception e) {
+                        throw new IOException(e.getMessage());
+                    } finally {
+                        if (stmt != null) {
+                            resetStatement(stmt);
+                        }
+                        if (in != null) {
+                            in.close();
+                        }
+                        // temp file can now safely be removed
+                        f.delete();
+                        f = null;
+                    }
+                }
+            };
+        } catch (Exception e) {
+            String msg = "failed to open output strean to file: " + filePath;
+            log.error(msg, e);
+            throw new FileSystemException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public RandomAccessOutputStream getRandomAccessOutputStream(final String filePath)
+            throws FileSystemException, UnsupportedOperationException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(filePath);
+
+        final String parentDir = FileSystemPathUtil.getParentDir(filePath);
+        final String name = FileSystemPathUtil.getName(filePath);
+
+        if (!isFolder(parentDir)) {
+            throw new FileSystemException("path not found: " + parentDir);
+        }
+
+        if (isFolder(filePath)) {
+            throw new FileSystemException("path denotes folder: " + filePath);
+        }
+
+        try {
+            TransientFileFactory fileFactory = TransientFileFactory.getInstance();
+            final File tmpFile = fileFactory.createTransientFile("bin", null, null);
+
+            // @todo FIXME use java.sql.Blob
+
+            if (isFile(filePath)) {
+                // file entry exists, spool contents to temp file first
+                InputStream in = getInputStream(filePath);
+                OutputStream out = new FileOutputStream(tmpFile);
+                try {
+                    int read;
+                    byte[] ba = new byte[8192];
+                    while ((read = in.read(ba, 0, ba.length)) != -1) {
+                        out.write(ba, 0, read);
+                    }
+                } finally {
+                    out.close();
+                    in.close();
+                }
+            }
+
+            return new RandomAccessOutputStream() {
+                File f = tmpFile;
+                RandomAccessFile raf = new RandomAccessFile(f, "rw");
+
+                public void close() throws IOException {
+                    raf.close();
+
+                    PreparedStatement stmt = null;
+                    InputStream in = null;
+                    try {
+                        if (isFile(filePath)) {
+                            stmt = updateDataStmt;
+                            synchronized (stmt) {
+                                long length = f.length();
+                                in = new FileInputStream(f);
+                                stmt.setBinaryStream(1, in, (int) length);
+                                stmt.setLong(2, System.currentTimeMillis());
+                                stmt.setLong(3, length);
+                                stmt.setString(4, parentDir);
+                                stmt.setString(5, name);
+                                stmt.executeUpdate();
+                            }
+                        } else {
+                            stmt = insertFileStmt;
+                            stmt.setString(1, parentDir);
+                            stmt.setString(2, name);
+                            long length = f.length();
+                            in = new FileInputStream(f);
+                            stmt.setBinaryStream(3, in, (int) length);
+                            stmt.setLong(4, System.currentTimeMillis());
+                            stmt.setLong(5, length);
+                            stmt.executeUpdate();
+                        }
+
+                    } catch (Exception e) {
+                        throw new IOException(e.getMessage());
+                    } finally {
+                        if (stmt != null) {
+                            resetStatement(stmt);
+                        }
+                        if (in != null) {
+                            in.close();
+                        }
+                        // temp file can now safely be removed
+                        f.delete();
+                        f = null;
+                        raf = null;
+                    }
+                }
+
+                public void seek(long position) throws IOException {
+                    raf.seek(position);
+                }
+
+                public void write(int b) throws IOException {
+                    raf.write(b);
+                }
+
+                public void flush() /*throws IOException*/ {
+                    // nop
+                }
+
+                public void write(byte b[]) throws IOException {
+                    raf.write(b);
+                }
+
+                public void write(byte b[], int off, int len) throws IOException {
+                    raf.write(b, off, len);
+                }
+            };
+        } catch (Exception e) {
+            String msg = "failed to open output strean to file: " + filePath;
+            log.error(msg, e);
+            throw new FileSystemException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void copy(String srcPath, String destPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(srcPath);
+        FileSystemPathUtil.checkFormat(destPath);
+
+        if (isFolder(srcPath)) {
+            // src is a folder
+            copyDeepFolder(srcPath, destPath);
+        } else {
+            // src is a file
+            copyFile(srcPath, destPath);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void move(String srcPath, String destPath) throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        FileSystemPathUtil.checkFormat(srcPath);
+        FileSystemPathUtil.checkFormat(destPath);
+
+        // @todo optimize move (use sql update stmts)
+        copy(srcPath, destPath);
+        if (isFile(srcPath)) {
+            deleteFile(srcPath);
+        } else {
+            deleteFolder(srcPath);
+        }
+    }
+
+    //-------------------------------------------------< misc. helper methods >
+    /**
+     * 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 {
+        DatabaseMetaData metaData = con.getMetaData();
+        String tableName = schemaObjectPrefix + "FSENTRY";
+        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 {
+            rs.close();
+        }
+
+        if (!schemaExists) {
+            // read ddl from resources
+            InputStream in = getClass().getResourceAsStream(schema + ".ddl");
+            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) {
+                    // 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();
+                }
+            } finally {
+                closeStream(in);
+                closeStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * Verifies that the root file system entry exists. If it doesn't exist yet
+     * it will be automatically created.
+     *
+     * @throws Exception if an error occurs
+     */
+    protected void verifyRoodExists() throws Exception {
+        // check if root file system entry exists
+        PreparedStatement stmt = selectFolderExistStmt;
+        synchronized (stmt) {
+            ResultSet rs = null;
+            try {
+                stmt.setString(1, FileSystem.SEPARATOR);
+                stmt.setString(2, "");
+                stmt.execute();
+                rs = stmt.getResultSet();
+
+                if (rs.next()) {
+                    // root entry exists
+                    return;
+                }
+            } catch (Exception e) {
+                String msg = "failed to check existence of file system root entry";
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                closeResultSet(rs);
+                resetStatement(stmt);
+            }
+        }
+
+        // the root entry doesn't exist yet, create it...
+        createDeepFolder(FileSystem.SEPARATOR);
+    }
+
+    /**
+     * Creates the specified files system folder entry, recursively creating
+     * any non-existing intermediate folder entries.
+     *
+     * @param folderPath folder entry to create
+     * @throws FileSystemException if an error occurs
+     */
+    protected void createDeepFolder(String folderPath)
+            throws FileSystemException {
+        String parentDir = FileSystemPathUtil.getParentDir(folderPath);
+        String name = FileSystemPathUtil.getName(folderPath);
+
+        if (!FileSystemPathUtil.denotesRoot(folderPath)) {
+            if (!exists(parentDir)) {
+                createDeepFolder(parentDir);
+            }
+        }
+
+        PreparedStatement stmt = insertFolderStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setString(1, parentDir);
+                stmt.setString(2, name);
+                stmt.setLong(3, System.currentTimeMillis());
+                stmt.executeUpdate();
+            } catch (Exception e) {
+                String msg = "failed to create folder entry: " + folderPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * Recursively copies the given folder to the given destination.
+     *
+     * @param srcPath folder to be copied
+     * @param destPath destination path to which the folder is to be copied
+     * @throws FileSystemException if an error occurs
+     */
+    protected void copyDeepFolder(String srcPath, String destPath)
+            throws FileSystemException {
+
+        if (!exists(destPath)) {
+            createDeepFolder(destPath);
+        }
+
+        String[] names = listFolders(srcPath);
+
+        for (int i = 0; i < names.length; i++) {
+            String src = (FileSystemPathUtil.denotesRoot(srcPath) ?
+                    srcPath + names[i] : srcPath + FileSystem.SEPARATOR + names[i]);
+            String dest = (FileSystemPathUtil.denotesRoot(destPath) ?
+                    destPath + names[i] : destPath + FileSystem.SEPARATOR + names[i]);
+            copyDeepFolder(src, dest);
+        }
+
+        PreparedStatement stmt = copyFilesStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setString(1, srcPath);
+                stmt.setString(2, destPath);
+                stmt.executeUpdate();
+            } catch (Exception e) {
+                String msg = "failed to copy file entries from " + srcPath + " to " + destPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * Copies the given file entry to the given destination path. The parent
+     * folder of the destination path will be created if it doesn't exist
+     * already. If the destination path refers to an existing file, the file
+     * will be overwritten.
+     *
+     * @param srcPath file to be copied
+     * @param destPath destination path to which the file is to be copied
+     * @throws FileSystemException if an error occurs
+     */
+    protected void copyFile(String srcPath, String destPath)
+            throws FileSystemException {
+
+        final String srcParentDir = FileSystemPathUtil.getParentDir(srcPath);
+        final String srcName = FileSystemPathUtil.getName(srcPath);
+
+        final String destParentDir = FileSystemPathUtil.getParentDir(destPath);
+        final String destName = FileSystemPathUtil.getName(destPath);
+
+        if (!exists(destParentDir)) {
+            createDeepFolder(destParentDir);
+        }
+        if (isFile(destPath)) {
+            deleteFile(destPath);
+        }
+
+        PreparedStatement stmt = copyFileStmt;
+        synchronized (stmt) {
+            try {
+                stmt.setString(1, destParentDir);
+                stmt.setString(2, destName);
+                stmt.setString(3, srcParentDir);
+                stmt.setString(4, srcName);
+                stmt.executeUpdate();
+            } catch (Exception e) {
+                String msg = "failed to copy file from " + srcPath + " to " + destPath;
+                log.error(msg, e);
+                throw new FileSystemException(msg, e);
+            } finally {
+                resetStatement(stmt);
+            }
+        }
+    }
+
+    /**
+     * 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) {
+                log.error("failed resetting PreparedStatement", se);
+            }
+        }
+    }
+
+    protected void closeResultSet(ResultSet rs) {
+        if (rs != null) {
+            try {
+                rs.close();
+            } catch (SQLException se) {
+                log.error("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) {
+                log.error("failed closing Statement", se);
+            }
+        }
+    }
+}

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java Mon Nov 21 09:46:10 2005
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed 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.fs.db;
+
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.log4j.Logger;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+/**
+ * <code>DerbyFileSystem</code> is a JDBC-based <code>FileSystem</code>
+ * implementation for Jackrabbit that persists file system entries in an
+ * embedded Derby database.
+ * <p/>
+ * It is configured through the following properties:
+ * <ul>
+ * <li><code>url</code>: the database url of the form
+ * <code>"jdbc:derby:[db];[attributes]"</code></li>
+ * <li><code>schemaObjectPrefix</code>: prefix to be prepended to schema objects</li>
+ * <li><code>driver</code>: the FQN name of the JDBC driver class
+ * (default: <code>"org.apache.derby.jdbc.EmbeddedDriver"</code>)</li>
+ * <li><code>schema</code>: type of schema to be used
+ * (default: <code>"derby"</code>)</li>
+ * <li><code>user</code>: the database user (default: <code>""</code>)</li>
+ * <li><code>password</code>: the user's password (default: <code>""</code>)</li>
+ * </ul>
+ * See also {@link DbFileSystem}.
+ * <p/>
+ * The following is a fragment from a sample configuration:
+ * <pre>
+ *   &lt;FileSystem class="org.apache.jackrabbit.core.fs.db.DerbyFileSystem"&gt;
+ *       &lt;param name="url" value="jdbc:derby:${rep.home}/db;create=true"/&gt;
+ *       &lt;param name="schemaObjectPrefix" value="rep_"/&gt;
+ *  &lt;/FileSystem&gt;
+ * </pre>
+ */
+public class DerbyFileSystem extends DbFileSystem {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = Logger.getLogger(DerbyFileSystem.class);
+
+    /**
+     * Creates a new <code>DerbyFileSystem</code> instance.
+     */
+    public DerbyFileSystem() {
+        // preset some attributes to reasonable defaults
+        schema = "derby";
+        driver = "org.apache.derby.jdbc.EmbeddedDriver";
+        schemaObjectPrefix = "";
+        user = "";
+        password = "";
+        initialized = false;
+    }
+
+    //-----------------------------------------------< DbFileSystem overrides >
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws FileSystemException {
+        if (!initialized) {
+            throw new IllegalStateException("not initialized");
+        }
+
+        // prepare connection url for issuing shutdown command
+        String url;
+        try {
+            url = con.getMetaData().getURL();
+        } catch (SQLException e) {
+            String msg = "error closing file system";
+            log.error(msg, e);
+            throw new FileSystemException(msg, e);
+        }
+
+        int pos = url.lastIndexOf(';');
+        if (pos != -1) {
+            // strip any attributes from connection url
+            url = url.substring(0, pos);
+        }
+        url += ";shutdown=true";
+
+        // call base class implementation
+        super.close();
+
+        // now it's safe to shutdown the embedded Derby database
+        try {
+            DriverManager.getConnection(url);
+        } catch (SQLException e) {
+            // a shutdown command always raises a SQLException
+            log.info(e.getMessage());
+        }
+    }
+}

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/daffodil.ddl
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/daffodil.ddl?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/daffodil.ddl (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/daffodil.ddl Mon Nov 21 09:46:10 2005
@@ -0,0 +1,2 @@
+create table ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH varchar(2048) not null, FSENTRY_NAME varchar(255) not null, FSENTRY_DATA blob, FSENTRY_LASTMOD bigint not null, FSENTRY_LENGTH bigint not null)
+create index ${schemaObjectPrefix}FSENTRY_IDX on ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH, FSENTRY_NAME)

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/default.ddl
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/default.ddl?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/default.ddl (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/default.ddl Mon Nov 21 09:46:10 2005
@@ -0,0 +1,2 @@
+create table ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH varchar not null, FSENTRY_NAME varchar not null, FSENTRY_DATA varbinary, FSENTRY_LASTMOD bigint not null, FSENTRY_LENGTH bigint not null)
+create unique index ${schemaObjectPrefix}FSENTRY_IDX on ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH, FSENTRY_NAME)

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/derby.ddl
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/derby.ddl?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/derby.ddl (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/derby.ddl Mon Nov 21 09:46:10 2005
@@ -0,0 +1,2 @@
+create table ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH varchar(2048) not null, FSENTRY_NAME varchar(255) not null, FSENTRY_DATA blob(100M), FSENTRY_LASTMOD bigint not null, FSENTRY_LENGTH bigint not null)
+create unique index ${schemaObjectPrefix}FSENTRY_IDX on ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH, FSENTRY_NAME)

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mssql.ddl
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mssql.ddl?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mssql.ddl (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mssql.ddl Mon Nov 21 09:46:10 2005
@@ -0,0 +1,2 @@
+create table ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH varchar(2048) not null, FSENTRY_NAME varchar(255) not null, FSENTRY_DATA image null, FSENTRY_LASTMOD bigint not null, FSENTRY_LENGTH bigint not null)
+create unique index ${schemaObjectPrefix}FSENTRY_IDX on ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH, FSENTRY_NAME)

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mysql.ddl
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mysql.ddl?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mysql.ddl (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/mysql.ddl Mon Nov 21 09:46:10 2005
@@ -0,0 +1,2 @@
+create table ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH text not null, FSENTRY_NAME varchar(255) not null, FSENTRY_DATA longblob, FSENTRY_LASTMOD bigint not null, FSENTRY_LENGTH bigint not null)
+create unique index ${schemaObjectPrefix}FSENTRY_IDX on ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH, FSENTRY_NAME)

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/package.html
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/package.html?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/package.html (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/package.html Mon Nov 21 09:46:10 2005
@@ -0,0 +1,17 @@
+<body>
+This package contains the class <code>{@link DbFileSystem}</code>,
+a simple generic JDBC-based <code>FileSystem</code> implementation
+for Jackrabbit.
+<p/>
+It also contains [schemaName].ddl files which are read by
+<code>{@link DbFileSystem}</code>  in order to automatically
+create the required schema objects on the target database. Every line in a
+[schemaName].ddl file is executed separatly by calling
+<code>java.sql.Statement.execute(String)</code> where every occurence of the
+the string <code>"${schemaObjectPrefix}"</code> has been replaced with the
+value of the property <code>schemaObjectPrefix</code> (see
+ <code>{@link DbFileSystem#setSchemaObjectPrefix(String)}</code>).
+The schema name is either set programmtically by calling
+<code>{@link DbFileSystem#setSchema(String)}</code> or configured
+through the <code>schema</code> bean property.
+</body>

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/package.html
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/postgresql.ddl
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/postgresql.ddl?rev=345937&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/postgresql.ddl (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/fs/db/postgresql.ddl Mon Nov 21 09:46:10 2005
@@ -0,0 +1,2 @@
+create table ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH varchar not null, FSENTRY_NAME varchar not null, FSENTRY_DATA bytea, FSENTRY_LASTMOD bigint not null, FSENTRY_LENGTH bigint not null)
+create unique index ${schemaObjectPrefix}FSENTRY_IDX on ${schemaObjectPrefix}FSENTRY (FSENTRY_PATH, FSENTRY_NAME)

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/DerbyPersistenceManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/DerbyPersistenceManager.java?rev=345937&r1=345936&r2=345937&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/DerbyPersistenceManager.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/DerbyPersistenceManager.java Mon Nov 21 09:46:10 2005
@@ -90,7 +90,7 @@
         String url = con.getMetaData().getURL();
         int pos = url.lastIndexOf(';');
         if (pos != -1) {
-            // strip amy attributes from connection url
+            // strip any attributes from connection url
             url = url.substring(0, pos);
         }
         url += ";shutdown=true";

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/SimpleDbPersistenceManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/SimpleDbPersistenceManager.java?rev=345937&r1=345936&r2=345937&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/SimpleDbPersistenceManager.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/db/SimpleDbPersistenceManager.java Mon Nov 21 09:46:10 2005
@@ -46,6 +46,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.FilterInputStream;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
@@ -1000,49 +1001,17 @@
                     if (!rs.next()) {
                         throw new Exception("no such BLOB: " + blobId);
                     }
-                    final InputStream in = rs.getBinaryStream(1);
+                    InputStream in = rs.getBinaryStream(1);
 
                     /**
                      * return an InputStream wrapper in order to
                      * close the ResultSet when the stream is closed
                      */
-                    return new InputStream() {
-                        public int read() throws IOException {
-                            return in.read();
-                        }
-
+                    return new FilterInputStream(in) {
                         public void close() throws IOException {
                             in.close();
                             // close ResultSet
                             closeResultSet(rs);
-                        }
-
-                        public int available() throws IOException {
-                            return in.available();
-                        }
-
-                        public void mark(int readlimit) {
-                            in.mark(readlimit);
-                        }
-
-                        public boolean markSupported() {
-                            return in.markSupported();
-                        }
-
-                        public int read(byte b[]) throws IOException {
-                            return in.read(b);
-                        }
-
-                        public int read(byte b[], int off, int len) throws IOException {
-                            return in.read(b, off, len);
-                        }
-
-                        public void reset() throws IOException {
-                            in.reset();
-                        }
-
-                        public long skip(long n) throws IOException {
-                            return in.skip(n);
                         }
                     };
                 } finally {

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueHelper.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueHelper.java?rev=345937&r1=345936&r2=345937&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueHelper.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/value/ValueHelper.java Mon Nov 21 09:46:10 2005
@@ -29,7 +29,6 @@
 import javax.jcr.Value;
 import javax.jcr.ValueFormatException;
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
 import java.io.StringWriter;
@@ -37,6 +36,8 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.FilterInputStream;
 
 /**
  * The <code>ValueHelper</code> class provides several <code>Value</code>
@@ -564,49 +565,25 @@
             // create an InputStream that keeps a hard reference to the temp file
             // in order to prevent its automatic deletion once the associated
             // File object is reclaimed by the garbage collector;
-            // pass InputStream to BinaryValue constructor
-            return new BinaryValue(new InputStream() {
-                File f = tmpFile;
-                InputStream in = new FileInputStream(f);
+            // pass InputStream wrapper to BinaryValue constructor
+            return new BinaryValue(new FilterInputStream(new FileInputStream(tmpFile)) {
 
-                public int available() throws IOException {
-                    return in.available();
-                }
+                File f = tmpFile;
 
                 public void close() throws IOException {
                     in.close();
-                    // now it's safe to prepare temp file to be gc'ed
+                    // temp file can now safely be removed
+                    f.delete();
                     f = null;
                 }
-
-                public synchronized void mark(int readlimit) {
-                    in.mark(readlimit);
-                }
-
-                public boolean markSupported() {
-                    return in.markSupported();
-                }
-
-                public int read(byte b[]) throws IOException {
-                    return in.read(b);
-                }
-
-                public int read(byte b[], int off, int len) throws IOException {
-                    return in.read(b, off, len);
-                }
-
-                public synchronized void reset() throws IOException {
-                    in.reset();
-                }
-
-                public long skip(long n) throws IOException {
-                    return in.skip(n);
-                }
-
-                public int read() throws IOException {
-                    return in.read();
-                }
             });
+/*
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            Base64.decode(reader, baos);
+            // no need to close ByteArrayOutputStream
+            //baos.close();
+            return new BinaryValue(baos.toByteArray());
+*/
         } else {
             char[] chunk = new char[8192];
             int read;