com.fullcontact.apilib.enrich.FullContact Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java8 Show documentation
Show all versions of java8 Show documentation
Client libraries for FullContact v3 APIs
package com.fullcontact.apilib.enrich;
import com.fullcontact.apilib.FCConstants;
import com.fullcontact.apilib.FullContactApi;
import com.fullcontact.apilib.FullContactException;
import com.fullcontact.apilib.auth.CredentialsProvider;
import com.fullcontact.apilib.auth.DefaultCredentialProvider;
import com.fullcontact.apilib.models.Request.CompanyRequest;
import com.fullcontact.apilib.models.Request.PersonRequest;
import com.fullcontact.apilib.models.Request.ResolveRequest;
import com.fullcontact.apilib.models.Response.*;
import com.fullcontact.apilib.models.enums.FCApiEndpoint;
import com.fullcontact.apilib.retry.DefaultRetryHandler;
import com.fullcontact.apilib.retry.RetryHandler;
import com.fullcontact.apilib.test.MockInterceptor;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import lombok.Builder;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* The FullContact class represents FullContact client. It supports V3 Person Enrich, Company
* Enrich, Company Search and Resolve endpoints. It uses Retrofit for sending all requests. All
* requests are converted to JSON and sent via POST method asynchronously
*/
public class FullContact implements AutoCloseable {
private final String baseUrl = FCConstants.API_BASE_DEFAULT;
private final OkHttpClient httpClient;
private final FullContactApi client;
private final CredentialsProvider credentialsProvider;
private final RetryHandler retryHandler;
private final Map headers;
private final long connectTimeoutMillis;
private final ScheduledExecutorService executor;
private boolean isShutdown = false;
private static final MediaType JSONMediaType = MediaType.parse("application/json; charset=utf-8");
private static final Type companySearchResponseType =
new TypeToken>() {}.getType();
private static final Gson gson = new Gson();
/**
* FullContact client constructor used to initialise the client
*
* @param credentialsProvider for auth
* @param headers custom client headers
* @param connectTimeoutMillis connection timout for all requests
* @param retryHandler RetryHandler specified for client
*/
@Builder
public FullContact(
CredentialsProvider credentialsProvider,
Map headers,
long connectTimeoutMillis,
RetryHandler retryHandler) {
this.credentialsProvider = credentialsProvider;
this.retryHandler = retryHandler;
this.headers = headers != null ? Collections.unmodifiableMap(headers) : null;
this.connectTimeoutMillis = connectTimeoutMillis > 0 ? connectTimeoutMillis : 3000;
this.httpClient = this.configureHTTPClientBuilder().build();
this.client = this.configureRetrofit().create(FullContactApi.class);
this.executor = new ScheduledThreadPoolExecutor(5);
}
/**
* Method to build and create OkHttpClient. All the custom headers and auth key is added here.
*
* @return OkHttpClient Builder
*/
protected OkHttpClient.Builder configureHTTPClientBuilder() {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.addInterceptor(
chain -> {
Request.Builder requestBuilder = chain.request().newBuilder();
requestBuilder.addHeader("Authorization", "Bearer " + credentialsProvider.getApiKey());
requestBuilder.addHeader("Content-Type", "application/json");
requestBuilder.addHeader("User-Agent", FCConstants.USER_AGENT_Java8);
if (headers != null) {
for (Map.Entry entry : headers.entrySet()) {
if (!entry.getKey().equalsIgnoreCase("authorization")
&& !entry.getKey().equalsIgnoreCase("Content-Type")
&& !entry.getKey().equalsIgnoreCase("User-Agent")
&& entry.getValue() != null) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
}
}
Request request = requestBuilder.build();
return chain.proceed(request);
});
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.NONE);
if (System.getProperty("FC_TEST_ENV", "").equals("FC_TEST")) {
httpClientBuilder.addInterceptor(new MockInterceptor());
}
httpClientBuilder.addInterceptor(logging);
httpClientBuilder.connectTimeout(this.connectTimeoutMillis, TimeUnit.MILLISECONDS);
return httpClientBuilder;
}
/**
* Method to create Retrofit client using httpClient and Gson converter.
*
* @return Retrofit client
*/
protected Retrofit configureRetrofit() {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(this.baseUrl)
.client(this.httpClient)
.build();
}
/**
* Method for Person Enrich without any custom RetryHandler, It converts the request to json, send
* the Asynchronous request using HTTP POST method. It also handles retries based on retryHandler
* specified at FullContact Client level.
*
* @param personRequest original request sent by client
* @return completed CompletableFuture with PersonResponse
* @throws FullContactException exception if client is shutdown
* @see CompletableFuture
*/
public CompletableFuture enrich(PersonRequest personRequest)
throws FullContactException {
return this.enrich(personRequest, this.retryHandler);
}
/**
* Method for Person Enrich. It converts the request to json, send the Asynchronous request using
* HTTP POST method. It also handles retries based on retry condition.
*
* @param personRequest original request sent by client
* @return completed CompletableFuture with PersonResponse
* @throws FullContactException exception if client is shutdown
* @see CompletableFuture
*/
public CompletableFuture enrich(
PersonRequest personRequest, RetryHandler retryHandler) throws FullContactException {
checkForShutdown();
CompletableFuture> responseCF = new CompletableFuture<>();
RequestBody httpRequest = buildHttpRequest(gson.toJson(personRequest));
CompletableFuture> httpResponseCompletableFuture =
this.client.personEnrich(httpRequest);
handleHttpResponse(
httpRequest,
retryHandler,
httpResponseCompletableFuture,
responseCF,
FCApiEndpoint.PERSON_ENRICH);
return responseCF.thenApply(FullContact::getPersonResponse);
}
/**
* Method for Company Enrich without any custom RetryHandler, It converts the request to json,
* send the Asynchronous request using HTTP POST method. It also handles retries based on
* retryHandler specified at FullContact Client level.
*
* @param companyRequest original request sent by client
* @return completed CompletableFuture with CompanyResponse
* @throws FullContactException exception if client is shutdown or request validation
* @see CompletableFuture
*/
public CompletableFuture enrich(CompanyRequest companyRequest)
throws FullContactException {
return this.enrich(companyRequest, this.retryHandler);
}
/**
* Method for Company Enrich. It converts the request to json, send the Asynchronous request using
* HTTP POST method. It also handles retries based on retry condition.
*
* @param companyRequest original request sent by client
* @return completed CompletableFuture with CompanyResponse
* @throws FullContactException exception if client is shutdown or request validation
* @see CompletableFuture
*/
public CompletableFuture enrich(
CompanyRequest companyRequest, RetryHandler retryHandler) throws FullContactException {
checkForShutdown();
CompletableFuture> responseCF = new CompletableFuture<>();
RequestBody httpRequest = buildHttpRequest(gson.toJson(companyRequest));
CompletableFuture> httpResponseCompletableFuture =
this.client.companyEnrich(httpRequest);
handleHttpResponse(
httpRequest,
retryHandler,
httpResponseCompletableFuture,
responseCF,
FCApiEndpoint.COMPANY_ENRICH);
return responseCF.thenApply(FullContact::getCompanyResponse);
}
/**
* Method for Company Search without any custom RetryHandler, It converts the request to json,
* send the Asynchronous request using HTTP POST method. It also handles retries based on
* retryHandler specified at FullContact Client level.
*
* @param companyRequest original request sent by client
* @return completed CompletableFuture with CompanySearchResponseList
* @throws FullContactException exception if client is shutdown or request fails validation
* @see CompletableFuture
*/
public CompletableFuture search(CompanyRequest companyRequest)
throws FullContactException {
return this.search(companyRequest, this.retryHandler);
}
/**
* Method for Company Search. It converts the request to json, send the Asynchronous request using
* HTTP POST method. It also handles retries based on retry condition.
*
* @param companyRequest original request sent by client
* @return completed CompletableFuture with CompanySearchResponseList
* @throws FullContactException exception if client is shutdown or request fails validation
* @see CompletableFuture
*/
public CompletableFuture search(
CompanyRequest companyRequest, RetryHandler retryHandler) throws FullContactException {
checkForShutdown();
CompletableFuture> responseCF = new CompletableFuture<>();
RequestBody httpRequest = buildHttpRequest(gson.toJson(companyRequest));
CompletableFuture> httpResponseCompletableFuture =
this.client.companySearch(httpRequest);
handleHttpResponse(
httpRequest,
retryHandler,
httpResponseCompletableFuture,
responseCF,
FCApiEndpoint.COMPANY_SEARCH);
return responseCF.thenApply(FullContact::getCompanySearchResponse);
}
/**
* Method for Resolve Identity Map. It converts the request to json, send the Asynchronous request
* using HTTP POST method. It also handles retries based on retryHandler specified at FullContact
* Client level.
*
* @param resolveRequest original request sent by client
* @return completed CompletableFuture with ResolveResponse
* @throws FullContactException exception if client is shutdown or request fails validation
* @see CompletableFuture
*/
public CompletableFuture identityMap(ResolveRequest resolveRequest)
throws FullContactException {
return this.identityMap(resolveRequest, this.retryHandler);
}
/**
* Method for Resolve Identity Map. It converts the request to json, send the Asynchronous request
* using HTTP POST method. It also handles retries based on retryHandler specified.
*
* @param resolveRequest original request sent by client
* @return completed CompletableFuture with ResolveResponse
* @throws FullContactException exception if client is shutdown or request fails validation
* @see CompletableFuture
*/
public CompletableFuture identityMap(
ResolveRequest resolveRequest, RetryHandler retryHandler) throws FullContactException {
resolveRequest.validateForIdentityMap();
return resolveRequest(resolveRequest, retryHandler, FCApiEndpoint.IDENTITY_MAP);
}
/**
* Method for Identity Resolve. It converts the request to json, send the Asynchronous request
* using HTTP POST method. It also handles retries based on retryHandler specified at FullContact
* Client level.
*
* @param resolveRequest original request sent by client
* @return completed CompletableFuture with ResolveResponse
* @throws FullContactException exception if client is shutdown
* @see CompletableFuture
*/
public CompletableFuture identityResolve(ResolveRequest resolveRequest)
throws FullContactException {
return this.identityResolve(resolveRequest, this.retryHandler);
}
/**
* Method for Resolve Identity Map. It converts the request to json, send the Asynchronous request
* using HTTP POST method. It also handles retries based on retryHandler specified.
*
* @param resolveRequest original request sent by client
* @return completed CompletableFuture with ResolveResponse
* @throws FullContactException exception if client is shutdown
* @see CompletableFuture
*/
public CompletableFuture identityResolve(
ResolveRequest resolveRequest, RetryHandler retryHandler) throws FullContactException {
resolveRequest.validateForIdentityResolve();
return resolveRequest(resolveRequest, retryHandler, FCApiEndpoint.IDENTITY_RESOLVE);
}
/**
* Method for Deleting mapped Record. It calls 'identity.delete' endpoint in Resolve. It converts
* the request to json, send the Asynchronous request using HTTP POST method. It also handles
* retries based on retryHandler specified at FullContact Client level.
*
* @param resolveRequest original request sent by client
* @return completed CompletableFuture with ResolveResponse
* @throws FullContactException exception if client is shutdown
* @see CompletableFuture
*/
public CompletableFuture identityDelete(ResolveRequest resolveRequest)
throws FullContactException {
return this.identityDelete(resolveRequest, this.retryHandler);
}
/**
* Method for Deleting mapped Record. It calls 'identity.delete' endpoint in Resolve. It converts
* the request to json, send the Asynchronous request using HTTP POST method. It also handles
* retries based on retryHandler specified.
*
* @param resolveRequest original request sent by client
* @return completed CompletableFuture with ResolveResponse
* @throws FullContactException exception if client is shutdown
* @see CompletableFuture
*/
public CompletableFuture identityDelete(
ResolveRequest resolveRequest, RetryHandler retryHandler) throws FullContactException {
resolveRequest.validateForIdentityDelete();
return resolveRequest(resolveRequest, retryHandler, FCApiEndpoint.IDENTITY_DELETE);
}
protected CompletableFuture resolveRequest(
ResolveRequest resolveRequest, RetryHandler retryHandler, FCApiEndpoint fcApiEndpoint)
throws FullContactException {
checkForShutdown();
CompletableFuture> responseCF = new CompletableFuture<>();
RequestBody httpRequest = buildHttpRequest(gson.toJson(resolveRequest));
CompletableFuture> httpResponseCompletableFuture;
switch (fcApiEndpoint) {
case IDENTITY_MAP:
httpResponseCompletableFuture = this.client.identityMap(httpRequest);
break;
case IDENTITY_RESOLVE:
httpResponseCompletableFuture = this.client.identityResolve(httpRequest);
break;
case IDENTITY_DELETE:
httpResponseCompletableFuture = this.client.identityDelete(httpRequest);
break;
default:
throw new FullContactException("Wrong API Endpoint provided for Resolve");
}
handleHttpResponse(
httpRequest,
retryHandler,
httpResponseCompletableFuture,
responseCF,
FCApiEndpoint.PERSON_ENRICH);
return responseCF.thenApply(FullContact::getResolveResponse);
}
protected void checkForShutdown() throws FullContactException {
if (isShutdown) {
throw new FullContactException("FullContact client is shutdown. Please create a new client");
}
}
protected static RequestBody buildHttpRequest(String request) {
return RequestBody.create(JSONMediaType, request);
}
protected void handleHttpResponse(
RequestBody httpRequest,
RetryHandler retryHandler,
CompletableFuture> currentResponse,
CompletableFuture> responseCF,
FCApiEndpoint fcApiEndpoint) {
currentResponse.handle(
(httpResponse, throwable) -> {
if (throwable != null) {
handleAutoRetry(
responseCF, httpResponse, httpRequest, throwable, 0, retryHandler, fcApiEndpoint);
} else if (httpResponse != null && !retryHandler.shouldRetry(httpResponse.code())) {
responseCF.complete(httpResponse);
} else {
handleAutoRetry(
responseCF, httpResponse, httpRequest, null, 0, retryHandler, fcApiEndpoint);
}
return null;
});
}
/**
* This method creates person enrich response and handle for different response codes
*
* @param response raw response from person enrich API
* @return PersonResponse
*/
protected static PersonResponse getPersonResponse(retrofit2.Response response) {
PersonResponse personResponse;
if (response.isSuccessful() && response.body() != null) {
personResponse = gson.fromJson(response.body(), PersonResponse.class);
if (response.code() == 200) {
personResponse.message = FCConstants.HTTP_RESPONSE_STATUS_200_MESSAGE;
}
} else {
personResponse = new PersonResponse();
personResponse.message = response.message();
}
personResponse.isSuccessful =
response.code() == 200 || response.code() == 202 || response.code() == 404;
personResponse.statusCode = response.code();
return personResponse;
}
/**
* This method creates company enrich response and handle for different response codes
*
* @param response raw response from company enrich API
* @return CompanyResponse
*/
protected static CompanyResponse getCompanyResponse(retrofit2.Response response) {
CompanyResponse companyResponse;
if (response.isSuccessful() && response.body() != null) {
companyResponse = gson.fromJson(response.body(), CompanyResponse.class);
if (response.code() == 200) {
companyResponse.message = FCConstants.HTTP_RESPONSE_STATUS_200_MESSAGE;
}
} else {
companyResponse = new CompanyResponse();
companyResponse.message = response.message();
}
companyResponse.isSuccessful =
response.code() == 200 || response.code() == 202 || response.code() == 404;
companyResponse.statusCode = response.code();
return companyResponse;
}
/**
* This method creates company search response and handle for different response codes
*
* @param response raw response from company search API
* @return CompanySearchResponseList
*/
protected static CompanySearchResponseList getCompanySearchResponse(
retrofit2.Response response) {
CompanySearchResponseList companySearchResponseList = new CompanySearchResponseList();
if (response.body() != null && !response.body().isJsonNull()) {
if (response.code() == 200) {
companySearchResponseList.companySearchResponses =
gson.fromJson(response.body(), companySearchResponseType);
companySearchResponseList.message = FCConstants.HTTP_RESPONSE_STATUS_200_MESSAGE;
companySearchResponseList.isSuccessful = true;
} else {
companySearchResponseList = gson.fromJson(response.body(), CompanySearchResponseList.class);
}
} else {
companySearchResponseList.message = response.message();
}
companySearchResponseList.isSuccessful =
(response.code() == 200) || (response.code() == 202) || (response.code() == 404);
companySearchResponseList.statusCode = response.code();
return companySearchResponseList;
}
/**
* This method create Resolve response and handle for different response codes
*
* @param response raw response from Resolve APIs
* @return ResolveResponse
*/
protected static ResolveResponse getResolveResponse(retrofit2.Response response) {
ResolveResponse resolveResponse;
if (response.isSuccessful() && response.body() != null) {
resolveResponse = gson.fromJson(response.body(), ResolveResponse.class);
if (response.code() == 200 || response.code() == 204) {
resolveResponse.message = FCConstants.HTTP_RESPONSE_STATUS_200_MESSAGE;
}
} else {
resolveResponse = new ResolveResponse();
resolveResponse.message = response.message();
}
resolveResponse.statusCode = response.code();
resolveResponse.isSuccessful =
(response.code() == 200) || (response.code() == 204) || (response.code() == 404);
return resolveResponse;
}
/**
* This method handles Auto Retry in case retry condition is true. It keeps retrying till the
* retryAttempts exhaust or the response is successful and completes the responseCF based on
* result. For retrying, it schedules the request using ScheduledThreadPoolExecutor with
* retryDelayMillis delay time.
*
* @param httpRequest original request by client
* @param httpResponse response of the last retry, used to complete responseCF if all retry
* attempts exhaust
* @param responseCF result completableFuture which is completed here and returned to client
* @param throwable exception from last retry, used to completeExceptionally
* responseCompletableFutureResult if all retries exhaust
* @param retryAttemptsDone track the number of retry attempts already done
* @param retryHandler RetryHandler used for current request
* @param fcApiEndpoint FullContact API Endpoint for current request
*/
protected void handleAutoRetry(
CompletableFuture> responseCF,
Response httpResponse,
RequestBody httpRequest,
Throwable throwable,
int retryAttemptsDone,
RetryHandler retryHandler,
FCApiEndpoint fcApiEndpoint) {
if (retryAttemptsDone < (Math.min(retryHandler.getRetryAttempts(), 5))) {
retryAttemptsDone++;
int finalRetryAttemptsDone = retryAttemptsDone;
this.executor.schedule(
() -> {
CompletableFuture> retryCF = null;
switch (fcApiEndpoint) {
case PERSON_ENRICH:
retryCF = this.client.personEnrich(httpRequest);
break;
case COMPANY_ENRICH:
retryCF = this.client.companyEnrich(httpRequest);
break;
case COMPANY_SEARCH:
retryCF = this.client.companySearch(httpRequest);
break;
case IDENTITY_MAP:
retryCF = this.client.identityMap(httpRequest);
break;
case IDENTITY_RESOLVE:
retryCF = this.client.identityResolve(httpRequest);
break;
case IDENTITY_DELETE:
retryCF = this.client.identityDelete(httpRequest);
break;
}
retryCF.handle(
(retryResponse, retryThrowable) -> {
if (retryThrowable != null) {
handleAutoRetry(
responseCF,
retryResponse,
httpRequest,
retryThrowable,
finalRetryAttemptsDone,
retryHandler,
fcApiEndpoint);
} else if (retryResponse != null
&& !retryHandler.shouldRetry(retryResponse.code())) {
responseCF.complete(retryResponse);
} else {
handleAutoRetry(
responseCF,
retryResponse,
httpRequest,
null,
finalRetryAttemptsDone,
retryHandler,
fcApiEndpoint);
}
return null;
});
},
Math.max(retryHandler.getRetryDelayMillis(), 500)
* (long) Math.pow(2, retryAttemptsDone - 1),
TimeUnit.MILLISECONDS);
} else if (throwable != null
&& retryAttemptsDone == (Math.min(retryHandler.getRetryAttempts(), 5))) {
responseCF.completeExceptionally(throwable);
} else {
responseCF.complete(httpResponse);
}
}
/** @return Person Request Builder for Person Enrich request */
public static PersonRequest.PersonRequestBuilder buildPersonRequest() {
return PersonRequest.builder();
}
/** @return Company Request Builder for Company Enrich and Company Search requests */
public static CompanyRequest.CompanyRequestBuilder buildCompanyRequest() {
return CompanyRequest.builder();
}
/** @return Resolve Request Builder for Resolve */
public static ResolveRequest.ResolveRequestBuilder buildResolveRequest() {
return ResolveRequest.builder();
}
/**
* This method is used to call shutdown on the ScheduledThreadPoolExecutor and close the
* FullContact client.
*/
@Override
public void close() {
if (!this.executor.isShutdown()) {
this.executor.shutdown();
this.isShutdown = true;
}
}
/** This method will be called by GC to close the client. */
@Override
public void finalize() {
this.close();
}
public static class FullContactBuilder {
/**
* Validates the builder for authentication and constructs the FullContact client with all the
* provided values.
*/
public FullContact build() throws FullContactException {
this.validate();
return new FullContact(credentialsProvider, headers, connectTimeoutMillis, retryHandler);
}
private void validate() throws FullContactException {
if (this.credentialsProvider == null) {
this.credentialsProvider = new DefaultCredentialProvider();
}
if (this.retryHandler == null) {
this.retryHandler = new DefaultRetryHandler();
}
}
/**
* Builder method to provide {@link com.fullcontact.apilib.auth.CredentialsProvider} for
* authentication.
*
* @param credentialsProvider implementation of credentialsProvider for auth
* @return FullContactBuilder
*/
public FullContactBuilder credentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
return this;
}
/**
* Builder method to provide custom Headers, which will be included in all requests
*
* @param headers customHeaders provided by client
* @return FullContactBuilder
*/
public FullContactBuilder headers(Map headers) {
this.headers = headers;
return this;
}
/**
* Builder method to provide connection timeout, default value is 3000ms
*
* @param connectTimeoutMillis Connection Timeout in milliseconds
* @return FullContactBuilder
*/
public FullContactBuilder connectTimeoutMillis(long connectTimeoutMillis) {
this.connectTimeoutMillis = connectTimeoutMillis;
return this;
}
/**
* Builder method to provide {@link com.fullcontact.apilib.retry.RetryHandler}
*
* @param retryHandler custom RetryHandler
* @return FullContactBuilder
*/
public FullContactBuilder retryHandler(RetryHandler retryHandler) {
this.retryHandler = retryHandler;
return this;
}
}
}