diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/DirectoryLockException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/DirectoryLockException.java new file mode 100644 index 000000000..3fbc7ae95 --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/DirectoryLockException.java @@ -0,0 +1,67 @@ +/* + * This file is part of dependency-check-core. + * + * Dependency-check-core is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Dependency-check-core is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * dependency-check-core. If not, see http://www.gnu.org/licenses/. + * + * Copyright (c) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.concurrency; + +/** + * If thrown, indicates that a problem occurred when locking a directory. + * + * @author Jeremy Long (jeremy.long@owasp.org) + */ +public class DirectoryLockException extends Exception { + + /** + * Default serial version UID. + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new Directory Lock Exception. + */ + public DirectoryLockException() { + super(); + } + + /** + * Constructs a new Directory Lock Exception. + * + * @param msg the message describing the exception + */ + public DirectoryLockException(String msg) { + super(msg); + } + + /** + * Constructs a new Directory Lock Exception. + * + * @param ex the cause of the exception + */ + public DirectoryLockException(Throwable ex) { + super(ex); + } + + /** + * Constructs a new Directory Lock Exception. + * + * @param msg the message describing the exception + * @param ex the cause of the exception + */ + public DirectoryLockException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/DirectorySpinLock.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/DirectorySpinLock.java new file mode 100644 index 000000000..2261846ed --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/DirectorySpinLock.java @@ -0,0 +1,267 @@ +/* + * This file is part of dependency-check-core. + * + * Dependency-check-core is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Dependency-check-core is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * dependency-check-core. If not, see http://www.gnu.org/licenses/. + * + * Copyright (c) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.concurrency; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.AsynchronousCloseException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.FileLockInterruptionException; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.OverlappingFileLockException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implements a spin lock on a given directory. If the lock cannot be obtained, + * the process will "spin" waiting for an opportunity to obtain the lock + * requested. + * + * @author Jeremy Long (jeremy.long@owasp.org) + */ +public class DirectorySpinLock implements Closeable /*, AutoCloseable*/ { + + /** + * The name of the lock file. + */ + public static final String LOCK_NAME = "data.lock"; + /** + * The maximum wait period used when attempting to obtain a lock. + */ + public static final int MAX_SPIN = 100; + /** + * The file channel used to perform the lock. + */ + private FileChannel channel = null; + /** + * The file used to perform the lock. + */ + private File lockFile = null; + /** + * The lock object. + */ + private FileLock lock = null; + /** + * The maximum number of seconds that the spin lock will wait while trying + * to obtain a lock. + */ + private long maxWait = MAX_SPIN; + + /** + * Get the maximum wait time, in seconds, that the spin lock will wait while + * trying to obtain a lock. + * + * @return the number of seconds the spin lock will wait + */ + public long getMaxWait() { + return maxWait / 2; //sleep is for 500, so / 2 + } + + /** + * Set the maximum wait time, in seconds, that the spin lock will wait while + * trying to obtain a lock. + * + * @param maxWait the number of seconds the spin lock will wait + */ + public void setMaxWait(long maxWait) { + this.maxWait = maxWait * 2; //sleep is for 500, so * 2 + } + + /** + * Constructs a new spin lock on the given directory. + * + * @param directory the directory to monitor/lock + * @throws InvalidDirectoryException thrown if there is an issue with the + * directory provided + * @throws DirectoryLockException thrown there is an issue obtaining a + * handle to the lock file + */ + public DirectorySpinLock(File directory) throws InvalidDirectoryException, DirectoryLockException { + checkDirectory(directory); + lockFile = new File(directory, LOCK_NAME); + RandomAccessFile file = null; + try { + file = new RandomAccessFile(lockFile, "rw"); + } catch (FileNotFoundException ex) { + throw new DirectoryLockException("Lock file not found", ex); + } + channel = file.getChannel(); + } + + /** + * Attempts to obtain an exclusive lock; an exception is thrown if the lock + * could not be obtained. This method may block for a few seconds if a lock + * cannot be obtained. + * + * @throws DirectoryLockException thrown if there is an exception obtaining + * the lock + */ + public void obtainSharedLock() throws DirectoryLockException { + obtainLock(true); + } + + /** + * Attempts to obtain an exclusive lock; an exception is thrown if the lock + * could not be obtained. This method may block for a few seconds if a lock + * cannot be obtained. + * + * @throws DirectoryLockException thrown if there is an exception obtaining + * the lock + */ + public void obtainExclusiveLock() throws DirectoryLockException { + obtainLock(false); + } + + /** + * Attempts to obtain a lock; an exception is thrown if the lock could not + * be obtained. This method may block for a few seconds if a lock cannot be + * obtained. + * + * @param shared true if the lock is shared, otherwise false + * @param maxWait the maximum time to wait, in seconds, while trying to + * obtain the lock + * @throws DirectoryLockException thrown if there is an exception obtaining + * the lock + */ + protected void obtainLock(boolean shared, long maxWait) throws DirectoryLockException { + setMaxWait(maxWait); + obtainLock(shared); + } + + /** + * Attempts to obtain a lock; an exception is thrown if the lock could not + * be obtained. This method may block for a few seconds if a lock cannot be + * obtained. + * + * @param shared true if the lock is shared, otherwise false + * @throws DirectoryLockException thrown if there is an exception obtaining + * the lock + */ + protected void obtainLock(boolean shared) throws DirectoryLockException { + if (lock != null) { + release(); + } + if (channel == null) { + throw new DirectoryLockException("Unable to create lock, no file channel exists"); + } + int count = 0; + Exception lastException = null; + while (lock == null && count++ < maxWait) { + try { + lock = channel.lock(0, Long.MAX_VALUE, shared); + } catch (AsynchronousCloseException ex) { + lastException = ex; + } catch (ClosedChannelException ex) { + lastException = ex; + } catch (FileLockInterruptionException ex) { + lastException = ex; + } catch (OverlappingFileLockException ex) { + lastException = ex; + } catch (NonWritableChannelException ex) { + lastException = ex; + } catch (IOException ex) { + lastException = ex; + } + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + if (lock == null) { + if (lastException == null) { + throw new DirectoryLockException("Unable to obtain lock"); + } else { + throw new DirectoryLockException("Unable to obtain lock", lastException); + } + } + } + + /** + * Performs a few simple rudimentary checks on the specified directory. + * Specifically, does the file exist and is it a directory. + * + * @param directory the File object to inspect + * @throws InvalidDirectoryException thrown if the directory is null or is + * not a directory + */ + private void checkDirectory(File directory) throws InvalidDirectoryException { + if (directory == null) { + throw new InvalidDirectoryException("Unable to obtain lock on a null File"); + } + if (!directory.isDirectory()) { + final String msg = String.format("File, '%s', does not exist or is not a directory", directory.getAbsolutePath()); + throw new InvalidDirectoryException(msg); + } + } + + /** + * Releases any locks and closes the underlying channel. + * + * @throws IOException if an IO Exception occurs + */ + @Override + public void close() throws IOException { + release(); +// TODO uncomment this once support for 1.6 is dropped. +// if (lock != null) { +// try { +// lock.close(); +// } catch (IOException ex) { +// Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to close file lock due to IO Exception", ex); +// } +// } + if (channel != null) { + try { + channel.close(); + } catch (IOException ex) { + Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to close the channel for the file lock", ex); + } + } + if (lockFile != null) { + if (lockFile.exists()) { + /* yes, this delete could fail which is totally fine. The other + * thread holding the lock while delete it. + */ + lockFile.delete(); + } + } + } + + /** + * Releases the lock. Any exceptions that are thrown by the underlying lock + * during the release are ignored. + */ + public void release() { + if (lock != null) { + try { + lock.release(); + } catch (ClosedChannelException ex) { + Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Uable to release file lock", ex); + } catch (IOException ex) { + Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to release file lock due to IO Exception", ex); + } + } + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/InvalidDirectoryException.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/InvalidDirectoryException.java new file mode 100644 index 000000000..08b29acff --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/InvalidDirectoryException.java @@ -0,0 +1,67 @@ +/* + * This file is part of dependency-check-core. + * + * Dependency-check-core is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Dependency-check-core is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * dependency-check-core. If not, see http://www.gnu.org/licenses/. + * + * Copyright (c) 2013 Jeremy Long. All Rights Reserved. + */ +package org.owasp.dependencycheck.concurrency; + +/** + * If thrown, indicates that there is a problem with a directory. + * + * @author Jeremy Long (jeremy.long@owasp.org) + */ +public class InvalidDirectoryException extends Exception { + + /** + * Default serial version UID. + */ + private static final long serialVersionUID = 1L; + + /** + * Constructs a new Invalid Directory Exception. + */ + public InvalidDirectoryException() { + super(); + } + + /** + * Constructs a new Invalid Directory Exception. + * + * @param msg the message describing the exception + */ + public InvalidDirectoryException(String msg) { + super(msg); + } + + /** + * Constructs a new Invalid Directory Exception. + * + * @param ex the cause of the exception + */ + public InvalidDirectoryException(Throwable ex) { + super(ex); + } + + /** + * Constructs a new Invalid Directory Exception. + * + * @param msg the message describing the exception + * @param ex the cause of the exception + */ + public InvalidDirectoryException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/package-info.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/package-info.java new file mode 100644 index 000000000..ab7fba4fd --- /dev/null +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/concurrency/package-info.java @@ -0,0 +1,11 @@ +/** + * + * + * org.owasp.dependencycheck.concurrency + * + * + * Contains classes used to create shared and exclusive locks on directories. + * + * + */ +package org.owasp.dependencycheck.concurrency;