com.bigcommerce.BigcommerceSdk Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bigcommerce-sdk Show documentation
Show all versions of bigcommerce-sdk Show documentation
Java SDK for BigCommerce REST APIs
The newest version!
package com.bigcommerce;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bigcommerce.catalog.models.Address;
import com.bigcommerce.catalog.models.AddressResponse;
import com.bigcommerce.catalog.models.CatalogSummary;
import com.bigcommerce.catalog.models.CatalogSummaryResponse;
import com.bigcommerce.catalog.models.Customer;
import com.bigcommerce.catalog.models.LineItem;
import com.bigcommerce.catalog.models.LineItemsResponse;
import com.bigcommerce.catalog.models.Order;
import com.bigcommerce.catalog.models.OrderStatus;
import com.bigcommerce.catalog.models.OrderStatusResponse;
import com.bigcommerce.catalog.models.OrdersResponse;
import com.bigcommerce.catalog.models.Pagination;
import com.bigcommerce.catalog.models.Product;
import com.bigcommerce.catalog.models.Products;
import com.bigcommerce.catalog.models.ProductsResponse;
import com.bigcommerce.catalog.models.Shipment;
import com.bigcommerce.catalog.models.ShipmentCreationRequest;
import com.bigcommerce.catalog.models.ShipmentResponse;
import com.bigcommerce.catalog.models.ShipmentUpdateRequest;
import com.bigcommerce.catalog.models.Store;
import com.bigcommerce.catalog.models.Variant;
import com.bigcommerce.catalog.models.VariantResponse;
import com.bigcommerce.exceptions.BigcommerceErrorResponseException;
import com.bigcommerce.exceptions.BigcommerceException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.github.rholder.retry.WaitStrategy;
public class BigcommerceSdk {
static final String API_VERSION_V2 = "v2";
static final String API_VERSION = "v3";
static final String CLIENT_ID_HEADER = "X-Auth-Client";
static final String ACCESS_TOKEN_HEADER = "X-Auth-Token";
static final String RATE_LIMIT_TIME_RESET_HEADER = "X-Rate-Limit-Time-Reset-Ms";
static final int TOO_MANY_REQUESTS_STATUS_CODE = 429;
private static final String CATALOG = "catalog";
private static final String SUMMARY = "summary";
private static final String PRODUCTS = "products";
private static final String ORDERS = "orders";
private static final String CUSTOMERS = "customers";
private static final String LIMIT = "limit";
private static final String PAGE = "page";
private static final String INCLUDE = "include";
private static final String VARIANTS = "variants";
private static final String SHIPPINGADDRESSES = "shipping_addresses";
private static final String SHIPMENTS = "shipments";
private static final String MIN_DATE_CREATED = "min_date_created";
private static final String STORE = "store";
private static final int MAX_LIMIT = 250;
private static final String MEDIA_TYPE = MediaType.APPLICATION_JSON + ";charset=UTF-8";
private static final String RETRY_FAILED_MESSAGE = "Request retry has failed.";
private static final Client CLIENT = ClientBuilder.newClient().register(JacksonFeature.class)
.property(ClientProperties.CONNECT_TIMEOUT, 60000).property(ClientProperties.READ_TIMEOUT, 600000).register(
new JacksonJaxbJsonProvider().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false));
private static final Logger LOGGER = LoggerFactory.getLogger(BigcommerceSdk.class);
private final WebTarget baseWebTarget;
/*
* Bigcommerce API has some differences between V2 & V3 and currently some
* API endpoints only exist in V2. The SDK uses V2 where applicable.
*/
private final WebTarget baseWebTargetV2;
private final String storeHash;
private final String clientId;
private final String accessToken;
private final long requestRetryTimeoutDuration;
private final TimeUnit requestRetryTimeoutUnit;
public static interface ApiUrlStep {
RequestRetryTimeoutStep withApiUrl(final String apiUrl);
}
public static interface RequestRetryTimeoutStep {
StoreHashStep withRequestRetryTimeout(final long requestRetryTimeoutDuration,
final TimeUnit requestRetryTimeoutUnit);
}
public static interface StoreHashStep {
ClientIdStep withStoreHash(final String storeHash);
}
public static interface ClientIdStep {
AccessTokenStep withClientId(final String clientId);
}
public static interface AccessTokenStep {
BuildStep withAccessToken(final String accessToken);
}
public static interface BuildStep {
BigcommerceSdk build();
}
public static StoreHashStep newBuilder() {
return new Steps();
}
static ApiUrlStep newSandboxBuilder() {
return new Steps();
}
public String getStoreHash() {
return storeHash;
}
public String getClientId() {
return clientId;
}
public String getAccessToken() {
return accessToken;
}
public CatalogSummary getCatalogSummary() {
final WebTarget webTarget = baseWebTarget.path(CATALOG).path(SUMMARY);
final CatalogSummaryResponse catalogSummaryResponse = get(webTarget, CatalogSummaryResponse.class);
return catalogSummaryResponse.getData();
}
public Products getProducts(final int page) {
return getProducts(page, MAX_LIMIT);
}
public Products getProducts(final int page, final int limit) {
final WebTarget webTarget = baseWebTarget.path(CATALOG).path(PRODUCTS).queryParam(INCLUDE, VARIANTS)
.queryParam(LIMIT, limit).queryParam(PAGE, page);
final ProductsResponse productsResponse = get(webTarget, ProductsResponse.class);
final List products = productsResponse.getData();
final Pagination pagination = productsResponse.getMeta().getPagination();
return new Products(products, pagination);
}
public List getOrders(final int page, DateTime earliestDate) {
return getOrders(page, MAX_LIMIT, earliestDate);
}
public List getOrders(final int page) {
return getOrders(page, MAX_LIMIT, null);
}
public List getOrders(final int page, final int limit, final DateTime earliestDate) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).queryParam(LIMIT, limit).queryParam(PAGE, page)
.queryParam(MIN_DATE_CREATED, earliestDate);
final OrdersResponse ordersResponse = get(webTarget, OrdersResponse.class);
return (ordersResponse == null) ? Collections.emptyList() : ordersResponse;
}
public Order getOrder(int orderId) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId));
return get(webTarget, Order.class);
}
public Order completeOrder(final int orderId) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId));
Order order = new Order();
order.setStatusId(getStatus(com.bigcommerce.catalog.models.Status.COMPLETED).getId());
return put(webTarget, order, Order.class);
}
public Order cancelOrder(final int orderId) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId));
Order order = new Order();
order.setStatusId(getStatus(com.bigcommerce.catalog.models.Status.CANCELLED).getId());
return put(webTarget, order, Order.class);
}
public OrderStatus getStatus(final com.bigcommerce.catalog.models.Status statusName) {
final WebTarget webTarget = baseWebTargetV2.path("order_statuses");
final OrderStatusResponse statusResponse = get(webTarget, OrderStatusResponse.class);
return statusResponse.stream().filter(status -> status.getName().equals(statusName.toString())).findFirst()
.get();
}
public List getLineItems(final int orderId, final int page) {
return getLineItems(orderId, page, MAX_LIMIT);
}
public List getLineItems(final int orderId, final int page, final int limit) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId)).path(PRODUCTS)
.queryParam(LIMIT, limit).queryParam(PAGE, page);
final LineItemsResponse lineItems = get(webTarget, LineItemsResponse.class);
return (lineItems == null) ? Collections.emptyList() : lineItems;
}
public Address getShippingAddress(final int orderId) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId)).path(SHIPPINGADDRESSES);
final AddressResponse addressResponse = get(webTarget, AddressResponse.class);
return (addressResponse == null) ? null : addressResponse.get(0);
}
public List getShipments(final int orderId, final int page) {
return getShipments(orderId, page, MAX_LIMIT);
}
public List getShipments(final int orderId, final int page, final int limit) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId)).path(SHIPMENTS)
.queryParam(LIMIT, limit).queryParam(PAGE, page);
final ShipmentResponse shipments = get(webTarget, ShipmentResponse.class);
return (shipments == null) ? Collections.emptyList() : shipments;
}
public Store getStore() {
final WebTarget webTarget = baseWebTargetV2.path(STORE);
return get(webTarget, Store.class);
}
public Customer getCustomer(final int customerId) {
final WebTarget webTarget = baseWebTargetV2.path(CUSTOMERS).path(String.valueOf(customerId));
return get(webTarget, Customer.class);
}
public Variant updateVariant(final Variant variant) {
final WebTarget webTarget = baseWebTarget.path(CATALOG).path(PRODUCTS).path(variant.getProductId())
.path(VARIANTS).path(variant.getId());
final VariantResponse variantResponse = put(webTarget, variant, VariantResponse.class);
return variantResponse.getData();
}
public Shipment createShipment(final ShipmentCreationRequest shipmentCreationRequest, final int orderId) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId)).path(SHIPMENTS);
return post(webTarget, shipmentCreationRequest.getRequest(), Shipment.class);
}
public Shipment updateShipment(final ShipmentUpdateRequest shipmentUpdateRequest, final int orderId,
final int shipmentId) {
final WebTarget webTarget = baseWebTargetV2.path(ORDERS).path(String.valueOf(orderId)).path(SHIPMENTS)
.path(String.valueOf(shipmentId));
return put(webTarget, shipmentUpdateRequest.getRequest(), Shipment.class);
}
private BigcommerceSdk(final Steps steps) {
this.baseWebTarget = CLIENT.target(steps.apiUrl).path(steps.storeHash).path(API_VERSION);
this.baseWebTargetV2 = CLIENT.target(steps.apiUrl).path(steps.storeHash).path(API_VERSION_V2);
this.storeHash = steps.storeHash;
this.clientId = steps.clientId;
this.accessToken = steps.accessToken;
this.requestRetryTimeoutDuration = steps.requestRetryTimeoutDuration;
this.requestRetryTimeoutUnit = steps.requestRetryTimeoutUnit;
}
private static class Steps
implements ApiUrlStep, RequestRetryTimeoutStep, StoreHashStep, ClientIdStep, AccessTokenStep, BuildStep {
private String apiUrl = "https://api.bigcommerce.com/stores";
private long requestRetryTimeoutDuration = 5;
private TimeUnit requestRetryTimeoutUnit = TimeUnit.MINUTES;
private String storeHash;
private String clientId;
private String accessToken;
@Override
public RequestRetryTimeoutStep withApiUrl(final String apiUrl) {
this.apiUrl = apiUrl;
return this;
}
@Override
public StoreHashStep withRequestRetryTimeout(final long requestRetryTimeoutDuration,
final TimeUnit requestRetryTimeoutUnit) {
this.requestRetryTimeoutDuration = requestRetryTimeoutDuration;
this.requestRetryTimeoutUnit = requestRetryTimeoutUnit;
return this;
}
@Override
public BuildStep withAccessToken(final String accessToken) {
this.accessToken = accessToken;
return this;
}
@Override
public AccessTokenStep withClientId(final String clientId) {
this.clientId = clientId;
return this;
}
@Override
public ClientIdStep withStoreHash(final String storeHash) {
this.storeHash = storeHash;
return this;
}
@Override
public BigcommerceSdk build() {
return new BigcommerceSdk(this);
}
}
private T get(final WebTarget webTarget, final Class entityType) {
final Callable responseCallable = new Callable() {
@Override
public Response call() throws Exception {
return webTarget.request(MediaType.APPLICATION_JSON).header(CLIENT_ID_HEADER, getClientId())
.header(ACCESS_TOKEN_HEADER, getAccessToken()).get();
}
};
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, entityType, Status.OK, Status.NO_CONTENT);
}
private V put(final WebTarget webTarget, final T object, final Class entityType) {
final Callable responseCallable = new Callable() {
@Override
public Response call() throws Exception {
final Entity entity = Entity.entity(object, MEDIA_TYPE);
return webTarget.request().header(CLIENT_ID_HEADER, getClientId())
.header(ACCESS_TOKEN_HEADER, getAccessToken()).put(entity);
}
};
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, entityType, Status.OK);
}
private V post(final WebTarget webTarget, final T object, final Class entityType) {
final Callable responseCallable = new Callable() {
@Override
public Response call() throws Exception {
final Entity entity = Entity.entity(object, MEDIA_TYPE);
return webTarget.request().header(CLIENT_ID_HEADER, getClientId())
.header(ACCESS_TOKEN_HEADER, getAccessToken()).post(entity);
}
};
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, entityType, Status.OK, Status.CREATED);
}
private Response invokeResponseCallable(final Callable responseCallable) {
final Retryer retryer = buildResponseRetyer();
try {
return retryer.call(responseCallable);
} catch (ExecutionException | RetryException e) {
throw new BigcommerceException(RETRY_FAILED_MESSAGE, e);
}
}
private Retryer buildResponseRetyer() {
return RetryerBuilder.newBuilder().retryIfResult(this::shouldRetryResponse)
.withWaitStrategy(new ResponseWaitStrategy())
.withStopStrategy(StopStrategies.stopAfterDelay(requestRetryTimeoutDuration, requestRetryTimeoutUnit))
.build();
}
private boolean shouldRetryResponse(final Response response) {
return (Status.Family.SERVER_ERROR == Status.Family.familyOf(response.getStatus()))
|| (TOO_MANY_REQUESTS_STATUS_CODE == response.getStatus());
}
private T handleResponse(final Response response, final Class entityType, final Status... expectedStatuses) {
final List expectedStatusCodes = Arrays.asList(expectedStatuses).stream().map(Status::getStatusCode)
.collect(Collectors.toList());
if (expectedStatusCodes.contains(response.getStatus())) {
return response.readEntity(entityType);
}
throw new BigcommerceErrorResponseException(response);
}
private class ResponseWaitStrategy implements WaitStrategy {
private static final String SLEEPING_BEFORE_RETRY_DUE_TO_RATE_LIMIT_MESSAGE = "Sleeping %s before trying request again. Exceeded rate limit.";
private static final String SLEEPING_BEFORE_RETRY_MESSAGE = "Sleeping %s before trying request again. Received unexpected status code of %s and body of:\n%s";
private final WaitStrategy fallbackWaitStrategy = WaitStrategies.fibonacciWait();
@Override
public long computeSleepTime(@SuppressWarnings("rawtypes") final Attempt failedAttempt) {
if (failedAttempt.hasResult()) {
if (failedAttempt.getResult() instanceof Response) {
final Response response = (Response) failedAttempt.getResult();
final String rateLimitTimeResetString = response.getHeaderString(RATE_LIMIT_TIME_RESET_HEADER);
if (TOO_MANY_REQUESTS_STATUS_CODE == response.getStatus() && rateLimitTimeResetString != null) {
final long sleepTime = Long.valueOf(rateLimitTimeResetString);
LOGGER.warn(String.format(SLEEPING_BEFORE_RETRY_DUE_TO_RATE_LIMIT_MESSAGE,
Duration.ofMillis(sleepTime).toString()));
return sleepTime;
}
response.bufferEntity();
final long sleepTime = fallbackWaitStrategy.computeSleepTime(failedAttempt);
LOGGER.warn(String.format(SLEEPING_BEFORE_RETRY_MESSAGE, Duration.ofMillis(sleepTime).toString(),
response.getStatus(), response.readEntity(String.class)));
return sleepTime;
}
}
return 0;
}
}
}