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