package org.corfudb.infrastructure;
import com.codahale.metrics.Timer;
import io.netty.channel.ChannelHandlerContext;
import org.corfudb.protocols.wireprotocol.CorfuMsg;
import org.corfudb.protocols.wireprotocol.CorfuMsgType;
import org.corfudb.util.MetricsUtils;
import java.lang.invoke.*;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class simplifies writing switch(msg.getType()) statements.
*
* For maximum performance, make the handlers static whenever possible.
*
* Created by mwei on 7/26/16.
*/
public class CorfuMsgHandler {
@FunctionalInterface
interface Handler<T extends CorfuMsg> {
void handle(T CorfuMsg, ChannelHandlerContext ctx, IServerRouter r, boolean isMetricsEnabled);
}
/** The handler map. */
private Map<CorfuMsgType, Handler> handlerMap;
/** Get the types this handler will handle.
*
* @return A set containing the types this handler will handle.
*/
public Set<CorfuMsgType> getHandledTypes() {
return handlerMap.keySet();
}
/** Construct a new instance of CorfuMsgHandler. */
public CorfuMsgHandler() {
handlerMap = new ConcurrentHashMap<>();
}
/** Add a handler to this message handler.
*
* @param messageType The type of CorfuMsg this handler will handle.
* @param handler The handler itself.
* @param <T> A CorfuMsg type.
* @return This handler, to support chaining.
*/
@SuppressWarnings("unchecked")
public <T extends CorfuMsg> CorfuMsgHandler
addHandler(CorfuMsgType messageType, Handler<T> handler) {
handlerMap.put(messageType, handler);
return this;
}
/** Handle an incoming CorfuMsg.
*
* @param message The message to handle.
* @param ctx The channel handler context.
* @param r The server router.
* @return True, if the message was handled.
* False otherwise.
*/
@SuppressWarnings("unchecked")
public boolean handle(CorfuMsg message, ChannelHandlerContext ctx, IServerRouter r, boolean isMetricsEnabled) {
if (handlerMap.containsKey(message.getMsgType())) {
handlerMap.get(message.getMsgType()).handle(message, ctx, r, isMetricsEnabled);
return true;
}
return false;
}
/** Generate handlers for a particular server.
*
* @param caller The context that is being used. Call MethodHandles.lookup() to obtain.
* @param o The object that implements the server.
* @return
*/
public CorfuMsgHandler generateHandlers(final MethodHandles.Lookup caller, final Object o) {
Arrays.stream(o.getClass().getDeclaredMethods())
.filter(x -> x.isAnnotationPresent(ServerHandler.class))
.forEach(x -> {
ServerHandler a = x.getAnnotation(ServerHandler.class);
if (!x.getParameterTypes()[0].isAssignableFrom(a.type().messageType.getRawType()))
{
throw new RuntimeException("Incorrect message type, expected " +
a.type().messageType.getRawType() + " but provided " + x.getParameterTypes()[0]);
}
if (handlerMap.containsKey(a.type())) {
throw new RuntimeException("Handler for " + a.type() + " already registered!");
}
// convert the method into a Java8 Lambda for maximum execution speed...
try {
Handler h;
if (Modifier.isStatic(x.getModifiers())) {
MethodHandle mh = caller.unreflect(x);
MethodType mt = mh.type().changeParameterType(0, CorfuMsg.class);
h = (Handler) LambdaMetafactory.metafactory(caller,
"handle", MethodType.methodType(Handler.class),
mt, mh, mh.type())
.getTarget().invokeExact();
} else {
// instance method, so we need to capture the type.
MethodType mt = MethodType.methodType(x.getReturnType(), x.getParameterTypes());
MethodHandle mh = caller.findVirtual(o.getClass(), x.getName(), mt);
MethodType mtGeneric = mh.type().changeParameterType(1, CorfuMsg.class);
h = (Handler) LambdaMetafactory.metafactory(caller,
"handle", MethodType.methodType(Handler.class, o.getClass()),
mtGeneric.dropParameterTypes(0,1), mh, mh.type().dropParameterTypes(0,1))
.getTarget().bindTo(o).invoke();
}
// If there is an annotation element for opTimer that is not "", then
// find/create its timer *outside* of the lambda that we create.
// The lambda may be called very frequently, so avoid touching the metrics
// registry on a hot code path.
final Timer t;
if (!a.opTimer().equals("")) {
t = ServerContext.getMetrics().timer(a.opTimer());
} else {
t = null;
}
// Now create the lambda that wraps the lambda-like-thing that's
// stored in 'h' and insert it into the handlerMap.
handlerMap.put(a.type(),
(CorfuMsg msg, ChannelHandlerContext ctx, IServerRouter r, boolean isMetricsEnabled) -> {
try (Timer.Context timerCxt = MetricsUtils.getConditionalContext(
t != null && isMetricsEnabled, t)) {
h.handle(msg, ctx, r, isMetricsEnabled);
}
});
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
return this;
}
}