package com.tesora.dve.cas.impl;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import com.tesora.dve.cas.AtomicState;
import com.tesora.dve.cas.CopyOnInvoke;
import com.tesora.dve.cas.EngineControl;
import org.apache.log4j.Logger;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
public class StateEngine<S> implements EngineControl<S>{
static final Logger log = Logger.getLogger(StateEngine.class);
static ThreadLocal<StateEngine.EngineInvocation> invocationOnThisThread = new ThreadLocal<StateEngine.EngineInvocation>();
protected String name;
protected AtomicState<S> atomicState;
protected Class<S> stateClass;
AtomicLong stateChangeOK = new AtomicLong(0L);
AtomicLong stateChangeCollide = new AtomicLong(0L);
public StateEngine(String name,Class<S> stateClass) {
this(name,stateClass,new SimpleAtomicState<S>(stateClass,null));
}
public StateEngine(String name,Class<S> stateClass, AtomicState<S> stateHolder) {
if (name == null)
throw new NullPointerException("name cannot be null");
if (stateClass == null)
throw new NullPointerException("state class cannot be null");
if (stateHolder == null)
throw new NullPointerException("state holder cannot be null");
this.name = name;
this.stateClass = stateClass;
this.atomicState = stateHolder;
}
@SuppressWarnings("unchecked")
void verifyLegalProxy(Class clazz) {
if (! clazz.isInterface() || !clazz.isAssignableFrom(this.stateClass) )
throw new IllegalArgumentException("requested proxy class is not an interface implemented by state class "+ this.stateClass);
}
@Override
public void redispatch() {
if (invocationOnThisThread.get() == null){
throw new IllegalStateException("Cannot invoke redispatch from outside a state machine invocation");
} else {
invocationOnThisThread.get().doRetry();
}
}
@Override
public void clearRedispatch() {
if (invocationOnThisThread.get() == null){
throw new IllegalStateException("Cannot invoke redispatch from outside a state machine invocation");
} else {
invocationOnThisThread.get().doClearRetry();
}
}
@Override
public <P> P getProxy(Class<P> firstRequestedInterface, Class... additionalRequestedInterfaces){
verifyLegalProxy(firstRequestedInterface);
for (Class clazz : additionalRequestedInterfaces)
verifyLegalProxy(clazz);
Class[] allRequested = Arrays.copyOf(additionalRequestedInterfaces,additionalRequestedInterfaces.length + 1);
allRequested[additionalRequestedInterfaces.length] = firstRequestedInterface;
return firstRequestedInterface.cast(
Proxy.newProxyInstance(
StateEngine.class.getClassLoader(),
allRequested,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return StateEngine.this.proxyDispatch(proxy, method, args);
}
}
)
);
}
@Override
public boolean trySetState(S expected, S newState) {
return atomicState.compareAndSet(expected,newState);
}
@Override
public boolean trySetState(S newState) {
if (invocationOnThisThread.get() == null){
throw new IllegalStateException("Cannot invoke single arg trySetState from outside a state machine invocation");
}
boolean changed = atomicState.compareAndSet(stateClass.cast(invocationOnThisThread.get().baseState), newState);
if (changed)
stateChangeSuccess();
else
stateChangeFailure();
return changed;
}
void stateChangeFailure() {
stateChangeCollide.incrementAndGet();
}
void stateChangeSuccess() {
stateChangeOK.incrementAndGet();
}
protected Object proxyDispatch(Object proxy, Method method, Object[] args) throws Exception {
EngineInvocation invocation = new EngineInvocation();
invocation.meth = method;
invocation.args = args;
invocation.shouldRedispatch = false;
return invocation.invoke();
}
public String toString(){
return String.format("StateEngine[name=%s,currentState=%s]", name, atomicState.get());
}
//****************************************************************************************************
//INNER CLASSES
//****************************************************************************************************
class EngineInvocation {
Object baseState;
Method meth;
Object[] args;
long spin = 0;
boolean shouldRedispatch = false;
public void doRetry(){
this.shouldRedispatch = true;
}
public void doClearRetry() {
this.shouldRedispatch = false;
}
public Object invoke() throws Exception {
final EngineInvocation previouslyRunning = invocationOnThisThread.get();
invocationOnThisThread.set(this);
try {
for(;;){
this.shouldRedispatch = false;
Object retVal;
baseState = atomicState.get();
Object callState;
if (baseState instanceof CopyOnInvoke){
CopyOnInvoke cloner = (CopyOnInvoke)baseState;
callState = stateClass.cast(cloner.mutableCopy(StateEngine.this));
} else
callState = baseState;
if (log.isDebugEnabled()){
String message = String.format("invocation [engine.name=%s,thread=%s,method=%s, baseState=%s, spin=%s]\n", name, Thread.currentThread(), meth.getName(), baseState, spin);
log.debug(message);
}
try {
retVal = meth.invoke(callState, args);
} catch (IllegalAccessException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t == null)
throw e;
else if (t instanceof Error)
throw (Error)t;
else if (t instanceof RuntimeException)
throw (RuntimeException)t;
else
throw (Exception)t;
}
if(shouldRedispatch){
spin ++;
continue;
}
else
return retVal;
}
} finally {
invocationOnThisThread.set(previouslyRunning);
}
}
}
}