package de.dfki.nlp.io;
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* Simple Wrapper around RestTemplate, which retries to download documents, in case of failures.
*/
@Component
@Slf4j
public class RetryHandler {
private final RestTemplate restTemplate;
public RetryHandler(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
restTemplate.getInterceptors().add(new PerfRequestSyncInterceptor());
}
@Retryable(value = ResourceAccessException.class, maxAttempts = 10, backoff = @Backoff(value = 1000, multiplier = 2, maxDelay = 60000))
public <T> T retryableGet(String urlpattern, Class<T> responseType, Object... uriVariables) {
try {
return restTemplate.getForObject(urlpattern, responseType, uriVariables);
} catch (HttpClientErrorException ex) {
if (ex.getStatusCode() != HttpStatus.NOT_FOUND) {
// retry
throw new ResourceAccessException("Client Error " + ex.getMessage());
} else {
log.error("Error '{}' downloading document id {} from {}", ex.getMessage(), uriVariables, urlpattern);
}
}
return BeanUtils.instantiate(responseType);
}
@Retryable(value = ResourceAccessException.class, maxAttempts = 10, backoff = @Backoff(value = 1000, multiplier = 2, maxDelay = 60000))
public <T> Optional<T> retryablePost(String urlpattern, Object request, Class<T> responseType, Object... uriVariables) {
try {
return Optional.of(restTemplate.postForObject(urlpattern, request, responseType, uriVariables));
} catch (HttpClientErrorException ex) {
if (ex.getStatusCode() != HttpStatus.NOT_FOUND) {
// retry
throw new ResourceAccessException("Client Error " + ex.getMessage());
} else {
log.error("Error '{}' downloading documents {} from {}", ex.getMessage(), request, urlpattern);
}
}
// return empty
return Optional.empty();
}
// handle 404
@Recover
public <T> T recoverGet(ResourceAccessException e, String urlpattern, Class<T> responseType, Object... uriVariables) throws IllegalAccessException, InstantiationException {
log.error("Failed to download after 10 tries {} ... continuing", e.getMessage());
return responseType.newInstance();
}
@Recover
public <T> Optional<T> recoverPost(ResourceAccessException e, String urlpattern, Object request, Class<T> responseType, Object... uriVariables) throws IllegalAccessException, InstantiationException {
log.error("Failed to download after 10 tries {} ... continuing", e.getMessage());
return Optional.empty();
}
/**
* Simple class that logs the time taken for a request.
*/
@Slf4j
public static class PerfRequestSyncInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest hr, byte[] bytes, ClientHttpRequestExecution chre) throws IOException {
Stopwatch stopwatch = Stopwatch.createStarted();
ClientHttpResponse response = chre.execute(hr, bytes);
stopwatch.stop();
log.info("corpus adapter performance: method={}, response_time={}, response_code={}, uri={}",
hr.getMethod(), stopwatch.elapsed(TimeUnit.MILLISECONDS), response.getStatusCode().value(), StringUtils.abbreviate(hr.getURI().toString(), 70));
return response;
}
}
}