Api { private static <Y, Z> Z call(final Function<Y, Z> function, final Y proxy, final RequestCounter counter, final int attemptNo) { LOGGER.trace("Executing..."); try { return function.apply(proxy); } catch (final ProcessingException ex) { if (!isConnectionIssue(ex)) { throw new ProcessingException("Operation failed and can not be retried.", ex); } else if (attemptNo > 2) { throw new ProcessingException("Operation failed even after " + attemptNo + " retries.", ex); } LOGGER.debug("Caught socket timeout. Retry #{} starting.", attemptNo, ex); return call(function, proxy, counter, attemptNo + 1); } finally { if (counter != null) { counter.mark(); LOGGER.trace("... done. (Request counts: {} in last 60 sec., {} in last 5 min.)", () -> counter.count(ONE_MINUTE), () -> counter.count(FIVE_MINUTES)); } else { LOGGER.trace("... done. (Not counting towards the API quota.)"); } } } Api(final T proxy);  Api(final T proxy, final RequestCounter counter);   }
@Test void executeFunction() { final LoanApi mock = mock(LoanApi.class); final Api<LoanApi> api = new Api<>(mock, counter); final String expected = UUID.randomUUID() .toString(); final Function<LoanApi, String> function = (a) -> expected; final String result = api.call(function); assertThat(result).isSameAs(expected); assertThat(counter.count()).isEqualTo(1); }
@Test void failsImmediately() { final LoanApi mock = mock(LoanApi.class); final Api<LoanApi> api = new Api<>(mock, counter); final Function<LoanApi, String> function = (a) -> { throw new IllegalStateException(); }; assertThatThrownBy(() -> api.call(function)).isInstanceOf(IllegalStateException.class); assertThat(counter.count()).isEqualTo(1); }
@Test void failsAndDoesNotRetry() { final LoanApi mock = mock(LoanApi.class); final Api<LoanApi> api = new Api<>(mock, counter); final Function<LoanApi, String> function = (a) -> { throw new ProcessingException(new IllegalStateException()); }; assertThatThrownBy(() -> api.call(function)) .isInstanceOf(ProcessingException.class) .hasCauseInstanceOf(ProcessingException.class); assertThat(counter.count()).isEqualTo(1); }
@Test void retriesAfterTimeout() { final LoanApi mock = mock(LoanApi.class); final Api<LoanApi> api = new Api<>(mock); final String expected = UUID.randomUUID() .toString(); final LongAdder adder = new LongAdder(); final Function<LoanApi, String> function = (a) -> { adder.add(1); if (adder.intValue() > 2) { return expected; } else if (adder.intValue() == 2) { throw new ProcessingException(new IOException()); } else { throw new ProcessingException(new SocketTimeoutException()); } }; final String result = api.call(function); assertThat(result).isSameAs(expected); }