Coverage Report - org.owasp.dependencycheck.concurrency.DirectorySpinLock
 
Classes in this File Line Coverage Branch Coverage Complexity
DirectorySpinLock
65%
51/78
66%
16/24
3.9
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Dependency-check-core is free software: you can redistribute it and/or modify it
 5  
  * under the terms of the GNU General Public License as published by the Free
 6  
  * Software Foundation, either version 3 of the License, or (at your option) any
 7  
  * later version.
 8  
  *
 9  
  * Dependency-check-core is distributed in the hope that it will be useful, but
 10  
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  
  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 12  
  * details.
 13  
  *
 14  
  * You should have received a copy of the GNU General Public License along with
 15  
  * dependency-check-core. If not, see http://www.gnu.org/licenses/.
 16  
  *
 17  
  * Copyright (c) 2013 Jeremy Long. All Rights Reserved.
 18  
  */
 19  
 package org.owasp.dependencycheck.concurrency;
 20  
 
 21  
 import java.io.Closeable;
 22  
 import java.io.File;
 23  
 import java.io.FileNotFoundException;
 24  
 import java.io.IOException;
 25  
 import java.io.RandomAccessFile;
 26  
 import java.nio.channels.AsynchronousCloseException;
 27  
 import java.nio.channels.ClosedChannelException;
 28  
 import java.nio.channels.FileChannel;
 29  
 import java.nio.channels.FileLock;
 30  
 import java.nio.channels.FileLockInterruptionException;
 31  
 import java.nio.channels.NonWritableChannelException;
 32  
 import java.nio.channels.OverlappingFileLockException;
 33  
 import java.util.logging.Level;
 34  
 import java.util.logging.Logger;
 35  
 
 36  
 /**
 37  
  * Implements a spin lock on a given directory. If the lock cannot be obtained,
 38  
  * the process will "spin" waiting for an opportunity to obtain the lock
 39  
  * requested.
 40  
  *
 41  
  * @author Jeremy Long (jeremy.long@owasp.org)
 42  
  */
 43  
 public class DirectorySpinLock implements Closeable /*, AutoCloseable*/ {
 44  
 
 45  
     /**
 46  
      * The name of the lock file.
 47  
      */
 48  
     public static final String LOCK_NAME = "data.lock";
 49  
     /**
 50  
      * The maximum wait period used when attempting to obtain a lock.
 51  
      */
 52  
     public static final int MAX_SPIN = 100;
 53  
     /**
 54  
      * The file channel used to perform the lock.
 55  
      */
 56  6
     private FileChannel channel = null;
 57  
     /**
 58  
      * The file used to perform the lock.
 59  
      */
 60  6
     private File lockFile = null;
 61  
     /**
 62  
      * The lock object.
 63  
      */
 64  6
     private FileLock lock = null;
 65  
     /**
 66  
      * The maximum number of seconds that the spin lock will wait while trying
 67  
      * to obtain a lock.
 68  
      */
 69  6
     private long maxWait = MAX_SPIN;
 70  
 
 71  
     /**
 72  
      * Get the maximum wait time, in seconds, that the spin lock will wait while
 73  
      * trying to obtain a lock.
 74  
      *
 75  
      * @return the number of seconds the spin lock will wait
 76  
      */
 77  
     public long getMaxWait() {
 78  0
         return maxWait / 2; //sleep is for 500, so / 2
 79  
     }
 80  
 
 81  
     /**
 82  
      * Set the maximum wait time, in seconds, that the spin lock will wait while
 83  
      * trying to obtain a lock.
 84  
      *
 85  
      * @param maxWait the number of seconds the spin lock will wait
 86  
      */
 87  
     public void setMaxWait(long maxWait) {
 88  3
         this.maxWait = maxWait * 2; //sleep is for 500, so * 2
 89  3
     }
 90  
 
 91  
     /**
 92  
      * Constructs a new spin lock on the given directory.
 93  
      *
 94  
      * @param directory the directory to monitor/lock
 95  
      * @throws InvalidDirectoryException thrown if there is an issue with the
 96  
      * directory provided
 97  
      * @throws DirectoryLockException thrown there is an issue obtaining a
 98  
      * handle to the lock file
 99  
      */
 100  6
     public DirectorySpinLock(File directory) throws InvalidDirectoryException, DirectoryLockException {
 101  6
         checkDirectory(directory);
 102  6
         lockFile = new File(directory, LOCK_NAME);
 103  6
         RandomAccessFile file = null;
 104  
         try {
 105  6
             file = new RandomAccessFile(lockFile, "rw");
 106  0
         } catch (FileNotFoundException ex) {
 107  0
             throw new DirectoryLockException("Lock file not found", ex);
 108  6
         }
 109  6
         channel = file.getChannel();
 110  6
     }
 111  
 
 112  
     /**
 113  
      * Attempts to obtain an exclusive lock; an exception is thrown if the lock
 114  
      * could not be obtained. This method may block for a few seconds if a lock
 115  
      * cannot be obtained.
 116  
      *
 117  
      * @throws DirectoryLockException thrown if there is an exception obtaining
 118  
      * the lock
 119  
      */
 120  
     public void obtainSharedLock() throws DirectoryLockException {
 121  2
         obtainLock(true);
 122  2
     }
 123  
 
 124  
     /**
 125  
      * Attempts to obtain an exclusive lock; an exception is thrown if the lock
 126  
      * could not be obtained. This method may block for a few seconds if a lock
 127  
      * cannot be obtained.
 128  
      *
 129  
      * @throws DirectoryLockException thrown if there is an exception obtaining
 130  
      * the lock
 131  
      */
 132  
     public void obtainExclusiveLock() throws DirectoryLockException {
 133  1
         obtainLock(false);
 134  1
     }
 135  
 
 136  
     /**
 137  
      * Attempts to obtain a lock; an exception is thrown if the lock could not
 138  
      * be obtained. This method may block for a few seconds if a lock cannot be
 139  
      * obtained.
 140  
      *
 141  
      * @param shared true if the lock is shared, otherwise false
 142  
      * @param maxWait the maximum time to wait, in seconds, while trying to
 143  
      * obtain the lock
 144  
      * @throws DirectoryLockException thrown if there is an exception obtaining
 145  
      * the lock
 146  
      */
 147  
     protected void obtainLock(boolean shared, long maxWait) throws DirectoryLockException {
 148  3
         setMaxWait(maxWait);
 149  3
         obtainLock(shared);
 150  2
     }
 151  
 
 152  
     /**
 153  
      * Attempts to obtain a lock; an exception is thrown if the lock could not
 154  
      * be obtained. This method may block for a few seconds if a lock cannot be
 155  
      * obtained.
 156  
      *
 157  
      * @param shared true if the lock is shared, otherwise false
 158  
      * @throws DirectoryLockException thrown if there is an exception obtaining
 159  
      * the lock
 160  
      */
 161  
     protected void obtainLock(boolean shared) throws DirectoryLockException {
 162  6
         if (lock != null) {
 163  0
             release();
 164  
         }
 165  6
         if (channel == null) {
 166  0
             throw new DirectoryLockException("Unable to create lock, no file channel exists");
 167  
         }
 168  6
         int count = 0;
 169  6
         Exception lastException = null;
 170  13
         while (lock == null && count++ < maxWait) {
 171  
             try {
 172  7
                 lock = channel.lock(0, Long.MAX_VALUE, shared);
 173  0
             } catch (AsynchronousCloseException ex) {
 174  0
                 lastException = ex;
 175  0
             } catch (ClosedChannelException ex) {
 176  0
                 lastException = ex;
 177  0
             } catch (FileLockInterruptionException ex) {
 178  0
                 lastException = ex;
 179  2
             } catch (OverlappingFileLockException ex) {
 180  2
                 lastException = ex;
 181  0
             } catch (NonWritableChannelException ex) {
 182  0
                 lastException = ex;
 183  0
             } catch (IOException ex) {
 184  0
                 lastException = ex;
 185  7
             }
 186  
             try {
 187  7
                 Thread.sleep(500);
 188  0
             } catch (InterruptedException ex) {
 189  0
                 Thread.currentThread().interrupt();
 190  7
             }
 191  
         }
 192  6
         if (lock == null) {
 193  1
             if (lastException == null) {
 194  0
                 throw new DirectoryLockException("Unable to obtain lock");
 195  
             } else {
 196  1
                 throw new DirectoryLockException("Unable to obtain lock", lastException);
 197  
             }
 198  
         }
 199  5
     }
 200  
 
 201  
     /**
 202  
      * Performs a few simple rudimentary checks on the specified directory.
 203  
      * Specifically, does the file exist and is it a directory.
 204  
      *
 205  
      * @param directory the File object to inspect
 206  
      * @throws InvalidDirectoryException thrown if the directory is null or is
 207  
      * not a directory
 208  
      */
 209  
     private void checkDirectory(File directory) throws InvalidDirectoryException {
 210  6
         if (directory == null) {
 211  0
             throw new InvalidDirectoryException("Unable to obtain lock on a null File");
 212  
         }
 213  6
         if (!directory.isDirectory()) {
 214  0
             final String msg = String.format("File, '%s', does not exist or is not a directory", directory.getAbsolutePath());
 215  0
             throw new InvalidDirectoryException(msg);
 216  
         }
 217  6
     }
 218  
 
 219  
     /**
 220  
      * Releases any locks and closes the underlying channel.
 221  
      *
 222  
      * @throws IOException if an IO Exception occurs
 223  
      */
 224  
     @Override
 225  
     public void close() throws IOException {
 226  6
         release();
 227  
 // TODO uncomment this once support for 1.6 is dropped.
 228  
 //        if (lock != null) {
 229  
 //            try {
 230  
 //                lock.close();
 231  
 //            } catch (IOException ex) {
 232  
 //                Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to close file lock due to IO Exception", ex);
 233  
 //            }
 234  
 //        }
 235  6
         if (channel != null) {
 236  
             try {
 237  6
                 channel.close();
 238  0
             } catch (IOException ex) {
 239  0
                 Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to close the channel for the file lock", ex);
 240  6
             }
 241  
         }
 242  6
         if (lockFile != null) {
 243  6
             if (lockFile.exists()) {
 244  
                 /* yes, this delete could fail which is totally fine. The other
 245  
                  * thread holding the lock while delete it.
 246  
                  */
 247  6
                 lockFile.delete();
 248  
             }
 249  
         }
 250  6
     }
 251  
 
 252  
     /**
 253  
      * Releases the lock. Any exceptions that are thrown by the underlying lock
 254  
      * during the release are ignored.
 255  
      */
 256  
     public void release() {
 257  6
         if (lock != null) {
 258  
             try {
 259  5
                 lock.release();
 260  0
             } catch (ClosedChannelException ex) {
 261  0
                 Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Uable to release file lock", ex);
 262  0
             } catch (IOException ex) {
 263  0
                 Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to release file lock due to IO Exception", ex);
 264  5
             }
 265  
         }
 266  6
     }
 267  
 }