package org.jboss.ejb.plugins;
import java.lang.reflect.Method;
import javax.ejb.EJBException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import org.jboss.ejb.BeanLock;
import org.jboss.ejb.Container;
import org.jboss.ejb.EntityCache;
import org.jboss.ejb.EntityContainer;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.GlobalTxEntityMap;
import org.jboss.invocation.Invocation;
import org.jboss.metadata.ConfigurationMetaData;
import org.jboss.util.NestedRuntimeException;
public class EntitySynchronizationInterceptor
extends AbstractInterceptor
{
private Thread vcrThread;
protected int commitOption;
protected long optionDRefreshRate;
protected EntityContainer container;
public Container getContainer()
{
return container;
}
public void setContainer(Container container)
{
this.container = (EntityContainer) container;
}
public void create()
throws Exception
{
try
{
ConfigurationMetaData configuration = container.getBeanMetaData().getContainerConfiguration();
commitOption = configuration.getCommitOption();
optionDRefreshRate = configuration.getOptionDRefreshRate();
}
catch(Exception e)
{
log.warn(e.getMessage());
}
}
public void start()
{
try
{
if (commitOption == ConfigurationMetaData.D_COMMIT_OPTION)
{
ValidContextsRefresher vcr = new ValidContextsRefresher(optionDRefreshRate);
vcrThread = new Thread(vcr);
vcrThread.start();
}
}
catch(Exception e)
{
vcrThread = null;
log.warn("problem starting valid contexts refresher thread", e);
} }
public void stop()
{
if(vcrThread != null)
{
Thread temp = vcrThread;
vcrThread = null;
temp.interrupt();
}
}
protected Synchronization createSynchronization(Transaction tx, EntityEnterpriseContext ctx)
{
return new InstanceSynchronization(tx, ctx);
}
protected void register(EntityEnterpriseContext ctx, Transaction tx)
{
boolean trace = log.isTraceEnabled();
if(trace)
log.trace("register, ctx=" + ctx + ", tx=" + tx);
EntityContainer ctxContainer = null;
try
{
ctxContainer = (EntityContainer)ctx.getContainer();
if(!ctx.hasTxSynchronization())
{
Synchronization synch = createSynchronization(tx, ctx);
tx.registerSynchronization(synch);
ctx.hasTxSynchronization(true);
}
if(!ctxContainer.isReadOnly())
{
ctx.getTxAssociation().scheduleSync(tx, ctx);
}
}
catch(RollbackException e)
{
synchronized(ctx)
{
ctx.setValid(false);
ctx.hasTxSynchronization(false);
ctx.setTransaction(null);
ctx.setTxAssociation(GlobalTxEntityMap.NONE);
}
throw new EJBException(e);
}
catch(Throwable t)
{
ctx.hasTxSynchronization(false);
ctx.setTxAssociation(GlobalTxEntityMap.NONE);
if(t instanceof RuntimeException)
throw (RuntimeException)t;
else if(t instanceof Error)
throw (Error)t;
else if(t instanceof Exception)
throw new EJBException((Exception)t);
else
throw new NestedRuntimeException(t);
}
}
public Object invokeHome(Invocation mi) throws Exception
{
EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
Transaction tx = mi.getTransaction();
Object rtn = getNext().invokeHome(mi);
if(ctx.getId() != null)
{
ctx.setValid(true);
if(tx != null)
{
BeanLock lock = container.getLockManager().getLock(ctx.getCacheKey());
try
{
lock.schedule(mi);
register(ctx, tx); lock.endInvocation(mi);
}
finally
{
container.getLockManager().removeLockRef(lock.getId());
}
}
}
return rtn;
}
public Object invoke(Invocation mi) throws Exception
{
EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
Transaction tx = mi.getTransaction();
if(log.isTraceEnabled())
log.trace("invoke called for ctx " + ctx + ", tx=" + tx);
if(!ctx.isValid())
{
container.getPersistenceManager().loadEntity(ctx);
ctx.setValid(true);
}
boolean didSetReadOnly = false;
if(!ctx.isReadOnly() &&
(container.isReadOnly() ||
container.getBeanMetaData().isMethodReadOnly(mi.getMethod())))
{
ctx.setReadOnly(true);
didSetReadOnly = true;
}
try
{
if(tx != null && tx.getStatus() != Status.STATUS_NO_TRANSACTION)
{
boolean isReadOnly = container.isReadOnly();
if(isReadOnly == false)
{
Method method = mi.getMethod();
if(method != null)
isReadOnly = container.getBeanMetaData().isMethodReadOnly(method.getName());
}
try
{
if(isReadOnly == false)
{
register(ctx, tx);
}
Object retVal = getNext().invoke(mi);
if(isReadOnly == false)
{
register(ctx, tx);
}
return retVal;
}
finally
{
if(isReadOnly && ctx.hasTxSynchronization() == false)
{
switch(commitOption)
{
case ConfigurationMetaData.B_COMMIT_OPTION:
ctx.setValid(false);
break;
case ConfigurationMetaData.C_COMMIT_OPTION:
try
{
if(ctx.getId() != null)
container.getInstanceCache().remove(ctx.getId());
}
catch(Exception e)
{
log.debug("Exception releasing context", e);
}
break;
}
}
}
}
else
{
try
{
Object result = getNext().invoke(mi);
if(ctx.getId() != null && !container.isReadOnly())
{
container.invokeEjbStore(ctx);
container.storeEntity(ctx);
}
return result;
}
catch(Exception e)
{
ctx.setValid(false);
throw e;
}
finally
{
switch(commitOption)
{
case ConfigurationMetaData.B_COMMIT_OPTION:
ctx.setValid(false);
break;
case ConfigurationMetaData.C_COMMIT_OPTION:
try
{
if(ctx.getId() != null)
container.getInstanceCache().release(ctx);
}
catch(Exception e)
{
log.debug("Exception releasing context", e);
}
break;
}
}
}
}
finally
{
if(didSetReadOnly)
{
ctx.setReadOnly(false);
}
}
}
protected class InstanceSynchronization
implements Synchronization
{
protected Transaction tx;
protected EntityEnterpriseContext ctx;
protected BeanLock lock;
InstanceSynchronization(Transaction tx, EntityEnterpriseContext ctx)
{
this.tx = tx;
this.ctx = ctx;
this.lock = container.getLockManager().getLock(ctx.getCacheKey());
}
public void beforeCompletion()
{
}
public void afterCompletion(int status)
{
boolean trace = log.isTraceEnabled();
ClassLoader oldCl = SecurityActions.getContextClassLoader();
boolean setCl = !oldCl.equals(container.getClassLoader());
if(setCl)
{
SecurityActions.setContextClassLoader(container.getClassLoader());
}
lock.sync();
ctx.hasTxSynchronization(false);
ctx.setTxAssociation(GlobalTxEntityMap.NONE);
ctx.setTransaction(null);
try
{
try
{
if(status == Status.STATUS_ROLLEDBACK)
{
container.getInstanceCache().remove(ctx.getCacheKey());
}
else
{
switch(commitOption)
{
case ConfigurationMetaData.A_COMMIT_OPTION:
case ConfigurationMetaData.D_COMMIT_OPTION:
ctx.setValid(true);
break;
case ConfigurationMetaData.B_COMMIT_OPTION:
ctx.setValid(false);
break;
case ConfigurationMetaData.C_COMMIT_OPTION:
try
{
if(ctx.getId() != null)
{
container.getInstanceCache().remove(ctx.getId());
container.getPersistenceManager().passivateEntity(ctx);
}
container.getInstancePool().free(ctx);
}
catch(Exception e)
{
log.debug("Exception releasing context", e);
}
break;
}
}
}
finally
{
if(trace)
log.trace("afterCompletion, clear tx for ctx=" + ctx + ", tx=" + tx);
lock.endTransaction(tx);
if(trace)
log.trace("afterCompletion, sent notify on TxLock for ctx=" + ctx);
}
} finally
{
lock.releaseSync();
container.getLockManager().removeLockRef(lock.getId());
if(setCl)
{
SecurityActions.setContextClassLoader(oldCl);
}
}
}
}
class ValidContextsRefresher implements Runnable
{
private long refreshRate;
public ValidContextsRefresher(long refreshRate)
{
this.refreshRate = refreshRate;
}
public void run()
{
while(vcrThread != null)
{
if(log.isTraceEnabled())
{
log.trace("Flushing the valid contexts");
}
try
{
Thread.sleep(refreshRate);
}
catch(InterruptedException ignored)
{
}
if(container != null)
{
EntityCache cache = (EntityCache)container.getInstanceCache();
if(cache != null)
cache.flush();
}
}
}
}
}