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.activemq.util;
018
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022import java.io.File;
023import java.io.IOException;
024import java.io.RandomAccessFile;
025import java.nio.channels.FileLock;
026import java.nio.channels.OverlappingFileLockException;
027import java.util.Date;
028
029/**
030 * Used to lock a File.
031 *
032 * @author chirino
033 */
034public class LockFile {
035
036    private static final boolean DISABLE_FILE_LOCK = Boolean.getBoolean("java.nio.channels.FileLock.broken");
037    final private File file;
038    private long lastModified;
039
040    private FileLock lock;
041    private RandomAccessFile randomAccessLockFile;
042    private int lockCounter;
043    private final boolean deleteOnUnlock;
044    private volatile boolean locked;
045
046    private static final Logger LOG = LoggerFactory.getLogger(LockFile.class);
047
048    public LockFile(File file, boolean deleteOnUnlock) {
049        this.file = file;
050        this.deleteOnUnlock = deleteOnUnlock;
051    }
052
053    /**
054     * @throws IOException
055     */
056    synchronized public void lock() throws IOException {
057        if (DISABLE_FILE_LOCK) {
058            return;
059        }
060
061        if (lockCounter > 0) {
062            return;
063        }
064
065        IOHelper.mkdirs(file.getParentFile());
066        synchronized (LockFile.class) {
067            if (System.getProperty(getVmLockKey()) != null) {
068                throw new IOException("File '" + file + "' could not be locked as lock is already held for this jvm.");
069            }
070            System.setProperty(getVmLockKey(), new Date().toString());
071        }
072        try {
073            if (lock == null) {
074                randomAccessLockFile = new RandomAccessFile(file, "rw");
075                IOException reason = null;
076                try {
077                    lock = randomAccessLockFile.getChannel().tryLock(0, Math.max(1, randomAccessLockFile.getChannel().size()), false);
078                } catch (OverlappingFileLockException e) {
079                    reason = IOExceptionSupport.create("File '" + file + "' could not be locked.", e);
080                } catch (IOException ioe) {
081                    reason = ioe;
082                }
083                if (lock != null) {
084                    //track lastModified only if we are able to successfully obtain the lock.
085                    randomAccessLockFile.writeLong(System.currentTimeMillis());
086                    randomAccessLockFile.getChannel().force(true);
087                    lastModified = file.lastModified();
088                    lockCounter++;
089                    System.setProperty(getVmLockKey(), new Date().toString());
090                    locked = true;
091                } else {
092                    // new read file for next attempt
093                    closeReadFile();
094                    if (reason != null) {
095                        throw reason;
096                    }
097                    throw new IOException("File '" + file + "' could not be locked.");
098                }
099
100            }
101        } finally {
102            synchronized (LockFile.class) {
103                if (lock == null) {
104                    System.getProperties().remove(getVmLockKey());
105                }
106            }
107        }
108    }
109
110    /**
111     */
112    public void unlock() {
113        if (DISABLE_FILE_LOCK) {
114            return;
115        }
116
117        lockCounter--;
118        if (lockCounter != 0) {
119            return;
120        }
121
122        // release the lock..
123        if (lock != null) {
124            try {
125                lock.release();
126                System.getProperties().remove(getVmLockKey());
127            } catch (Throwable ignore) {
128            }
129            lock = null;
130        }
131        closeReadFile();
132
133        if (locked && deleteOnUnlock) {
134            file.delete();
135        }
136    }
137
138    private String getVmLockKey() throws IOException {
139        return getClass().getName() + ".lock." + file.getCanonicalPath();
140    }
141
142    private void closeReadFile() {
143        // close the file.
144        if (randomAccessLockFile != null) {
145            try {
146                randomAccessLockFile.close();
147            } catch (Throwable ignore) {
148            }
149            randomAccessLockFile = null;
150        }
151    }
152
153    /**
154     * @return true if the lock file's last modified does not match the locally cached lastModified, false otherwise
155     */
156    private boolean hasBeenModified() {
157        boolean modified = false;
158
159        //Create a new instance of the File object so we can get the most up to date information on the file.
160        File localFile = new File(file.getAbsolutePath());
161
162        if (localFile.exists()) {
163            if(localFile.lastModified() != lastModified) {
164                LOG.info("Lock file " + file.getAbsolutePath() + ", locked at " + new Date(lastModified) + ", has been modified at " + new Date(localFile.lastModified()));
165                modified = true;
166            }
167        }
168        else {
169            //The lock file is missing
170            LOG.info("Lock file " + file.getAbsolutePath() + ", does not exist");
171            modified = true;
172        }
173
174        return modified;
175    }
176
177    public boolean keepAlive() {
178        locked = locked && lock != null && lock.isValid() && !hasBeenModified();
179        return locked;
180    }
181
182}