package org.jboss.cache.invocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheImpl;
import org.jboss.cache.CacheStatus;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.interceptors.Interceptor;
import org.jboss.cache.marshall.MethodCall;
/**
* The JBoss Cache hand-wired interceptor stack. A "minimal" AOP framework which uses delegation through an
* interceptor chain rather than any bytecode manipulation.
* <p/>
* This class provides some generic behaviour such as the construction of an {@link org.jboss.cache.InvocationContext}
* which is passed up the interceptor chain.
* <p/>
*
* @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @see org.jboss.cache.interceptors.Interceptor
* @see org.jboss.cache.InvocationContext
* @since 2.1.0
*/
public abstract class AbstractInvocationDelegate
{
protected Interceptor interceptorChain;
protected CacheImpl cache;
protected Log log = LogFactory.getLog(AbstractInvocationDelegate.class);
protected Configuration configuration;
protected boolean originLocal = true;
protected InvocationContextContainer invocationContextContainer;
/**
* Used by the interceptor chain factory to inject dependencies.
*
* @param interceptorChain interceptor chain to pass calls up
* @param cache cache instance
* @param configuration configuration for the cache
*/
@Inject
private void initialise(Interceptor interceptorChain, CacheImpl cache, Configuration configuration, InvocationContextContainer invocationContextContainer)
{
this.interceptorChain = interceptorChain;
this.cache = cache;
this.configuration = configuration;
this.invocationContextContainer = invocationContextContainer;
}
/**
* Passes a method call up the interceptor chain.
*
* @param call methodcall to pass
* @return an Object, the generic return type for the interceptors.
* @throws Throwable in the event of problems
*/
protected Object invoke(MethodCall call) throws CacheException
{
assertIsConstructed();
InvocationContext ctx = invocationContextContainer.get();
return invoke(call, ctx.getOptionOverrides().isSkipCacheStatusCheck(), ctx);
}
/**
* Passes a method call up the interceptor chain, optionally allowing you to skip cache status checks.
*
* @param call methodcall to pass
* @return an Object, the generic return type for the interceptors.
* @throws Throwable in the event of problems
*/
protected Object invoke(MethodCall call, boolean skipCacheStatusCheck) throws CacheException
{
assertIsConstructed();
return invoke(call, skipCacheStatusCheck, invocationContextContainer.get());
}
protected void assertIsConstructed()
{
if (invocationContextContainer == null) throw new IllegalStateException("The cache has been destroyed!");
}
private Object invoke(MethodCall call, boolean skipCacheStatusCheck, InvocationContext ctx) throws CacheException
{
if (!cache.getCacheStatus().allowInvocations() && !skipCacheStatusCheck)
{
// only throw an exception if this is a locally originating call - JBCACHE-1179
if (originLocal)
{
throw new IllegalStateException("Cache not in STARTED state!");
}
else
{
if (cache.getCacheStatus() == CacheStatus.STARTING)
{
try
{
blockUntilCacheStarts();
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
else
{
log.warn("Received a remote call but the cache is not in STARTED state - ignoring call.");
return null;
}
}
}
MethodCall oldCall = null;
try
{
// check if we had a method call lurking around
oldCall = ctx.getMethodCall();
ctx.setMethodCall(call);
// only set this if originLocal is EXPLICITLY passed in as FALSE. Otherwise leave it as a default.
if (!originLocal) ctx.setOriginLocal(false);
return interceptorChain.invoke(ctx);
}
catch (CacheException e)
{
throw e;
}
catch (RuntimeException e)
{
throw e;
}
catch (Throwable t)
{
throw new CacheException(t);
}
finally
{
if (!originLocal) ctx.setOriginLocal(true);
// reset old method call
if (ctx == null) ctx = invocationContextContainer.get();
ctx.setMethodCall(oldCall);
}
}
/**
* Blocks until the current cache instance is in it's {@link org.jboss.cache.CacheStatus#STARTED started} phase. Blocks
* for up to {@link org.jboss.cache.config.Configuration#getStateRetrievalTimeout()} milliseconds, throwing an IllegalStateException
* if the cache doesn't reach this state even after this maximum wait time.
*
* @throws InterruptedException if interrupted while waiting
* @throws IllegalStateException if even after waiting the cache has not started.
*/
private void blockUntilCacheStarts() throws InterruptedException, IllegalStateException
{
int pollFrequencyMS = 100;
long startupWaitTime = configuration.getStateRetrievalTimeout();
long giveUpTime = System.currentTimeMillis() + startupWaitTime;
while (System.currentTimeMillis() < giveUpTime)
{
if (cache.getCacheStatus().allowInvocations()) break;
Thread.sleep(pollFrequencyMS);
}
// check if we have started.
if (!cache.getCacheStatus().allowInvocations())
throw new IllegalStateException("Cache not in STARTED state, even after waiting " + configuration.getStateRetrievalTimeout() + " millis.");
}
}