package org.distributeme.core.routing;
import net.anotheria.util.StringUtils;
import org.configureme.ConfigurationManager;
import org.distributeme.core.ClientSideCallContext;
import org.distributeme.core.failing.FailDecision;
import org.distributeme.core.failing.FailingStrategy;
import org.distributeme.core.stats.RoutingStatsCollector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* TODO comment this class
*
* @author lrosenberg
* @since 13.03.15 00:37
*/
abstract class AbstractRouterWithFailover extends AbstractRouter implements ConfigurableRouter, FailingStrategy {
/**
* Under line constant.
*/
public static final String UNDER_LINE = "_";
/**
* AbstractRouterImplementation logger.
*/
private final Logger log;
/**
* Router configuration.
*/
private GenericRouterConfiguration configuration = new GenericRouterConfiguration();
/**
* AbstractRouterImplementation delegation counter.
*/
private AtomicInteger delegateCallCounter;
/**
* AbstractRouterImplementation 'modRouteMethodRegistry'. Contains information about
* methods which should be routed using MOD strategy.
* Additionally maps method name to modable parameter position.
*/
private final Map<String, Integer> modRouteMethodRegistry;
/**
* <p>Constructor for AbstractRouterWithFailover.</p>
*/
public AbstractRouterWithFailover() {
log = LoggerFactory.getLogger(Constants.ROUTING_LOGGER_NAME);
delegateCallCounter = new AtomicInteger(0);
if (getStrategy() == null)
throw new AssertionError("getStrategy() method should not return NULL. Please check " + this.getClass() + " implementation");
modRouteMethodRegistry = new HashMap<String, Integer>();
}
/** {@inheritDoc} */
@Override
public FailDecision callFailed(final ClientSideCallContext clientSideCallContext) {
RoutingStatsCollector stats = getRoutingStats(clientSideCallContext.getServiceId());
stats.addFailedCall();
if (!failingSupported()) {
stats.addFailDecision();
return FailDecision.fail();
}
if (clientSideCallContext.getCallCount() < getServiceAmount() - 1) {
stats.addRetryDecision();
return FailDecision.retry();
}
stats.addFailDecision();
return FailDecision.fail();
}
/**
* Returns serviceId based on RoundRobin strategy.
*
* @param context {@link org.distributeme.core.ClientSideCallContext}
* @return serviceId string
*/
protected String getRRBasedServiceId(ClientSideCallContext context) {
if (configuration.getNumberOfInstances() == 0)
return context.getServiceId();
int fromCounter = delegateCallCounter.incrementAndGet();
if (fromCounter >= configuration.getNumberOfInstances()){
int oldCounter = fromCounter;
fromCounter = 0;
delegateCallCounter.compareAndSet(oldCounter, 0);
}
return context.getServiceId()+UNDER_LINE+fromCounter;
}
/**
* Return serviceId based on Mod routing strategy.
* NOTE : it's native that not all methods supports such kind of routing strategy [MOD].
* So, if some method does not supports MOD - strategy - then ROUND-ROBIN strategy will be used for such call.
*
* @param context {@link org.distributeme.core.ClientSideCallContext}
* @return serviceId string
*/
protected String getModBasedServiceId(ClientSideCallContext context) {
if (modRouteMethodRegistry.containsKey(context.getMethodName())) {
List<?> parameters = context.getParameters();
if (parameters == null)
throw new AssertionError("Method parameters can't be NULL for MOD-Based routing strategy");
int parameterPosition = modRouteMethodRegistry.get(context.getMethodName());
if (parameters.size() < parameterPosition + 1)
throw new AssertionError("Not properly configured router, parameter count is less than expected - actual: " + parameters.size() + ", expected: " + parameterPosition);
Object parameter = parameters.get(parameterPosition);
long parameterValue = getModableValue(parameter);
if (parameterValue<0)
parameterValue *= -1;
String result = context.getServiceId() + UNDER_LINE + (parameterValue % getServiceAmount());
if (getLog().isDebugEnabled())
getLog().debug("Returning mod based result : " + result + " for " + context + " where : serversAmount[" + getServiceAmount() + "], modableValue[" + parameterValue + "]");
return result;
}
if (getLog().isDebugEnabled())
getLog().debug("Call to method " + context.getMethodName() + " can't be routed using MOD strategy. Building RR strategy based serviceId.");
return getRRBasedServiceId(context);
}
/**
* Simply return configured {@link org.slf4j.Logger} instance.
*
* @return {@link org.slf4j.Logger}
*/
protected Logger getLog() {
return log;
}
/**
* <p>Getter for the field <code>configuration</code>.</p>
*
* @return a {@link org.distributeme.core.routing.GenericRouterConfiguration} object.
*/
protected GenericRouterConfiguration getConfiguration(){
return configuration;
}
/** {@inheritDoc} */
@Override
public void setConfigurationName(String serviceId, String configurationName) {
setServiceId(serviceId);
try{
ConfigurationManager.INSTANCE.configureAs(configuration, configurationName);
}catch(IllegalArgumentException e){
throw new IllegalStateException("Can't configure router and this leaves us in undefined state, probably configuration not found: "+configurationName, e);
}
}
/**
* Return amount of services for which routing should be performed.
* Current method should not return less then (int) 2 result, cause in that case
* router usage makes no sense, value validation will be performed in constructor.
*
* @return int value
*/
protected int getServiceAmount() {
return getConfiguration().getNumberOfInstances();
}
/**
* Allow to turn on and off failing support.
* Actually can be enabled or disabled per some implementation.
*
* @return boolean value
*/
protected abstract boolean failingSupported();
/**
* Return RouterStrategy - which should be used for current Router implementation.
* Current method should not return NULL, value validation will be performed in constructor.
*
* @return {@link org.distributeme.core.routing.RouterStrategy}
*/
protected abstract RouterStrategy getStrategy();
/**
* Return long value for mod calculation.
*
* @param parameter some method incoming parameter
* @return long value
*/
protected abstract long getModableValue(Object parameter);
/**
* Allow to add some custom method with some name and modable parameter position to mod method registry.
* Illegal argument exception will be thrown if any incoming parameter is not valid ( mName - is null or empty, modableParameterPosition is negative).
*
* @param mName name of method which should be routed using MOD strategy
* @param modableParameterPosition position of method argument for mod calculations
*/
protected void addModRoutedMethod(String mName, int modableParameterPosition) {
if (StringUtils.isEmpty(mName))
throw new IllegalArgumentException("mName parameter can't be null || empty");
if (modableParameterPosition < 0)
throw new IllegalArgumentException("modableParameterPosition should be greater or equal to 0, no negative values supported");
modRouteMethodRegistry.put(mName, modableParameterPosition);
}
/**
* Allow to add some custom method with some name using default 0 modable parameter position.
* Illegal argument exception will be thrown if mName - is null or empty.
*
* @param mName name of method which should be routed using MOD strategy
*/
protected void addModRoutedMethod(String mName) {
if (StringUtils.isEmpty(mName))
throw new IllegalArgumentException("mName parameter can't be null || empty");
modRouteMethodRegistry.put(mName, 0);
}
}