DavidA
DavidA

Reputation: 4184

How to modify unknown number of method args with aspectj around aspect

I want to create an annotation which uses an around aspect to scrub parameters with that annotation.

For example, a method could look like:

public void setName(@Scrubbed String name) { ... }

or perhaps

public void setFullName(@Scrubbed String firstName, @Scrubbed String lastName) { ... }

What I would like to do is something along these lines:

Object around(String arg) : call(* *(.., @Scrubbed (String), ..)) {
    return proceed(this.scrubString(arg));
}

However, I want to handle any number of arguments in any order. What I have working, but it seems like a hack is this:

Object around() : call(public * *(.., @Scrubbed (String), ..)) {
    Method method = MethodSignature.class.cast(thisJoinPoint.getSignature()).getMethod();
    Object[] args = thisJoinPoint.getArgs();
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    for (int argIndex = 0; argIndex < args.length; argIndex++) {
        for (Annotation paramAnnotation : parameterAnnotations[argIndex]) {
            if (!(paramAnnotation instanceof Scrubbed)) {
                continue;
            }
            args[argIndex] = this.scrubString((String)args[argIndex]);
        }
    }
    try {
        return method.invoke(thisJoinPoint.getTarget(), args);
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
    }
    return null;
}

I am basically using the joinPoint to gain access to reflection information, and end up using method.invoke instead of proceed().

I would love to be able to access ProceedingJoinPoint and call the proceed(Ojbect[] args) method it provides, but I don't know how to do that using native aspectj syntax.

Any ideas. I supposed I could use annotation @AJ aspectj syntax, but the rest of our aspects are using native syntax.

Upvotes: 2

Views: 212

Answers (1)

kriegaex
kriegaex

Reputation: 67297

I am so terribly sorry that I cannot offer you an elegant solution in native AspectJ syntax because I much prefer it to the annotation-based syntax. But this is one of the rare cases in which what you want to achieve is easier in @AspectJ syntax.

As far as the parameter annotations in arbitrary places and numbers are concerned, you need to use reflection the way you suggested already. This kind of uncertainty with regard to method signatures cannot be handled by conventional pointcut and parameter binding syntax.

But proceeding with a modified version of the getArgs() parameter array is indeed possible in @AspectJ syntax. Here we go:

Marker annotation:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Scrubbed {}

Target class + driver application:

package de.scrum_master.app;

public class Application {
  public void setName(@Scrubbed String name) {
    System.out.println("name = " + name);
  }

  public void setFullName(@Scrubbed String firstName, @Scrubbed String lastName) {
    System.out.println("firstName = " + firstName + ", lastName = " + lastName);
  }

  public static void main(String[] args) {
    Application application = new Application();
    application.setName("Albert Einstein");
    application.setFullName("Albert", "Einstein");
  }
}

Aspect:

package de.scrum_master.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import de.scrum_master.app.Scrubbed;

@Aspect
public class StringScrubberAspect {
  @Around("call(public * *(.., @de.scrum_master.app.Scrubbed (String), ..))")
  public Object scrubStringAdvice(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    Method method = MethodSignature.class.cast(thisJoinPoint.getSignature()).getMethod();
    Object[] args = thisJoinPoint.getArgs();
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    for (int argIndex = 0; argIndex < args.length; argIndex++) {
      for (Annotation paramAnnotation : parameterAnnotations[argIndex]) {
        if (paramAnnotation instanceof Scrubbed)
          args[argIndex] = scrubString((String) args[argIndex]);
      }
    }
    return thisJoinPoint.proceed(args);
  }

  private String scrubString(String string) {
    return string.replaceAll("[Ee]", "#");
  }
}

Console log:

name = Alb#rt #inst#in
firstName = Alb#rt, lastName = #inst#in

Update: I just saw that you already suggested this approach:

I supposed I could use annotation @AJ aspectj syntax, but the rest of our aspects are using native syntax.

That might be a cosmetic flaw, but there is no reason why you cannot mix the two syntax types, as long as you do not mix them within the same aspect. (Even the latter works in many cases, but in some it does not.)

Upvotes: 2

Related Questions