001package org.apache.archiva.common.filelock; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.commons.lang.time.StopWatch; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025import org.springframework.stereotype.Service; 026 027import java.io.File; 028import java.io.FileNotFoundException; 029import java.io.IOException; 030import java.io.RandomAccessFile; 031import java.nio.channels.ClosedChannelException; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.ConcurrentMap; 034 035/** 036 * @author Olivier Lamy 037 * @since 2.0.0 038 */ 039@Service("fileLockManager#default") 040public class DefaultFileLockManager 041 implements FileLockManager 042{ 043 // TODO currently we create lock for read and write!! 044 // the idea could be to store lock here with various clients read/write 045 // only read could be a more simple lock and acquire a write lock means waiting the end of all reading threads 046 private static final ConcurrentMap<File, Lock> lockFiles = new ConcurrentHashMap<File, Lock>( 64 ); 047 048 private boolean skipLocking = true; 049 050 private Logger log = LoggerFactory.getLogger( getClass() ); 051 052 private int timeout = 0; 053 054 055 @Override 056 public Lock readFileLock( File file ) 057 throws FileLockException, FileLockTimeoutException 058 { 059 if ( skipLocking ) 060 { 061 return new Lock( file ); 062 063 } 064 StopWatch stopWatch = new StopWatch(); 065 boolean acquired = false; 066 mkdirs( file.getParentFile() ); 067 068 Lock lock = null; 069 070 stopWatch.start(); 071 072 while ( !acquired ) 073 { 074 075 if ( timeout > 0 ) 076 { 077 long delta = stopWatch.getTime(); 078 log.debug( "delta {}, timeout {}", delta, timeout ); 079 if ( delta > timeout ) 080 { 081 log.warn( "Cannot acquire read lock within {} millis. Will skip the file: {}", timeout, file ); 082 // we could not get the lock within the timeout period, so throw FileLockTimeoutException 083 throw new FileLockTimeoutException(); 084 } 085 } 086 087 Lock current = lockFiles.get( file ); 088 089 if ( current != null ) 090 { 091 log.debug( "read lock file exist continue wait" ); 092 continue; 093 } 094 095 try 096 { 097 lock = new Lock( file, false ); 098 createNewFileQuietly( file ); 099 lock.openLock( false, timeout > 0 ); 100 acquired = true; 101 } 102 catch ( FileNotFoundException e ) 103 { 104 // can happen if an other thread has deleted the file 105 // close RandomAccessFile!!! 106 if ( lock != null ) 107 { 108 closeQuietly( lock.getRandomAccessFile() ); 109 } 110 log.debug( "read Lock skip: {} try to create file", e.getMessage() ); 111 createNewFileQuietly( file ); 112 } 113 catch ( IOException e ) 114 { 115 throw new FileLockException( e.getMessage(), e ); 116 } 117 catch ( IllegalStateException e ) 118 { 119 log.debug( "openLock {}:{}", e.getClass(), e.getMessage() ); 120 } 121 } 122 Lock current = lockFiles.putIfAbsent( file, lock ); 123 if ( current != null ) 124 { 125 lock = current; 126 } 127 return lock; 128 129 } 130 131 132 @Override 133 public Lock writeFileLock( File file ) 134 throws FileLockException, FileLockTimeoutException 135 { 136 if ( skipLocking ) 137 { 138 return new Lock( file ); 139 } 140 141 mkdirs( file.getParentFile() ); 142 143 StopWatch stopWatch = new StopWatch(); 144 boolean acquired = false; 145 146 Lock lock = null; 147 148 stopWatch.start(); 149 150 while ( !acquired ) 151 { 152 153 if ( timeout > 0 ) 154 { 155 long delta = stopWatch.getTime(); 156 log.debug( "delta {}, timeout {}", delta, timeout ); 157 if ( delta > timeout ) 158 { 159 log.warn( "Cannot acquire read lock within {} millis. Will skip the file: {}", timeout, file ); 160 // we could not get the lock within the timeout period, so throw FileLockTimeoutException 161 throw new FileLockTimeoutException(); 162 } 163 } 164 165 Lock current = lockFiles.get( file ); 166 167 try 168 { 169 170 if ( current != null ) 171 { 172 log.debug( "write lock file exist continue wait" ); 173 174 continue; 175 } 176 lock = new Lock( file, true ); 177 createNewFileQuietly( file ); 178 lock.openLock( true, timeout > 0 ); 179 acquired = true; 180 } 181 catch ( FileNotFoundException e ) 182 { 183 // can happen if an other thread has deleted the file 184 // close RandomAccessFile!!! 185 if ( lock != null ) 186 { 187 closeQuietly( lock.getRandomAccessFile() ); 188 } 189 log.debug( "write Lock skip: {} try to create file", e.getMessage() ); 190 createNewFileQuietly( file ); 191 } 192 catch ( IOException e ) 193 { 194 throw new FileLockException( e.getMessage(), e ); 195 } 196 catch ( IllegalStateException e ) 197 { 198 log.debug( "openLock {}:{}", e.getClass(), e.getMessage() ); 199 } 200 } 201 202 Lock current = lockFiles.putIfAbsent( file, lock ); 203 if ( current != null ) 204 { 205 lock = current; 206 } 207 208 return lock; 209 210 211 } 212 213 private void closeQuietly( RandomAccessFile randomAccessFile ) 214 { 215 if ( randomAccessFile == null ) 216 { 217 return; 218 } 219 220 try 221 { 222 randomAccessFile.close(); 223 } 224 catch ( IOException e ) 225 { 226 // ignore 227 } 228 } 229 230 private void createNewFileQuietly( File file ) 231 { 232 try 233 { 234 file.createNewFile(); 235 } 236 catch ( IOException e ) 237 { 238 // skip that 239 } 240 } 241 242 @Override 243 public void release( Lock lock ) 244 throws FileLockException 245 { 246 if ( lock == null ) 247 { 248 log.debug( "skip releasing null" ); 249 return; 250 } 251 if ( skipLocking ) 252 { 253 return; 254 } 255 try 256 { 257 lockFiles.remove( lock.getFile() ); 258 lock.close(); 259 } 260 catch ( ClosedChannelException e ) 261 { 262 // skip this one 263 log.debug( "ignore ClosedChannelException: {}", e.getMessage() ); 264 } 265 catch ( IOException e ) 266 { 267 throw new FileLockException( e.getMessage(), e ); 268 } 269 } 270 271 @Override 272 public void clearLockFiles() 273 { 274 lockFiles.clear(); 275 } 276 277 private boolean mkdirs( File directory ) 278 { 279 return directory.mkdirs(); 280 } 281 282 @Override 283 public int getTimeout() 284 { 285 return timeout; 286 } 287 288 @Override 289 public void setTimeout( int timeout ) 290 { 291 this.timeout = timeout; 292 } 293 294 @Override 295 public boolean isSkipLocking() 296 { 297 return skipLocking; 298 } 299 300 @Override 301 public void setSkipLocking( boolean skipLocking ) 302 { 303 this.skipLocking = skipLocking; 304 } 305}