![JAR search and dependency download from the Maven repository](/logo.png)
com.dft.api.shopify.ShopifySdkNew Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shopify-admin-rest Show documentation
Show all versions of shopify-admin-rest Show documentation
Shopify Admin REST API using JDK 11 and Reactive Programming
The newest version!
package com.dft.api.shopify;
import com.dft.api.shopify.exceptions.ShopifyClientException;
import com.dft.api.shopify.exceptions.ShopifyErrorResponseException;
import com.dft.api.shopify.mappers.DateDeserializer;
import com.dft.api.shopify.mappers.LocalDateTimeSerializer;
import com.dft.api.shopify.mappers.ShopifySdkObjectMapper;
import com.dft.api.shopify.model.*;
import com.dft.api.shopify.model.auth.AccessCredential;
import com.dft.api.shopify.v202307.model.metafields.ShopifyMetafieldRequest;
import com.dft.api.shopify.v202307.model.metafields.ShopifyMetafieldWrapper;
import com.fasterxml.jackson.core.util.JacksonFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.github.rholder.retry.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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 java.lang.reflect.Field;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.dft.api.shopify.constantcode.ConstantCodes.*;
@Slf4j
public class ShopifySdkNew {
private static final Integer TOO_MANY_REQUESTS_STATUS_CODE = 429;
private static final Integer UNPROCESSABLE_ENTITY_STATUS_CODE = 422;
private static final Integer LOCKED_STATUS_CODE = 423;
static final String DEPRECATED_REASON_HEADER = "X-Shopify-API-Deprecated-Reason";
private static final String DEPRECATED_SHOPIFY_CALL_ERROR_MESSAGE = "Shopify call is deprecated. Please take note of the X-Shopify-API-Deprecated-Reason and correct the call.\nRequest Location of {}\nResponse Status Code of {}\nResponse Headers of:\n{}";
private static final String RETRY_FAILED_MESSAGE = "Request retry has failed.";
private static final String COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE = "could not successfully be saved";
static final String RETRY_AFTER_HEADER = "Retry-After";
private static final Long DEFAULT_MAXIMUM_REQUEST_RETRY_TIMEOUT_IN_MILLISECONDS = 180000L;
private static final Long DEFAULT_MAXIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS = 5000L;
private static final Long DEFAULT_MINIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS = 1000L;
private static final Client CLIENT = buildClient();
private WebTarget webTarget;
private long minimumRequestRetryRandomDelayMilliseconds;
private long maximumRequestRetryRandomDelayMilliseconds;
private long maximumRequestRetryTimeoutMilliseconds;
private static final String ADMIN_API_ENDPOINT = "/admin/api";
private static final String JSON_SUFFIX = ".json";
public static final String SHOPIFY_DOMAIN_SUFFIX = ".myshopify.com";
protected String baseUrl;
protected HttpClient client;
protected AccessCredential accessCredential;
protected ObjectMapper objectMapper;
public ShopifySdkNew(AccessCredential accessCredential) {
this.client = HttpClient.newHttpClient();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
module.addDeserializer(LocalDateTime.class, new DateDeserializer());
objectMapper.registerModule(module);
this.objectMapper = objectMapper;
this.accessCredential = accessCredential;
this.baseUrl = HTTPS + getStoreDomain() + ADMIN_API_ENDPOINT;
this.minimumRequestRetryRandomDelayMilliseconds = DEFAULT_MINIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS;
this.maximumRequestRetryRandomDelayMilliseconds = DEFAULT_MAXIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS;
this.maximumRequestRetryTimeoutMilliseconds = DEFAULT_MAXIMUM_REQUEST_RETRY_TIMEOUT_IN_MILLISECONDS;
}
private String getStoreDomain() {
String domain = this.accessCredential.getStoreDomain();
if (!domain.endsWith(SHOPIFY_DOMAIN_SUFFIX)) {
domain += SHOPIFY_DOMAIN_SUFFIX;
}
return domain;
}
@SneakyThrows
protected HttpRequest get(URI uri) {
return HttpRequest.newBuilder(uri).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).GET().build();
}
public URI baseUrl(String version, String path) {
return URI.create(baseUrl + version + path + JSON_SUFFIX);
}
protected static Client buildClient() {
ObjectMapper mapper = ShopifySdkObjectMapper.buildMapper();
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(mapper);
return ClientBuilder.newClient().register(JacksonFeature.class).register(provider);
}
protected WebTarget getWebTarget() {
if (this.webTarget == null) {
if (this.accessCredential.getStoreDomain() != null && !this.accessCredential.getStoreDomain().trim().isEmpty()) {
this.webTarget = CLIENT.target(HTTPS + this.accessCredential.getStoreDomain());
}
}
return webTarget;
}
protected Response get(WebTarget webTarget) {
Callable responseCallable = () -> webTarget.request(MediaType.APPLICATION_JSON).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).get();
Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, Response.Status.OK);
}
protected Response put(WebTarget webTarget, T object) {
Callable responseCallable = () -> {
Entity entity = Entity.entity(object, MediaType.APPLICATION_JSON);
return webTarget.request(MediaType.APPLICATION_JSON).header(ACCESS_TOKEN_HEADER, accessCredential.getAccessToken()).put(entity);
};
Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, Response.Status.OK);
}
public static class ShopifySdkRetryListener implements RetryListener {
private static final String RETRY_EXCEPTION_ATTEMPT_MESSAGE = "An exception occurred while making an API call to shopify: {} on attempt number {} and {} seconds since first attempt";
private static final String RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE = "Waited {} seconds since first retry attempt. This is attempt {}. Please review the following failed request information.\nRequest Location of {}\nResponse Status Code of {}\nResponse Headers of:\n{}\nResponse Body of:\n{}";
@Override
public void onRetry(Attempt attempt) {
if (attempt.hasResult()) {
Response response = (Response) attempt.getResult();
response.bufferEntity();
String responseBody = response.readEntity(String.class);
if (log.isWarnEnabled() && !hasExceededRateLimit(response) && shouldRetryResponse(response)) {
long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(attempt.getDelaySinceFirstAttempt());
log.warn(RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE, delaySinceFirstAttemptInSeconds, attempt.getAttemptNumber(), response.getLocation(), response.getStatus(), response.getStringHeaders(), responseBody);
}
} else if (log.isWarnEnabled() && attempt.hasException()) {
long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(attempt.getDelaySinceFirstAttempt());
log.warn(RETRY_EXCEPTION_ATTEMPT_MESSAGE, attempt.getAttemptNumber(), delaySinceFirstAttemptInSeconds, attempt.getExceptionCause());
}
}
private long convertMillisecondsToSeconds(long milliiseconds) {
return TimeUnit.SECONDS.convert(milliiseconds, TimeUnit.MILLISECONDS);
}
}
protected Response handleResponse(Response response, Response.Status... expectedStatus) {
List expectedStatusCodes = getExpectedStatusCodes(expectedStatus);
if (expectedStatusCodes.contains(response.getStatus())) {
return response;
} else {
if ((response.getHeaders() != null) && response.getHeaders().containsKey(DEPRECATED_REASON_HEADER)) {
log.error(DEPRECATED_SHOPIFY_CALL_ERROR_MESSAGE, response.getLocation(), response.getStatus(), response.getStringHeaders());
}
}
throw new ShopifyErrorResponseException(response);
}
protected List getExpectedStatusCodes(Response.Status... expectedStatus) {
return Arrays.stream(expectedStatus).map(Response.Status::getStatusCode).collect(Collectors.toList());
}
protected Response invokeResponseCallable(Callable responseCallable) {
Retryer retryer = buildResponseRetyer();
try {
return retryer.call(responseCallable);
} catch (ExecutionException | RetryException exception) {
throw new ShopifyClientException(RETRY_FAILED_MESSAGE, exception);
}
}
protected Retryer buildResponseRetyer() {
return RetryerBuilder.newBuilder().retryIfResult(ShopifySdkNew::shouldRetryResponse).retryIfException().withWaitStrategy(WaitStrategies.randomWait(minimumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS, maximumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS)).withStopStrategy(StopStrategies.stopAfterDelay(maximumRequestRetryTimeoutMilliseconds, TimeUnit.MILLISECONDS)).withRetryListener(new ShopifySdkRetryListener()).build();
}
private static boolean shouldRetryResponse(Response response) {
return isServerError(response) || hasExceededRateLimit(response) || hasNotBeenSaved(response) || hasWaitCall(response);
}
private static boolean isServerError(Response response) {
return (Response.Status.Family.SERVER_ERROR == Response.Status.Family.familyOf(response.getStatus())) || (LOCKED_STATUS_CODE == response.getStatus());
}
private static boolean hasExceededRateLimit(Response response) {
return TOO_MANY_REQUESTS_STATUS_CODE == response.getStatus() && response.getHeaders().containsKey(RETRY_AFTER_HEADER);
}
private static boolean hasNotBeenSaved(Response response) {
response.bufferEntity();
if ((UNPROCESSABLE_ENTITY_STATUS_CODE == response.getStatus()) && response.hasEntity()) {
String shopifyErrorResponse = response.readEntity(String.class);
log.debug(shopifyErrorResponse);
return shopifyErrorResponse.contains(COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE);
}
return false;
}
private static boolean hasWaitCall(Response response) {
return 202 == response.getStatus();
}
@SneakyThrows
protected HttpRequest post(URI uri, String json) {
return HttpRequest.newBuilder(uri).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).POST(HttpRequest.BodyPublishers.ofString(json)).build();
}
@SneakyThrows
protected HttpRequest postWithObject(URI uri, Object object) {
String jsonBody = objectMapper.writeValueAsString(object);
return HttpRequest.newBuilder(uri).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build();
}
@SneakyThrows
protected HttpRequest put(URI uri, String json) {
return HttpRequest.newBuilder(uri).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).PUT(HttpRequest.BodyPublishers.ofString(json)).build();
}
@SneakyThrows
protected HttpRequest putWithObject(URI uri, Object object) {
String jsonBody = objectMapper.writeValueAsString(object);
return HttpRequest.newBuilder(uri).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).PUT(HttpRequest.BodyPublishers.ofString(jsonBody)).build();
}
@SneakyThrows
protected HttpRequest delete(URI uri) {
return HttpRequest.newBuilder(uri).header(ACCESS_TOKEN_HEADER, this.accessCredential.getAccessToken()).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).DELETE().build();
}
@SneakyThrows
protected HttpRequest postWithoutAccessToken(URI uri) {
return HttpRequest.newBuilder(uri).header(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_VALUE_APPLICATION_JSON).POST(HttpRequest.BodyPublishers.noBody()).build();
}
@SneakyThrows
protected URI addParameters(URI uri, HashMap params) {
String query = uri.getQuery();
StringBuilder builder = new StringBuilder();
if (query != null) builder.append(query);
for (Map.Entry entry : params.entrySet()) {
String keyValueParam = entry.getKey() + "=" + entry.getValue();
if (!builder.toString().isEmpty()) builder.append("&");
builder.append(keyValueParam);
}
return new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), builder.toString(), uri.getFragment());
}
@SneakyThrows
public T deleteRequestWrapped(HttpRequest request, HttpResponse.BodyHandler handler) {
return client.sendAsync(request, handler).thenComposeAsync(response -> tryResend(client, request, handler, response, 1)).get().body();
}
@SneakyThrows
protected T getRequestWrapped(HttpRequest request, Class tClass) {
HttpResponse stringHttpResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenComposeAsync(response -> tryResend(client, request, HttpResponse.BodyHandlers.ofString(), response, 1)).get();
T resp = convertBody(stringHttpResponse.body(), tClass);
Pagination paginationLinks = getPaginationLinks(stringHttpResponse);
Field[] fields = tClass.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equalsIgnoreCase("pagination")) {
field.setAccessible(true);
field.set(resp, paginationLinks);
break;
}
}
return resp;
}
@SneakyThrows
protected CompletableFuture> tryResend(HttpClient client, HttpRequest request, HttpResponse.BodyHandler handler, HttpResponse resp, Integer count) {
if (resp.statusCode() == 429 && count < MAX_ATTEMPTS) {
Thread.sleep(TIME_OUT_DURATION);
return client.sendAsync(request, handler).thenComposeAsync(response -> tryResend(client, request, handler, response, count + 1));
}
return CompletableFuture.completedFuture(resp);
}
public Pagination getPaginationLinks(HttpResponse response) {
Pagination pagination = new Pagination();
if (response.headers().firstValue("Link").isEmpty()) {
return pagination;
}
String values = response.headers().allValues("Link").get(0);
if (values == null) {
return pagination;
}
String[] sUrl = values.split(",");
for (String navigation : sUrl) {
String[] urlRel = navigation.split(";");
String url = urlRel[0].replaceAll("<", "").replaceAll(">", "").split("\\?")[1];
String rel = urlRel[1].split("=")[1].replaceAll("\"", "");
StringTokenizer stringTokenizer = new StringTokenizer(url, "&");
while (stringTokenizer.hasMoreTokens()) {
String token = stringTokenizer.nextToken();
if (token.startsWith("page_info")) {
url = token.split("=")[1];
}
}
if (rel.equalsIgnoreCase("next")) {
pagination.setNext(url);
} else if (rel.equalsIgnoreCase("previous")) {
pagination.setPrevious(url);
}
}
return pagination;
}
@SneakyThrows
private T convertBody(String body, Class tClass) {
return objectMapper.readValue(body, tClass);
}
public ShopifyInventoryItem updateInventoryItem(ShopifyInventoryItem shopifyInventoryItem) {
ShopifyInventoryItemRoot shopifyInventoryItemRoot = new ShopifyInventoryItemRoot();
shopifyInventoryItemRoot.setInventoryItem(shopifyInventoryItem);
String url = INVENTORY_ITEMS + FORWARD_SLASH + shopifyInventoryItem.getId();
HttpRequest request = putWithObject(baseUrl(VERSION_2024_04, url), shopifyInventoryItemRoot);
ShopifyInventoryItemRoot shopifyInventoryItemRoot1 = getRequestWrapped(request, ShopifyInventoryItemRoot.class);
return shopifyInventoryItemRoot1.getInventoryItem();
}
public List getProductMetafields(final String productId) {
String url = PRODUCTS + FORWARD_SLASH + productId + FORWARD_SLASH + METAFIELDS;
HttpRequest request = get(baseUrl(VERSION_2024_04, url));
ShopifyMetafieldsRoot metafieldsRootResponse = getRequestWrapped(request, ShopifyMetafieldsRoot.class);
return metafieldsRootResponse.getMetafields();
}
public ShopifyProduct createProduct(final ShopifyProductCreationRequest shopifyProductCreationRequest) {
final ShopifyProductRoot shopifyProductRootRequest = new ShopifyProductRoot();
final ShopifyProduct shopifyProduct = shopifyProductCreationRequest.getRequest();
shopifyProductRootRequest.setProduct(shopifyProduct);
HttpRequest request = postWithObject(baseUrl(VERSION_2024_04, PRODUCTS), shopifyProductRootRequest);
ShopifyProductRoot shopifyProductRoot = getRequestWrapped(request, ShopifyProductRoot.class);
return shopifyProductRoot.getProduct();
}
public ShopifyProduct updateProduct(final ShopifyProductUpdateRequest shopifyProductUpdateRequest) {
final ShopifyProductRoot shopifyProductRootRequest = new ShopifyProductRoot();
final ShopifyProduct shopifyProduct = shopifyProductUpdateRequest.getRequest();
shopifyProductRootRequest.setProduct(shopifyProduct);
String url = PRODUCTS + FORWARD_SLASH + shopifyProduct.getId();
HttpRequest request = putWithObject(baseUrl(VERSION_2024_04, url), shopifyProductRootRequest);
ShopifyProductRoot shopifyProductRoot = getRequestWrapped(request, ShopifyProductRoot.class);
return shopifyProductRoot.getProduct();
}
public void deleteProduct(final String productId) {
String url = PRODUCTS + FORWARD_SLASH + productId;
HttpRequest request = delete(baseUrl(VERSION_2024_04, url));
deleteRequestWrapped(request, HttpResponse.BodyHandlers.ofString());
}
public ShopifyMetafieldWrapper createShopMetafields(Long metaFiledId, ShopifyMetafieldRequest shopifyMetafieldRequest) {
String url = FORWARD_SLASH + METAFIELDS + FORWARD_SLASH + metaFiledId;
HttpRequest request = putWithObject(baseUrl(VERSION_2024_04, url), shopifyMetafieldRequest);
return getRequestWrapped(request, ShopifyMetafieldWrapper.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy