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

com.geotab.http.invoker.ServerInvoker Maven / Gradle / Ivy

/*
 *
 * 2020 Copyright (C) Geotab Inc. All rights reserved.
 */

package com.geotab.http.invoker;

import static com.geotab.http.exception.ErrorHandler.checkForError;
import static org.apache.hc.core5.http.io.entity.HttpEntities.create;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.geotab.http.exception.ResponseFailException;
import com.geotab.http.request.BaseRequest;
import com.geotab.http.response.BaseResponse;
import com.geotab.model.serialization.ApiJsonSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.util.Timeout;

/**
 * A class for communicating over the Internet to MyGeotab.
 */
@Slf4j
public class ServerInvoker {

  public static final int DEFAULT_TIMEOUT = 300000;
  public static final String DEFAULT_SERVICE_PATH = "apiv1";

  @Getter
  private String url;
  @Getter
  private Integer timeout;
  @Getter
  private String servicePath;
  @Getter
  private CloseableHttpClient httpClient;

  public ServerInvoker(String url) {
    this(url, DEFAULT_TIMEOUT, DEFAULT_SERVICE_PATH);
  }

  public ServerInvoker(String url, Integer timeout, String servicePath) {
    this(url, timeout, servicePath, null);
  }

  public ServerInvoker(String url, Integer timeout, String servicePath,
      CloseableHttpClient httpClient) {
    this.servicePath = servicePath;
    setUrl(url);
    this.timeout = Optional.ofNullable(timeout).orElse(DEFAULT_TIMEOUT);
    this.httpClient = Optional.ofNullable(httpClient).orElse(buildDefaultHttpClient());
    log.debug("ServerInvoker params: \n url = {}  \n timeout = {} \n {}",
        this.url, this.timeout,
        httpClient != null ? "custom http client" : "default http client");
  }

  public void setUrl(String url) {
    log.debug("ServerInvoker url set to {}", url);
    this.url = url + "/" + Optional.ofNullable(this.servicePath).orElse(DEFAULT_SERVICE_PATH);
  }

  /**
   * Make the http call to MyGeotab server.
   *
   * @param request      The request.
   * @param responseType Response type class, used to deserialize the response.
   * @return The result for queries (Get) and null for non queries (Add, Set, Remove).
   * @throws Exception Exception which can occur while executing the call.
   */
  @SuppressWarnings("Indentation")
  public , ResultT>
  Optional
  invoke(RequestT request, Class responseType) throws Exception {

    log.debug("Method params: \n request = {}  \n responseType = {}", request, responseType);

    ResponseT response = doInvoke(this.url, request, responseType);

    if (response == null) {
      return Optional.empty();
    }

    return Optional.ofNullable(response.getResult());
  }

  @SuppressWarnings("Indentation")
  private , ResultT> ResponseT
  doInvoke(String url, RequestT request, Class responseType) throws Exception {
    log.debug("Method params: \n url = {} \n request = {}  \n responseType = {}",
        url, request, responseType);

    HttpPost httpPost = new HttpPost(url);
    httpPost.setEntity(create(outputStream -> {
      ApiJsonSerializer.getInstance().getObjectMapper().writeValue(outputStream, request);
      outputStream.flush();
    }, ContentType.APPLICATION_JSON));

    HttpClientResponseHandler responseHandler = getDefaultResponseHandler(request,
        responseType);
    ResponseT response = httpClient.execute(httpPost, responseHandler);

    checkForError(request.getMethod(), response);

    return response;
  }

  @SuppressWarnings("Indentation")
  private 
  HttpClientResponseHandler
  getDefaultResponseHandler(RequestT request, Class responseType) {

    return response -> {
      if (response == null) {
        log.error("Got null response while calling {} with request {}", this.url, request);
        throw new HttpException("Unexpected empty response from " + this.url);
      }

      try (HttpEntity httpEntity = response.getEntity();
          InputStream inputStream = httpEntity.getContent()) {

        boolean isSuccess =
            isSuccessCode(response.getCode()) || isRedirectCode(response.getCode());
        if (!isSuccess) {
          handleErrorResponse(inputStream, response.getCode());
        }

        if (isRedirectCode(response.getCode())) {
          String redirectUrl = response.getHeader("Location").getValue();
          log.info("Got redirect response to {}", redirectUrl);
          try {
            return (ResponseT) doInvoke(redirectUrl, request, responseType);
          } catch (Exception exception) {
            log.error("Can not redirect call to url {}", redirectUrl);
            throw new HttpException("Can not redirect call to url " + redirectUrl,
                new ResponseFailException(redirectUrl, response.getCode(), exception.getMessage(),
                    exception));
          }
        }

        JsonFactory jsonFactory = new JsonFactory();
        JsonParser jsonParser = jsonFactory.createParser(inputStream);
        jsonParser.setCodec(ApiJsonSerializer.getInstance().getObjectMapper());

        return jsonParser.readValueAs(responseType);
      }
    };
  }

  private CloseableHttpClient buildDefaultHttpClient() {
    log.debug("Building http client");
    return HttpClients.custom()
        .disableAutomaticRetries()
        .disableRedirectHandling()
        .setDefaultRequestConfig(RequestConfig.custom()
            .setConnectTimeout(Timeout.ofSeconds(timeout))
            .setResponseTimeout(Timeout.ofSeconds(timeout))
            .build())
        .build();
  }

  private boolean isSuccessCode(int httpStatusCode) {
    return httpStatusCode == HttpStatus.SC_OK || httpStatusCode == HttpStatus.SC_PARTIAL_CONTENT;
  }

  private boolean isRedirectCode(int httpStatusCode) {
    return httpStatusCode == HttpStatus.SC_MOVED_TEMPORARILY
        || httpStatusCode == HttpStatus.SC_MOVED_PERMANENTLY;
  }

  private void handleErrorResponse(InputStream responseInputStream, int httpStatusCode)
      throws HttpException {
    try {
      String responseAsString = ApiJsonSerializer.getInstance().getObjectMapper()
          .readValue(responseInputStream, String.class);
      log.error("Unsuccessful response (code {}) : \n{}", httpStatusCode, responseAsString);
      throw new HttpException(responseAsString,
          new ResponseFailException(this.url, httpStatusCode, responseAsString, null));
    } catch (Exception exception) {
      log.error("Can not read unsuccessful response (code {})", httpStatusCode, exception);
      throw new HttpException(exception.getMessage(),
          new ResponseFailException(this.url, httpStatusCode, exception.getMessage(),
              exception));
    }
  }

  /**
   * Closes httpClient's connections.
   */
  public void disconnect() {
    try {
      log.debug("Disconnecting http client from {} ...", this.url);
      if (httpClient != null) {
        httpClient.close();
      }
      log.info("Disconnected http client from {}", this.url);
    } catch (IOException e) {
      log.error("Can not disconnect http client from {}", this.url, e);
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy