de.mklinger.qetcher.client.impl.retry.RetryingQetcherClientImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qetcher-client-bundle Show documentation
Show all versions of qetcher-client-bundle Show documentation
Qetcher Java client, OSGi bundle, minimal dependencies
package de.mklinger.qetcher.client.impl.retry;
import java.io.File;
import java.io.IOException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.mklinger.micro.throwables.Throwables;
import de.mklinger.qetcher.client.InputConversionFile;
import de.mklinger.qetcher.client.InputJob;
import de.mklinger.qetcher.client.QetcherRemoteException;
import de.mklinger.qetcher.client.common.concurrent.Delay;
import de.mklinger.qetcher.client.impl.QetcherClientBuilderImpl;
import de.mklinger.qetcher.client.impl.QetcherClientImpl;
import de.mklinger.qetcher.client.impl.lookup.ServiceUriSupplier;
import de.mklinger.qetcher.client.model.v1.AvailableConversion;
import de.mklinger.qetcher.client.model.v1.AvailableNode;
import de.mklinger.qetcher.client.model.v1.ConversionFile;
import de.mklinger.qetcher.client.model.v1.Job;
/**
* Retry on 404 responses and IOExceptions.
*
* This can not be implemented as a pure decorator with delegate on top of
* QetcherClient interface, as we also have to wrap calls made internally,
* especially calls initiated by {@link #getJobDone(String)}.
*
* @author Marc Klinger - mklinger[at]mklinger[dot]de
*/
public class RetryingQetcherClientImpl extends QetcherClientImpl {
private static final Logger LOG = LoggerFactory.getLogger(RetryingQetcherClientImpl.class);
private static final int MAX_TRIES = 10;
public RetryingQetcherClientImpl(final QetcherClientBuilderImpl builder, final ServiceUriSupplier serviceUriSupplier) {
super(builder, serviceUriSupplier);
}
@Override
public CompletableFuture uploadFile(final InputConversionFile inputFile) {
return doWithRetry(() -> super.uploadFile(inputFile));
}
@Override
public CompletableFuture getFile(final String fileId) {
return doWithRetry(() -> super.getFile(fileId));
}
@Override
public CompletableFuture> getFiles() {
return doWithRetry(() -> super.getFiles());
}
@Override
public CompletableFuture deleteFile(final String fileId) {
return doWithRetry(() -> super.deleteFile(fileId));
}
@Override
public CompletableFuture downloadAsFile(final String fileId, final Path file, final OpenOption... openOptions) {
return doWithRetry(() -> super.downloadAsFile(fileId, file, openOptions));
}
@Override
public CompletableFuture createJob(final InputJob inputJob) {
return doWithRetry(() -> super.createJob(inputJob));
}
@Override
public CompletableFuture getJob(final String jobId) {
return doWithRetry(() -> super.getJob(jobId));
}
@Override
public CompletableFuture> getJobs() {
return doWithRetry(() -> super.getJobs());
}
@Override
public CompletableFuture deleteJob(final String jobId) {
return doWithRetry(() -> super.deleteJob(jobId));
}
@Override
public CompletableFuture> getAvailableConversions() {
return doWithRetry(() -> super.getAvailableConversions());
}
@Override
public CompletableFuture> getAvailableNodes() {
return doWithRetry(() -> super.getAvailableNodes());
}
@Override
public CompletableFuture getFile(final ConversionFile file) {
return doWithRetry(() -> super.getFile(file));
}
@Override
public CompletableFuture deleteFile(final ConversionFile file) {
return doWithRetry(() -> super.deleteFile(file));
}
@Override
public CompletableFuture downloadAsFile(final String fileId, final Path file) {
return doWithRetry(() -> super.downloadAsFile(fileId, file));
}
@Override
public CompletableFuture downloadAsFile(final String fileId, final File file) {
return doWithRetry(() -> super.downloadAsFile(fileId, file));
}
@Override
public CompletableFuture downloadAsTempFile(final String fileId) {
return doWithRetry(() -> super.downloadAsTempFile(fileId));
}
@Override
public CompletableFuture downloadAsTempFile(final String fileId, final Path dir) {
return doWithRetry(() -> super.downloadAsTempFile(fileId, dir));
}
@Override
public CompletableFuture getJob(final Job job) {
return doWithRetry(() -> super.getJob(job));
}
@Override
public CompletableFuture deleteJob(final Job job) {
return doWithRetry(() -> super.deleteJob(job));
}
@Override
public CompletableFuture getJobDone(final Job job) {
return doWithRetry(() -> super.getJobDone(job));
}
@Override
public CompletableFuture getJobDone(final String jobId) {
return doWithRetry(() -> super.getJobDone(jobId));
}
private CompletableFuture doWithRetry(final Supplier> action) {
final CompletableFuture cf = new CompletableFuture<>();
new Retrier<>(cf, action).run();
return cf;
}
private static class Retrier implements Runnable {
private final Supplier> action;
private final CompletableFuture cf;
private final AtomicInteger tries;
private final AtomicReference error;
public Retrier(final CompletableFuture cf, final Supplier> action) {
this.cf = cf;
this.action = action;
this.tries = new AtomicInteger(0);
this.error = new AtomicReference<>();
}
@Override
public void run() {
tries.incrementAndGet();
if (tries.get() > 1) {
LOG.info("Try #{}", tries.get());
}
action.get()
.thenAccept(cf::complete)
.exceptionally(newError -> {
if (!this.error.compareAndSet(null, newError)) {
this.error.get().addSuppressed(newError);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Completing retrier with error: {}", this.error.get().toString());
}
if (tries.get() < MAX_TRIES && isRetryCandidate(newError)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Triggering try #{} for error {}", tries.get() + 1, newError.toString());
}
Delay.delayedExecutor(100, TimeUnit.MILLISECONDS).execute(this);
} else {
cf.completeExceptionally(this.error.get());
}
return null;
});
}
private boolean isRetryCandidate(final Throwable error) {
return Throwables.firstCause(error, IOException.class)
.isPresent()
||
Throwables.firstCause(error, QetcherRemoteException.class)
.map(QetcherRemoteException::getStatusCode)
.map(statusCode -> statusCode == 404)
.orElse(false);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy