/*
* This is a common dao with basic CRUD operations and is not limited to any
* persistent layer implementation
*
* Copyright (C) 2008 Imran M Yousuf (imyousuf@smartitengineering.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.smartitengineering.dao.common.cache.impl;
import com.smartitengineering.dao.common.cache.Lock;
import com.smartitengineering.dao.common.cache.Mutex;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
*
* @author imyousuf
*/
public final class MutexImpl<KeyType extends Object>
implements Mutex<KeyType> {
private Map<KeyType, Semaphore> lockMap;
private Map<Thread, Lock<KeyType>> threadLocks;
private Map<KeyType, Long> idleDuration;
private int concurrentPermits;
private long defaultWaitInMillisecond;
private final ExpiryPolicy expiryPolicy;
private long expirationDuration;
private Timer timer;
protected MutexImpl(ExpiryPolicy expiryPolicy, int concurrentPermissions) {
if (expiryPolicy == null) {
expiryPolicy = ExpiryPolicy.SIMPLE_TIME_TRIGGERED_EXPIRATION;
}
idleDuration = new ConcurrentHashMap<KeyType, Long>();
lockMap = new MonitorableMap<KeyType, Semaphore>(new ConcurrentHashMap<KeyType, Semaphore>(),
new IdleTimeMapMonitor());
threadLocks = new WeakHashMap<Thread, Lock<KeyType>>();
this.expiryPolicy = expiryPolicy;
setConcurrentPermissions(concurrentPermissions);
setDefaultWaitTimeoutInMillisecond(2000);
setExpiryPeriod(30000);
initExpirationExecutor();
}
@Override
public Lock acquire(KeyType key)
throws InterruptedException {
return acquire(key, getDefaultWaitTimeoutInMillisecond());
}
@Override
public int getConcurrentPermissions() {
return concurrentPermits;
}
@Override
public void setConcurrentPermissions(int permissions) {
concurrentPermits = permissions;
}
@Override
public Lock<KeyType> acquire(KeyType key,
long waitTimeout)
throws InterruptedException {
if (key == null) {
return null;
}
final int eConcurrentPermits = getConcurrentPermissions();
Lock eLock = null;
if (lockMap.containsKey(key)) {
boolean acquired = lockMap.get(key).tryAcquire(1,
getDefaultWaitTimeoutInMillisecond(), TimeUnit.MILLISECONDS);
if (acquired) {
eLock = CacheAPIFactory.getLock(1, key);
}
}
else {
boolean eAdded;
synchronized (this) {
eAdded = lockMap.containsKey(key);
if (!eAdded) {
Semaphore eSemaphore = new Semaphore(eConcurrentPermits,
true);
lockMap.put(key, eSemaphore);
int eLockedPermissions = eSemaphore.drainPermits();
eLock = CacheAPIFactory.getLock(eLockedPermissions, key);
}
}
if (eAdded) {
boolean acquired = lockMap.get(key).tryAcquire(1,
getDefaultWaitTimeoutInMillisecond(), TimeUnit.MILLISECONDS);
if (acquired) {
eLock = CacheAPIFactory.getLock(1, key);
}
}
}
if (eLock != null) {
threadLocks.put(Thread.currentThread(), eLock);
}
return eLock;
}
@Override
public void release(Lock<KeyType> lock) {
if (lock == null) {
lock = getAttainedLockForCurrentThread();
if (lock == null) {
return;
}
}
if (lockMap.containsKey(lock.getKey())) {
KeyType eKey = lock.getKey();
Semaphore eSemaphore = lockMap.get(eKey);
int ePermissions = lock.getNumberOfPermissionsAttained();
eSemaphore.release(ePermissions);
}
}
@Override
public void setDefaultWaitTimeoutInMillisecond(long defaultWait) {
defaultWaitInMillisecond = defaultWait;
}
@Override
public long getDefaultWaitTimeoutInMillisecond() {
return defaultWaitInMillisecond;
}
@Override
public Lock<KeyType> getAttainedLockForCurrentThread() {
return threadLocks.get(Thread.currentThread());
}
@Override
public void expire(KeyType key) {
lockMap.remove(key);
}
@Override
public void setExpiryPeriod(long durationInMillisecond) {
expirationDuration = durationInMillisecond;
}
@Override
public long getExpiryPeriod() {
return expirationDuration;
}
@Override
public ExpiryPolicy getExpiryPolicy() {
return expiryPolicy;
}
protected void initExpirationExecutor() {
if (timer != null) {
timer.cancel();
}
timer = new Timer();
timer.schedule(new ExpireExecutor(), expirationDuration,
expirationDuration / 4);
}
private class IdleTimeMapMonitor
implements MapMonitor<KeyType, Semaphore> {
@Override
public void notifyPut(KeyType key,
Semaphore value) {
idleDuration.put(key, System.currentTimeMillis());
}
@Override
public void notifyGet(KeyType key) {
if (ExpiryPolicy.IDLE_FOR_DURATION.equals(expiryPolicy)) {
idleDuration.put((KeyType) key, System.currentTimeMillis());
}
}
@Override
public void notifyPutAll(
Map<? extends KeyType, ? extends Semaphore> values) {
throw new UnsupportedOperationException();
}
@Override
public void notifyRemove(KeyType key) {
idleDuration.remove(key);
}
}
private class ExpireExecutor
extends TimerTask
implements Runnable {
@Override
public void run() {
Set<Map.Entry<KeyType, Long>> entries = idleDuration.entrySet();
for (Map.Entry<KeyType, Long> entry : entries) {
if ((System.currentTimeMillis() -
entry.getValue().longValue()) >=
getExpiryPeriod()) {
expire(entry.getKey());
}
}
}
}
}