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());
+ }
+ }
+}