/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors.behaviors;
import co.paralleluniverse.actors.ActorLoader;
import co.paralleluniverse.actors.ActorRef;
import co.paralleluniverse.actors.ActorRefDelegate;
import co.paralleluniverse.actors.LocalActor;
import co.paralleluniverse.actors.MailboxConfig;
import co.paralleluniverse.common.util.Pair;
import co.paralleluniverse.concurrent.util.MapUtil;
import co.paralleluniverse.fibers.Instrumented;
import co.paralleluniverse.fibers.RuntimeSuspendExecution;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.fibers.Suspendable;
import co.paralleluniverse.strands.Strand;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import net.bytebuddy.ByteBuddy;
//import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import static net.bytebuddy.matcher.ElementMatchers.anyOf;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
/**
* Wraps a Java object in a {@link ServerActor} that exposes the object's methods as an interface and processes them in an actor
* (on a dedicated strand).
* <p/>
* You can either supply a target object to any of the public constructors, or extend this class and use the subclass itself as the target,
* in which case use the protected constructors that don't take a {@code target} argument.
* <p/>
* The interface(s) exposed must
*
* @author pron
*/
public class ProxyServerActor extends ServerActor<ProxyServerActor.Invocation, Object, ProxyServerActor.Invocation> {
private final Class<?>[] interfaces;
private Object target;
private final boolean callOnVoidMethods;
/**
* Creates a new {@code ProxyServerActor}
*
* @param name the actor's name (may be null)
* @param strand the actor's strand (may be null)
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; {@code target} must implement all these interfaces.
*/
public ProxyServerActor(String name, Strand strand, MailboxConfig mailboxConfig, boolean callOnVoidMethods, Object target, Class<?>[] interfaces) {
super(name, null, 0L, null, strand, mailboxConfig);
this.callOnVoidMethods = callOnVoidMethods;
this.target = ActorLoader.getReplacementFor(target != null ? target : this);
this.interfaces = interfaces != null ? Arrays.copyOf(interfaces, interfaces.length) : this.target.getClass().getInterfaces();
if (this.interfaces == null)
throw new IllegalArgumentException("No interfaces provided, and target of class " + this.target.getClass().getName() + " implements no interfaces");
}
//<editor-fold defaultstate="collapsed" desc="Constructors">
/////////// Constructors ///////////////////////////////////
/**
* Creates a new {@code ProxyServerActor}
*
* @param name the actor's name (may be null)
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; {@code target} must implement all these interfaces.
*/
public ProxyServerActor(String name, MailboxConfig mailboxConfig, boolean callOnVoidMethods, Object target, Class<?>... interfaces) {
this(name, null, mailboxConfig, callOnVoidMethods, target, interfaces);
}
/**
* Creates a new {@code ProxyServerActor} with the default mailbox settings.
*
* @param name the actor's name (may be null)
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; {@code target} must implement all these interfaces.
*/
public ProxyServerActor(String name, boolean callOnVoidMethods, Object target, Class<?>... interfaces) {
this(name, null, null, callOnVoidMethods, target, interfaces);
}
/**
* Creates a new {@code ProxyServerActor}
*
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; {@code target} must implement all these interfaces.
*/
public ProxyServerActor(MailboxConfig mailboxConfig, boolean callOnVoidMethods, Object target, Class<?>... interfaces) {
this(null, null, mailboxConfig, callOnVoidMethods, target, interfaces);
}
/**
* Creates a new {@code ProxyServerActor} with the default mailbox settings.
*
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; {@code target} must implement all these interfaces.
*/
public ProxyServerActor(boolean callOnVoidMethods, Object target, Class<?>... interfaces) {
this(null, null, null, callOnVoidMethods, target, interfaces);
}
/**
* Creates a new {@code ProxyServerActor}, which exposes all interfaces implemented by the given {@code target}.
*
* @param name the actor's name (may be null)
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; {@code target} must implement all these interfaces.
*/
public ProxyServerActor(String name, MailboxConfig mailboxConfig, boolean callOnVoidMethods, Object target) {
this(name, null, mailboxConfig, callOnVoidMethods, target, null);
}
/**
* Creates a new {@code ProxyServerActor} with the default mailbox settings,
* which exposes all interfaces implemented by the given {@code target}.
*
* @param name the actor's name (may be null)
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
*/
public ProxyServerActor(String name, boolean callOnVoidMethods, Object target) {
this(name, null, null, callOnVoidMethods, target, null);
}
/**
* Creates a new {@code ProxyServerActor}, which exposes all interfaces implemented by the given {@code target}.
*
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
*/
public ProxyServerActor(MailboxConfig mailboxConfig, boolean callOnVoidMethods, Object target) {
this(null, null, mailboxConfig, callOnVoidMethods, target, null);
}
/**
* Creates a new {@code ProxyServerActor} with the default mailbox settings,
* which exposes all interfaces implemented by the given {@code target}.
*
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param target the object implementing the actor's behaviors, on which the exposed interface methods will be called.
*/
public ProxyServerActor(boolean callOnVoidMethods, Object target) {
this(null, null, null, callOnVoidMethods, target, null);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls.
*
* @param name the actor's name (may be null)
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; this class must implement all these interfaces.
*/
protected ProxyServerActor(String name, MailboxConfig mailboxConfig, boolean callOnVoidMethods, Class<?>... interfaces) {
this(name, null, mailboxConfig, callOnVoidMethods, null, interfaces);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls. The default mailbox settings will be used.
*
* @param name the actor's name (may be null)
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; this class must implement all these interfaces.
*/
protected ProxyServerActor(String name, boolean callOnVoidMethods, Class<?>... interfaces) {
this(name, null, null, callOnVoidMethods, null, interfaces);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls. The default mailbox settings will be used.
*
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; this class must implement all these interfaces.
*/
protected ProxyServerActor(MailboxConfig mailboxConfig, boolean callOnVoidMethods, Class<?>... interfaces) {
this(null, null, mailboxConfig, callOnVoidMethods, null, interfaces);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls. The default mailbox settings will be used.
*
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
* @param interfaces the interfaces this actor's {@link ActorRef} will implement; this class must implement all these interfaces.
*/
protected ProxyServerActor(boolean callOnVoidMethods, Class<?>... interfaces) {
this(null, null, null, callOnVoidMethods, null, interfaces);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls, and all of the interfaces implemented by the subclass will be exposed by the {@link ActorRef}.
*
* @param name the actor's name (may be null)
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
*/
protected ProxyServerActor(String name, MailboxConfig mailboxConfig, boolean callOnVoidMethods) {
this(name, null, mailboxConfig, callOnVoidMethods, null, null);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls, and all of the interfaces implemented by the subclass will be exposed by the {@link ActorRef}.
* The default mailbox settings will be used.
*
* @param name the actor's name (may be null)
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
*/
protected ProxyServerActor(String name, boolean callOnVoidMethods) {
this(name, null, null, callOnVoidMethods, null, null);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls, and all of the interfaces implemented by the subclass will be exposed by the {@link ActorRef}.
*
* @param mailboxConfig this actor's mailbox settings.
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
*/
protected ProxyServerActor(MailboxConfig mailboxConfig, boolean callOnVoidMethods) {
this(null, null, mailboxConfig, callOnVoidMethods, null, null);
}
/**
* This constructor is for use by subclasses that are intended to serve as the target. This object will serve as the target
* for the method calls, and all of the interfaces implemented by the subclass will be exposed by the {@link ActorRef}.
* The default mailbox settings will be used.
*
* @param callOnVoidMethods whether calling void methods will block until they have completed execution
*/
protected ProxyServerActor(boolean callOnVoidMethods) {
this(null, null, null, callOnVoidMethods, null, null);
}
//</editor-fold>
@Override
protected final Server<Invocation, Object, Invocation> makeRef(ActorRef<Object> ref) {
try {
return getProxyClass(interfaces, callOnVoidMethods).getConstructor(ActorRef.class).newInstance(ref);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static final ConcurrentMap<Pair<Set<Class<?>>, Boolean>, Class<? extends Server>> classes = MapUtil.newConcurrentHashMap();
private static final ObjectProxyServerImpl handler1 = new ObjectProxyServerImpl(true);
private static final ObjectProxyServerImpl handler2 = new ObjectProxyServerImpl(false);
private static Class<? extends Server> getProxyClass(Class<?>[] interfaces, boolean callOnVoidMethods) {
final Pair<Set<Class<?>>, Boolean> key = new Pair(ImmutableSet.copyOf(interfaces), callOnVoidMethods);
Class<? extends Server> clazz = classes.get(key);
if (clazz == null) {
clazz = new ByteBuddy() // http://bytebuddy.net/
.subclass(Server.class)
.implement(interfaces)
.implement(java.io.Serializable.class)
.method(isDeclaredBy(anyOf(interfaces))).intercept(InvocationHandlerAdapter.of(callOnVoidMethods ? handler1 : handler2))
.make()
.load(ProxyServerActor.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
final Class<? extends Server> old = classes.putIfAbsent(key, clazz);
if (old != null)
clazz = old;
}
return clazz;
}
private static class ObjectProxyServerImpl implements InvocationHandler, java.io.Serializable {
private final boolean callOnVoidMethods;
private ObjectProxyServerImpl(boolean callOnVoidMethods) {
this.callOnVoidMethods = callOnVoidMethods;
}
boolean isInActor(Server<Invocation, Object, Invocation> ref) {
return Objects.equals(ref, LocalActor.self());
}
@Override
@Suspendable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
assert !method.getDeclaringClass().isAssignableFrom(ActorRefDelegate.class);
assert !method.getDeclaringClass().isAssignableFrom(Server.class);
// final Class<?> cls = method.getDeclaringClass();
// if (cls.isAssignableFrom(Server.class) || cls.isAssignableFrom(SendPort.class)) {
// try {
// return method.invoke(ref, args);
// } catch (InvocationTargetException e) {
// throw e.getCause();
// }
// }
final Server<Invocation, Object, Invocation> ref = (Server<Invocation, Object, Invocation>) proxy;
try {
if (isInActor(ref)) {
try {
return method.invoke(ServerActor.currentServerActor(), args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
} else {
final Invocation m = new Invocation(method, args, false);
if (callOnVoidMethods || (method.getReturnType() != void.class && method.getReturnType() != Void.class))
return ref.call(m);
else {
ref.cast(m);
return null;
}
}
} catch (SuspendExecution e) {
throw RuntimeSuspendExecution.of(e);
}
}
protected Object readResolve() throws java.io.ObjectStreamException {
return callOnVoidMethods ? handler1 : handler2;
}
}
@Override
protected void checkCodeSwap() throws SuspendExecution {
verifyInActor();
Object _target = ActorLoader.getReplacementFor(target);
if (_target != target)
log().info("Upgraded ProxyServerActor implementation: {}", _target);
this.target = _target;
super.checkCodeSwap();
}
@Override
protected Object handleCall(ActorRef<?> from, Object id, Invocation m) throws Exception, SuspendExecution {
try {
Object res = m.invoke(target);
return res == null ? NULL_RETURN_VALUE : res;
} catch (InvocationTargetException e) {
assert !(e.getCause() instanceof SuspendExecution);
log().error("handleCall: Invocation " + m + " has thrown an exception.", e.getCause());
throw rethrow(e.getCause());
}
}
@Override
protected void handleCast(ActorRef<?> from, Object id, Invocation m) throws SuspendExecution {
try {
m.invoke(target);
} catch (InvocationTargetException e) {
assert !(e.getCause() instanceof SuspendExecution);
log().error("handleCast: Invocation " + m + " has thrown an exception.", e.getCause());
}
}
protected static class Invocation implements java.io.Serializable {
private final Method method;
private final Object[] params;
public Invocation(Method method, List<Object> params) {
this.method = method;
this.params = params.toArray(new Object[params.size()]);
}
public Invocation(Method method, Object... params) {
this(method, params, false);
}
Invocation(Method method, Object[] params, boolean copy) {
this.method = method;
this.params = copy ? Arrays.copyOf(params, params.length) : params;
}
public Method getMethod() {
return method;
}
public List<Object> getParams() {
return Collections.unmodifiableList(Arrays.asList(params));
}
Object invoke(Object target) throws SuspendExecution, InvocationTargetException {
try {
return method.invoke(target, params);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() {
return method.toString() + Arrays.toString(params);
}
}
private static RuntimeException rethrow(Throwable t) throws Exception {
if (t instanceof Exception)
throw (Exception) t;
if (t instanceof Error)
throw (Error) t;
throw new RuntimeException(t);
}
}