/* * (C) Copyright 2016 Kurento (http://kurento.org/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.kurento.client.internal.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 java.util.Collections; import java.util.List; import java.util.Set; import org.kurento.client.Continuation; import org.kurento.client.Event; import org.kurento.client.EventListener; import org.kurento.client.KurentoObject; import org.kurento.client.Transaction; import org.kurento.client.internal.ParamAnnotationUtils; import org.kurento.client.internal.server.EventSubscription; import org.kurento.client.internal.transport.serialization.ParamsFlattener; import org.kurento.jsonrpc.Props; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableSet; public class RemoteObjectInvocationHandler extends DefaultInvocationHandler { private static final Logger log = LoggerFactory.getLogger(RemoteObjectInvocationHandler.class); private static final Set<String> REMOTE_OBJECT_METHODS = ImmutableSet.of("isCommited", "waitCommited", "whenCommited", "beginTransaction"); private RemoteObject remoteObject; private final RomManager manager; @SuppressWarnings("unchecked") public static <E> E newProxy(RemoteObject remoteObject, RomManager manager, Class<E> clazz) { RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(remoteObject, manager); KurentoObject kurentoObject = (KurentoObject) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, handler); remoteObject.setKurentoObject(kurentoObject); return (E) kurentoObject; } public static RemoteObjectInvocationHandler getFor(Object object) { return (RemoteObjectInvocationHandler) Proxy.getInvocationHandler(object); } private RemoteObjectInvocationHandler(RemoteObject remoteObject, RomManager manager) { this.remoteObject = remoteObject; this.manager = manager; } @Override public Object internalInvoke(final Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (REMOTE_OBJECT_METHODS.contains(methodName)) { Method remoteObjectMethod = findMethod(remoteObject, methodName, args); return remoteObjectMethod.invoke(remoteObject, args); } log.trace("Invoking method {} on object {}", method, proxy); Continuation<?> cont = null; Transaction tx = null; List<String> paramNames = Collections.emptyList(); if (args != null && args.length > 0) { paramNames = ParamAnnotationUtils.getParamNames(method); if (args[args.length - 1] instanceof Continuation) { cont = (Continuation<?>) args[args.length - 1]; args = Arrays.copyOf(args, args.length - 1); paramNames = paramNames.subList(0, paramNames.size() - 1); } else if (args != null && args.length > 0 && args[0] instanceof Transaction) { tx = (Transaction) args[0]; args = Arrays.copyOfRange(args, 1, args.length); paramNames = paramNames.subList(1, paramNames.size()); } } if (methodName.equals("release")) { return release(cont, tx); } else if (method.getAnnotation(EventSubscription.class) != null) { EventSubscription eventSubscription = method.getAnnotation(EventSubscription.class); if (methodName.startsWith("add")) { return subscribeEventListener(proxy, args, methodName, eventSubscription.value(), cont, tx); } else if (methodName.startsWith("remove")) { return unsubscribeEventListener(proxy, args, methodName, eventSubscription.value(), cont, tx); } else { throw new IllegalStateException("Method " + methodName + " undefined for events"); } } else { return invoke(method, paramNames, args, cont, tx); } } private Object invoke(Method method, List<String> paramNames, Object[] args, Continuation<?> cont, Transaction tx) { Props props = ParamAnnotationUtils.extractProps(paramNames, 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; } else if (tx != null) { Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { ParameterizedType futureType = (ParameterizedType) returnType; Type methodReturnType = futureType.getActualTypeArguments()[0]; return remoteObject.invoke(method.getName(), props, methodReturnType, tx); } else { return remoteObject.invoke(method.getName(), props, Void.class, tx); } } else { return remoteObject.invoke(method.getName(), props, method.getGenericReturnType()); } } @SuppressWarnings("unchecked") private Object release(Continuation<?> cont, Transaction tx) { if (cont != null) { remoteObject.release((Continuation<Void>) cont); } else if (tx != null) { remoteObject.release(tx); } else { remoteObject.release(); } return null; } @SuppressWarnings("unchecked") private Object subscribeEventListener(final Object proxy, final Object[] args, String methodName, final Class<? extends Event> eventClass, Continuation<?> cont, Transaction tx) { String eventName = eventClass.getSimpleName().substring(0, eventClass.getSimpleName().length() - "Event".length()); RemoteObjectEventListener listener = new RemoteObjectEventListener() { @Override public void onEvent(String eventType, Props data) { propagateEventTo(proxy, eventClass, data, (EventListener<?>) args[0]); } }; if (cont != null) { remoteObject.addEventListener(eventName, listener, (Continuation<ListenerSubscriptionImpl>) cont); return null; } else if (tx != null) { return remoteObject.addEventListener(eventName, listener, tx); } else { return remoteObject.addEventListener(eventName, listener); } } @SuppressWarnings("unchecked") private Object unsubscribeEventListener(final Object proxy, final Object[] args, String methodName, final Class<? extends Event> eventClass, Continuation<?> cont, Transaction tx) { ListenerSubscriptionImpl listenerSubscription = (ListenerSubscriptionImpl) args[0]; if (cont != null) { remoteObject.removeEventListener(listenerSubscription, (Continuation<Void>) cont); } else if (tx != null) { remoteObject.removeEventListener(listenerSubscription, tx); } else { remoteObject.removeEventListener(listenerSubscription); } return null; } @SuppressWarnings({ "unchecked", "rawtypes" }) protected void propagateEventTo(Object object, Class<? extends Event> eventClass, Props data, EventListener<?> listener) { // TODO Optimize this to create only one event for all listeners try { log.debug("Event class '" + eventClass.getSimpleName() + " Data: " + data); Constructor<?> constructor = eventClass.getConstructors()[0]; data.add("source", ((KurentoObject) object).getId()); Object[] params = ParamsFlattener.getInstance().unflattenParams( constructor.getParameterAnnotations(), constructor.getGenericParameterTypes(), data, manager); Event event = (Event) constructor.newInstance(params); ((EventListener) listener).onEvent(event); } catch (Exception e) { log.error("Exception while processing event '" + eventClass.getSimpleName() + "' with params '" + data + "'", e); } } public RemoteObject getRemoteObject() { return remoteObject; } public void setRemoteObject(RemoteObject remoteObject) { this.remoteObject = remoteObject; } public RomManager getRomManager() { return manager; } @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; } RemoteObjectInvocationHandler other = getFor(obj); if (other == null) { return false; } if (remoteObject == null) { if (other.remoteObject != null) { return false; } } else if (!remoteObject.equals(other.remoteObject)) { return false; } return true; } }