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

com.geotab.api.GeotabApi Maven / Gradle / Ivy

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

package com.geotab.api;

import static com.geotab.http.invoker.ServerInvoker.DEFAULT_TIMEOUT;

import com.geotab.http.exception.InvalidUserException;
import com.geotab.http.invoker.ServerInvoker;
import com.geotab.http.request.AuthenticatedRequest;
import com.geotab.http.request.BaseRequest;
import com.geotab.http.request.MultiCallRequest;
import com.geotab.http.request.param.Parameters;
import com.geotab.http.response.AuthenticateResponse;
import com.geotab.http.response.BaseResponse;
import com.geotab.model.login.Credentials;
import com.geotab.model.login.LoginResult;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;

@Slf4j
public class GeotabApi implements Api {

  public static final String THIS_SERVER = "ThisServer";
  public static final String DEFAULT_SERVER = "my.geotab.com";
  public static final String PROTOCOL = "https://";

  protected final Credentials credentials;
  protected final String server;
  protected final int timeout;
  protected final AtomicReference loginResultReference = new AtomicReference<>();
  protected final AtomicReference serverInvokerReference = new AtomicReference<>();

  /**
   * Create new api instance.
   *
   * @param credentials {@link Credentials} used to authenticate.
   */
  public GeotabApi(Credentials credentials) {
    this(credentials, DEFAULT_SERVER, DEFAULT_TIMEOUT);
  }

  /**
   * Create new api instance.
   *
   * @param credentials {@link Credentials} used to authenticate.
   * @param server      Server url without protocol. Example: my.geotab.com
   * @param timeout     Request timeout
   */
  public GeotabApi(Credentials credentials, String server, int timeout) {
    this(credentials, server, timeout, null, null);
  }

  /**
   * Create new api instance.
   *
   * @param credentials {@link Credentials} used to authenticate.
   * @param server      Server url without protocol. Example: my.geotab.com
   * @param timeout     Request timeout
   * @param servicePath Service path. Default value is: apiv1
   * @param httpClient  Custom {@link org.apache.hc.client5.http.classic.HttpClient} in case cutom
   *                    configuration is needed
   */
  public GeotabApi(Credentials credentials, String server, int timeout, String servicePath,
      CloseableHttpClient httpClient) {
    log.debug(
        "API params: \n credentials = {}  \n server = {} \n timeout = {} \n servicePath = {} \n {}",
        credentials, server, timeout, servicePath,
        httpClient != null ? "custom http client" : "default http client");

    if (credentials == null) {
      throw new IllegalArgumentException("Credentials not provided");
    }
    credentials.validate();

    this.credentials = credentials;
    this.timeout = timeout;
    this.server = Optional.ofNullable(server).orElse(DEFAULT_SERVER);
    this.serverInvokerReference
        .set(buildServerInvoker(PROTOCOL + this.server, this.timeout, servicePath, httpClient));
  }

  @Override
  public LoginResult authenticate() throws Exception {
    if (isAuthenticated()) {
      return loginResultReference.get();
    }

    LoginResult loginResult;
    if (StringUtils.isNotEmpty(credentials.getSessionId())) {
      log.debug("Geotab session id is provided as part of the credentials; "
          + "will not call Geotab Authenticate method");
      loginResult = LoginResult.builder()
          .credentials(
              Credentials.builder()
                  .database(credentials.getDatabase())
                  .userName(credentials.getUserName())
                  .password(null)
                  .sessionId(credentials.getSessionId())
                  .build()
          )
          .path(server)
          .build();
      loginResultReference.set(loginResult);
    } else {
      log.debug("Calling Geotab Authenticate method ...");
      // Authenticate
      BaseRequest authenticationRequest = BaseRequest.requestBuilder()
          .method("Authenticate")
          .params(this.credentials)
          .build();
      loginResult = serverInvokerReference.get()
          .invoke(authenticationRequest, AuthenticateResponse.class)
          .orElse(null);
      loginResultReference.set(loginResult);
      log.info("Geotab Authenticate is successful");
    }

    // Change invoker to use the request URL to that of the db as returned by Authenticate.
    if (!THIS_SERVER.equals(loginResultReference.get().getPath())) {
      String newPath = buildServerPath();
      serverInvokerReference.updateAndGet(serverInvoker -> {
        serverInvoker.setUrl(newPath);
        return serverInvoker;
      });
    }

    return loginResult;
  }

  @Override
  @SuppressWarnings({"Indentation", "LineLength"})
  public , ResponseT extends BaseResponse, ResultT>
  Optional call(RequestT request, Class responseType) throws Exception {
    log.debug("Method params: \n request = {}  \n responseType = {}",
        request, responseType);

    validateCallParameters(request, responseType);

    boolean retry = false;
    while (true) {

      // Must be authenticated; will auto authenticate if not authenticated.
      if ((!request.getCredentials().isPresent() || retry) && !isAuthenticated()) {
        authenticate();
      }

      if (!request.getCredentials().isPresent()) {
        LoginResult loginResult = loginResultReference.get();
        request.setCredentials(loginResult.getCredentials());
      }

      // Invoke
      try {
        return serverInvokerReference.get().invoke(request, responseType);
      } catch (InvalidUserException invalidUserException) {
        request.setCredentials(null);
        loginResultReference.set(null);
        if (retry || StringUtils.isEmpty(credentials.getPassword())) {
          // If we retried request after a success full re-authentication throw, there is something
          // happening between the time we authenticate and try to make a request
          // Must have password to login again
          log.error(
              "Geotab call failed due to authentication; trying to re-authenticate also failed.",
              invalidUserException);
          throw invalidUserException;
        }

        // Token may have expired or DB may have moved so try to authenticate and retry request
        log.warn("Geotab call failed due to authentication; trying to re-authenticate and re-call");
        retry = true;
      }
    }
  }

  @Override
  @SuppressWarnings("Indentation")
  public , ResultT>
  Optional multiCall(RequestT request, Class responseType) throws Exception {
    log.debug("Method params: \n request = {}  \n responseType = {}",
        request, responseType);

    return call(request, responseType);
  }

  @Override
  public boolean isAuthenticated() {
    LoginResult loginResult = loginResultReference.get();
    return loginResult != null && loginResult.getCredentials() != null;
  }

  public void disconnect() {
    if (serverInvokerReference.get() != null) {
      log.debug("Disconnecting API ...");
      serverInvokerReference.get().disconnect();
    }
  }

  @Override
  public void close() {
    disconnect();
  }

  protected ServerInvoker buildServerInvoker(String url, Integer timeout, String servicePath,
      CloseableHttpClient httpClient) {
    return new ServerInvoker(url, timeout, servicePath, httpClient);
  }

  private String buildServerPath() {
    String newPath = PROTOCOL + loginResultReference.get().getPath();
    newPath = newPath.endsWith("/") ? newPath.substring(0, newPath.length() - 1) : newPath;

    return newPath;
  }

  @SuppressWarnings("Indentation")
  private , ResponseT extends BaseResponse> void
  validateCallParameters(RequestT request, Class responseType) {
    if (request == null) {
      throw new IllegalArgumentException("Request must be provided");
    }
    if (responseType == null) {
      throw new IllegalArgumentException("Response type must be provided");
    }

    request.validate();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy