001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.kahadb.util;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.RandomAccessFile;
022import java.nio.channels.FileLock;
023import java.nio.channels.OverlappingFileLockException;
024import java.util.Date;
025
026/**
027 * Used to lock a File.
028 * 
029 * @author chirino
030 */
031public class LockFile {
032    
033    private static final boolean DISABLE_FILE_LOCK = "true".equals(System.getProperty("java.nio.channels.FileLock.broken", "false"));
034    final private File file;
035    
036    private FileLock lock;
037    private RandomAccessFile readFile;
038    private int lockCounter;
039    private final boolean deleteOnUnlock;
040    
041    public LockFile(File file, boolean deleteOnUnlock) {
042        this.file = file;
043        this.deleteOnUnlock = deleteOnUnlock;
044    }
045
046    /**
047     * @throws IOException
048     */
049    synchronized public void lock() throws IOException {
050        if (DISABLE_FILE_LOCK) {
051            return;
052        }
053
054        if( lockCounter>0 ) {
055            return;
056        }
057        
058        IOHelper.mkdirs(file.getParentFile());
059        if (System.getProperty(getVmLockKey()) != null) {
060            throw new IOException("File '" + file + "' could not be locked as lock is already held for this jvm.");
061        }
062        if (lock == null) {
063            readFile = new RandomAccessFile(file, "rw");
064            IOException reason = null;
065            try {
066                lock = readFile.getChannel().tryLock(0, readFile.getChannel().size(), false);
067            } catch (OverlappingFileLockException e) {
068                reason = IOExceptionSupport.create("File '" + file + "' could not be locked.",e);
069            } catch (IOException ioe) {
070                reason = ioe;
071            }
072            if (lock != null) {
073                lockCounter++;
074                System.setProperty(getVmLockKey(), new Date().toString());
075            } else {
076                // new read file for next attempt
077                closeReadFile();
078                if (reason != null) {
079                    throw reason;
080                }
081                throw new IOException("File '" + file + "' could not be locked.");
082            }
083              
084        }
085    }
086
087    /**
088     */
089    public void unlock() {
090        if (DISABLE_FILE_LOCK) {
091            return;
092        }
093        
094        lockCounter--;
095        if( lockCounter!=0 ) {
096            return;
097        }
098        
099        // release the lock..
100        if (lock != null) {
101            try {
102                lock.release();
103                System.getProperties().remove(getVmLockKey());
104            } catch (Throwable ignore) {
105            }
106            lock = null;
107        }
108        closeReadFile();
109        
110        if( deleteOnUnlock ) {
111            file.delete();
112        }
113    }
114
115    private String getVmLockKey() throws IOException {
116        return getClass().getName() + ".lock." + file.getCanonicalPath();
117    }
118
119    private void closeReadFile() {
120        // close the file.
121        if (readFile != null) {
122            try {
123                readFile.close();
124            } catch (Throwable ignore) {
125            }
126            readFile = null;
127        }
128        
129    }
130
131}