package com.rubiconproject.oss.kv.distributed.impl;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.rubiconproject.oss.kv.distributed.Configuration;
import com.rubiconproject.oss.kv.distributed.Context;
import com.rubiconproject.oss.kv.distributed.ContextFilter;
import com.rubiconproject.oss.kv.distributed.ContextFilterResult;
import com.rubiconproject.oss.kv.distributed.DistributedKeyValueStoreException;
import com.rubiconproject.oss.kv.distributed.Node;
import com.rubiconproject.oss.kv.distributed.Operation;
import com.rubiconproject.oss.kv.distributed.OperationStatus;
/**
* A simple context filter that orders results by node rank from the preference
* list. Other nodes that responde with a null value or an exception will be
* updated with the selected value (if configured to do so).
*
* @author Sam Tingleff <sam@tingleff.com>
*
* @param <V>
*/
public class NodeRankContextFilter<V> implements ContextFilter<V> {
private Log log = LogFactory.getLog(getClass());
private Log backfillLog = LogFactory.getLog("haymitch.backfilllog");
private Comparator<Context<V>> comparator = new NodeRankComparator<V>();
private Configuration config;
public NodeRankContextFilter(Configuration config) {
this.config = config;
}
public ContextFilterResult<V> filter(List<Context<V>> contexts)
throws DistributedKeyValueStoreException {
Collections.sort(contexts, comparator);
// Select the first non-null value and call set() on any null nodes.
Context<V> lowestNonNullValueContext = null;
List<Node> nodesRequiringUpdate = new LinkedList<Node>();
for (Context<V> context : contexts) {
if (lowestNonNullValueContext == null)
lowestNonNullValueContext = context;
if ((lowestNonNullValueContext.getValue() == null)
&& (context.getValue() != null)) {
lowestNonNullValueContext = context;
}
}
if (log.isDebugEnabled() && (lowestNonNullValueContext != null))
log.debug(String.format("Selected context from node # %1$d",
lowestNonNullValueContext.getSourceNode().getId()));
if ((lowestNonNullValueContext != null)
&& (lowestNonNullValueContext.getValue() != null)) {
for (Context<V> context : contexts) {
if (context.getValue() == null) {
OperationStatus status = context.getResult().getStatus();
if ((status.equals(OperationStatus.NullValue))
&& (config.getFillNullGetResults())
&& (context.getNodeRank() < config.getWriteReplicas())) {
if (log.isDebugEnabled())
log
.debug(String
.format(
"Node # %1$d requires update due to null value",
context.getSourceNode()
.getId()));
nodesRequiringUpdate.add(context.getSourceNode());
} else if ((OperationStatus.Error.equals(status))
&& (config.getFillErrorGetResults())) {
if (log.isDebugEnabled())
log.debug(String.format(
"Node # %1$d requires update due to error",
context.getSourceNode().getId()));
nodesRequiringUpdate.add(context.getSourceNode());
}
}
}
}
List<Operation<V>> ops = null;
if ((lowestNonNullValueContext != null)
&& (nodesRequiringUpdate.size() > 0)) {
ops = new LinkedList<Operation<V>>();
for (Node node : nodesRequiringUpdate) {
Operation<V> op = new SetOperation<V>(null,
lowestNonNullValueContext.getKey(),
lowestNonNullValueContext.getValue());
op.setNode(node);
ops.add(op);
}
}
log(lowestNonNullValueContext, ops);
return new DefaultContextFilterResult<V>(lowestNonNullValueContext, ops);
}
private void log(Context<V> choice, List<Operation<V>> ops) {
if (backfillLog.isInfoEnabled() && (choice != null) && (ops != null)) {
StringBuffer format = new StringBuffer();
for (int i = 0; i < ops.size(); ++i) {
if (i > 0)
format.append(',');
format.append(ops.get(i).getNode().getId());
}
backfillLog.info(String.format("%1$s\t%2$s", choice.getSourceNode()
.getId(), format.toString()));
}
}
private static class NodeRankComparator<V> implements
Comparator<Context<V>> {
public int compare(Context<V> o1, Context<V> o2) {
return new Integer(o1.getNodeRank()).compareTo(new Integer(o2
.getNodeRank()));
}
}
}