YerivanLazerev
YerivanLazerev

Reputation: 365

Modifying return type of method based on parameter of a calling method with aspectJ

I need to modify return type of a method of a legacy code using aspectj.

class MyClass{
   public void processTracker(TrackInfo trackInfo) {        
     if (isValid(this.getStatus()) {
        process(trackInfo);
     }
   }

  boolean isValid(Status status){
   ...
  } 
}

I want isValid method to return true/false by some other logic based state of TrackInfo object (which is passed parameter to processTracker method)

aspecting processTracker method will give me paramaeter but won't give option to modify return value of isValid

@Around("execution(* MyClass.processTracker(..))

aspecting isValid won't give me access to parameter trackInfo

2 aspects it's not possible as this code runs in multi-threaded... I am not using Spring and can't add custom annotation to the legacy code.

any ideas?

Upvotes: 1

Views: 199

Answers (1)

kriegaex
kriegaex

Reputation: 67297

Actually, your question is hard to understand, maybe because your command of English is not particularly good. In particular, I have no idea why you think that multi-threading should be any issue here. Maybe you can explain that in a little more detail.

Anyway, I am offering you two AOP solutions here:

  1. Just call process(TrackInfo) directly from the aspect if the if condition really is the whole logic in processTracker(TrackInfo), as indicated by your sample code. Semantically, you just replace the whole logic of the intercepted method.

  2. If there is in fact more logic within processTracker(TrackInfo) and your sample code was over-simplified, like a surgeon you need to cut with a finer knive and apply what in AOP terms is often referenced to as a wormhole pattern.

Application + helper classes:

Because your sample code is incomplete, I had to guess and make up an MCVE, which next time I expect you to do as it is actually your job, not mine.

package de.scrum_master.app;

public enum Status {
    VALID, INVALID
}
package de.scrum_master.app;

public class TrackInfo {
  private String info;

  public TrackInfo(String info) {
    this.info = info;
  }

  public String getInfo() {
    return info;
  }

  @Override
  public String toString() {
    return "TrackInfo(" + info + ")";
  }
}
package de.scrum_master.app;

import static de.scrum_master.app.Status.*;

public class MyClass {
  private Status status = VALID;

  public void processTracker(TrackInfo trackInfo) {
    if (isValid(getStatus()))
      process(trackInfo);
  }

  public void process(TrackInfo trackInfo) {
    System.out.println("Processing " + trackInfo);
  }

  private Status getStatus() {
    if (status == VALID)
      status = INVALID;
    else
      status = VALID;
    return status;
  }

  boolean isValid(Status status) {
    return status == VALID;
  }

  public static void main(String[] args) {
    MyClass myClass = new MyClass();
    myClass.processTracker(new TrackInfo("normal"));
    myClass.processTracker(new TrackInfo("whatever"));
    myClass.processTracker(new TrackInfo("special"));
  }
}

As you can see, I am just alternating the validity from invalid to valid and back with every call, just to get different results when running the main method.

The console log is:

Processing TrackInfo(whatever)

So far, so good. No let us assume that if the TrackInfo matches the string "special", we want to always assume the validity check evaluates to true.

1.) Aspect replacing logic of processTracker(TrackInfo)

package de.scrum_master.aspect;

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

import de.scrum_master.app.MyClass;
import de.scrum_master.app.TrackInfo;

@Aspect
public class SimpleAspect {
  @Around("execution(* de.scrum_master.app.MyClass.processTracker(..)) && args(trackInfo) && target(myClass)")
  public void modifyValidityCheck(ProceedingJoinPoint thisJoinPoint, TrackInfo trackInfo, MyClass myClass) throws Throwable {
    if (trackInfo.getInfo().equalsIgnoreCase("special")) {
      // Kick off processing based on some special logic
      myClass.process(trackInfo);
    }
    else {
      // Proceed normally
      thisJoinPoint.proceed();
    }
  }
}

Here we do not need to know what the validity check would evaluate to but just call process(TrackInfo) directly if needed. The log output changes to:

Processing TrackInfo(whatever)
Processing TrackInfo(special)

2.) Wormhole pattern solution

Here we actually pull the TrackInfo from the calling method processTracker(TrackInfo) as context information into isValid(Status status) so we can directly modify the validity check result if necessary.

package de.scrum_master.aspect;

import static de.scrum_master.app.Status.*;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import de.scrum_master.app.Status;
import de.scrum_master.app.TrackInfo;

@Aspect
public class WormholePatternAspect {
  @Pointcut("execution(* de.scrum_master.app.MyClass.processTracker(..)) && args(trackInfo)")
  public static void processTracker(TrackInfo trackInfo) {}

  @Pointcut("execution(* de.scrum_master.app.MyClass.getStatus())")
  public static void getStatus() {}

  @Around("getStatus() && cflow(processTracker(trackInfo))")
  public Status modifyValidityCheck(ProceedingJoinPoint thisJoinPoint, TrackInfo trackInfo) throws Throwable {
    if (trackInfo.getInfo().equalsIgnoreCase("special")) {
      // Return true based on some special logic
      return VALID;
    }
    else {
      // Proceed normally
      return (Status) thisJoinPoint.proceed();
    }
  }
}

The console log is the same as with the first aspect, but if there is more logic within processTracker(TrackInfo), the rest of it would also be executed, not cut off (replaced) as in the first aspect.

Take your pick. I recommend to go with the simpler solution if applicable. The wormhole pattern is elegant but more difficult to understand and requires runtime call-stack analysis due to cflow(), thus it should be slightly slower as well.

Upvotes: 1

Related Questions