예제 #1
0
/**
 * 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)
                      .withMethodCache())
              .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);
  }
}
예제 #2
0
/** @author pron */
public class Retransform {
  static volatile Instrumentation instrumentation;
  static volatile MethodDatabase db;
  static volatile Set<WeakReference<ClassLoader>> classLoaders =
      Collections.newSetFromMap(
          MapUtil.<WeakReference<ClassLoader>, Boolean>newConcurrentHashMap());

  private static final CopyOnWriteArrayList<ClassLoadListener> listeners =
      new CopyOnWriteArrayList<ClassLoadListener>();

  public static void retransform(Class<?> clazz) throws UnmodifiableClassException {
    instrumentation.retransformClasses(clazz);
  }

  public static void redefine(Collection<ClassDefinition> classDefinitions) {
    try {
      instrumentation.redefineClasses(classDefinitions.toArray(new ClassDefinition[0]));
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public static MethodDatabase getMethodDB() {
    return db;
  }

  public static boolean isInstrumented(Class clazz) {
    return SuspendableHelper.isInstrumented(clazz);
  }

  //    public static boolean isInstrumented(String className) {
  //        for (Iterator<WeakReference<ClassLoader>> it = classLoaders.iterator(); it.hasNext();) {
  //            final WeakReference<ClassLoader> ref = it.next();
  //            final ClassLoader loader = ref.get();
  //            if (loader == null)
  //                it.remove();
  //            else {
  //                try {
  //                    if (isInstrumented(Class.forName(className, false, loader)))
  //                        return true;
  //                } catch (ClassNotFoundException ex) {
  //                }
  //            }
  //        }
  //        return false;
  //    }
  public static void addWaiver(String className, String methodName) {
    SuspendableHelper.addWaiver(className, methodName);
  }

  public static boolean isWaiver(String className, String methodName) {
    return SuspendableHelper.isWaiver(className, methodName);
  }

  public static Boolean isSuspendable(String className, String methodName) {
    if (db == null) return null;
    final MethodDatabase.ClassEntry ce = db.getClassEntry(className);
    if (ce == null) return null;
    return ce.isSuspendable(methodName);
  }

  static void beforeTransform(String className, Class clazz, byte[] data) {
    for (ClassLoadListener listener : listeners) listener.beforeTransform(className, clazz, data);
  }

  static void afterTransform(String className, Class clazz, byte[] data) {
    for (ClassLoadListener listener : listeners) listener.afterTransform(className, clazz, data);
  }

  public static void dumpClass(String className, byte[] data) {
    System.out.println("DUMP OF CLASS: " + className);
    ClassReader cr = new ClassReader(data);
    ClassVisitor cv = new TraceClassVisitor(null, new Textifier(), new PrintWriter(System.out));
    cr.accept(cv, ClassReader.SKIP_FRAMES);
    System.out.println("=================");
  }

  public static void addClassLoadListener(ClassLoadListener listener) {
    listeners.addIfAbsent(listener);
  }

  public interface ClassLoadListener {
    void beforeTransform(String className, Class clazz, byte[] data);

    void afterTransform(String className, Class clazz, byte[] data);
  }
}