/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb;
import java.util.HashMap;
import org.jboss.monitor.EntityLockMonitor;
import org.jboss.monitor.LockMonitor;
import org.jboss.logging.Logger;
import javax.naming.InitialContext;
/**
* Manages BeanLocks. All BeanLocks have a reference count.
* When the reference count goes to 0, the lock is released from the
* id -> lock mapping.
*
* @author <a href="bill@burkecentral.com">Bill Burke</a>
* @author <a href="marc.fleury@jboss.org">Marc Fleury</a>
* @author Scott.Stark@jboss.org
*/
public class BeanLockManager
{
private static final int NUMBER_OF_INSTANCES=40;
private static Logger log = Logger.getLogger(BeanLockManager.class);
/** Multiple instances of hashMap to diminish locking contentions.
* Rules for accessing this are determined by {@link getHashMap(Object)}*/
private HashMap map[] = new HashMap[NUMBER_OF_INSTANCES];
/** The container this manager reports to */
private Container container;
/** Reentrancy of calls */
private boolean reentrant = false;
private int txTimeout = 5000;
/** The logging trace flag, only set in ctor */
private boolean trace;
public Class lockClass;
protected LockMonitor monitor = null;
private BeanLockManager()
{
for (int i=0;i<map.length;i++)
{
map[i] = new HashMap();
}
}
public BeanLockManager(Container container)
{
this();
this.container = container;
trace = log.isTraceEnabled();
try
{
InitialContext ctx = new InitialContext();
EntityLockMonitor elm = (EntityLockMonitor) ctx.lookup(EntityLockMonitor.JNDI_NAME);
String jndiName = container.getBeanMetaData().getContainerObjectNameJndiName();
monitor = elm.getEntityLockMonitor(jndiName);
}
catch (Exception ignored)
{
// Ignore the lack of an EntityLockMonitor
}
}
public LockMonitor getLockMonitor()
{
return monitor;
}
private HashMap getHashMap(Object id)
{
final int mapInUse = id.hashCode()%NUMBER_OF_INSTANCES;
if (mapInUse>0)
{
return map[mapInUse];
}
else
{
return map[mapInUse*-1];
}
}
/**
* returns the lock associated with the key passed. If there is
* no lock one is created this call also increments the number of
* references interested in Lock.
*
* WARNING: All access to this method MUST have an equivalent
* removeLockRef cleanup call, or this will create a leak in the map,
*/
public BeanLock getLock(Object id)
{
if (id == null)
throw new IllegalArgumentException("Attempt to get lock ref with a null object");
HashMap mapInUse = getHashMap(id);
synchronized (mapInUse)
{
BeanLock lock = (BeanLock) mapInUse.get(id);
if (lock!=null)
{
lock.addRef();
return lock;
}
}
try
{
BeanLock lock2 = (BeanLock)createLock(id);
synchronized(mapInUse)
{
BeanLock lock = (BeanLock) mapInUse.get(id);
// in case of bad luck, this might happen
if (lock != null)
{
lock.addRef();
return lock;
}
mapInUse.put(id, lock2);
lock2.addRef();
return lock2;
}
}
catch (Exception e)
{
// schrouf: should we really proceed with lock object
// in case of exception ??
log.warn("Failed to initialize lock:"+id, e);
throw new RuntimeException (e);
}
}
private BeanLock createLock(Object id) throws Exception
{
BeanLock lock = (BeanLock) lockClass.newInstance();
lock.setId(id);
lock.setTimeout(txTimeout);
lock.setContainer(container);
return lock;
}
public void removeLockRef(Object id)
{
if (id == null)
throw new IllegalArgumentException("Attempt to remove lock ref with a null object");
HashMap mapInUse = getHashMap(id);
synchronized(mapInUse)
{
BeanLock lock = (BeanLock) mapInUse.get(id);
if (lock != null)
{
try
{
lock.removeRef();
if( trace )
log.trace("Remove ref lock:"+lock);
}
finally
{
// schrouf: ALLWAYS ensure proper map lock removal even in case
// of exception within lock.removeRef ! There seems to be a bug
// in the ref counting of QueuedPessimisticEJBLock under certain
// conditions ( lock.ref < 0 should never happen !!! )
if (lock.getRefs() <= 0)
{
Object mapLock = mapInUse.remove(lock.getId());
if( trace )
log.trace("Lock no longer referenced, lock: "+lock);
}
}
}
}
}
public boolean canPassivate(Object id)
{
if (id == null)
throw new IllegalArgumentException("Attempt to passivate with a null object");
HashMap mapInUse = getHashMap(id);
synchronized (mapInUse)
{
BeanLock lock = (BeanLock) mapInUse.get(id);
if (lock == null)
throw new IllegalStateException("Attempt to passivate without a lock");
return (lock.getRefs() <= 1);
}
}
public void setLockCLass(Class lockClass)
{
this.lockClass = lockClass;
}
public void setReentrant(boolean reentrant)
{
this.reentrant = reentrant;
}
public void setContainer(Container container)
{
this.container = container;
}
}