Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "proxy" a method in Java

First off, I'm not sure how to best word my solution so if I seem to be babbling at times then please consider this.

There is an interface in a library I wish to modify without touching the physical code,

public interface ProxiedPlayer {
    // .. other code
    public void setPermission(String permission, boolean state);
}

I have written a third party library for handling permissions and having to hook into my API to edit permissions may be a step some developers do not want to take. So I ask that when setPermission is called is it possible to have it invoke my invoke the appropriate method in my library that will handle permission setting whilst ignoring the pre-programmed code or not?

Here is the full interface I am attempting to proxy.

I have looked into the Java Proxy class but it seems you need an instance of the object you're trying to proxy in the first place. Given that the method can be called any time I do not believe this to be my solution but will happily stand corrected.

I do not have control over instantiation of classes implementing the ProxiedPlayer interface.

EDIT: Ignorant me, there several events that I can subscribe to where it is possible to get an instance of the player, would this be the appropriate place to attempt to proxy the method? One of these events is fired when a player joins the server and getting the instance of the player is possible.

Would the Proxy code need to be called for every instance of the ProxiedPlayer interface or is it possible to simply proxy every invocation of the method in an easier way?

My library is a plugin loaded after everything else that is essential has finished loading.

Edit #2:

import net.md_5.bungee.api.connection.ProxiedPlayer;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class InvocationProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ProxiedPlayer player = (ProxiedPlayer) proxy;
        if(method.getName().equals("setPermission")) {
            // Call my code here?
        }
        return method.invoke(player, args);
    }
}

Would something along the lines of what I have above work or am I barking up the wrong tree entirely?

like image 831
Connor Spencer Harries Avatar asked Oct 19 '22 18:10

Connor Spencer Harries


1 Answers

If you do not want to touch the original source, then you only solve this problem by using a Java agent that redefines any class that implements the ProxiedPlayer interface to enforce your security check before calling the actual method. AspectJ together with a load-time-weaving agent was already mentioned as a possible solution for this but you can also implement a pure Java solution using my library Byte Buddy:

public class InterceptionAgent {
  public static void premain(String arguments, 
                             Instrumentation instrumentation) {
    new AgentBuilder.Default()
      .rebase(isSubtypeOf(ProxiedPlayer.class))
      .transform(new AgentBuilder.Transformer() {
        @Override
        public DynamicType.Builder transform(DynamicType.Builder builder) {
          return builder.method(named("setPermission"))
                        .intercept(MethodDelegation.to(MyInterceptor.class)
                      .andThen(SuperMethodInvocation.INSTANCE));
        }
      }).installOn(instrumentation);
  }
}

With this agent, you more or less specify that you want to redefine any class that is a subtype of ProxiedPlayer to redefine (any) method named setPermisson in order to call a MyInterceptor (that would be your code) and to subsequently call the original implementation.

Note that the suggested implementation assumes that all classes implementing ProxiedPlayer implement the method of this interface and that there is only a single method of this signature. This might be too simple but it shows what direction to go.

like image 188
Rafael Winterhalter Avatar answered Oct 29 '22 03:10

Rafael Winterhalter