View Javadoc

    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      private FileChannel channel = null;
    57      /**
    58       * The file used to perform the lock.
    59       */
    60      private File lockFile = null;
    61      /**
    62       * The lock object.
    63       */
    64      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      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          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          this.maxWait = maxWait * 2; //sleep is for 500, so * 2
    89      }
    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     public DirectorySpinLock(File directory) throws InvalidDirectoryException, DirectoryLockException {
    101         checkDirectory(directory);
    102         lockFile = new File(directory, LOCK_NAME);
    103         RandomAccessFile file = null;
    104         try {
    105             file = new RandomAccessFile(lockFile, "rw");
    106         } catch (FileNotFoundException ex) {
    107             throw new DirectoryLockException("Lock file not found", ex);
    108         }
    109         channel = file.getChannel();
    110     }
    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         obtainLock(true);
    122     }
    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         obtainLock(false);
    134     }
    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         setMaxWait(maxWait);
    149         obtainLock(shared);
    150     }
    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         if (lock != null) {
    163             release();
    164         }
    165         if (channel == null) {
    166             throw new DirectoryLockException("Unable to create lock, no file channel exists");
    167         }
    168         int count = 0;
    169         Exception lastException = null;
    170         while (lock == null && count++ < maxWait) {
    171             try {
    172                 lock = channel.lock(0, Long.MAX_VALUE, shared);
    173             } catch (AsynchronousCloseException ex) {
    174                 lastException = ex;
    175             } catch (ClosedChannelException ex) {
    176                 lastException = ex;
    177             } catch (FileLockInterruptionException ex) {
    178                 lastException = ex;
    179             } catch (OverlappingFileLockException ex) {
    180                 lastException = ex;
    181             } catch (NonWritableChannelException ex) {
    182                 lastException = ex;
    183             } catch (IOException ex) {
    184                 lastException = ex;
    185             }
    186             try {
    187                 Thread.sleep(500);
    188             } catch (InterruptedException ex) {
    189                 Thread.currentThread().interrupt();
    190             }
    191         }
    192         if (lock == null) {
    193             if (lastException == null) {
    194                 throw new DirectoryLockException("Unable to obtain lock");
    195             } else {
    196                 throw new DirectoryLockException("Unable to obtain lock", lastException);
    197             }
    198         }
    199     }
    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         if (directory == null) {
    211             throw new InvalidDirectoryException("Unable to obtain lock on a null File");
    212         }
    213         if (!directory.isDirectory()) {
    214             final String msg = String.format("File, '%s', does not exist or is not a directory", directory.getAbsolutePath());
    215             throw new InvalidDirectoryException(msg);
    216         }
    217     }
    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         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         if (channel != null) {
    236             try {
    237                 channel.close();
    238             } catch (IOException ex) {
    239                 Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to close the channel for the file lock", ex);
    240             }
    241         }
    242         if (lockFile != null) {
    243             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                 lockFile.delete();
    248             }
    249         }
    250     }
    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         if (lock != null) {
    258             try {
    259                 lock.release();
    260             } catch (ClosedChannelException ex) {
    261                 Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Uable to release file lock", ex);
    262             } catch (IOException ex) {
    263                 Logger.getLogger(DirectorySpinLock.class.getName()).log(Level.FINEST, "Unable to release file lock due to IO Exception", ex);
    264             }
    265         }
    266     }
    267 }