Skip to content

Commit 2c675f4

Browse files
committed
Merge branch 'release/1.4.0'
2 parents c7dfc2b + ee97ce3 commit 2c675f4

File tree

5 files changed

+589
-360
lines changed

5 files changed

+589
-360
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ final ShopifySdk shopifySdk = ShopifySdk.newBuilder()
2020
final ShopifyShop shopifyShop = shopifySdk.getShop();
2121
```
2222

23+
## Optional Configuration
24+
The final parameters of the SDK builder are optional and will use default values when not supplied:
25+
26+
| Parameter | Description | Default |
27+
|-----------|---------|--------|
28+
|Minimum Request Retry Random Delay|Shopify SDK uses a random wait strategy when calculating to perform the next attempt. This is the minimum duration to wait before performing the failed request.|1 second|
29+
|Maximum Request Retry Random Delay|Shopify SDK uses a random wait strategy when calculating to perform the next attempt. This is the maximum duration to wait before performing the failed request.|5 seconds|
30+
|Maximum Request Retry Timeout|The maximum time to keep retrying failed requests.|3 minutes|
31+
|Connection Timeout|The duration to attempt to read a response from Shopify's API.|1 minute|
32+
|Read Timeout|The duration to attempt to read a response from Shopify's API.|15 Seconds|
33+
2334
## Building from source
2435

2536
1. Install Maven

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.channelape</groupId>
66
<artifactId>shopify-sdk</artifactId>
7-
<version>1.3.0-SNAPSHOT</version>
7+
<version>1.4.0</version>
88

99
<name>Shopify SDK</name>
1010
<description>Java SDK for Shopify REST API.</description>

src/main/java/com/shopify/ShopifySdk.java

Lines changed: 168 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@
9292

9393
public class ShopifySdk {
9494

95+
private static final String MINIMUM_REQUEST_RETRY_DELAY_CANNOT_BE_LARGER_THAN_MAXIMUM_REQUEST_RETRY_DELAY_MESSAGE = "Maximum request retry delay must be larger than minimum request retry delay.";
96+
97+
private static final String INVALID_MAXIMUM_REQUEST_RETRY_TIMEOUT_MESSAGE = "Maximum request retry timeout cannot be set lower than 2 seconds.";
98+
99+
private static final String INVALID_MAXIMUM_REQUEST_RETRY_DELAY_MESSAGE = "Maximum request retry delay cannot be set lower than 2 seconds.";
100+
101+
private static final String INVALID_MINIMUM_REQUEST_RETRY_DELAY_MESSAGE = "Minimum request retry delay cannot be set lower than 1 second.";
102+
95103
private static final Logger LOGGER = LoggerFactory.getLogger(ShopifySdk.class);
96104

97105
private static final String HTTPS = "https://";
@@ -141,13 +149,17 @@ public class ShopifySdk {
141149

142150
private static final String SHOP_RETRIEVED_MESSAGE = "Starting to make calls for Shopify store with ID of {} and name of {}";
143151
private static final String COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE = "could not successfully be saved";
144-
private static final String RETRY_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{}";
145152
private static final String RETRY_FAILED_MESSAGE = "Request retry has failed.";
146153
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{}";
147154
static final String GENERAL_ACCESS_TOKEN_EXCEPTION_MESSAGE = "There was a problem generating access token using shop subdomain of %s and authorization code of %s.";
148155

149-
private static final int ONE_MINUTE_IN_MILLISECONDS = 60000;
150-
private static final int FIVE_MINUTES_IN_MILLISECONDS = 300000;
156+
private static final Long TWO_SECONDS_IN_MILLISECONDS = 2000L;
157+
private static final Long ONE_SECOND_IN_MILLISECONDS = 1000L;
158+
private static final Long DEFAULT_MAXIMUM_REQUEST_RETRY_TIMEOUT_IN_MILLISECONDS = 180000L;
159+
private static final Long DEFAULT_MAXIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS = 5000L;
160+
private static final Long DEFAULT_MINIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS = 1000L;
161+
private static final long DEFAULT_READ_TIMEOUT_IN_MILLISECONDS = 15000L;
162+
private static final long DEFAULT_CONNECTION_TIMEOUT_IN_MILLISECONDS = 60000L;
151163

152164
private String shopSubdomain;
153165
private String apiUrl;
@@ -156,15 +168,76 @@ public class ShopifySdk {
156168
private String authorizationToken;
157169
private WebTarget webTarget;
158170
private String accessToken;
171+
private long minimumRequestRetryRandomDelayMilliseconds;
172+
private long maximumRequestRetryRandomDelayMilliseconds;
173+
private long maximumRequestRetryTimeoutMilliseconds;
159174

160175
private static final Client CLIENT = buildClient();
161176

162-
public static interface BuildStep {
177+
public static interface OptionalsStep {
178+
179+
/**
180+
* The Shopify SDK uses random waits in between retry attempts. Minimum duration
181+
* time to wait before retrying a failed request. Value must also be less than
182+
* {@link #withMaximumRequestRetryRandomDelay(int, TimeUnit) Maximum Request
183+
* Retry Random Delay}.<br>
184+
* Default value is: 1 second.
185+
*
186+
* @param duration
187+
* @param timeUnit
188+
* @return {@link OptionalsStep}
189+
*/
190+
OptionalsStep withMinimumRequestRetryRandomDelay(int duration, TimeUnit timeUnit);
191+
192+
/**
193+
* The Shopify SDK uses random waits in between retry attempts. Maximum duration
194+
* time to wait before retrying a failed request. Value must also be more than
195+
* {@link #withMinimumRequestRetryRandomDelay(int, TimeUnit) Minimum Request
196+
* Retry Random Delay}.<br>
197+
* Default value is: 5 seconds.
198+
*
199+
* @param duration
200+
* @param timeUnit
201+
* @return {@link OptionalsStep}
202+
*/
203+
OptionalsStep withMaximumRequestRetryRandomDelay(int duration, TimeUnit timeUnit);
204+
205+
/**
206+
* Maximum duration time to keep attempting requests <br>
207+
* Default value is: 3 minutes.
208+
*
209+
* @param duration
210+
* @param timeUnit
211+
* @return {@link OptionalsStep}
212+
*/
213+
OptionalsStep withMaximumRequestRetryTimeout(int duration, TimeUnit timeUnit);
214+
215+
/**
216+
* The duration to wait when connecting to Shopify's API. <br>
217+
* Default value is: 1 minute.
218+
*
219+
* @param duration
220+
* @param timeUnit
221+
* @return {@link OptionalsStep}
222+
*/
223+
OptionalsStep withConnectionTimeout(int duration, TimeUnit timeUnit);
224+
225+
/**
226+
* The duration to attempt to read a response from Shopify's API. <br>
227+
* Default value is: 15 seconds.
228+
*
229+
* @param duration
230+
* @param timeUnit
231+
* @return {@link OptionalsStep}
232+
*/
233+
OptionalsStep withReadTimeout(int duration, TimeUnit timeUnit);
234+
163235
ShopifySdk build();
236+
164237
}
165238

166239
public static interface AuthorizationTokenStep {
167-
BuildStep withAuthorizationToken(final String authorizationToken);
240+
OptionalsStep withAuthorizationToken(final String authorizationToken);
168241

169242
}
170243

@@ -174,7 +247,7 @@ public static interface ClientSecretStep {
174247
}
175248

176249
public static interface AccessTokenStep {
177-
BuildStep withAccessToken(final String accessToken);
250+
OptionalsStep withAccessToken(final String accessToken);
178251

179252
ClientSecretStep withClientId(final String clientId);
180253
}
@@ -197,26 +270,56 @@ protected ShopifySdk(final Steps steps) {
197270
this.clientSecret = steps.clientSecret;
198271
this.authorizationToken = steps.authorizationToken;
199272
this.apiUrl = steps.apiUrl;
273+
this.minimumRequestRetryRandomDelayMilliseconds = steps.minimumRequestRetryRandomDelayMilliseconds;
274+
this.maximumRequestRetryRandomDelayMilliseconds = steps.maximumRequestRetryRandomDelayMilliseconds;
275+
this.maximumRequestRetryTimeoutMilliseconds = steps.maximumRequestRetryTimeoutMilliseconds;
276+
277+
CLIENT.property(ClientProperties.CONNECT_TIMEOUT, Math.toIntExact(steps.connectionTimeoutMilliseconds));
278+
CLIENT.property(ClientProperties.READ_TIMEOUT, Math.toIntExact(steps.readTimeoutMilliseconds));
279+
validateConstructionOfShopifySdk();
200280
}
201281

202282
}
203283

284+
private void validateConstructionOfShopifySdk() {
285+
if (this.minimumRequestRetryRandomDelayMilliseconds < ONE_SECOND_IN_MILLISECONDS) {
286+
throw new IllegalArgumentException(INVALID_MINIMUM_REQUEST_RETRY_DELAY_MESSAGE);
287+
}
288+
if (this.maximumRequestRetryRandomDelayMilliseconds < TWO_SECONDS_IN_MILLISECONDS) {
289+
throw new IllegalArgumentException(INVALID_MAXIMUM_REQUEST_RETRY_DELAY_MESSAGE);
290+
}
291+
if (this.maximumRequestRetryTimeoutMilliseconds < TWO_SECONDS_IN_MILLISECONDS) {
292+
throw new IllegalArgumentException(INVALID_MAXIMUM_REQUEST_RETRY_TIMEOUT_MESSAGE);
293+
}
294+
295+
if (minimumRequestRetryRandomDelayMilliseconds > maximumRequestRetryRandomDelayMilliseconds) {
296+
throw new IllegalArgumentException(
297+
MINIMUM_REQUEST_RETRY_DELAY_CANNOT_BE_LARGER_THAN_MAXIMUM_REQUEST_RETRY_DELAY_MESSAGE);
298+
}
299+
}
300+
204301
protected static class Steps
205-
implements SubdomainStep, ClientSecretStep, AuthorizationTokenStep, AccessTokenStep, BuildStep {
302+
implements SubdomainStep, ClientSecretStep, AuthorizationTokenStep, AccessTokenStep, OptionalsStep {
303+
206304
private String subdomain;
207305
private String accessToken;
208306
private String clientId;
209307
private String clientSecret;
210308
private String authorizationToken;
211309
private String apiUrl;
310+
private long minimumRequestRetryRandomDelayMilliseconds = DEFAULT_MINIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS;
311+
private long maximumRequestRetryRandomDelayMilliseconds = DEFAULT_MAXIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS;
312+
private long maximumRequestRetryTimeoutMilliseconds = DEFAULT_MAXIMUM_REQUEST_RETRY_TIMEOUT_IN_MILLISECONDS;
313+
private long connectionTimeoutMilliseconds = DEFAULT_CONNECTION_TIMEOUT_IN_MILLISECONDS;
314+
private long readTimeoutMilliseconds = DEFAULT_READ_TIMEOUT_IN_MILLISECONDS;
212315

213316
@Override
214317
public ShopifySdk build() {
215318
return new ShopifySdk(this);
216319
}
217320

218321
@Override
219-
public BuildStep withAccessToken(final String accessToken) {
322+
public OptionalsStep withAccessToken(final String accessToken) {
220323
this.accessToken = accessToken;
221324
return this;
222325
}
@@ -240,7 +343,7 @@ public ClientSecretStep withClientId(final String clientId) {
240343
}
241344

242345
@Override
243-
public BuildStep withAuthorizationToken(final String authorizationToken) {
346+
public OptionalsStep withAuthorizationToken(final String authorizationToken) {
244347
this.authorizationToken = authorizationToken;
245348
return this;
246349
}
@@ -251,6 +354,36 @@ public AuthorizationTokenStep withClientSecret(final String clientSecret) {
251354
return this;
252355
}
253356

357+
@Override
358+
public OptionalsStep withMinimumRequestRetryRandomDelay(final int duration, final TimeUnit timeUnit) {
359+
this.minimumRequestRetryRandomDelayMilliseconds = timeUnit.toMillis(duration);
360+
return this;
361+
}
362+
363+
@Override
364+
public OptionalsStep withMaximumRequestRetryRandomDelay(final int duration, final TimeUnit timeUnit) {
365+
this.maximumRequestRetryRandomDelayMilliseconds = timeUnit.toMillis(duration);
366+
return this;
367+
}
368+
369+
@Override
370+
public OptionalsStep withMaximumRequestRetryTimeout(final int duration, final TimeUnit timeUnit) {
371+
this.maximumRequestRetryTimeoutMilliseconds = timeUnit.toMillis(duration);
372+
return this;
373+
}
374+
375+
@Override
376+
public OptionalsStep withConnectionTimeout(final int duration, final TimeUnit timeUnit) {
377+
this.connectionTimeoutMilliseconds = timeUnit.toMillis(duration);
378+
return this;
379+
}
380+
381+
@Override
382+
public OptionalsStep withReadTimeout(final int duration, final TimeUnit timeUnit) {
383+
this.readTimeoutMilliseconds = timeUnit.toMillis(duration);
384+
return this;
385+
}
386+
254387
}
255388

256389
public boolean revokeOAuthToken() {
@@ -462,6 +595,7 @@ public ShopifyFulfillment createFulfillment(
462595
final ShopifyFulfillmentCreationRequest shopifyFulfillmentCreationRequest) {
463596
final ShopifyFulfillmentRoot shopifyFulfillmentRoot = new ShopifyFulfillmentRoot();
464597
final ShopifyFulfillment shopifyFulfillment = shopifyFulfillmentCreationRequest.getRequest();
598+
465599
shopifyFulfillmentRoot.setFulfillment(shopifyFulfillment);
466600
final Response response = post(
467601
getWebTarget().path(ORDERS).path(shopifyFulfillment.getOrderId()).path(FULFILLMENTS),
@@ -703,10 +837,11 @@ private Response invokeResponseCallable(final Callable<Response> responseCallabl
703837
}
704838

705839
private Retryer<Response> buildResponseRetyer() {
706-
707-
return RetryerBuilder.<Response> newBuilder().retryIfResult(ShopifySdk::shouldRetryResponse)
708-
.withWaitStrategy(WaitStrategies.randomWait(2, TimeUnit.SECONDS, 30, TimeUnit.SECONDS))
709-
.withStopStrategy(StopStrategies.stopAfterDelay(15, TimeUnit.MINUTES))
840+
return RetryerBuilder.<Response>newBuilder().retryIfResult(ShopifySdk::shouldRetryResponse).retryIfException()
841+
.withWaitStrategy(WaitStrategies.randomWait(minimumRequestRetryRandomDelayMilliseconds,
842+
TimeUnit.MILLISECONDS, maximumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS))
843+
.withStopStrategy(
844+
StopStrategies.stopAfterDelay(maximumRequestRetryTimeoutMilliseconds, TimeUnit.MILLISECONDS))
710845
.withRetryListener(new ShopifySdkRetryListener()).build();
711846
}
712847

@@ -777,9 +912,8 @@ private static Client buildClient() {
777912
final ObjectMapper mapper = buildMapper();
778913
final JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
779914
provider.setMapper(mapper);
780-
return ClientBuilder.newClient().property(ClientProperties.CONNECT_TIMEOUT, ONE_MINUTE_IN_MILLISECONDS)
781-
.register(JacksonFeature.class).property(ClientProperties.READ_TIMEOUT, FIVE_MINUTES_IN_MILLISECONDS)
782-
.register(provider);
915+
916+
return ClientBuilder.newClient().register(JacksonFeature.class).register(provider);
783917
}
784918

785919
static ObjectMapper buildMapper() {
@@ -797,21 +931,33 @@ static ObjectMapper buildMapper() {
797931

798932
private static class ShopifySdkRetryListener implements RetryListener {
799933

934+
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 with exception {}";
935+
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{}";
936+
800937
@Override
801938
public <V> void onRetry(final Attempt<V> attempt) {
802939
if (LOGGER.isWarnEnabled() && attempt.hasResult()) {
803940
final Response response = (Response) attempt.getResult();
804941
response.bufferEntity();
805942
if (shouldRetryResponse(response) && !hasExceededRateLimit(response)) {
806-
final long delaySinceFirstAttemptInMilliseconds = attempt.getDelaySinceFirstAttempt();
807-
final long delaySinceFirstAttemptInSeconds = TimeUnit.SECONDS
808-
.convert(delaySinceFirstAttemptInMilliseconds, TimeUnit.MILLISECONDS);
809-
LOGGER.warn(RETRY_ATTEMPT_MESSAGE, delaySinceFirstAttemptInSeconds, attempt.getAttemptNumber(),
810-
response.getLocation(), response.getStatus(), response.getStringHeaders(),
811-
response.readEntity(String.class));
943+
final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(
944+
attempt.getDelaySinceFirstAttempt());
945+
LOGGER.warn(RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE, delaySinceFirstAttemptInSeconds,
946+
attempt.getAttemptNumber(), response.getLocation(), response.getStatus(),
947+
response.getStringHeaders(), response.readEntity(String.class));
812948
}
949+
} else if (LOGGER.isWarnEnabled() && attempt.hasException()) {
950+
951+
final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(
952+
attempt.getDelaySinceFirstAttempt());
953+
LOGGER.warn(RETRY_EXCEPTION_ATTEMPT_MESSAGE, attempt.getAttemptNumber(),
954+
delaySinceFirstAttemptInSeconds, attempt.getExceptionCause());
813955
}
814956
}
957+
958+
private long convertMillisecondsToSeconds(final long milliiseconds) {
959+
return TimeUnit.SECONDS.convert(milliiseconds, TimeUnit.MILLISECONDS);
960+
}
815961
}
816962

817963
}

0 commit comments

Comments
 (0)