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

com.symphony.bdk.bot.sdk.lib.restclient.RestClientImpl Maven / Gradle / Ivy

package com.symphony.bdk.bot.sdk.lib.restclient;

import io.github.resilience4j.bulkhead.Bulkhead;
import io.github.resilience4j.bulkhead.BulkheadConfig;
import io.github.resilience4j.bulkhead.BulkheadRegistry;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.symphony.bdk.bot.sdk.lib.restclient.model.RestResponse;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.function.Supplier;

/**
 * Spring RestTemplate-based implementation of the {@link RestClient}
 *
 * @author Marcus Secato
 *
 */
public class RestClientImpl implements RestClient {
  private static final Logger LOGGER = LoggerFactory.getLogger(RestClientImpl.class);

  private RestTemplate restTemplate;

  private CircuitBreakerConfig cbConfig;

  private CircuitBreakerRegistry cbRegistry;

  private BulkheadConfig bhConfig;

  private BulkheadRegistry bhRegistry;

  public RestClientImpl(
      RestTemplate restTemplate, CircuitBreakerConfig cbConfig, BulkheadConfig bhConfig) {
    this.restTemplate = restTemplate;
    this.cbConfig = cbConfig;
    this.bhConfig = bhConfig;

    cbRegistry = CircuitBreakerRegistry.of(cbConfig);
    bhRegistry = BulkheadRegistry.of(bhConfig);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse getRequest(String url, Class clazz) {
    return doRequest(url, HttpMethod.GET, null, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse getRequest(String url,
      Map headers, Class clazz) {
    return doRequest(url, HttpMethod.GET, headers, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse postRequest(String url, U body,
      Class clazz) {
    return doRequestWithBody(url, HttpMethod.POST, null, body, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse postRequest(String url, U body,
      Map headers, Class clazz) {
    return doRequestWithBody(url, HttpMethod.POST, headers, body, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse putRequest(String url, U body,
      Class clazz) {
    return doRequestWithBody(url, HttpMethod.PUT, null, body, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse putRequest(String url, U body,
      Map headers, Class clazz) {
    return doRequestWithBody(url, HttpMethod.PUT, headers, body, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse deleteRequest(String url, Class clazz) {
    return doRequest(url, HttpMethod.DELETE, null, clazz);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public  RestResponse deleteRequest(String url,
      Map headers, Class clazz) {
    return doRequest(url, HttpMethod.DELETE, headers, clazz);
  }

  private  RestResponse doRequest(String url, HttpMethod httpMethod,
     Map headers, Class clazz) {
    HttpEntity httpEntity = new HttpEntity<>(convertHeaders(headers));

    return applyResourceControl(url, httpMethod, httpEntity, clazz);
  }

  private  RestResponse doRequestWithBody(String url,
      HttpMethod httpMethod, Map headers, U body, Class clazz) {
    HttpEntity httpEntity = new HttpEntity<>(body, convertHeaders(headers));

    return applyResourceControl(url, httpMethod, httpEntity, clazz);
  }

  /**
   * Applies circuit-breaker and bulkhead controls to improve resources usage
   * when communicating with external systems.
   */
  private  RestResponse applyResourceControl(String url,
      HttpMethod httpMethod, HttpEntity httpEntity, Class clazz) {

    String registryKey = getBaseUrl(url);

    CircuitBreaker cb = cbRegistry.circuitBreaker(registryKey, cbConfig);
    Supplier> cbSupplier = CircuitBreaker.decorateSupplier(cb,
        () -> internalDoRequest(url, httpMethod, httpEntity, clazz));

    Bulkhead bh = bhRegistry.bulkhead(registryKey, bhConfig);
    Supplier> bhSupplier = Bulkhead.decorateSupplier(
        bh, cbSupplier);

    try {
      return bhSupplier.get();
    } catch (CallNotPermittedException cnpe) {
      LOGGER.warn("Circuit breaker is now OPEN. Rejecting request {}", url);
      throw new RestClientConnectionException();
    }
  }

  private  RestResponse internalDoRequest(String url,
      HttpMethod httpMethod, HttpEntity httpEntity, Class clazz) {
    LOGGER.debug("Performing request {} {}", httpMethod, url);

    RestResponse response = null;
    try {
      ResponseEntity re = restTemplate.exchange(
          url, httpMethod, httpEntity, clazz);

      T body = re.hasBody() ? re.getBody() : null;
      response = new RestResponse<>(body,
          re.getHeaders().toSingleValueMap(), re.getStatusCodeValue());
    } catch (HttpClientErrorException | HttpServerErrorException reqEx) {
      LOGGER.debug("Unsuccessful response executing request {} {}\n",
          httpMethod, url, reqEx);
      response = new RestResponse<>(
          reqEx.getResponseHeaders().toSingleValueMap(),
          reqEx.getRawStatusCode());
    } catch (RestClientException rce) {
      LOGGER.error("Unexpected error executing request {} {}\n{}",
          httpMethod, url, rce);
      throw new RestClientConnectionException();
    }

    return response;
  }

  private MultiValueMap convertHeaders(
      Map headers) {
    if (headers != null) {
      MultiValueMap resttemplateHeaders =
          new LinkedMultiValueMap<>();

      headers.entrySet().stream()
      .forEach(e -> resttemplateHeaders.add(e.getKey(), e.getValue()));
      return resttemplateHeaders;
    }

    return null;
  }

  private String getBaseUrl(String url) {
    try {
      URI uri = new URI(url);
      return uri.getHost();
    } catch (URISyntaxException use) {
      LOGGER.debug("Could not parse URL: {}", url);
      return url;
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy