You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2014/02/05 10:27:23 UTC

svn commit: r1564687 [5/6] - in /jackrabbit/trunk: ./ jackrabbit-aws-ext/ jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/ jackrabbit-aws-ext/src/test/java/org/apache/jac...

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,39 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Extends the regular <code>java.io.OutputStream</code> with a random
+ * access facility. Multiple <code>write()</code> operations can be
+ * positioned off sequence with the {@link #seek} method.
+ *
+ * @deprecated this class should no longer be used
+ */
+public abstract class RandomAccessOutputStream extends OutputStream {
+
+    /**
+     * Sets the current position in the resource where the next write
+     * will occur.
+     *
+     * @param position the new position in the resource.
+     * @throws IOException if an error occurs while seeking to the position.
+     */
+    public abstract void seek(long position) throws IOException;
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,99 @@
+/*
+ * 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.fs.local;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * Static utility methods for recursively copying and deleting files and
+ * directories.
+ */
+public final class FileUtil {
+
+    /**
+     * private constructor
+     */
+    private FileUtil() {
+    }
+
+    /**
+     * Recursively copies the given file or directory to the
+     * given destination.
+     *
+     * @param src source file or directory
+     * @param dest destination file or directory
+     * @throws IOException if the file or directory cannot be copied
+     */
+    public static void copy(File src, File dest) throws IOException {
+        if (!src.canRead()) {
+            throw new IOException(src.getPath() + " can't be read from.");
+        }
+        if (src.isDirectory()) {
+            // src is a folder
+            if (dest.isFile()) {
+                throw new IOException("can't copy a folder to a file");
+            }
+            if (!dest.exists()) {
+                dest.mkdirs();
+            }
+            if (!dest.canWrite()) {
+                throw new IOException("can't write to " + dest.getPath());
+            }
+            File[] children = src.listFiles();
+            for (int i = 0; i < children.length; i++) {
+                copy(children[i], new File(dest, children[i].getName()));
+            }
+        } else {
+            // src is a file
+            File destParent;
+            if (dest.isDirectory()) {
+                // dest is a folder
+                destParent = dest;
+                dest = new File(destParent, src.getName());
+            } else {
+                destParent = dest.getParentFile();
+            }
+            if (!destParent.canWrite()) {
+                throw new IOException("can't write to " + destParent.getPath());
+            }
+
+            FileUtils.copyFile(src, dest);
+        }
+    }
+
+    /**
+     * Recursively deletes the given file or directory.
+     *
+     * @param f file or directory
+     * @throws IOException if the file or directory cannot be deleted
+     */
+    public static void delete(File f) throws IOException {
+        if (f.isDirectory()) {
+            // it's a folder, list children first
+            File[] children = f.listFiles();
+            for (int i = 0; i < children.length; i++) {
+                delete(children[i]);
+            }
+        }
+        if (!f.delete()) {
+            throw new IOException("Unable to delete " + f.getPath());
+        }
+    }
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,217 @@
+/*
+ * 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.fs.local;
+
+import org.apache.jackrabbit.util.LazyFileInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This Class implements a very simple open handle monitor for the local
+ * file system. This is usefull, if the list of open handles, referenced by
+ * an open FileInputStream() should be tracked. This can cause problems on
+ * windows filesystems where open files cannot be deleted.
+ */
+public class HandleMonitor {
+
+    /**
+     * The default logger
+     */
+    private static Logger log = LoggerFactory.getLogger(HandleMonitor.class);
+
+    /**
+     * the map of open handles (key=File, value=Handle)
+     */
+    private HashMap<File, Handle> openHandles = new HashMap<File, Handle>();
+
+    /**
+     * Opens a file and returns an InputStream
+     *
+     * @param file
+     * @return
+     * @throws FileNotFoundException
+     */
+    public InputStream open(File file) throws FileNotFoundException {
+        Handle handle = getHandle(file);
+        InputStream in = handle.open();
+        return in;
+    }
+
+    /**
+     * Checks, if the file is open
+     * @param file
+     * @return
+     */
+    public boolean isOpen(File file) {
+        return openHandles.containsKey(file);
+    }
+
+    /**
+     * Closes a file
+     * @param file
+     */
+    private void close(File file) {
+        openHandles.remove(file);
+    }
+
+    /**
+     * Returns the handle for a file.
+     * @param file
+     * @return
+     */
+    private Handle getHandle(File file) {
+        Handle handle = openHandles.get(file);
+        if (handle == null) {
+            handle = new Handle(file);
+            openHandles.put(file, handle);
+        }
+        return handle;
+    }
+
+    /**
+     * Dumps the contents of this monitor
+     */
+    public void dump() {
+        log.info("Number of open files: " + openHandles.size());
+        for (File file : openHandles.keySet()) {
+            Handle handle = openHandles.get(file);
+            handle.dump();
+        }
+    }
+
+    /**
+     * Dumps the information for a file
+     * @param file
+     */
+    public void dump(File file) {
+        Handle handle = openHandles.get(file);
+        if (handle != null) {
+            handle.dump(true);
+        }
+    }
+
+    /**
+     * Class representing all open handles to a file
+     */
+    private class Handle {
+
+        /**
+         * the file of this handle
+         */
+        private File file;
+
+        /**
+         * all open streams of this handle
+         */
+        private HashSet<Handle.MonitoredInputStream> streams = new HashSet<Handle.MonitoredInputStream>();
+
+        /**
+         * Creates a new handle for a file
+         * @param file
+         */
+        private Handle(File file) {
+            this.file = file;
+        }
+
+        /**
+         * opens a stream for this handle
+         * @return
+         * @throws FileNotFoundException
+         */
+        private InputStream open() throws FileNotFoundException {
+            Handle.MonitoredInputStream in = new Handle.MonitoredInputStream(file);
+            streams.add(in);
+            return in;
+        }
+
+        /**
+         * Closes a stream
+         * @param in
+         */
+        private void close(MonitoredInputStream in) {
+            streams.remove(in);
+            if (streams.isEmpty()) {
+                HandleMonitor.this.close(file);
+            }
+        }
+
+        /**
+         * Dumps this handle
+         */
+        private void dump() {
+            dump(false);
+        }
+
+        /**
+         * Dumps this handle
+         */
+        private void dump(boolean detailed) {
+            if (detailed) {
+                log.info("- " + file.getPath() + ", " + streams.size());
+                for (Handle.MonitoredInputStream in : streams) {
+                    in.dump();
+                }
+            } else {
+                log.info("- " + file.getPath() + ", " + streams.size());
+            }
+        }
+
+        /**
+         * Delegating input stream that registers/unregisters itself from the
+         * handle.
+         */
+        private class MonitoredInputStream extends LazyFileInputStream {
+
+            /**
+             * throwable of the time, the stream was created
+             */
+            private final Throwable throwable = new Exception();
+
+            /**
+             * {@inheritDoc}
+             */
+            private MonitoredInputStream(File file) throws FileNotFoundException {
+                super(file);
+            }
+
+            /**
+             * dumps this stream
+             */
+            private void dump() {
+                log.info("- opened by : ", throwable);
+            }
+
+            /**
+             * {@inheritDoc}
+             */
+            public void close() throws IOException {
+                // remove myself from the set
+                Handle.this.close(this);
+                super.close();
+            }
+
+        }
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,388 @@
+/*
+ * 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.fs.local;
+
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.local.FileUtil;
+import org.apache.jackrabbit.core.fs.local.HandleMonitor;
+import org.apache.jackrabbit.util.LazyFileInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A <code>LocalFileSystem</code> ...
+ */
+public class LocalFileSystem implements FileSystem {
+
+    private static Logger log = LoggerFactory.getLogger(LocalFileSystem.class);
+
+    private File root;
+
+    private HandleMonitor monitor;
+
+    /**
+     * Default constructor
+     */
+    public LocalFileSystem() {
+    }
+
+    public String getPath() {
+        if (root != null) {
+            return root.getPath();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the path to the root directory of this local filesystem. please note
+     * that this method can be called via reflection during initialization and
+     * must not be altered.
+     *
+     * @param rootPath the path to the root directory
+     */
+    public void setPath(String rootPath) {
+        setRoot(new File(osPath(rootPath)));
+    }
+
+    public void setRoot(File root) {
+        this.root = root;
+    }
+
+    /**
+     * Enables/Disables the use of the handle monitor.
+     *
+     * @param enable
+     */
+    public void setEnableHandleMonitor(String enable) {
+        setEnableHandleMonitor(Boolean.valueOf(enable).booleanValue());
+    }
+
+    /**
+     * Enables/Disables the use of the handle monitor.
+     *
+     * @param enable flag
+     */
+    public void setEnableHandleMonitor(boolean enable) {
+        if (enable && monitor == null) {
+            monitor = new HandleMonitor();
+        }
+        if (!enable && monitor != null) {
+            monitor = null;
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if use of the handle monitor is currently
+     * enabled, otherwise returns <code>false</code>.
+     *
+     * @see #setEnableHandleMonitor(boolean)
+     */
+    public String getEnableHandleMonitor() {
+        return monitor == null ? "false" : "true";
+    }
+
+    private String osPath(String genericPath) {
+        if (File.separator.equals(SEPARATOR)) {
+            return genericPath;
+        }
+        return genericPath.replace(SEPARATOR_CHAR, File.separatorChar);
+    }
+
+    //-------------------------------------------< java.lang.Object overrides >
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof LocalFileSystem) {
+            LocalFileSystem other = (LocalFileSystem) obj;
+            if (root == null) {
+                return other.root == null;
+            } else {
+                return root.equals(other.root);
+            }
+        }
+        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 (root == null) {
+            String msg = "root directory not set";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+
+        if (root.exists()) {
+            if (!root.isDirectory()) {
+                String msg = "path does not denote a folder";
+                log.debug(msg);
+                throw new FileSystemException(msg);
+            }
+        } else {
+            if (!root.mkdirs()) {
+                String msg = "failed to create root";
+                log.debug(msg);
+                throw new FileSystemException(msg);
+            }
+        }
+        log.info("LocalFileSystem initialized at path " + root.getPath());
+        if (monitor != null) {
+            log.info("LocalFileSystem using handle monitor");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws FileSystemException {
+        root = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void createFolder(String folderPath) throws FileSystemException {
+        File f = new File(root, osPath(folderPath));
+        if (f.exists()) {
+            String msg = f.getPath() + " already exists";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        if (!f.mkdirs()) {
+            String msg = "failed to create folder " + f.getPath();
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFile(String filePath) throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        if (!f.isFile()) {
+            String msg = f.getPath() + " does not denote an existing file";
+            throw new FileSystemException(msg);
+        }
+        try {
+            FileUtil.delete(f);
+        } catch (IOException ioe) {
+            String msg = "failed to delete " + f.getPath();
+            if (monitor != null && monitor.isOpen(f)) {
+                log.error("Unable to delete. There are still open streams.");
+                monitor.dump(f);
+            }
+
+            throw new FileSystemException(msg, ioe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFolder(String folderPath) throws FileSystemException {
+        File f = new File(root, osPath(folderPath));
+        if (!f.isDirectory()) {
+            String msg = f.getPath() + " does not denote an existing folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        try {
+            FileUtil.delete(f);
+        } catch (IOException ioe) {
+            String msg = "failed to delete " + f.getPath();
+            log.debug(msg);
+            throw new FileSystemException(msg, ioe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.exists();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getInputStream(String filePath)
+            throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        try {
+            if (monitor == null) {
+                return new LazyFileInputStream(f);
+            } else {
+                return monitor.open(f);
+            }
+        } catch (FileNotFoundException fnfe) {
+            String msg = f.getPath() + " does not denote an existing file";
+            log.debug(msg);
+            throw new FileSystemException(msg, fnfe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public OutputStream getOutputStream(String filePath)
+            throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        try {
+            return new FileOutputStream(f);
+        } catch (FileNotFoundException fnfe) {
+            String msg = "failed to get output stream for " + f.getPath();
+            log.debug(msg);
+            throw new FileSystemException(msg, fnfe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasChildren(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        if (!f.exists()) {
+            String msg = f.getPath() + " does not exist";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        if (f.isFile()) {
+            return false;
+        }
+        return (f.list().length > 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFile(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.isFile();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFolder(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.isDirectory();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long lastModified(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.lastModified();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long length(String filePath) throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        if (!f.exists()) {
+            return -1;
+        }
+        return f.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] list(String folderPath) throws FileSystemException {
+        File f = new File(root, osPath(folderPath));
+        String[] entries = f.list();
+        if (entries == null) {
+            String msg = folderPath + " does not denote a folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        return entries;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFiles(String folderPath) throws FileSystemException {
+        File folder = new File(root, osPath(folderPath));
+        File[] files = folder.listFiles(new FileFilter() {
+            public boolean accept(File f) {
+                return f.isFile();
+            }
+        });
+        if (files == null) {
+            String msg = folderPath + " does not denote a folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        String[] entries = new String[files.length];
+        for (int i = 0; i < files.length; i++) {
+            entries[i] = files[i].getName();
+        }
+        return entries;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFolders(String folderPath) throws FileSystemException {
+        File file = new File(root, osPath(folderPath));
+        File[] folders = file.listFiles(new FileFilter() {
+            public boolean accept(File f) {
+                return f.isDirectory();
+            }
+        });
+        if (folders == null) {
+            String msg = folderPath + " does not denote a folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        String[] entries = new String[folders.length];
+        for (int i = 0; i < folders.length; i++) {
+            entries[i] = folders[i].getName();
+        }
+        return entries;
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,113 @@
+/*
+ * 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.util.db;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.util.Text;
+
+/**
+ * An operation which synchronously checks the DB schema in the {@link #run()} method. The
+ * {@link #addVariableReplacement(String, String)} method return the instance to enable method chaining.
+ */
+public class CheckSchemaOperation {
+
+    public static final String SCHEMA_OBJECT_PREFIX_VARIABLE = "${schemaObjectPrefix}";
+
+    public static final String TABLE_SPACE_VARIABLE = "${tableSpace}";
+
+    private final ConnectionHelper conHelper;
+
+    private final InputStream ddl;
+
+    private final String table;
+
+    private final Map<String, String> varReplacement = new HashMap<String, String>();
+
+    /**
+     * @param connectionhelper the connection helper
+     * @param ddlStream the stream of the DDL to use to create the schema if necessary (closed by the
+     *            {@link #run()} method)
+     * @param tableName the name of the table to use for the schema-existence-check
+     */
+    public CheckSchemaOperation(ConnectionHelper connectionhelper, InputStream ddlStream, String tableName) {
+        conHelper = connectionhelper;
+        ddl = ddlStream;
+        table = tableName;
+    }
+
+    /**
+     * Adds a variable replacement mapping.
+     * 
+     * @param var the variable
+     * @param replacement the replacement value
+     * @return this
+     */
+    public CheckSchemaOperation addVariableReplacement(String var, String replacement) {
+        varReplacement.put(var, replacement);
+        return this;
+    }
+
+    /**
+     * Checks if the required schema objects exist and creates them if they don't exist yet.
+     * 
+     * @throws SQLException if an error occurs
+     * @throws IOException if an error occurs
+     */
+    public void run() throws SQLException, IOException {
+        try {
+            if (!conHelper.tableExists(table)) {
+                BufferedReader reader = new BufferedReader(new InputStreamReader(ddl));
+                String sql = reader.readLine();
+                while (sql != null) {
+                    // Skip comments and empty lines
+                    if (!sql.startsWith("#") && sql.length() > 0) {
+                        // replace prefix variable
+                        sql = replace(sql);
+                        // execute sql stmt
+                        conHelper.exec(sql);
+                    }
+                    // read next sql stmt
+                    sql = reader.readLine();
+                }
+            }
+        } finally {
+            IOUtils.closeQuietly(ddl);
+        }
+    }
+
+    /**
+     * Applies the variable replacement to the given string.
+     * 
+     * @param sql the string in which to replace variables
+     * @return the new string
+     */
+    private String replace(String sql) {
+        String result = sql;
+        for (Map.Entry<String, String> entry : varReplacement.entrySet()) {
+            result = Text.replace(result, entry.getKey(), entry.getValue()).trim();
+        }
+        return result;
+    }
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,377 @@
+/*
+ * 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.util.db;
+
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbcp.DelegatingConnection;
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.jackrabbit.core.config.DataSourceConfig;
+import org.apache.jackrabbit.core.config.DataSourceConfig.DataSourceDefinition;
+import org.apache.jackrabbit.core.util.db.DataSourceWrapper;
+import org.apache.jackrabbit.util.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for new database connections.
+ * Supported are regular JDBC drivers, as well as
+ * JNDI resources.
+ *
+ * FIXME: the registry currently is ClassLoader wide. I.e., if you start two repositories
+ * then you share the registered datasources...
+ */
+public final class ConnectionFactory {
+
+    private static final Logger log = LoggerFactory.getLogger(ConnectionFactory.class);
+
+    /**
+     * The lock to protect the fields of this class.
+     */
+    private final Object lock = new Object();
+
+    /**
+     * The data sources without logical name. The keys in the map are based on driver-url-user combination.
+     */
+    private final Map<String, DataSource> keyToDataSource = new HashMap<String, DataSource>();
+
+    /**
+     * The configured data sources with logical name. The keys in the map are the logical name.
+     */
+    private final Map<String, DataSource> nameToDataSource = new HashMap<String, DataSource>();
+
+    /**
+     * The configured data source defs. The keys in the map are the logical name.
+     */
+    private final Map<String, DataSourceDefinition> nameToDataSourceDef = new HashMap<String, DataSourceDefinition>();
+
+    /**
+     * The list of data sources created by this factory.
+     */
+    private final List<BasicDataSource> created = new ArrayList<BasicDataSource>();
+
+    private boolean closed = false;
+
+    /**
+     * Registers a number of data sources.
+     *
+     * @param dsc the {@link DataSourceConfig} which contains the configuration
+     */
+    public void registerDataSources(DataSourceConfig dsc) throws RepositoryException {
+        synchronized (lock) {
+            sanityCheck();
+            for (DataSourceDefinition def : dsc.getDefinitions()) {
+                Class<?> driverClass = getDriverClass(def.getDriver());
+                if (driverClass != null
+                        && Context.class.isAssignableFrom(driverClass)) {
+                    DataSource ds = getJndiDataSource((Class<Context>) driverClass, def.getUrl());
+                    nameToDataSource.put(def.getLogicalName(), ds);
+                    nameToDataSourceDef.put(def.getLogicalName(), def);
+                } else {
+                    BasicDataSource bds =
+                        getDriverDataSource(driverClass, def.getUrl(), def.getUser(), def.getPassword());
+                    if (def.getMaxPoolSize() > 0) {
+                        bds.setMaxActive(def.getMaxPoolSize());
+                    }
+                    if (def.getValidationQuery() != null && !"".equals(def.getValidationQuery().trim())) {
+                        bds.setValidationQuery(def.getValidationQuery());
+                    }
+                    nameToDataSource.put(def.getLogicalName(), bds);
+                    nameToDataSourceDef.put(def.getLogicalName(), def);
+                }
+            }
+        }
+    }
+
+    /**
+     * Retrieves a configured data source by logical name.
+     *
+     * @param logicalName the name of the {@code DataSource}
+     * @return a {@code DataSource}
+     * @throws RepositoryException if there is no {@code DataSource} with the given name
+     */
+    public DataSource getDataSource(String logicalName) throws RepositoryException {
+        synchronized (lock) {
+            sanityCheck();
+            DataSource ds = nameToDataSource.get(logicalName);
+            if (ds == null) {
+                throw new RepositoryException("DataSource with logicalName " + logicalName
+                        + " has not been configured");
+            }
+            return ds;
+        }
+    }
+
+    /**
+     * @param logicalName the name of the {@code DataSource}
+     * @return the configured database type
+     * @throws RepositoryException if there is no {@code DataSource} with the given name
+     */
+    public String getDataBaseType(String logicalName) throws RepositoryException {
+        synchronized (lock) {
+            sanityCheck();
+            DataSourceDefinition def = nameToDataSourceDef.get(logicalName);
+            if (def == null) {
+                throw new RepositoryException("DataSource with logicalName " + logicalName
+                        + " has not been configured");
+            }
+            return def.getDbType();
+        }
+    }
+
+    /**
+     * Retrieve a {@code DataSource} for the specified properties.
+     * This can be a JNDI Data Source as well. To do that,
+     * the driver class name must reference a {@code javax.naming.Context} class
+     * (for example {@code javax.naming.InitialContext}), and the URL must be the JNDI URL
+     * (for example {@code java:comp/env/jdbc/Test}).
+     *
+     * @param driver the JDBC driver or the Context class
+     * @param url the database URL
+     * @param user the user name
+     * @param password the password
+     * @return the {@code DataSource}
+     * @throws RepositoryException if the driver could not be loaded
+     * @throws SQLException if the connection could not be established
+     */
+    public DataSource getDataSource(String driver, String url, String user, String password)
+            throws RepositoryException, SQLException    {
+        final String key = driver + url + user;
+        synchronized(lock) {
+            sanityCheck();
+            DataSource ds = keyToDataSource.get(key);
+            if (ds == null) {
+                ds = createDataSource(
+                        driver, url, user, Base64.decodeIfEncoded(password));
+                keyToDataSource.put(key, ds);
+            }
+            return ds;
+        }
+    }
+
+    /**
+     *
+     */
+    public void close() {
+        synchronized(lock) {
+            sanityCheck();
+            for (BasicDataSource ds : created) {
+                try {
+                    ds.close();
+                } catch (SQLException e) {
+                    log.error("failed to close " + ds, e);
+                }
+            }
+            keyToDataSource.clear();
+            nameToDataSource.clear();
+            nameToDataSourceDef.clear();
+            created.clear();
+            closed = true;
+        }
+    }
+
+    /**
+     * Needed for pre-10R2 Oracle blob support....:(
+     *
+     * This method actually assumes that we are using commons DBCP 1.2.2.
+     *
+     * @param con the commons-DBCP {@code DelegatingConnection} to unwrap
+     * @return the unwrapped connection
+     */
+    public static Connection unwrap(Connection con) throws SQLException {
+        if (con instanceof DelegatingConnection) {
+            return ((DelegatingConnection)con).getInnermostDelegate();
+        } else {
+            throw new SQLException("failed to unwrap connection of class " + con.getClass().getName() +
+                ", expected it to be a " + DelegatingConnection.class.getName());
+        }
+    }
+
+    private void sanityCheck() {
+        if (closed) {
+            throw new IllegalStateException("this factory has already been closed");
+        }
+    }
+
+    /**
+     * Create a new pooling data source or finds an existing JNDI data source (depends on driver).
+     *
+     * @param driver
+     * @param url
+     * @param user
+     * @param password
+     * @return
+     * @throws RepositoryException
+     */
+    private DataSource createDataSource(String driver, String url, String user, String password)
+            throws RepositoryException {
+        Class<?> driverClass = getDriverClass(driver);
+        if (driverClass != null
+                && Context.class.isAssignableFrom(driverClass)) {
+            @SuppressWarnings("unchecked")
+            DataSource database = getJndiDataSource((Class<Context>) driverClass, url);
+            if (user == null && password == null) {
+                return database;
+            } else {
+                return new DataSourceWrapper(database, user, password);
+            }
+        } else {
+            return getDriverDataSource(driverClass, url, user, password);
+        }
+    }
+
+    /**
+     * Loads and returns the given JDBC driver (or JNDI context) class.
+     * Returns <code>null</code> if a class name is not given.
+     *
+     * @param driver driver class name
+     * @return driver class, or <code>null</code>
+     * @throws RepositoryException if the class can not be loaded
+     */
+    private Class<?> getDriverClass(String driver)
+            throws RepositoryException {
+        try {
+            if (driver != null && driver.length() > 0) {
+                return Class.forName(driver);
+            } else {
+                return null;
+            }
+        } catch (ClassNotFoundException e) {
+            throw new RepositoryException(
+                    "Could not load JDBC driver class " + driver, e);
+        }
+    }
+
+    /**
+     * Returns the JDBC {@link DataSource} bound to the given name in
+     * the JNDI {@link Context} identified by the given class.
+     *
+     * @param contextClass class that is instantiated to get the JNDI context
+     * @param name name of the DataSource within the JNDI context
+     * @return the DataSource bound in JNDI
+     * @throws RepositoryException if the JNDI context can not be accessed,
+     *                             or if the named DataSource is not found
+     */
+    private DataSource getJndiDataSource(
+            Class<Context> contextClass, String name)
+            throws RepositoryException {
+        try {
+            Object object = contextClass.newInstance().lookup(name);
+            if (object instanceof DataSource) {
+                return (DataSource) object;
+            } else {
+                throw new RepositoryException(
+                        "Object " + object + " with JNDI name "
+                        + name + " is not a JDBC DataSource");
+            }
+        } catch (InstantiationException e) {
+            throw new RepositoryException(
+                    "Invalid JNDI context: " + contextClass.getName(), e);
+        } catch (IllegalAccessException e) {
+            throw new RepositoryException(
+                    "Invalid JNDI context: " + contextClass.getName(), e);
+        } catch (NamingException e) {
+            throw new RepositoryException(
+                    "JNDI name not found: " + name, e);
+        }
+    }
+
+    /**
+     * Creates and returns a pooling JDBC {@link DataSource} for accessing
+     * the database identified by the given driver class and JDBC
+     * connection URL. The driver class can be <code>null</code> if
+     * a specific driver has not been configured.
+     *
+     * @param driverClass the JDBC driver class, or <code>null</code>
+     * @param url the JDBC connection URL
+     * @return pooling DataSource for accessing the specified database
+     */
+    private BasicDataSource getDriverDataSource(
+            Class<?> driverClass, String url, String user, String password) {
+        BasicDataSource ds = new BasicDataSource();
+        created.add(ds);
+
+        if (driverClass != null) {
+        	Driver instance = null;
+            try {
+                // Workaround for Apache Derby:
+                // The JDBC specification recommends the Class.forName
+                // method without the .newInstance() method call,
+                // but it is required after a Derby 'shutdown'
+                instance = (Driver) driverClass.newInstance();
+            } catch (Throwable e) {
+                // Ignore exceptions as there's no requirement for
+                // a JDBC driver class to have a public default constructor
+            }
+            if (instance != null) {
+                if (instance.jdbcCompliant()) {
+                	// JCR-3445 At the moment the PostgreSQL isn't compliant because it doesn't implement this method...                	
+                    ds.setValidationQueryTimeout(3);
+                }
+            }
+            ds.setDriverClassName(driverClass.getName());
+        }
+
+        ds.setUrl(url);
+        ds.setUsername(user);
+        ds.setPassword(password);
+        ds.setDefaultAutoCommit(true);
+        ds.setTestOnBorrow(false);
+        ds.setTestWhileIdle(true);
+        ds.setTimeBetweenEvictionRunsMillis(600000); // 10 Minutes
+        ds.setMinEvictableIdleTimeMillis(60000); // 1 Minute
+        ds.setMaxActive(-1); // unlimited
+        ds.setMaxIdle(GenericObjectPool.DEFAULT_MAX_IDLE + 10);
+        ds.setValidationQuery(guessValidationQuery(url));
+        ds.setAccessToUnderlyingConnectionAllowed(true);
+        ds.setPoolPreparedStatements(true);
+        ds.setMaxOpenPreparedStatements(-1); // unlimited
+        return ds;
+    }
+
+    private String guessValidationQuery(String url) {
+        if (url.contains("derby")) {
+            return "values(1)";
+        } else if (url.contains("mysql")) {
+            return "select 1";
+        } else if (url.contains("sqlserver") || url.contains("jtds")) {
+            return "select 1";
+        } else if (url.contains("oracle")) {
+            return "select 'validationQuery' from dual";
+        } else if (url.contains("postgresql")) {
+            return "select 1";
+        } else if (url.contains("h2")) {
+            return "select 1";
+        } else if (url.contains("db2")) {
+            return "values(1)";
+        }
+        log.warn("Failed to guess validation query for URL " + url);
+        return null;
+    }
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,600 @@
+/*
+ * 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.util.db;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.jackrabbit.core.util.db.Oracle10R1ConnectionHelper;
+import org.apache.jackrabbit.core.util.db.ResultSetWrapper;
+import org.apache.jackrabbit.data.core.TransactionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class provides convenience methods to execute SQL statements. They can be either executed in isolation
+ * or within the context of a JDBC transaction; the so-called <i>batch mode</i> (use the {@link #startBatch()}
+ * and {@link #endBatch(boolean)} methods for this).
+ *
+ * <p/>
+ *
+ * This class contains logic to retry execution of SQL statements. If this helper is <i>not</i> in batch mode
+ * and if a statement fails due to an {@code SQLException}, then it is retried. If the {@code block} argument
+ * of the constructor call was {@code false} then it is retried only once. Otherwise the statement is retried
+ * until either it succeeds or the thread is interrupted. This clearly assumes that the only cause of {@code
+ * SQLExceptions} is faulty {@code Connections} which are restored eventually. <br/> <strong>Note</strong>:
+ * This retry logic only applies to the following methods:
+ * <ul>
+ * <li>{@link #exec(String, Object...)}</li>
+ * <li>{@link #update(String, Object[])}</li>
+ * <li>{@link #exec(String, Object[], boolean, int)}</li>
+ * </ul>
+ *
+ * <p/>
+ *
+ * This class is not thread-safe and if it is to be used by multiple threads then the clients must make sure
+ * that access to this class is properly synchronized.
+ *
+ * <p/>
+ *
+ * <strong>Implementation note</strong>: The {@code Connection} that is retrieved from the {@code DataSource}
+ * in {@link #getConnection()} may be broken. This is so because if an internal {@code DataSource} is used,
+ * then this is a commons-dbcp {@code DataSource} with a <code>testWhileIdle</code> validation strategy (see
+ * the {@code ConnectionFactory} class). Furthermore, if it is a {@code DataSource} obtained through JNDI then we
+ * can make no assumptions about the validation strategy. This means that our retry logic must either assume that
+ * the SQL it tries to execute can do so without errors (i.e., the statement is valid), or it must implement its
+ * own validation strategy to apply. Currently, the former is in place.
+ */
+public class ConnectionHelper {
+
+    static Logger log = LoggerFactory.getLogger(ConnectionHelper.class);
+
+    private static final int RETRIES = 1;
+
+    private static final int SLEEP_BETWEEN_RETRIES_MS = 100;
+
+    final boolean blockOnConnectionLoss;
+
+    private final boolean checkTablesWithUserName;
+
+    protected final DataSource dataSource;
+
+    private Map<Object, Connection> batchConnectionMap = Collections.synchronizedMap(new HashMap<Object, Connection>());
+
+    /**
+     * The default fetchSize is '0'. This means the fetchSize Hint will be ignored 
+     */
+    private int fetchSize = 0;
+
+    /**
+     * @param dataSrc the {@link DataSource} on which this instance acts
+     * @param block whether the helper should transparently block on DB connection loss (otherwise it retries
+     *            once and if that fails throws exception)
+     */
+    public ConnectionHelper(DataSource dataSrc, boolean block) {
+        dataSource = dataSrc;
+        checkTablesWithUserName = false;
+        blockOnConnectionLoss = block;
+    }
+
+    /**
+     * @param dataSrc the {@link DataSource} on which this instance acts
+     * @param checkWithUserName whether the username is to be used for the {@link #tableExists(String)} method
+     * @param block whether the helper should transparently block on DB connection loss (otherwise it throws exceptions)
+     */
+    protected ConnectionHelper(DataSource dataSrc, boolean checkWithUserName, boolean block) {
+        dataSource = dataSrc;
+        checkTablesWithUserName = checkWithUserName;
+        blockOnConnectionLoss = block;
+    }
+
+    /**
+     * @param dataSrc the {@link DataSource} on which this instance acts
+     * @param checkWithUserName whether the username is to be used for the {@link #tableExists(String)} method
+     * @param block whether the helper should transparently block on DB connection loss (otherwise it throws exceptions)
+     * @param fetchSize the fetchSize that will be used per default
+     */
+    protected ConnectionHelper(DataSource dataSrc, boolean checkWithUserName, boolean block, int fetchSize) {
+        dataSource = dataSrc;
+        checkTablesWithUserName = checkWithUserName;
+        blockOnConnectionLoss = block;
+        this.fetchSize = fetchSize;
+    }
+
+    /**
+     * A utility method that makes sure that <code>identifier</code> does only consist of characters that are
+     * allowed in names on the target database. Illegal characters will be escaped as necessary.
+     *
+     * This method is not affected by the
+     *
+     * @param identifier the identifier to convert to a db specific identifier
+     * @return the db-normalized form of the given identifier
+     * @throws SQLException if an error occurs
+     */
+    public final String prepareDbIdentifier(String identifier) throws SQLException {
+        if (identifier == null) {
+            return null;
+        }
+        String legalChars = "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
+        legalChars += getExtraNameCharacters();
+        String id = identifier.toUpperCase();
+        StringBuilder escaped = new StringBuilder();
+        for (int i = 0; i < id.length(); i++) {
+            char c = id.charAt(i);
+            if (legalChars.indexOf(c) == -1) {
+                replaceCharacter(escaped, c);
+            } else {
+                escaped.append(c);
+            }
+        }
+        return escaped.toString();
+    }
+
+    /**
+     * Called from {@link #prepareDbIdentifier(String)}. Default implementation replaces the illegal
+     * characters with their hexadecimal encoding.
+     *
+     * @param escaped the escaped db identifier
+     * @param c the character to replace
+     */
+    protected void replaceCharacter(StringBuilder escaped, char c) {
+        escaped.append("_x");
+        String hex = Integer.toHexString(c);
+        escaped.append("0000".toCharArray(), 0, 4 - hex.length());
+        escaped.append(hex);
+        escaped.append("_");
+    }
+
+    /**
+     * Returns true if we are currently in a batch mode, false otherwise.
+     * 
+     * @return true if the current thread or the active transaction is running in batch mode, false otherwise.
+     */
+    protected boolean inBatchMode() {
+    	return getTransactionAwareBatchConnection() != null;
+    }
+
+	/**
+     * The default implementation returns the {@code extraNameCharacters} provided by the databases metadata.
+     *
+     * @return the additional characters for identifiers supported by the db
+     * @throws SQLException on error
+     */
+    private String getExtraNameCharacters() throws SQLException {
+        Connection con = dataSource.getConnection();
+        try {
+            DatabaseMetaData metaData = con.getMetaData();
+            return metaData.getExtraNameCharacters();
+        } finally {
+            DbUtility.close(con, null, null);
+        }
+    }
+
+    /**
+     * Checks whether the given table exists in the database.
+     *
+     * @param tableName the name of the table
+     * @return whether the given table exists
+     * @throws SQLException on error
+     */
+    public final boolean tableExists(String tableName) throws SQLException {
+        Connection con = dataSource.getConnection();
+        ResultSet rs = null;
+        boolean schemaExists = false;
+        String name = tableName;
+        try {
+            DatabaseMetaData metaData = con.getMetaData();
+            if (metaData.storesLowerCaseIdentifiers()) {
+                name = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                name = tableName.toUpperCase();
+            }
+            String userName = null;
+            if (checkTablesWithUserName) {
+                userName = metaData.getUserName();
+            }
+            rs = metaData.getTables(null, userName, name, null);
+            schemaExists = rs.next();
+        } finally {
+            DbUtility.close(con, null, rs);
+        }
+        return schemaExists;
+    }
+
+    /**
+     * Starts the <i>batch mode</i>. If an {@link SQLException} is thrown, then the batch mode is not started. <p/>
+     * <strong>Important:</strong> clients that call this method must make sure that
+     * {@link #endBatch(boolean)} is called eventually.
+     *
+     * @throws SQLException on error
+     */
+    public final void startBatch() throws SQLException {
+        if (inBatchMode()) {
+            throw new SQLException("already in batch mode");
+        }
+        Connection batchConnection = null;
+        try {
+            batchConnection = getConnection(false);
+            batchConnection.setAutoCommit(false);
+            setTransactionAwareBatchConnection(batchConnection);
+        } catch (SQLException e) {
+            removeTransactionAwareBatchConnection();
+            // Strive for failure atomicity
+            if (batchConnection != null) {
+                DbUtility.close(batchConnection, null, null);
+            }
+            throw e;
+        }
+    }
+
+	/**
+     * This method always ends the <i>batch mode</i>.
+     *
+     * @param commit whether the changes in the batch should be committed or rolled back
+     * @throws SQLException if the commit or rollback of the underlying JDBC Connection threw an {@code
+     *             SQLException}
+     */
+    public final void endBatch(boolean commit) throws SQLException {
+        if (!inBatchMode()) {
+            throw new SQLException("not in batch mode");
+        }
+        Connection batchConnection = getTransactionAwareBatchConnection(); 
+        try {
+            if (commit) {
+            	batchConnection.commit();
+            } else {
+            	batchConnection.rollback();
+            }
+        } finally {
+            removeTransactionAwareBatchConnection();
+            if (batchConnection != null) {
+            	DbUtility.close(batchConnection, null, null);
+            }
+        }
+    }
+
+    /**
+     * Executes a general SQL statement and immediately closes all resources.
+     *
+     * Note: We use a Statement if there are no parameters to avoid a problem on
+     * the Oracle 10g JDBC driver w.r.t. :NEW and :OLD keywords that triggers ORA-17041.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @throws SQLException on error
+     */
+    public final void exec(final String sql, final Object... params) throws SQLException {
+        new RetryManager<Void>(params) {
+
+            @Override
+            protected Void call() throws SQLException {
+                reallyExec(sql, params);
+                return null;
+            }
+
+        }.doTry();
+    }
+
+    void reallyExec(String sql, Object... params) throws SQLException {
+        Connection con = null;
+        Statement stmt = null;
+        boolean inBatchMode = inBatchMode();
+        try {
+            con = getConnection(inBatchMode);
+            if (params == null || params.length == 0) {
+                stmt = con.createStatement();
+                stmt.execute(sql);
+            } else {
+                PreparedStatement p = con.prepareStatement(sql);
+                stmt = p;
+                execute(p, params);
+            }
+        } finally {
+            closeResources(con, stmt, null, inBatchMode);
+        }
+    }
+
+    /**
+     * Executes an update or delete statement and returns the update count.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @return the update count
+     * @throws SQLException on error
+     */
+    public final int update(final String sql, final Object... params) throws SQLException {
+        return new RetryManager<Integer>(params) {
+
+            @Override
+            protected Integer call() throws SQLException {
+                return reallyUpdate(sql, params);
+            }
+
+        }.doTry();
+    }
+
+    int reallyUpdate(String sql, Object... params) throws SQLException {
+        Connection con = null;
+        PreparedStatement stmt = null;
+        boolean inBatchMode = inBatchMode();
+        try {
+            con = getConnection(inBatchMode);
+            stmt = con.prepareStatement(sql);
+            return execute(stmt, params).getUpdateCount();
+        } finally {
+            closeResources(con, stmt, null, inBatchMode);
+        }
+    }
+
+    /**
+     * Executes a SQL query and returns the {@link ResultSet}. The
+     * returned {@link ResultSet} should be closed by clients.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @return a {@link ResultSet}
+     */
+    public final ResultSet query(String sql, Object... params) throws SQLException {
+        return exec(sql, params, false, 0);
+    }
+
+    /**
+     * Executes a general SQL statement and returns the {@link ResultSet} of the executed statement. The
+     * returned {@link ResultSet} should be closed by clients.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @param returnGeneratedKeys whether generated keys should be returned
+     * @param maxRows the maximum number of rows in a potential {@link ResultSet} (0 means no limit)
+     * @return a {@link ResultSet}
+     * @throws SQLException on error
+     */
+    public final ResultSet exec(final String sql, final Object[] params, final boolean returnGeneratedKeys,
+            final int maxRows) throws SQLException {
+        return new RetryManager<ResultSet>(params) {
+
+            @Override
+            protected ResultSet call() throws SQLException {
+            	return reallyExec(sql, params, returnGeneratedKeys, maxRows);
+            }
+
+        }.doTry();
+    }
+
+    ResultSet reallyExec(String sql, Object[] params, boolean returnGeneratedKeys, int maxRows)
+            throws SQLException {
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        boolean inBatchMode = inBatchMode();
+        try {
+            con = getConnection(inBatchMode);
+            if (returnGeneratedKeys) {
+                stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
+            } else {
+                stmt = con.prepareStatement(sql);
+            }
+            stmt.setMaxRows(maxRows);
+            int currentFetchSize = this.fetchSize;
+            if (0 < maxRows && maxRows < currentFetchSize) {
+            	currentFetchSize = maxRows; // JCR-3090
+            }
+            stmt.setFetchSize(currentFetchSize);
+            execute(stmt, params);
+            if (returnGeneratedKeys) {
+                rs = stmt.getGeneratedKeys();
+            } else {
+                rs = stmt.getResultSet();
+            }
+            // Don't wrap null
+            if (rs == null) {
+            	closeResources(con, stmt, rs, inBatchMode);
+                return null;
+            }
+            if (inBatchMode) {
+                return ResultSetWrapper.newInstance(null, stmt, rs);
+            } else {
+                return ResultSetWrapper.newInstance(con, stmt, rs);
+            }
+        } catch (SQLException e) {
+            closeResources(con, stmt, rs, inBatchMode);
+            throw e;
+        }
+    }
+
+	/**
+     * Gets a connection based on the {@code batchMode} state of this helper. The connection should be closed
+     * by a call to {@link #closeResources(Connection, Statement, ResultSet)} which also takes the {@code
+     * batchMode} state into account.
+     *
+     * @param inBatchMode indicates if we are in a batchMode
+     * @return a {@code Connection} to use, based on the batch mode state
+     * @throws SQLException on error
+     */
+    protected final Connection getConnection(boolean inBatchMode) throws SQLException {
+        if (inBatchMode) {
+            return getTransactionAwareBatchConnection();
+        } else {
+            Connection con = dataSource.getConnection();
+            // JCR-1013: Setter may fail unnecessarily on a managed connection
+            if (!con.getAutoCommit()) {
+                con.setAutoCommit(true);
+            }
+            return con;
+        }
+    }
+
+    /**
+     * Returns the Batch Connection.
+     * 
+     * @return Connection
+     */
+    private Connection getTransactionAwareBatchConnection() {
+    	Object threadId = TransactionContext.getCurrentThreadId();
+       	return batchConnectionMap.get(threadId);
+	}
+
+    /**
+     * Stores the given Connection to the batchConnectionMap.
+     * If we are running in a XA Environment the globalTransactionId will be used as Key.
+     * In Non-XA Environment the ThreadName is used.
+     * 
+     * @param batchConnection
+     */
+	private void setTransactionAwareBatchConnection(Connection batchConnection) {
+    	Object threadId = TransactionContext.getCurrentThreadId();
+    	batchConnectionMap.put(threadId, batchConnection);
+	}
+
+    /**
+     * Removes the Batch Connection from the batchConnectionMap
+     */
+	private void removeTransactionAwareBatchConnection() {
+    	Object threadId = TransactionContext.getCurrentThreadId();
+    	batchConnectionMap.remove(threadId);
+	}
+	
+	/**
+     * Closes the given resources given the {@code batchMode} state.
+     *
+     * @param con the {@code Connection} obtained through the {@link #getConnection()} method
+     * @param stmt a {@code Statement}
+     * @param rs a {@code ResultSet}
+     * @param inBatchMode indicates if we are in a batchMode
+     */
+    protected final void closeResources(Connection con, Statement stmt, ResultSet rs, boolean inBatchMode) {
+        if (inBatchMode) {
+            DbUtility.close(null, stmt, rs);
+        } else {
+            DbUtility.close(con, stmt, rs);
+        }
+    }
+
+    /**
+     * This method is used by all methods of this class that execute SQL statements. This default
+     * implementation sets all parameters and unwraps {@link StreamWrapper} instances. Subclasses may override
+     * this method to do something special with the parameters. E.g., the {@link Oracle10R1ConnectionHelper}
+     * overrides it in order to add special blob handling.
+     *
+     * @param stmt the {@link PreparedStatement} to execute
+     * @param params the parameters
+     * @return the executed statement
+     * @throws SQLException on error
+     */
+    protected PreparedStatement execute(PreparedStatement stmt, Object[] params) throws SQLException {
+        for (int i = 0; params != null && i < params.length; i++) {
+            Object p = params[i];
+            if (p instanceof StreamWrapper) {
+                StreamWrapper wrapper = (StreamWrapper) p;
+                stmt.setBinaryStream(i + 1, wrapper.getStream(), (int) wrapper.getSize());
+            } else {
+                stmt.setObject(i + 1, p);
+            }
+        }
+        try {
+        	stmt.execute();
+        } catch (SQLException e) {
+        	//Reset Stream for retry ...
+            for (int i = 0; params != null && i < params.length; i++) {
+                Object p = params[i];
+                if (p instanceof StreamWrapper) {
+                    StreamWrapper wrapper = (StreamWrapper) p;
+                    if(!wrapper.resetStream()) {
+                    	wrapper.cleanupResources();
+                    	throw new RuntimeException("Unable to reset the Stream.");
+                    }
+                }
+            }
+        	throw e;
+        }
+        return stmt;
+    }
+
+    /**
+     * This class encapsulates the logic to retry a method invocation if it threw an SQLException.
+     * The RetryManager must cleanup the Params it will get.
+     *
+     * @param <T> the return type of the method which is retried if it failed
+     */
+    public abstract class RetryManager<T> {
+
+    	private Object[] params;
+    	
+    	public RetryManager(Object[] params) {
+    		this.params = params;
+    	}
+    	
+        public final T doTry() throws SQLException {
+            if (inBatchMode()) {
+                return call();
+            } else {
+                boolean sleepInterrupted = false;
+                int failures = 0;
+                SQLException lastException = null;
+                while (!sleepInterrupted && (blockOnConnectionLoss || failures <= RETRIES)) {
+                    try {
+                    	T object = call(); 
+                        cleanupParamResources();
+                        return object;
+                    } catch (SQLException e) {
+                        lastException = e;
+                    }
+                    log.error("Failed to execute SQL (stacktrace on DEBUG log level): " + lastException);
+                    log.debug("Failed to execute SQL", lastException);
+                    failures++;
+                    if (blockOnConnectionLoss || failures <= RETRIES) { // if we're going to try again
+                        try {
+                            Thread.sleep(SLEEP_BETWEEN_RETRIES_MS);
+                        } catch (InterruptedException e1) {
+                            Thread.currentThread().interrupt();
+                            sleepInterrupted = true;
+                            log.error("Interrupted: canceling retry");
+                        }
+                    }
+                }
+                cleanupParamResources();
+                throw lastException;
+            }
+        }
+
+        protected abstract T call() throws SQLException;
+
+		/**
+		 * Cleans up the Parameter resources that are not automatically closed or deleted.
+		 *
+		 * @param params
+		 */
+		protected void cleanupParamResources() {
+		    for (int i = 0; params != null && i < params.length; i++) {
+		        Object p = params[i];
+		        if (p instanceof StreamWrapper) {
+		            StreamWrapper wrapper = (StreamWrapper) p;
+		            wrapper.cleanupResources();
+		        }
+		    }
+		}
+    }
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,119 @@
+/*
+ * 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.util.db;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+/**
+ * This class delegates all calls to the corresponding method on the wrapped {@code DataSource} except for the {@link #getConnection()} method,
+ * which delegates to {@code DataSource#getConnection(String, String)} with the username and password
+ * which are given on construction.
+ */
+public class DataSourceWrapper implements DataSource {
+
+    private final DataSource dataSource;
+
+    private final String username;
+
+    private final String password;
+
+    /**
+     * @param dataSource the {@code DataSource} to wrap
+     * @param username the username to use
+     * @param password the password to use
+     */
+    public DataSourceWrapper(DataSource dataSource, String username, String password) {
+        this.dataSource = dataSource;
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Java 6 method.
+     * 
+     * {@inheritDoc}
+     */
+    public boolean isWrapperFor(Class<?> arg0) throws SQLException {
+        throw new UnsupportedOperationException("Java 6 method not supported");
+    }
+
+    /**
+     * Java 6 method.
+     * 
+     * {@inheritDoc}
+     */
+    public <T> T unwrap(Class<T> arg0) throws SQLException {
+        throw new UnsupportedOperationException("Java 6 method not supported");
+    }
+
+    /**
+     * Unsupported Java 7 method.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-3167">JCR-3167</a>
+     */
+    public Logger getParentLogger() {
+        throw new UnsupportedOperationException("Java 7 method not supported");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Connection getConnection() throws SQLException {
+        return dataSource.getConnection(username, password);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Connection getConnection(String username, String password) throws SQLException {
+        return dataSource.getConnection(username, password);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public PrintWriter getLogWriter() throws SQLException {
+        return dataSource.getLogWriter();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getLoginTimeout() throws SQLException {
+        return dataSource.getLoginTimeout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setLogWriter(PrintWriter out) throws SQLException {
+        dataSource.setLogWriter(out);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setLoginTimeout(int seconds) throws SQLException {
+        dataSource.setLoginTimeout(seconds);
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,30 @@
+/*
+ * 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.util.db;
+
+/**
+ * Bean components (i.e., classes that appear in the repository descriptor) that implement this interface will
+ * get the repositories {@link ConnectionFactory} instance injected just after construction and before
+ * initialization.
+ */
+public interface DatabaseAware {
+
+    /**
+     * @param connectionFactory
+     */
+    void setConnectionFactory(ConnectionFactory connectionFactory);
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,98 @@
+/*
+ * 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.util.db;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class contains some database utility methods.
+ */
+public final class DbUtility {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DbUtility.class);
+
+    /**
+     * Private constructor for utility class pattern.
+     */
+    private DbUtility() {
+    }
+
+    /**
+     * This is a utility method which closes the given resources without throwing exceptions. Any exceptions
+     * encountered are logged instead.
+     * 
+     * @param rs the {@link ResultSet} to close, may be null
+     */
+    public static void close(ResultSet rs) {
+        close(null, null, rs);
+    }
+
+    /**
+     * This is a utility method which closes the given resources without throwing exceptions. Any exceptions
+     * encountered are logged instead.
+     * 
+     * @param con the {@link Connection} to close, may be null
+     * @param stmt the {@link Statement} to close, may be null
+     * @param rs the {@link ResultSet} to close, may be null
+     */
+    public static void close(Connection con, Statement stmt, ResultSet rs) {
+        try {
+            if (rs != null) {
+                rs.close();
+            }
+        } catch (SQLException e) {
+            logException("failed to close ResultSet", e);
+        } finally {
+            try {
+                if (stmt != null) {
+                    stmt.close();
+                }
+            } catch (SQLException e) {
+                logException("failed to close Statement", e);
+            } finally {
+                try {
+                    if (con != null && !con.isClosed()) {
+                        con.close();
+                    }
+                } catch (SQLException e) {
+                    logException("failed to close Connection", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Logs an SQL exception on error level, and debug level (more detail).
+     * 
+     * @param message the message
+     * @param e the exception
+     */
+    public static void logException(String message, SQLException e) {
+        if (message != null) {
+            LOG.error(message);
+        }
+        LOG.error("       Reason: " + e.getMessage());
+        LOG.error("   State/Code: " + e.getSQLState() + "/" + e.getErrorCode());
+        LOG.debug("   dump:", e);
+    }
+}

Added: jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java?rev=1564687&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java (added)
+++ jackrabbit/trunk/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java Wed Feb  5 09:27:20 2014
@@ -0,0 +1,97 @@
+/*
+ * 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.util.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 
+ */
+public final class DerbyConnectionHelper extends ConnectionHelper {
+
+    /** name of the embedded driver */
+    public static final String DERBY_EMBEDDED_DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
+
+    private static Logger log = LoggerFactory.getLogger(DerbyConnectionHelper.class);
+
+    /**
+     * @param dataSrc the {@code DataSource} on which this helper acts
+     * @param block whether to block on connection loss until the db is up again
+     */
+    public DerbyConnectionHelper(DataSource dataSrc, boolean block) {
+        super(dataSrc, block);
+    }
+
+    /**
+     * Shuts the embedded Derby database down.
+     * 
+     * @param driver the driver
+     * @throws SQLException on failure
+     */
+    public void shutDown(String driver) throws SQLException {
+        // check for embedded driver
+        if (!DERBY_EMBEDDED_DRIVER.equals(driver)) {
+            return;
+        }
+
+        // prepare connection url for issuing shutdown command
+        String url = null;
+        Connection con = null;
+        
+        try {
+            con = dataSource.getConnection();
+            try {
+                url = con.getMetaData().getURL();
+            } catch (SQLException e) {
+                // JCR-1557: embedded derby db probably already shut down;
+                // this happens when configuring multiple FS/PM instances
+                // to use the same embedded derby db instance.
+                log.debug("failed to retrieve connection url: embedded db probably already shut down", e);
+                return;
+            }
+            // 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
+            con.setAutoCommit(true);
+        }
+        finally {
+            DbUtility.close(con, null, null);
+        }
+        int pos = url.lastIndexOf(';');
+        if (pos != -1) {
+            // strip any attributes from connection url
+            url = url.substring(0, pos);
+        }
+        url += ";shutdown=true";
+
+        // 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());
+        }
+    }
+}