title: How to write an aspect to cater to different objects and method arguments
body: <p>I am looking to write an Aspect which can be used to mock data depending on some value passed in the method. This mock will replace the actual REST call. If the value doesn't match, go back to the actual method and make the call to the REST endpoint.</p>
  
  <p>I already have an aspect written that works in production but is very messy and had to implement multiple different methods in there to accommodate the need to return different objects and handle different method arguments. Is there a way I could generalise this so that I could have like a single method to perform the work and able to use if for future cases where I do not have to worry about object types and method signatures.</p>
  
  <p>Current implementation as follows. How could I amend this to work as a single method which can cater to future methods and response types without having to change my Aspect? </p>
  
  <p>To clarify I have the following 3 methods which I want to mock by using Aspect. as you can see I have a custom annotation that takes in a name. None of the method's arguments line up.  </p>
  
  <pre><code>@MyMock(name = "ABC")
  public AResponse getAResponse(ARequest aRequest){
      // make a REST call
  }
  
  @MyMock(name = "DEF")
  public BResponse getBResponse(String fruit, BRequest bRequest){
      // make a REST call
  }
  
  @MyMock(name = "GHJ")
  public CResponse getCResponse(String vehicle, CRequest cRequest, int id){
      // make a REST call
  }
  </code></pre>
  
  <p>The Aspect Class currently is as follows. I use the name value in the annotation to determine which method to call and which value type to return. As you can see this is not very scalable. I need to write a new method and logic every single time I implement new mocks.   </p>
  
  <pre><code>@Around("@annotation(myMock)")
  public Object getMockedData(ProceedingJoinPoint pjp, MyMock myMock) throws Throwable {
      String servName = performMocking.name();
      Object[] methodArguments = pjp.getArgs();
      MethodSignature signature = (MethodSignature) pjp.getSignature();
      Class returnType = signature.getReturnType();
  
      switch (myMock.name()) {
          case "ABC":
          getA(methodArguments[0]);
          break;
          case "DEF":
          getB(methodArguments[0]);
          break;
          case "GHJ":
          getC(methodArguments[2]);
          break;
      }
      return pjp.proceed(methodArguments);
  }
  
  private AResponse getA(ARequest aRequest){
       // I use methodArguments[0] (which is aRequest) to decide what value to return as a mock response here.
       // There is a getName value in that object which I use to reference  
  }
  
  private BResponse getB(BRequest bRequest){
       // I use methodArguments[0] (which is a String) to decide what value to return as a mock response here. 
  }
  
  private CResponse getC(CRequest cRequest){
       // I use methodArguments[2] (which is an int) to decide what value to return as a mock response here. 
  }
  </code></pre>
  
  <p>All the above get methods makes a call to an external JSON file to fetch the mock data. The files content as follows. If the keys match, a mock response would be given else go back to making actual REST call. </p>
  
  <pre><code>{
      "ABC": {
          "enabled": "true",
          "responses": [
              {
                  "a_name": "{some valid json response matching the AResponse Object structure}"
              }
          ]
      },
      "DEF": {
          "enabled": "true",
          "responses": [
              {
                  "d_name": "{some valid json response matching the BResponse Object structure}"
              }
          ]
      },
      "GHJ": {
          "enabled": "true",
          "responses": [
              {
                  "123": "{some valid json response matching the CResponse Object structure}"
              }
          ]
      }
  }
  </code></pre>
  
  <p>EDIT: Added my ASPECT class as follows: </p>
  
  <pre><code>package com.company.a.b.mock;
  
  import com.domain.abc.b.logging.util.MyLogger;
  import com.domain.abc.a.util.generic.ConfigHelper;
  import com.fasterxml.jackson.databind.DeserializationFeature;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import org.aspectj.lang.ProceedingJoinPoint;
  import org.aspectj.lang.annotation.Around;
  import org.aspectj.lang.annotation.Aspect;
  import org.aspectj.lang.reflect.MethodSignature;
  import org.springframework.stereotype.Component;
  
  import java.io.IOException;
  import java.util.List;
  import java.util.Map;
  
  @Aspect
  @Component
  public class AspectMocking {
  
      private final ConfigHelper configHelper;
      private final MyLogger myLogger;
  
      public AspectMocking(ConfigHelper configHelper, MyLogger myLogger) {
          this.configHelper = configHelper;
          this.myLogger = myLogger;
      }
  
      @Around("@annotation(myMock)")
      public Object getMockedData(ProceedingJoinPoint pjp, MyMock myMock) throws Throwable {
  
          final String env = System.getProperty("spring.profiles.active");
          String response = null;
          Object returnObject = null;
          String logMessage = null;
          String servName = myMock.name();
          Object[] methodArguments = pjp.getArgs();
          try {
              if ("test_env1".equals(env) || "test_env2".equals(env)) {
  
                  MethodSignature signature = (MethodSignature) pjp.getSignature();
                  Class returnType = signature.getReturnType();
  
                  Map allDataFromMockedFile = getMockedDataFromFile();
                  Map getResultForKey = (Map) allDataFromMockedFile.get(myMock.name());
  
                  List result = null;
                  if(getResultForKey != null){
                      result  = (List) getResultForKey.get("responses");
                  }
  
                  switch (myMock.name()) {
                  case "ABC":
                      response = fetchABCMockedData(result, (String) methodArguments[0]);
                      logMessage = "Fetching ABC mock data for ABC Response: " + response;
                      break;
                  case "V2_ABC":
                      response = fetchABCMockedData(getIntlResult(myMock.name(), (String)methodArguments[2]), (String) methodArguments[0]);
                      logMessage = "Fetching V2 ABC mock data for V2 ABC Response: " + response;
                      break;
                  case "DEF":
                      response = fetchDEFMockedData(result, (String) methodArguments[0]);
                      logMessage = "Fetching DEF mock data: " + response;
                      break;
                  case "GHJ":
                      response = fetchGHJMockedOfferData(result, (String) methodArguments[0], (String) methodArguments[1], (String) methodArguments[2]);
                      logMessage = "Fetching GHJ mock data: " + response;
                      break;
                  }
  
                  ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                  returnObject = mapper.readValue(response, returnType);
              }
          } catch (Exception exp) {
              myLogger.addMessageAsWarning(String
                      .format("Exception occured for service %s while loading the mocked json, so hitting the actual service:"
                              + exp.getMessage(), servName));
          }
  
          if (returnObject == null) {
              returnObject = pjp.proceed(methodArguments);
          }
  
          myLogger.addMessage(logMessage);
          return returnObject;
      }
  
  
      private List getIntlResult(String name, String locale){
          Map localBasedMockFile = getMockedDataFromFile(locale.toLowerCase());
          Map localeSpecificMockedData = (Map) localBasedMockFile.get(name);
          return (List) localeSpecificMockedData.get("responses");
      }
  
      // had to add this recently as we needed locale values now to go to a diff file. 
      private Map getMockedDataFromFile(String countryCode){
          final String DATA_URL = String.format("/a/b/c/%s/data.json", countryCode);
          return configHelper.getMAVDataAsObject(DATA_URL, Map.class);
      }
  
      private Map getMockedDataFromFile() {
          final String DATA_URL = "/a/b/c/zh/mock.json";
          return configHelper.getMAVDataAsObject(DATA_URL, Map.class);
      }
  
      private String fetchABCMockedData(List&lt;Map&gt; allResponses, String vinNumber) throws IOException {
          String response = null;
          for (Map m : allResponses) {
              String mockedVinNumber = m.keySet().toString().replaceAll("[\\[\\]]", "");
              if (vinNumber.equals(mockedVinNumber)) {
                  response = (String) m.get(mockedVinNumber);
              }
          }
          return response;
      }
  
      private String fetchDEFMockedData(List&lt;String&gt; allResponses, String vinNumber) {
          return allResponses.contains(vinNumber) ? "true" : "false";
      }
  
      private String fetchGHJMockedOfferData(List&lt;Map&gt; allResponses, String journey, String name, String pin) {
          String response = null;
          String key = journey+"_"+name + "_" + pin;
  
          for (Map m : allResponses) {
              String mockedKey = m.keySet().toString().replaceAll("[\\[\\]]", "");
              if (mockedKey.equals(key)) {
                  response = (String) m.get(mockedKey);
              }
          }
  
          return response;
      }
  }
  </code></pre>