CultureQuestORG/SDP2023

View on GitHub
app/src/main/java/ch/epfl/culturequest/backend/map_collection/RetryingOTMProvider.java

Summary

Maintainability
A
0 mins
Test Coverage
D
68%
package ch.epfl.culturequest.backend.map_collection;

import com.google.android.gms.maps.model.LatLng;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * A decorator for an OTMProvider that retries the request if it fails
 */
public class RetryingOTMProvider implements OTMProvider{

    // The wrapped OTMProvider
    private final OTMProvider wrapped;

    // The number of times the provider will retry the request
    private final int numberOfRetries;

    private final static int DEFAULT_NUMBER_OF_RETRIES = 5;

    /**
     * Creates a new RetryingOTMProvider
     * @param wrapped the OTMProvider to wrap
     */
    public RetryingOTMProvider(OTMProvider wrapped, int numberOfRetries) {
        if(numberOfRetries <= 0) throw new IllegalArgumentException("Number of retries must be positive!");

        this.wrapped = Objects.requireNonNull(wrapped);
        this.numberOfRetries = numberOfRetries;
    }

    /**
     * Creates a new RetryingOTMProvider with the default number of retries
     * @param wrapped the OTMProvider to wrap
     */
    public RetryingOTMProvider(OTMProvider wrapped) {
        this(wrapped, DEFAULT_NUMBER_OF_RETRIES);
    }

    // Returns true if the throwable or one of its causes is an OTMException
    private boolean isOTMException(Throwable throwable) {
        Throwable current = throwable;
        while (current != null && current != current.getCause()) {
            if (current instanceof OTMException) {
                return true;
            }
            current = current.getCause();
        }
        return false;
    }

    // Overloaded method that will retry the request if it fails a number of times
    private CompletableFuture<List<OTMLocation>> getLocationsNTimes(LatLng upperLeft, LatLng lowerRight, int n){
        return wrapped.getLocations(upperLeft, lowerRight).handle((result, throwable) -> {
            CompletableFuture<List<OTMLocation>> future = new CompletableFuture<>();
            if (throwable == null){
                future.complete(result);
                return future;
            }
            else if (isOTMException(throwable)) {
                future.completeExceptionally(new OTMException(throwable.getMessage()));
                return future;
            } else if (n == 0){
                future.completeExceptionally(new OTMException("Too many retries"));
                return future;
            } else {
                return getLocationsNTimes(upperLeft, lowerRight, n-1);
            }
        }).thenApply(CompletableFuture::join);
    }


    private CompletableFuture<List<OTMLocation>> getLocationsNTimes(String city, int n){
        return wrapped.getLocations(city).handle((result, throwable) -> {
            CompletableFuture<List<OTMLocation>> future = new CompletableFuture<>();
            if (throwable == null){
                future.complete(result);
                return future;
            }
            else if (isOTMException(throwable)) {
                future.completeExceptionally(new OTMException(throwable.getMessage()));
                return future;
            } else if (n == 0){
                future.completeExceptionally(new OTMException("Too many retries"));
                return future;
            } else {
                return getLocationsNTimes(city, n-1);
            }
        }).thenApply(CompletableFuture::join);
    }



    @Override
    public CompletableFuture<List<OTMLocation>> getLocations(LatLng upperLeft, LatLng lowerRight) {
       return getLocationsNTimes(upperLeft, lowerRight, numberOfRetries);
    }

    @Override
    public CompletableFuture<List<OTMLocation>> getLocations(String city) {
        return getLocationsNTimes(city, DEFAULT_NUMBER_OF_RETRIES);
    }
}