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

com.mindee.http.MindeeHttpApi Maven / Gradle / Ivy

The newest version!
package com.mindee.http;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mindee.MindeeException;
import com.mindee.MindeeSettings;
import com.mindee.parsing.common.ApiResponse;
import com.mindee.parsing.common.AsyncPredictResponse;
import com.mindee.parsing.common.ErrorDetails;
import com.mindee.parsing.common.Inference;
import com.mindee.parsing.common.PredictResponse;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import lombok.Builder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;

/**
 * HTTP Client class.
 */
public final class MindeeHttpApi extends MindeeApi {

  private static final ObjectMapper mapper = new ObjectMapper();
  private final Function buildBaseUrl = this::buildUrl;
  /**
   * The MindeeSetting needed to make the api call.
   */
  private final MindeeSettings mindeeSettings;
  /**
   * The HttpClientBuilder used to create HttpClient objects used to make api calls over http.
   * Defaults to HttpClientBuilder.create().useSystemProperties()
   */
  private final HttpClientBuilder httpClientBuilder;
  /**
   * The function used to generate the API endpoint URL. Only needs to be set if the api calls need
   * to be directed through internal URLs.
   */
  private final Function urlFromEndpoint;
  /**
   * The function used to generate the API endpoint URL for Async calls. Only needs to be set if the
   * api calls need to be directed through internal URLs.
   */
  private final Function asyncUrlFromEndpoint;
  /**
   * The function used to generate the Job status URL for Async calls. Only needs to be set if the
   * api calls need to be directed through internal URLs.
   */
  private final Function documentUrlFromEndpoint;

  public MindeeHttpApi(MindeeSettings mindeeSettings) {
    this(
        mindeeSettings,
        null,
        null,
        null,
        null
    );
  }

  @Builder
  private MindeeHttpApi(
      MindeeSettings mindeeSettings,
      HttpClientBuilder httpClientBuilder,
      Function urlFromEndpoint,
      Function asyncUrlFromEndpoint,
      Function documentUrlFromEndpoint
  ) {
    this.mindeeSettings = mindeeSettings;

    if (httpClientBuilder != null) {
      this.httpClientBuilder = httpClientBuilder;
    } else {
      this.httpClientBuilder = HttpClientBuilder.create().useSystemProperties();
    }

    if (urlFromEndpoint != null) {
      this.urlFromEndpoint = urlFromEndpoint;
    } else {
      this.urlFromEndpoint = buildBaseUrl.andThen((url) -> url.concat("/predict"));
    }

    if (asyncUrlFromEndpoint != null) {
      this.asyncUrlFromEndpoint = asyncUrlFromEndpoint;
    } else {
      this.asyncUrlFromEndpoint = this.urlFromEndpoint.andThen((url) -> url.concat("_async"));
    }

    if (documentUrlFromEndpoint != null) {
      this.documentUrlFromEndpoint = documentUrlFromEndpoint;
    } else {
      this.documentUrlFromEndpoint = this.buildBaseUrl.andThen(
          (url) -> url.concat("/documents/queue/"));
    }
  }

  /**
   * GET job status and document for an enqueued job
   */
  public  AsyncPredictResponse documentQueueGet(
      Class documentClass,
      Endpoint endpoint,
      String jobId
  ) {
    String endpointUrl = documentUrlFromEndpoint.apply(endpoint).concat(jobId);
    HttpGet get = new HttpGet(endpointUrl);

    // required to register jackson date module format to deserialize
    mapper.findAndRegisterModules();
    JavaType parametricType = mapper.getTypeFactory().constructParametricType(
        AsyncPredictResponse.class,
        documentClass
    );

    if (this.mindeeSettings.getApiKey().isPresent()) {
      get.setHeader(HttpHeaders.AUTHORIZATION, this.mindeeSettings.getApiKey().get());
    }
    get.setHeader(HttpHeaders.USER_AGENT, getUserAgent());

    try (
        CloseableHttpClient httpClient = httpClientBuilder.build();
        CloseableHttpResponse response = httpClient.execute(get)
    ) {
      HttpEntity responseEntity = response.getEntity();
      int statusCode = response.getStatusLine().getStatusCode();
      if (!is2xxStatusCode(statusCode)) {
        throw getHttpError(parametricType, response);
      }
      String rawResponse = readRawResponse(responseEntity);
      AsyncPredictResponse mappedResponse = mapper.readValue(rawResponse, parametricType);
      mappedResponse.setRawResponse(rawResponse);
      if (
          mappedResponse.getJob() != null
          && mappedResponse.getJob().getError() != null
          && mappedResponse.getJob().getError().getCode() != null
      ) {
        throw new MindeeHttpException(
          500,
          mappedResponse.getJob().getError().getMessage(),
          mappedResponse.getJob().getError().getDetails().toString(),
          mappedResponse.getJob().getError().getCode()
        );
      }
      return mappedResponse;
    } catch (IOException err) {
      throw new MindeeException(err.getMessage(), err);
    }
  }

  /**
   * POST a prediction request for a custom product.
   */
  public  PredictResponse predictPost(
      Class documentClass,
      Endpoint endpoint,
      RequestParameters requestParameters
  ) throws IOException {

    String url = urlFromEndpoint.apply(endpoint);
    HttpPost post = buildHttpPost(url, requestParameters);

    // required to register jackson date module format to deserialize
    mapper.findAndRegisterModules();
    JavaType parametricType = mapper.getTypeFactory().constructParametricType(
        PredictResponse.class,
        documentClass
    );
    try (
        CloseableHttpClient httpClient = httpClientBuilder.build();
        CloseableHttpResponse response = httpClient.execute(post)
    ) {
      HttpEntity responseEntity = response.getEntity();
      int statusCode = response.getStatusLine().getStatusCode();
      if (!is2xxStatusCode(statusCode)) {
        throw getHttpError(parametricType, response);
      }
      if (responseEntity.getContentLength() == 0) {
        throw new MindeeException("Empty response from server.");
      }
      String rawResponse = readRawResponse(responseEntity);
      PredictResponse mappedResponse = mapper.readValue(rawResponse, parametricType);
      mappedResponse.setRawResponse(rawResponse);
      return mappedResponse;
    } catch (IOException err) {
      throw new MindeeException(err.getMessage(), err);
    }
  }

  /**
   * POST a prediction request for a custom product.
   */
  public  AsyncPredictResponse predictAsyncPost(
      Class documentClass,
      Endpoint endpoint,
      RequestParameters requestParameters
  ) throws IOException {

    String url = asyncUrlFromEndpoint.apply(endpoint);
    HttpPost post = buildHttpPost(url, requestParameters);

    // required to register jackson date module format to deserialize
    mapper.findAndRegisterModules();
    JavaType parametricType = mapper.getTypeFactory().constructParametricType(
        AsyncPredictResponse.class,
        documentClass
    );
    try (
        CloseableHttpClient httpClient = httpClientBuilder.build();
        CloseableHttpResponse response = httpClient.execute(post)
    ) {
      HttpEntity responseEntity = response.getEntity();
      int statusCode = response.getStatusLine().getStatusCode();
      if (!is2xxStatusCode(statusCode)) {
        throw getHttpError(parametricType, response);
      }
      if (responseEntity.getContentLength() == 0) {
        throw new MindeeException("Empty response from server.");
      }
      String rawResponse = readRawResponse(responseEntity);
      AsyncPredictResponse mappedResponse = mapper.readValue(rawResponse, parametricType);
      mappedResponse.setRawResponse(rawResponse);
      return mappedResponse;
    } catch (IOException err) {
      throw new MindeeException(err.getMessage(), err);
    }
  }

  private  MindeeHttpException getHttpError(
      JavaType parametricType,
      CloseableHttpResponse response
  ) {
    int statusCode = response.getStatusLine().getStatusCode();
    String message = "HTTP Status " + statusCode + " - ";
    String details;
    String errorCode;
    String rawResponse;
    try {
      rawResponse = readRawResponse(response.getEntity());
    } catch (IOException err) {
      message += "Could not read server response, check details.";
      errorCode = "";
      details = err.getMessage();
      return new MindeeHttpException(statusCode, message, details, errorCode);
    }
    try {
      ResponseT predictResponse = mapper.readValue(rawResponse, parametricType);
      message = predictResponse.getApiRequest().getError().getMessage();
      ErrorDetails errorDetails = predictResponse.getApiRequest().getError().getDetails();
      if (errorDetails != null) {
        details = errorDetails.toString();
      }
      else {
        details = "";
      }
      errorCode = predictResponse.getApiRequest().getError().getCode();
    } catch (IOException mapperError) {
      message += "Unhandled server response, check details.";
      details = rawResponse;
      errorCode = "";
    }
    return new MindeeHttpException(statusCode, message, details, errorCode);
  }

  private String buildUrl(Endpoint endpoint) {
    return this.mindeeSettings.getBaseUrl()
        + "/products/"
        + endpoint.getAccountName()
        + "/"
        + endpoint.getEndpointName()
        + "/v"
        + endpoint.getVersion();
  }

  private HttpPost buildHttpPost(
      String url,
      RequestParameters requestParameters
  ) throws JsonProcessingException {
    HttpPost post;
    try {
      URIBuilder uriBuilder = new URIBuilder(url);
      uriBuilder.addParameters(buildPostParams(requestParameters));
      post = new HttpPost(uriBuilder.build());
    }
    // This exception will never happen because we are providing the URL internally.
    // Do this to avoid declaring the exception in the method signature.
    catch (URISyntaxException err) {
      return new HttpPost("invalid URI");
    }

    if (this.mindeeSettings.getApiKey().isPresent()) {
      post.setHeader(HttpHeaders.AUTHORIZATION, this.mindeeSettings.getApiKey().get());
    }
    post.setHeader(HttpHeaders.USER_AGENT, getUserAgent());
    post.setEntity(buildHttpBody(requestParameters));
    return post;
  }

  private List buildPostParams(
      RequestParameters requestParameters
  ) {
    ArrayList params = new ArrayList();
    if (Boolean.TRUE.equals(requestParameters.getPredictOptions().getCropper())) {
      params.add(new BasicNameValuePair("cropper", "true"));
    }
    if (Boolean.TRUE.equals(requestParameters.getPredictOptions().getFullText())) {
      params.add(new BasicNameValuePair("full_text_ocr", "true"));
    }
    return params;
  }

  private HttpEntity buildHttpBody(
      RequestParameters requestParameters
  ) throws JsonProcessingException {
    if (requestParameters.getFile() != null) {
      MultipartEntityBuilder builder = MultipartEntityBuilder.create();
      builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
      builder.addBinaryBody(
          "document",
          requestParameters.getFile(),
          ContentType.DEFAULT_BINARY,
          requestParameters.getFileName()
      );
      if (Boolean.TRUE.equals(requestParameters.getPredictOptions().getAllWords())) {
        builder.addTextBody("include_mvision", "true");
      }
      return builder.build();
    } else if (requestParameters.getFileUrl() != null) {
      Map urlMap = new HashMap<>();
      urlMap.put("document", requestParameters.getFileUrl());
      return new StringEntity(mapper.writeValueAsString(urlMap),
          ContentType.APPLICATION_JSON);
    } else {
      throw new MindeeException("Either document bytes or a document URL are needed");
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy