/*
*
* This is a simple Content Management System (CMS)
* Copyright (C) 2011 Imran M Yousuf (imyousuf@smartitengineering.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.smartitengineering.cms.spi.lock.impl.distributed;
import com.smartitengineering.cms.api.factory.write.Lock;
import com.smartitengineering.cms.spi.lock.Key;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author imyousuf
*/
public class ZKLock implements Lock, Watcher, LockTimeoutListener {
protected final ZKConfig config;
protected final Key key;
protected String localLockId;
protected final ReentrantLock lock = new ReentrantLock();
protected final transient Logger logger = LoggerFactory.getLogger(getClass());
protected ZKLock(ZKConfig config, Key key) {
this.config = config;
this.key = key;
}
public boolean isLockOwned() {
lock.lock();
try {
return StringUtils.isNotBlank(localLockId);
}
finally {
lock.unlock();
}
}
public void lock() {
boolean locked = false;
try {
locked = tryLock(1, TimeUnit.DAYS);
}
catch (Exception ex) {
logger.warn(ex.getMessage(), ex);
}
if (!locked) {
throw new IllegalStateException("Could not attain lock!");
}
}
public boolean tryLock() {
try {
return tryLock(-1, TimeUnit.DAYS);
}
catch (Exception ex) {
logger.warn(ex.getMessage(), ex);
return false;
}
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if (logger.isDebugEnabled()) {
logger.debug("Attempting to attain lock with wait option of " + time + " " + unit.name());
}
if (isLockOwned()) {
return true;
}
lock.lock();
try {
final long waitInMilliSeconds = time > 0 ? TimeUnit.MILLISECONDS.convert(time, unit) : time;
final long start = System.currentTimeMillis();
String lockId = config.getRegistrar().lock(key, this, waitInMilliSeconds);
final long availableMillisForRemoteLock = waitInMilliSeconds - (System.currentTimeMillis() - start);
return tryRemoteLock(lockId, availableMillisForRemoteLock);
}
finally {
lock.unlock();
}
}
protected boolean tryRemoteLock(String lockId, final long availableMillisForRemoteLock) throws IllegalStateException,
InterruptedException {
final LocalLockRegistrar registrar = config.getRegistrar();
final ZooKeeper keeper = config.getZooKeeper();
final String node = getNode();
if (logger.isDebugEnabled()) {
logger.debug("Attained local lock " + lockId);
}
try {
if (StringUtils.isNotBlank(lockId)) {
keeper.create(node, org.apache.commons.codec.binary.StringUtils.getBytesUtf8(config.getNodeId()),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
keeper.exists(node, this);
localLockId = lockId;
return true;
}
else {
return false;
}
}
catch (KeeperException ke) {
if (ke.code() == KeeperException.Code.NODEEXISTS) {
logger.debug("Lock alrady exists!");
if (availableMillisForRemoteLock > 0) {
synchronized (ZKLock.this) {
try {
keeper.exists(node, new Watcher() {
public void process(WatchedEvent event) {
if (event.getType().equals(Event.EventType.NodeDeleted)) {
synchronized (ZKLock.this) {
ZKLock.this.notifyAll();
}
}
}
});
}
catch (Exception ex) {
logger.error("Could not attach watcher", ex);
}
final long remoteStart = System.currentTimeMillis();
ZKLock.this.wait(availableMillisForRemoteLock);
return tryRemoteLock(lockId, availableMillisForRemoteLock - (System.currentTimeMillis() - remoteStart));
}
}
else {
registrar.unlock(key, lockId);
return false;
}
}
else {
logger.error(ke.getMessage(), ke);
throw new IllegalStateException(ke);
}
}
catch (Exception ex) {
registrar.unlock(key, lockId);
logger.error(ex.getMessage(), ex);
throw new IllegalStateException(ex);
}
}
public void unlock() {
if (isLockOwned()) {
lock.lock();
try {
ZooKeeper keeper = config.getZooKeeper();
final String node = getNode();
Stat stat = keeper.exists(node, false);
String nodeId = org.apache.commons.codec.binary.StringUtils.newStringUtf8(keeper.getData(node, false, stat));
if (config.getNodeId().equals(nodeId)) {
keeper.delete(node, stat.getVersion());
config.getRegistrar().unlock(key, localLockId);
}
else {
logger.error("Lock is owned but remote lock is not owned! System inconsistency! Releasing local lock.");
config.getRegistrar().unlock(key, localLockId);
throw new IllegalStateException("Lock is owned but remote lock is not owned! Released local lock!");
}
localLockId = null;
}
catch (Exception ex) {
logger.error(ex.getMessage(), ex);
throw new IllegalStateException(ex);
}
finally {
lock.unlock();
}
}
}
public void process(WatchedEvent event) {
if (!event.getType().equals(Event.EventType.NodeDeleted)) {
logger.warn("Remote lock changed unexpectedly! This may cause system inconsistency");
}
}
public void lockTimedOut(Key key) {
unlock();
}
protected String getNode() {
return new StringBuilder(config.getRootNode()).append('/').append(key.getKeyStringRep()).toString();
}
}