/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.core.util;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import javax.jcr.RepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Exclusive lock on a repository home directory. This class encapsulates
* collective experience on how to acquire an exclusive lock on a given
* directory. The lock is expected to be exclusive both across process
* boundaries and within a single JVM. The lock mechanism must also work
* consistently on a variety of operating systems and JVM implementations.
*
* @see <a href="https://issues.apache.org/jira/browse/JCR-213">JCR-213</a>
* @see <a href="https://issues.apache.org/jira/browse/JCR-233">JCR-233</a>
* @see <a href="https://issues.apache.org/jira/browse/JCR-254">JCR-254</a>
* @see <a href="https://issues.apache.org/jira/browse/JCR-912">JCR-912</a>
* @see <a href="https://issues.apache.org/jira/browse/JCR-933">JCR-933</a>
*/
public class RepositoryLock implements RepositoryLockMechanism {
/**
* Name of the lock file within a directory.
*/
private static final String LOCK = ".lock";
/**
* Logger instance.
*/
private static final Logger LOG =
LoggerFactory.getLogger(RepositoryLock.class);
/**
* The locked directory.
*/
private File directory;
/**
* The lock file within the given directory.
*/
private File file;
/**
* The random access file.
*/
private RandomAccessFile randomAccessFile;
/**
* Unique identifier (canonical path name) of the locked directory.
* Used to ensure exclusive locking within a single JVM.
*
* @see https://issues.apache.org/jira/browse/JCR-933
*/
private String identifier;
/**
* The file lock. Used to ensure exclusive locking across process boundaries.
*
* @see https://issues.apache.org/jira/browse/JCR-233
*/
private FileLock lock;
public RepositoryLock() {
// used by the factory
}
/**
* Create a new RepositoryLock object and initialize it.
* @deprecated
* This constructor is deprecated; use the default constructor
* and {@link #init(String)} instead.
*
* @param path directory path
* @throws RepositoryException if the canonical path of the directory
* can not be determined
*/
public RepositoryLock(String path) throws RepositoryException {
init(path);
}
/**
* Initialize the instance for the given directory path. The lock still needs to be
* explicitly acquired using the {@link #acquire()} method.
*
* @param path directory path
* @throws RepositoryException if the canonical path of the directory
* can not be determined
*/
public void init(String path) throws RepositoryException {
try {
directory = new File(path).getCanonicalFile();
file = new File(directory, LOCK);
identifier =
(RepositoryLock.class.getName() + ":" + directory.getPath())
.intern();
lock = null;
} catch (IOException e) {
throw new RepositoryException(
"Unable to determine canonical path of " + path, e);
}
}
/**
* Lock the repository home.
*
* @throws RepositoryException if the repository lock can not be acquired
*/
public void acquire() throws RepositoryException {
if (file.exists()) {
LOG.warn("Existing lock file " + file + " detected."
+ " Repository was not shut down properly.");
}
try {
tryLock();
} catch (RepositoryException e) {
closeRandomAccessFile();
throw e;
}
}
/**
* Try to lock the random access file.
*
* @throws RepositoryException
*/
private void tryLock() throws RepositoryException {
try {
randomAccessFile = new RandomAccessFile(file, "rw");
lock = randomAccessFile.getChannel().tryLock();
} catch (IOException e) {
throw new RepositoryException(
"Unable to create or lock file " + file, e);
} catch (OverlappingFileLockException e) {
// JCR-912: OverlappingFileLockException with JRE 1.6
throw new RepositoryException(
"The repository home " + directory + " appears to be in use"
+ " since the file named " + file.getName()
+ " is already locked by the current process.");
}
if (lock == null) {
throw new RepositoryException(
"The repository home " + directory + " appears to be in use"
+ " since the file named " + file.getName()
+ " is locked by another process.");
}
// JCR-933: due to a bug in java 1.4/1.5 on *nix platforms
// it's possible that java.nio.channels.FileChannel.tryLock()
// returns a non-null FileLock object although the lock is already
// held by *this* jvm process
synchronized (identifier) {
if (null != System.getProperty(identifier)) {
// note that the newly acquired (redundant) file lock
// is deliberately *not* released because this could
// potentially cause, depending on the implementation,
// the previously acquired lock(s) to be released
// as well
throw new RepositoryException(
"The repository home " + directory + " appears to be"
+ " already locked by the current process.");
} else {
try {
System.setProperty(identifier, identifier);
} catch (SecurityException e) {
LOG.warn("Unable to set system property: " + identifier, e);
}
}
}
}
/**
* Close the random access file if it is open, and set it to null.
*/
private void closeRandomAccessFile() {
if (randomAccessFile != null) {
try {
randomAccessFile.close();
} catch (IOException e) {
LOG.warn("Unable to close the random access file " + file, e);
}
randomAccessFile = null;
}
}
/**
* Releases repository lock.
*/
public void release() {
if (lock != null) {
try {
FileChannel channel = lock.channel();
lock.release();
channel.close();
} catch (IOException e) {
// ignore
}
lock = null;
closeRandomAccessFile();
}
if (!file.delete()) {
LOG.warn("Unable to delete repository lock file");
}
// JCR-933: see #acquire()
synchronized (identifier) {
try {
System.getProperties().remove(identifier);
} catch (SecurityException e) {
LOG.error("Unable to clear system property: " + identifier, e);
}
}
}
}