João Pinto
João Pinto

Reputation: 1998

Pointcut for methods with a specific annotation that override methods of an interface

Please consider the following setup:

public interface IVehicle {
    public void start() {}  
    public void move() {}  
    public void stop() {}
}
public class RaceCar implements IVehicle {
  @Override
  @Authenticated(Role.Pilot)
  public void start() { /* Logic to start the car. */ }  
  @Override
  @Authenticated(Role.Pilot)
  public void move() { /* Logic to move the car. */ }  
  @Override
  @Authenticated(Role.Pilot)
  public void stop() { /* Logic to the stop car. */ }
}
public class RacingApp {
    public static void main(String[] args) {
        IVehicle raceCar = new RaceCar();
        raceCar.start();
        raceCar.move();
        raceCar.stop();        
    }  
} 

I need to get all calls to methods of RaceCar, that have the annotation @Authenticated, made inside the RacingApp class. The problem is that the calls are made to the interface IVehicle and not to the class RaceCar itself. Through polymorphism the methods are inferred to be from the RaceCar class at run time.

I have tried many pointcuts but I have not been able to achieve this yet. My best pointcut so far, in my opinion, is the following:

@Pointcut("call(@Authenticated * IVehicle+.*(..)) && within(RacingApp)")

I think that I am pretty close but I can't seem to get this to work. Does anyone know how this can be achieved?

Upvotes: 0

Views: 2425

Answers (1)

kriegaex
kriegaex

Reputation: 67317

At first, a little correction: In IVehicle the methods must not have bodies but semicolons after their declarations and the public for each method is superfluous because all methods declared in interfaces are public by definition. So it should look like this to at least make it compile:

package de.scrum_master.app;

public interface IVehicle {
  void start();
  void move();
  void stop();
}

In order to make your example compile for me I also recreated the other helper classes like this so as go get a minimal, complete, and verifiable example (which I think should have been your job, BTW):

package de.scrum_master.app;

public enum Role {
  Pilot, Passenger
}
package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated {
  Role value();
}
package de.scrum_master.app;

public class RaceCar implements IVehicle {
  @Override
  @Authenticated(Role.Pilot)
  public void start() {
    System.out.println("Starting");
  }

  @Override
  @Authenticated(Role.Pilot)
  public void move() {
    System.out.println("Moving");
  }

  @Override
  @Authenticated(Role.Pilot)
  public void stop() {
    System.out.println("Stopping");
  }
}
package de.scrum_master.app;

public class RacingApp {
  public static void main(String[] args) {
    IVehicle raceCar = new RaceCar();
    raceCar.start();
    raceCar.move();
    raceCar.stop();
  }
}

Concerning your aspect, there is a detail that keeps it from functioning: When intercepting a call() to an interface method, the JVM and AspectJ only know that the interface does not have the annotations you are filtering with. Due to polymorphism, as you already mentioned, only during execution() it is clear which concrete implementing class's methods are executed.

Please also note that if you do not use native AspectJ syntax but the tedious annotation-driven @AspectJ style, you need to use fully qualified class names (i.e. incl. packages) in order to make your pointcuts match. For example:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class VehicleActionInterceptor {
  @Before("execution(@de.scrum_master.app.Authenticated * de.scrum_master.app.IVehicle+.*(..))")
  public void beforeAction(JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint);
  }
}

This yields the following console output when running the driver application:

execution(void de.scrum_master.app.RaceCar.start())
Starting
execution(void de.scrum_master.app.RaceCar.move())
Moving
execution(void de.scrum_master.app.RaceCar.stop())
Stopping

If you want to do something with the annotation, you can also bind it to a parameter:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import de.scrum_master.app.Authenticated;

@Aspect
public class VehicleActionInterceptor {
  @Before("execution(* de.scrum_master.app.IVehicle+.*(..)) && @annotation(authenticated)")
  public void beforeAction(JoinPoint thisJoinPoint, Authenticated authenticated) {
    System.out.println(thisJoinPoint + " -> " + authenticated);
  }
}

Now the output becomes:

execution(void de.scrum_master.app.RaceCar.start()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Starting
execution(void de.scrum_master.app.RaceCar.move()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Moving
execution(void de.scrum_master.app.RaceCar.stop()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Stopping

The same aspect in more elegant native syntax and without the need to use fully qualified class names because you can use imports:

package de.scrum_master.aspect;

import de.scrum_master.app.IVehicle;
import de.scrum_master.app.Authenticated;

public aspect VehicleActionInterceptor {
  before(Authenticated authenticated) : execution(* IVehicle+.*(..)) && @annotation(authenticated) {
    System.out.println(thisJoinPoint + " -> " + authenticated);
  }
}

Update: The OP asked how to limit joinpoint matching to only those executions which result from calls made by a certain class. The solution is to combine the execution() pointcut with a control clow pointcut like cflow(call(...) && within(ClassOfInterest)).

But first let us extend out test case with a little more log output and a second application class because we need a negative test case:

package de.scrum_master.app;

public class RacingApp {
  public static void main(String[] args) {
    System.out.println("=== Racing app ===");
    IVehicle raceCar = new RaceCar();
    raceCar.start();
    raceCar.move();
    raceCar.stop();
    AnotherApp.main(args);
  }
}
package de.scrum_master.app;

public class AnotherApp {
  public static void main(String[] args) {
    System.out.println("=== Another app ===");
    IVehicle raceCar = new RaceCar();
    raceCar.start();
    raceCar.move();
    raceCar.stop();
  }
}

Now we extend our aspect:

package de.scrum_master.aspect;

import de.scrum_master.app.IVehicle;
import de.scrum_master.app.Authenticated;
import de.scrum_master.app.RacingApp;

public aspect VehicleActionInterceptor {
  before(Authenticated authenticated) :
    execution(* IVehicle+.*(..)) && @annotation(authenticated) &&
    cflow(call(* IVehicle+.*(..)) && within(RacingApp))
  {
    System.out.println(thisJoinPoint + " -> " + authenticated);
  }
}

The log output now becomes:

=== Racing app ===
execution(void de.scrum_master.app.RaceCar.start()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Starting
execution(void de.scrum_master.app.RaceCar.move()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Moving
execution(void de.scrum_master.app.RaceCar.stop()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Stopping
=== Another app ===
Starting
Moving
Stopping

Et voilà!

Upvotes: 1

Related Questions