package com.kurento.tool.rom.client;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.kurento.kmf.jsonrpcconnector.Props;
import com.kurento.kmf.media.Continuation;
import com.kurento.kmf.media.events.Event;
import com.kurento.kmf.media.events.MediaEventListener;
import com.kurento.tool.rom.ParamAnnotationUtils;
import com.kurento.tool.rom.server.FactoryMethod;
public class RemoteObjectInvocationHandler extends DefaultInvocationHandler {
private static final Logger LOG = LoggerFactory
.getLogger(RemoteObjectInvocationHandler.class);
private final RemoteObject remoteObject;
private final RemoteObjectFactory factory;
@SuppressWarnings("unchecked")
public static <E> E newProxy(RemoteObject remoteObject,
RemoteObjectFactory factory, Class<E> clazz) {
RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(
remoteObject, factory);
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(),
new Class[] { clazz }, handler);
// This automatically unflatten this remote object with the wrapper
// instead of with the remote object itself
remoteObject.setWrapperForUnflatten(proxy);
return (E) proxy;
}
private RemoteObjectInvocationHandler(RemoteObject remoteObject,
RemoteObjectFactory factory) {
this.remoteObject = remoteObject;
this.factory = factory;
}
@Override
public Object internalInvoke(final Object proxy, Method method,
Object[] args) throws Throwable {
Continuation<?> cont = null;
if (args != null && args[args.length - 1] instanceof Continuation) {
cont = (Continuation<?>) args[args.length - 1];
args = Arrays.copyOf(args, args.length - 1);
}
String methodName = method.getName();
if (method.getAnnotation(FactoryMethod.class) != null) {
Props props = ParamAnnotationUtils.extractProps(
method.getParameterAnnotations(), args);
return createBuilderObject(proxy, method, methodName, props);
} else if (methodName.equals("release")) {
return release(cont);
} else if (methodName.startsWith("add")
&& methodName.endsWith("Listener")) {
return subscribeEventListener(proxy, args, methodName, cont);
} else {
return invoke(method, args, cont);
}
}
private Object invoke(Method method, Object[] args, Continuation<?> cont) {
Props props = ParamAnnotationUtils.extractProps(
method.getParameterAnnotations(), args);
if (cont != null) {
Type[] paramTypes = method.getGenericParameterTypes();
ParameterizedType contType = (ParameterizedType) paramTypes[paramTypes.length - 1];
Type returnType = contType.getActualTypeArguments()[0];
remoteObject.invoke(method.getName(), props, returnType, cont);
return null;
}
return remoteObject.invoke(method.getName(), props,
method.getGenericReturnType());
}
@SuppressWarnings("unchecked")
private Object release(Continuation<?> cont) {
if (cont != null) {
remoteObject.release((Continuation<Void>) cont);
} else {
remoteObject.release();
}
return null;
}
@SuppressWarnings("unchecked")
private Object subscribeEventListener(final Object proxy,
final Object[] args, String methodName, Continuation<?> cont) {
String event = methodName.substring(3,
methodName.length() - "Listener".length());
RemoteObject.EventListener listener = new RemoteObject.EventListener() {
@Override
public void onEvent(String eventType, Props data) {
propagateEventTo(proxy, eventType, data,
(MediaEventListener<?>) args[0]);
}
};
if (cont != null) {
remoteObject.addEventListener(event,
(Continuation<ListenerSubscription>) cont, listener);
return null;
}
return remoteObject.addEventListener(event, listener);
}
private Object createBuilderObject(final Object proxy, Method method,
String methodName, Props props) {
if (props == null) {
props = new Props();
}
FactoryMethod annotation = method.getAnnotation(FactoryMethod.class);
props.add(annotation.value(), remoteObject.getObjectRef());
Class<?> builderClass = method.getReturnType();
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[] { method.getReturnType() },
new BuilderInvocationHandler(builderClass.getEnclosingClass(),
props, factory));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void propagateEventTo(Object object, String eventType,
Props data, MediaEventListener<?> listener) {
// TODO Optimise this to create only one event for all listeners
try {
Class<?> eventClass = Class.forName("com.kurento.kmf.media.events."
+ eventType + "Event");
Constructor<?> constructor = eventClass.getConstructors()[0];
Object[] params = ParamAnnotationUtils.extractEventParams(
constructor.getParameterAnnotations(), data);
params[0] = object;
Event e = (Event) constructor.newInstance(params);
((MediaEventListener) listener).onEvent(e);
} catch (Exception e) {
LOG.error("Exception while processing event '" + eventType
+ "' with params '" + data + "'", e);
}
}
public RemoteObject getRemoteObject() {
return remoteObject;
}
@Override
public String toString() {
return "[RemoteObject: type=" + this.remoteObject.getType()
+ " remoteRef=" + remoteObject.getObjectRef() + "";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((remoteObject == null) ? 0 : remoteObject.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
RemoteObjectInvocationHandler other = (RemoteObjectInvocationHandler) obj;
if (remoteObject == null) {
if (other.remoteObject != null) {
return false;
}
} else if (!remoteObject.equals(other.remoteObject)) {
return false;
}
return true;
}
}