##### 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;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 |  |
     }