din_oops
din_oops

Reputation: 708

Pass function and arguments to another function

I have some functions which does the same kind of operation in catch block.

private void fun1(int a){
  try{
    // do api calls
  }catch(Exception e){
    refreshToken();
    fun1(a);
  }
}

private int fun2(int a, String b){
  try{
    // do api calls
  }catch(Exception e){
    refreshToken();
    fun2(a,b);
  }
}

private void fun3(String a, long b, char c){
  try{
    // do api calls
  }catch(Exception e){
    refreshToken();
    fun3(a,b,c);
  }
}

Here when the application token expires, I need to call a function for fetching the new token and need to call the parent function.

In the catch block, the code is duplicated. So i would like to perform these operations in a single function. For that I need to pass function and arguments. Is it possible to do it in Java?

Upvotes: 0

Views: 514

Answers (5)

Adrian
Adrian

Reputation: 3134

consider using a library like Failsafe which offers far more flexibility in handling different error cases.

First of all you'll have to declare a retry policy that defines which failures should be handled and when retries should be performed. You want to retry only when token expires and not when another error is being thrown.

RetryPolicy<Object> retryPolicy = new RetryPolicy<>()
            .handle(TokenExpiredException.class)
            .withMaxRetries(3);

and call it as follows

Failsafe.with(retryPolicy)
            .onFailure(e -> refreshToken())
            .run(() -> fun1(42));

Also, it can be used with a (checked) supplier as is your case with fun2 (it returns an int):

int result = Failsafe.with(retryPolicy)
                .onFailure(e -> refreshToken())
                .get(() -> fun2(12, "cat"));

Upvotes: 1

Roland
Roland

Reputation: 23232

If you require such functionality more often, you may want to build your own support utility for that (which you can then also improve based on your needs), e.g.:

public class Retry {
  public static <T> T endlessTryCall(Callable<T> callable, Consumer<Exception> exceptionHandler) {
    for(;;) try {
        return callable.call();
      } catch (Exception e) {
        exceptionHandler.accept(e);
      }
  }

  public static void endlessTryRun(Runnable runnable, Consumer<Exception> exceptionHandler) {
    for(;;) try {
        runnable.run();
        return;
      } catch (Exception e) {
        exceptionHandler.accept(e);
      }
  }
}

Your functions could then look as follows:

private void fun1(int a){
  Retry.endlessTryRun(() -> {
    // the API calls
  }, e -> refreshToken());
}

private int fun2(int a, String b){
  return Retry.endlessTryCall(() -> {
    // the API calls
    return ...;
  }, e -> refreshToken());
}

private void fun3(String a, long b, char c){
  Retry.endlessTryRun(() -> {
    // the API calls
    }, e -> refreshToken());
}

Additional improvements could be to have something like a tryCall(numberOfTrials, ...)-function in place or another function that deals with the exception the way you need it. Demonstrating the latter:

private void fun1(int a){
  refreshTokenOnException(() -> {
    // the API calls
  });
}

private int fun2(int a, String b){
  return refreshTokenOnException(() -> {
    // the API calls
    return ...;
  });
}

private void fun3(String a, long b, char c){
  refreshTokenOnException(() -> {
    // the API calls
  });
}

private <T> T refreshTokenOnException(Callable<T> callable) {
  return Retry.endlessTryCall(callable, e -> refreshToken());
}
private void refreshTokenOnException(Runnable runnable) {
  Retry.endlessTryRun(runnable, e -> refreshToken());
}

The benefit of having such a utility in place is probably questionable. I think it is a bit more verbose regarding what is done. But then again one has to find suitable names for it... so... it depends as always.

Upvotes: 2

Holger
Holger

Reputation: 298103

You did already the most reasonable thing here, extract the common code into a method, refreshToken(), and invoke this method at each place where necessary. That’s how we do it and the “code duplication” of a single method invocation is acceptable. It’s not constructive to blow up the code to something far more complicated, even if that complicated code consists of a lot of different boilerplate for each case.

The only thing, you should stay away from, is to repeat the operation via recursion. Since the invalidation of the current token may happen an arbitrary number of times, as you are not in control of how long the evaluation will take, there could be an arbitrary number of recursions, raising the risk of a StackOverflowError. Also, the recursive call truly is avoidable code here.
Just use loops.

private void fun1(final int a) {
  for(;;) try {
    // do api calls, don't modify a
    return;
  } catch(Exception e) {
    refreshToken();
  }
}

private int fun2(final int a, final String b) {
  try {
    // do api calls, don't modify a nor b
    return result;
  } catch(Exception e) {
    refreshToken();
  }
}

private void fun3(final String a, final long b, final char c) {
  for(;;) try {
    // do api calls, don't modify a, b, nor c
    return;
  } catch(Exception e) {
    refreshToken();
  }
}

Upvotes: 1

Conffusion
Conffusion

Reputation: 4465

package be.test;

public class FuncTest {

    private void fun1(int a) {
        // do api calls
    }

    private int fun2(int a, String b) {
        // do api calls
        return 0;
    }

    private void fun3(String a, long b, char c) {
        // do api calls
    }

    private void refreshToken() {
        // do your refresh token logic
    }

    // functional interface
    public interface MyFunc {
        public void doIt();
    }

    public void callWithRefresh(MyFunc func)
    {
        try {
            func.doIt();
        } catch (Exception e) {
            refreshToken();
            func.doIt();
        }
    }
    public static void main(String[] args) {
        FuncTest ftest=new FuncTest();
        ftest.callWithRefresh(()->ftest.fun1(1));
        ftest.callWithRefresh(()->ftest.fun2(1,"hopla"));
        ftest.callWithRefresh(()->ftest.fun3("hopla",1,'c'));
    }
}

callWithRefreshcontains the generic logic to call refreshToken when an exception occures. (hoping the exception cause is related to the expired token :)

MyFunc is just an interface to be able to call doIt(). It is not necessary to implement this interface anywhere.

Upvotes: 0

Petronella
Petronella

Reputation: 2535

You can do a workaround and do a "switch...case", and give the switch key as a parameter...

public void catchCase(...){
refreshToken();
switch(expression) {
  case x:
    fun1(...)
     break;
  case y:
    fun2(...)
    break;
  case z:
    fun3(...)
  default:
    // code block

} }

you will need though to give in all the parameters for all 3 functions as argument...

Another option is using java reflection

Upvotes: 0

Related Questions