/*
* 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.plugins;
import org.jboss.ejb.EnterpriseContext;
import org.jboss.ejb.Container;
import org.jboss.ejb.EntityContainer;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.BeanLock;
import org.jboss.ejb.EntityCache;
import org.jboss.ejb.txtimer.EJBTimerService;
import org.jboss.tm.TransactionLocal;
import org.jboss.logging.Logger;
import java.rmi.RemoteException;
import java.rmi.NoSuchObjectException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import javax.ejb.TimerService;
import javax.management.ObjectName;
/**
* Per transaction instance cache.
*
* @jmx:mbean
*
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @author Galder Zamarreño
* @version <tt>$Revision: 88748 $</tt>
*/
public class PerTxEntityInstanceCache
implements EntityCache, PerTxEntityInstanceCacheMBean
{
private static final Logger log = Logger.getLogger(PerTxEntityInstanceCache.class);
// per container tx instance cache
private final TransactionLocal txLocalCache = new TransactionLocal()
{
protected Object initialValue()
{
return new HashMap();
}
};
private EntityContainer container;
// EntityCache implementation
public Object createCacheKey(Object id)
{
return id;
}
public EnterpriseContext get(Object id) throws RemoteException, NoSuchObjectException
{
if(id == null) throw new IllegalArgumentException("Can't get an object with a null key");
Map cache = getLocalCache();
EntityEnterpriseContext instance = (EntityEnterpriseContext) cache.get(id);
if(instance == null)
{
try
{
// acquire
instance = (EntityEnterpriseContext) container.getInstancePool().get();
// set key
instance.setId(id);
instance.setCacheKey(id);
// activate
container.getPersistenceManager().activateEntity(instance);
// insert
cache.put(id, instance);
}
catch(Throwable x)
{
throw new NoSuchObjectException(x.getMessage());
}
}
return instance;
}
public void insert(EnterpriseContext instance)
{
if(instance == null) throw new IllegalArgumentException("Can't insert a null object in the cache");
EntityEnterpriseContext entity = (EntityEnterpriseContext) instance;
getLocalCache().put(entity.getCacheKey(), instance);
}
public void release(EnterpriseContext instance)
{
if(instance == null) throw new IllegalArgumentException("Can't release a null object");
tryToPassivate(instance);
}
public void remove(Object id)
{
// By default, commit option C is used with this cache which means that cache instances are
// removed immediately without trying to passivate, hence, remove associated timer if empty.
removeTimerServiceIfAllCancelledOrExpired(id);
getLocalCache().remove(id);
}
public boolean isActive(Object id)
{
return getLocalCache().containsKey(id);
}
public long getCacheSize()
{
return 0;
}
public void flush()
{
}
// ContainerPlugin implementation
public void setContainer(Container con)
{
this.container = (EntityContainer) con;
}
// Service implementation
public void create() throws Exception
{
}
public void start() throws Exception
{
}
public void stop()
{
}
public void destroy()
{
}
// Protected
protected void tryToPassivate(EnterpriseContext instance)
{
Object id = instance.getId();
if(id != null)
{
BeanLock lock = container.getLockManager().getLock(id);
try
{
lock.sync();
if(canPassivate(instance))
{
try
{
remove(id);
EntityEnterpriseContext entity = (EntityEnterpriseContext) instance;
container.getPersistenceManager().passivateEntity(entity);
container.getInstancePool().free(instance);
}
catch(Exception ignored)
{
log.warn("failed to passivate, id=" + id, ignored);
}
}
else
{
log.warn("Unable to passivate due to ctx lock, id=" + id);
}
}
finally
{
lock.releaseSync();
container.getLockManager().removeLockRef(id);
}
}
}
protected boolean canPassivate(EnterpriseContext ctx)
{
if(ctx.isLocked())
{
// The context is in the interceptor chain
return false;
}
if(ctx.getTransaction() != null)
{
return false;
}
Object key = ((EntityEnterpriseContext) ctx).getCacheKey();
return container.getLockManager().canPassivate(key);
}
protected void removeTimerServiceIfAllCancelledOrExpired(final Object id)
{
final boolean trace = log.isTraceEnabled();
if (trace)
{
log.trace("Check whether all timers are cancelled or expired for this entity: " + id);
}
final EJBTimerService service = container.getTimerService();
final ObjectName containerId = container.getJmxName();
AccessController.doPrivileged(new PrivilegedAction<Object>()
{
public Object run()
{
TimerService timerService = service.getTimerService(containerId, id);
if (timerService != null && timerService.getTimers().isEmpty())
{
// Assuming that active timers do not include cancelled or expired ones.
if (trace)
{
log.trace("No active timers available for " + containerId + " and primary key " + id);
}
service.removeTimerService(containerId, id);
}
return null;
}
});
}
// Private
private Map getLocalCache()
{
return (Map) txLocalCache.get();
}
}