package org.corfudb.runtime.clients;
import io.netty.channel.ChannelHandlerContext;
import org.corfudb.protocols.wireprotocol.CorfuMsg;
import org.corfudb.protocols.wireprotocol.CorfuMsgType;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by mwei on 7/27/16.
*/
public class ClientMsgHandler {
@FunctionalInterface
public interface Handler<T extends CorfuMsg> {
Object handle(T CorfuMsg, ChannelHandlerContext ctx, IClientRouter r) throws Exception;
}
/** The handler map. */
private Map<CorfuMsgType, ClientMsgHandler.Handler> handlerMap;
/** The client. */
private IClient client;
/** Construct a new instance of ClientMsgHandler. */
public ClientMsgHandler(IClient client) {
this.client = client;
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.
*/
public <T extends CorfuMsg> ClientMsgHandler
addHandler(CorfuMsgType messageType, ClientMsgHandler.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.
* @return True, if the message was handled.
* False otherwise.
*/
@SuppressWarnings("unchecked")
public boolean handle(CorfuMsg message, ChannelHandlerContext ctx) {
if (handlerMap.containsKey(message.getMsgType())) {
try {
Object ret = handlerMap.get(message.getMsgType()).handle(message, ctx, client.getRouter());
if (ret != null) {
client.getRouter().completeRequest(message.getRequestID(), ret);
}
} catch (Exception ex) {
client.getRouter().completeExceptionally(message.getRequestID(), ex);
}
return true;
}
return false;
}
/** Generate handlers for a particular client.
*
* @param caller The context that is being used. Call MethodHandles.lookup() to obtain.
* @param o The object that implements the client.
* @return
*/
public ClientMsgHandler generateHandlers(final MethodHandles.Lookup caller, final Object o) {
Arrays.stream(o.getClass().getDeclaredMethods())
.filter(x -> x.isAnnotationPresent(ClientHandler.class))
.forEach(x -> {
ClientHandler a = x.getAnnotation(ClientHandler.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 {
if (Modifier.isStatic(x.getModifiers())) {
MethodHandle mh = caller.unreflect(x);
MethodType mt = mh.type().changeParameterType(0, CorfuMsg.class);
handlerMap.put(a.type(), (ClientMsgHandler.Handler) LambdaMetafactory.metafactory(caller,
"handle", MethodType.methodType(ClientMsgHandler.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)
.changeReturnType(Object.class);
handlerMap.put(a.type(), (ClientMsgHandler.Handler) LambdaMetafactory.metafactory(caller,
"handle", MethodType.methodType(ClientMsgHandler.Handler.class, o.getClass()),
mtGeneric.dropParameterTypes(0,1), mh, mh.type().dropParameterTypes(0,1))
.getTarget().bindTo(o).invoke());
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
return this;
}
/** Get the types this handler will handle.
*
* @return The types this handler will handle.
*/
public Set<CorfuMsgType> getHandledTypes() {
return handlerMap.keySet();
}
}