All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.sornerol.chess.pubapi.client.PubApiClientBase Maven / Gradle / Ivy

The newest version!
package io.github.sornerol.chess.pubapi.client;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.sornerol.chess.pubapi.client.enums.ResponseCode;
import io.github.sornerol.chess.pubapi.client.enums.RetryStrategy;
import io.github.sornerol.chess.pubapi.exception.ChessComPubApiException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.message.BasicHeader;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * The base class of all the PubAPI client classes.
 */
@Slf4j
abstract class PubApiClientBase {

    /**
     * Default value for retryInterval.
     */
    public static final Integer DEFAULT_RETRY_INTERVAL = 3;

    /**
     * Default value for maxRetries.
     */
    public static final Integer DEFAULT_MAX_RETRIES = 2;

    /**
     * Default value for retryStrategy.
     */
    public static final RetryStrategy DEFAULT_RETRY_STRATEGY = RetryStrategy.RETRY_N_TIMES;

    /**
     * If supplied, the userAgent value will be passed in the User-Agent header when making requests to Chess.com.
     * By specifying this value with your contact information, Chess.com can contact you in the event they need to
     * block access for your application.
     *
     * @see Rate Limiting
     */
    @Getter
    @Setter
    private String userAgent;

    /**
     * Number of seconds to wait before retrying if a request fails due to a 429 response code. If retryStrategy is set
     * to NEVER, this field does nothing.
     *
     * @see Rate Limiting
     */
    @Getter
    @Setter
    private Integer retryInterval;

    /**
     * Maximum number of retries when retryStrategy is RETRY_N_TIMES. This does not include the initial attempt
     * (e.g. if maxRetries is 2, the client will make three total attempts before giving up).
     *
     * @see Rate Limiting
     */
    @Getter
    @Setter
    private Integer maxRetries;

    /**
     * Defines the behavior to use when receiving a 429 response from the Chess.com API.
     *
     * @see Rate Limiting
     */
    @Getter
    @Setter
    private RetryStrategy retryStrategy;

    /**
     * Determines whether the {@link ObjectMapper} should fail if the response contains an
     * unknown property. For most applications, this value should be false. Turning this feature
     * on is really only useful for testing scenarios.
     */
    @Getter
    @Setter
    private Boolean failOnUnknownProperties;

    private final CloseableHttpClient httpClient;

    protected PubApiClientBase() {
        httpClient = HttpClients.createDefault();
        retryInterval = DEFAULT_RETRY_INTERVAL;
        maxRetries = DEFAULT_MAX_RETRIES;
        retryStrategy = DEFAULT_RETRY_STRATEGY;
        failOnUnknownProperties = false;
    }

    /**
     * Execute a GET request on the specified endpoint and deserialize the response into a domain object.
     *
     * @param endpoint The full Chess.com PubAPI URL.
     * @param clazz    The class of the object to return.
     * @param       TDescribes the type parameter.
     * @return The deserialized object of the type specified.
     * @throws IOException             if there is a problem connecting to Chess.com.
     * @throws ChessComPubApiException if Chess.com returns a non-success response code.
     */
    protected  T getRequest(String endpoint, Class clazz) throws IOException, ChessComPubApiException {
        String responseJson = getRequest(endpoint);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
        return objectMapper.readValue(responseJson, clazz);
    }

    /**
     * Execute a GET request on the specified endpoint and return the response as a String.
     *
     * @param endpoint The full Chess.com PubAPI URL.
     * @return The deserialized object of the type specified.
     * @throws IOException             if there is a problem connecting to Chess.com.
     * @throws ChessComPubApiException if Chess.com returns a non-success response code.
     */
    protected String getRequest(String endpoint) throws IOException, ChessComPubApiException {
        HttpGet request = new HttpGet(endpoint);
        if (userAgent != null) {
            request.addHeader(new BasicHeader("User-Agent", userAgent));
        } else {
            log.warn("No User-Agent provided. Your request may not work. See " +
                    "https://www.chess.com/announcements/view/breaking-change-user-agent-contact-info-required for" +
                    "additional details");
        }

        String responseBody;
        Integer attempts = 1;

        boolean keepTrying = false;
        do {
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                int statusCode = response.getCode();
                if (statusCode == ResponseCode.RATE_LIMIT_EXCEEDED.getValue() && shouldTryRequestAgain(attempts)) {
                    keepTrying = true;
                    attempts++;
                    Thread.sleep(retryInterval * 1000);
                } else if (statusCode != ResponseCode.OK.getValue()) {
                    throw new ChessComPubApiException("Error executing GET request: API returned status code " + statusCode);
                }
                InputStream inputStream = response.getEntity().getContent();
                responseBody = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            } catch (InterruptedException e) {
                throw new ChessComPubApiException("Error while executing request: " + e.getMessage());
            }
        } while (keepTrying);

        return responseBody;
    }

    private boolean shouldTryRequestAgain(Integer attemptsSoFar) {
        if (retryStrategy == RetryStrategy.RETRY_FOREVER) {
            return true;
        }

        return retryStrategy == RetryStrategy.RETRY_N_TIMES && attemptsSoFar <= maxRetries;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy