package org.exist.storage.lock;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.EXistException;
import org.exist.storage.BrokerPool;
import org.exist.storage.BrokerPoolService;
import org.exist.storage.BrokerPoolServiceException;
import org.exist.util.Configuration;
import org.exist.util.ReadOnlyException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
/**
* A Simple Service wrapper for {@link FileLock}
*/
public class FileLockService implements BrokerPoolService {
private final static Logger LOG = LogManager.getLogger(FileLockService.class);
private final String lockFileName;
private final String confDirPropName;
private final String defaultDirName;
private Path dataDir;
private boolean writable;
private AtomicReference<FileLock> dataLock = new AtomicReference<>();
public FileLockService(final String lockFileName, final String confDirPropName, final String defaultDirName) {
this.lockFileName = lockFileName;
this.confDirPropName = confDirPropName;
this.defaultDirName = defaultDirName;
}
@Override
public void configure(final Configuration configuration) throws BrokerPoolServiceException {
dataDir = Optional.ofNullable((Path) configuration.getProperty(confDirPropName))
.orElse(Paths.get(defaultDirName));
if(!Files.exists(dataDir)) {
try {
//TODO : shall we force the creation ? use a parameter to decide ?
LOG.info("Data directory '" + dataDir.toAbsolutePath().toString() + "' does not exist. Creating one ...");
Files.createDirectories(dataDir);
} catch(final SecurityException | IOException e) {
throw new BrokerPoolServiceException("Cannot create data directory '" + dataDir.toAbsolutePath().toString() + "'", e);
}
}
//Save it for further use.
configuration.setProperty(confDirPropName, dataDir);
if(!Files.isWritable(dataDir)) {
LOG.warn("Cannot write to data directory: " + dataDir.toAbsolutePath().toString());
writable = false;
} else {
writable = true;
}
}
@Override
public void prepare(final BrokerPool brokerPool) throws BrokerPoolServiceException {
// try to acquire lock on the data dir
final FileLock fileLock = new FileLock(brokerPool, dataDir.resolve(lockFileName));
this.dataLock.compareAndSet(null, fileLock);
try {
final boolean locked = fileLock.tryLock();
if(!locked) {
throw new BrokerPoolServiceException(new EXistException("The directory seems to be locked by another " +
"database instance. Found a valid lock file: " + fileLock.getFile().toAbsolutePath().toString()));
}
} catch(final ReadOnlyException e) {
LOG.warn(e);
writable = false;
}
if(!writable) {
brokerPool.setReadOnly();
}
}
/**
* Is this directory managed by this File Lock read-only?
*
* @return true if the directory is read-only
*/
public boolean isReadOnly() {
return !writable;
}
public Path getFile() {
final FileLock fileLock = dataLock.get();
if(fileLock == null) {
return null;
} else {
return fileLock.getFile();
}
}
//TODO(AR) instead we should implement a BrokerPoolService#shutdown() and BrokerPoolServicesManager#shutdown()
public void release() {
final FileLock fileLock = dataLock.get();
if(fileLock != null) {
fileLock.release();
}
dataLock.compareAndSet(fileLock, null);
}
}