Skip to content

Commit 711028b

Browse files
committed
Merge branch 'release/1.5.0'
2 parents 2c675f4 + e86b88e commit 711028b

22 files changed

+1716
-442
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The final parameters of the SDK builder are optional and will use default values
2828
|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|
2929
|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|
3030
|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|
31+
|Connection Timeout|The duration to attempt to connect to Shopify's API.|1 minute|
3232
|Read Timeout|The duration to attempt to read a response from Shopify's API.|15 Seconds|
3333

3434
## Building from source

pom.xml

Lines changed: 3 additions & 2 deletions
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.4.0</version>
7+
<version>1.5.0</version>
88

99
<name>Shopify SDK</name>
1010
<description>Java SDK for Shopify REST API.</description>
@@ -159,7 +159,8 @@
159159
<goal>mutationCoverage</goal>
160160
</goals>
161161
<configuration>
162-
<mutationThreshold>82</mutationThreshold>
162+
<mutationThreshold>87</mutationThreshold>
163+
<threads>10</threads>
163164
<targetClasses>
164165
<param>com.shopify.*</param>
165166
</targetClasses>

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

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,8 @@
2323
import org.slf4j.Logger;
2424
import org.slf4j.LoggerFactory;
2525

26-
import com.fasterxml.jackson.annotation.JsonInclude;
27-
import com.fasterxml.jackson.databind.AnnotationIntrospector;
28-
import com.fasterxml.jackson.databind.DeserializationFeature;
29-
import com.fasterxml.jackson.databind.MapperFeature;
3026
import com.fasterxml.jackson.databind.ObjectMapper;
31-
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
32-
import com.fasterxml.jackson.databind.type.TypeFactory;
3327
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
34-
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
3528
import com.github.rholder.retry.Attempt;
3629
import com.github.rholder.retry.RetryException;
3730
import com.github.rholder.retry.RetryListener;
@@ -41,6 +34,7 @@
4134
import com.github.rholder.retry.WaitStrategies;
4235
import com.shopify.exceptions.ShopifyClientException;
4336
import com.shopify.exceptions.ShopifyErrorResponseException;
37+
import com.shopify.mappers.ShopifySdkObjectMapper;
4438
import com.shopify.model.Count;
4539
import com.shopify.model.Image;
4640
import com.shopify.model.ImageAltTextCreationRequest;
@@ -50,6 +44,10 @@
5044
import com.shopify.model.Shop;
5145
import com.shopify.model.ShopifyAccessTokenRoot;
5246
import com.shopify.model.ShopifyCancelOrderRequest;
47+
import com.shopify.model.ShopifyCustomer;
48+
import com.shopify.model.ShopifyCustomerRoot;
49+
import com.shopify.model.ShopifyCustomerUpdateRequest;
50+
import com.shopify.model.ShopifyCustomerUpdateRoot;
5351
import com.shopify.model.ShopifyFulfillment;
5452
import com.shopify.model.ShopifyFulfillmentCreationRequest;
5553
import com.shopify.model.ShopifyFulfillmentRoot;
@@ -67,6 +65,8 @@
6765
import com.shopify.model.ShopifyOrderRisk;
6866
import com.shopify.model.ShopifyOrderRisksRoot;
6967
import com.shopify.model.ShopifyOrderRoot;
68+
import com.shopify.model.ShopifyOrderShippingAddressUpdateRequest;
69+
import com.shopify.model.ShopifyOrderUpdateRoot;
7070
import com.shopify.model.ShopifyOrdersRoot;
7171
import com.shopify.model.ShopifyProduct;
7272
import com.shopify.model.ShopifyProductCreationRequest;
@@ -171,9 +171,12 @@ public class ShopifySdk {
171171
private long minimumRequestRetryRandomDelayMilliseconds;
172172
private long maximumRequestRetryRandomDelayMilliseconds;
173173
private long maximumRequestRetryTimeoutMilliseconds;
174+
private final ShopifySdkRetryListener shopifySdkRetryListener = new ShopifySdkRetryListener();
174175

175176
private static final Client CLIENT = buildClient();
176177

178+
private static final String CUSTOMERS = "customers";
179+
177180
public static interface OptionalsStep {
178181

179182
/**
@@ -623,6 +626,25 @@ public ShopifyOrder createOrder(final ShopifyOrderCreationRequest shopifyOrderCr
623626
return shopifyOrderRootResponse.getOrder();
624627
}
625628

629+
public ShopifyOrder updateOrderShippingAddress(
630+
final ShopifyOrderShippingAddressUpdateRequest shopifyOrderUpdateRequest) {
631+
final ShopifyOrderUpdateRoot shopifyOrderRoot = new ShopifyOrderUpdateRoot();
632+
shopifyOrderRoot.setOrder(shopifyOrderUpdateRequest);
633+
final Response response = put(getWebTarget().path(ORDERS).path(shopifyOrderUpdateRequest.getId()),
634+
shopifyOrderRoot);
635+
final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class);
636+
return shopifyOrderRootResponse.getOrder();
637+
}
638+
639+
public ShopifyCustomer updateCustomer(final ShopifyCustomerUpdateRequest shopifyCustomerUpdateRequest) {
640+
final ShopifyCustomerUpdateRoot shopifyCustomerUpdateRequestRoot = new ShopifyCustomerUpdateRoot();
641+
shopifyCustomerUpdateRequestRoot.setCustomer(shopifyCustomerUpdateRequest);
642+
final Response response = put(getWebTarget().path(CUSTOMERS).path(shopifyCustomerUpdateRequest.getId()),
643+
shopifyCustomerUpdateRequestRoot);
644+
final ShopifyCustomerRoot shopifyCustomerRootResponse = response.readEntity(ShopifyCustomerRoot.class);
645+
return shopifyCustomerRootResponse.getCustomer();
646+
}
647+
626648
public ShopifyFulfillment cancelFulfillment(final String orderId, final String fulfillmentId) {
627649
final Response response = post(
628650
getWebTarget().path(ORDERS).path(orderId).path(FULFILLMENTS).path(fulfillmentId).path(CANCEL),
@@ -811,6 +833,7 @@ private <T> Response put(final WebTarget webTarget, final T object) {
811833
}
812834

813835
private Response handleResponse(final Response response, final Status... expectedStatus) {
836+
814837
if ((response.getHeaders() != null) && response.getHeaders().containsKey(DEPRECATED_REASON_HEADER)) {
815838
LOGGER.error(DEPRECATED_SHOPIFY_CALL_ERROR_MESSAGE, response.getLocation(), response.getStatus(),
816839
response.getStringHeaders());
@@ -820,7 +843,8 @@ private Response handleResponse(final Response response, final Status... expecte
820843
if (expectedStatusCodes.contains(response.getStatus())) {
821844
return response;
822845
}
823-
throw new ShopifyErrorResponseException(response);
846+
847+
throw new ShopifyErrorResponseException(response, shopifySdkRetryListener.getResponseBody());
824848
}
825849

826850
private List<Integer> getExpectedStatusCodes(final Status... expectedStatus) {
@@ -842,7 +866,7 @@ private Retryer<Response> buildResponseRetyer() {
842866
TimeUnit.MILLISECONDS, maximumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS))
843867
.withStopStrategy(
844868
StopStrategies.stopAfterDelay(maximumRequestRetryTimeoutMilliseconds, TimeUnit.MILLISECONDS))
845-
.withRetryListener(new ShopifySdkRetryListener()).build();
869+
.withRetryListener(shopifySdkRetryListener).build();
846870
}
847871

848872
private static boolean shouldRetryResponse(final Response response) {
@@ -859,9 +883,10 @@ private static boolean isServerError(final Response response) {
859883
}
860884

861885
private static boolean hasNotBeenSaved(final Response response) {
886+
response.bufferEntity();
862887
if ((UNPROCESSABLE_ENTITY_STATUS_CODE == response.getStatus()) && response.hasEntity()) {
863-
response.bufferEntity();
864888
final String shopifyErrorResponse = response.readEntity(String.class);
889+
LOGGER.debug(shopifyErrorResponse);
865890
return shopifyErrorResponse.contains(COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE);
866891
}
867892
return false;
@@ -909,43 +934,38 @@ private WebTarget getWebTarget() {
909934
}
910935

911936
private static Client buildClient() {
912-
final ObjectMapper mapper = buildMapper();
937+
final ObjectMapper mapper = ShopifySdkObjectMapper.buildMapper();
913938
final JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
914939
provider.setMapper(mapper);
915940

916941
return ClientBuilder.newClient().register(JacksonFeature.class).register(provider);
917942
}
918943

919-
static ObjectMapper buildMapper() {
920-
final ObjectMapper mapper = new ObjectMapper();
921-
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
922-
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
923-
924-
final AnnotationIntrospector pair = AnnotationIntrospector.pair(
925-
new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()), new JacksonAnnotationIntrospector());
926-
mapper.setAnnotationIntrospector(pair);
927-
928-
mapper.enable(MapperFeature.USE_ANNOTATIONS);
929-
return mapper;
930-
}
931-
932-
private static class ShopifySdkRetryListener implements RetryListener {
944+
public class ShopifySdkRetryListener implements RetryListener {
933945

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 {}";
946+
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";
935947
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{}";
936948

949+
private String responseBody;
950+
937951
@Override
938952
public <V> void onRetry(final Attempt<V> attempt) {
939-
if (LOGGER.isWarnEnabled() && attempt.hasResult()) {
953+
if (attempt.hasResult()) {
940954
final Response response = (Response) attempt.getResult();
955+
941956
response.bufferEntity();
942-
if (shouldRetryResponse(response) && !hasExceededRateLimit(response)) {
957+
this.responseBody = response.readEntity(String.class);
958+
959+
if (LOGGER.isWarnEnabled() && !hasExceededRateLimit(response) && shouldRetryResponse(response)) {
960+
943961
final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(
944962
attempt.getDelaySinceFirstAttempt());
945963
LOGGER.warn(RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE, delaySinceFirstAttemptInSeconds,
946964
attempt.getAttemptNumber(), response.getLocation(), response.getStatus(),
947-
response.getStringHeaders(), response.readEntity(String.class));
965+
response.getStringHeaders(), this.responseBody);
966+
948967
}
968+
949969
} else if (LOGGER.isWarnEnabled() && attempt.hasException()) {
950970

951971
final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(
@@ -955,9 +975,14 @@ public <V> void onRetry(final Attempt<V> attempt) {
955975
}
956976
}
957977

978+
public String getResponseBody() {
979+
return responseBody;
980+
}
981+
958982
private long convertMillisecondsToSeconds(final long milliiseconds) {
959983
return TimeUnit.SECONDS.convert(milliiseconds, TimeUnit.MILLISECONDS);
960984
}
985+
961986
}
962987

963988
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.shopify.exceptions;
2+
3+
import java.io.Serializable;
4+
5+
public class ShopifyErrorCode implements Serializable {
6+
7+
private static final long serialVersionUID = -3870975240510101019L;
8+
9+
public enum Type implements Serializable {
10+
SHIPPING_ADDRESS, UNKNOWN
11+
}
12+
13+
private final Type type;
14+
private final String message;
15+
16+
public ShopifyErrorCode(final Type type, final String message) {
17+
this.type = type;
18+
this.message = message;
19+
}
20+
21+
public Type getType() {
22+
return type;
23+
}
24+
25+
public String getMessage() {
26+
return message;
27+
}
28+
29+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.shopify.exceptions;
2+
3+
import java.util.LinkedList;
4+
import java.util.List;
5+
import java.util.stream.Collectors;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.shopify.mappers.ShopifySdkObjectMapper;
12+
import com.shopify.model.ShopifyErrorsRoot;
13+
14+
public class ShopifyErrorCodeFactory {
15+
private static final Logger LOGGER = LoggerFactory.getLogger(ShopifyErrorCodeFactory.class);
16+
private static final String COULD_NOT_PARSE_ERROR_RESPONSE_MESSAGE = "Could not parse error message from shopify for response body: {}";
17+
private static final String NO_VALID_ERROR_CODES_FOUND_MESSAGE = "Shopify error format is not readable %s";
18+
19+
private ShopifyErrorCodeFactory() {
20+
}
21+
22+
public static final List<ShopifyErrorCode> create(final String responseBody) {
23+
final List<ShopifyErrorCode> shopifyErrorCodes = new LinkedList<>();
24+
try {
25+
final ObjectMapper objectMapper = ShopifySdkObjectMapper.buildMapper();
26+
27+
final ShopifyErrorsRoot shopifyErrorsRoot = objectMapper.readValue(responseBody, ShopifyErrorsRoot.class);
28+
final List<ShopifyErrorCode> shippingAddressErrorCodes = shopifyErrorsRoot.getErrors()
29+
.getShippingAddressErrors().stream()
30+
.map(shippingAddress -> new ShopifyErrorCode(ShopifyErrorCode.Type.SHIPPING_ADDRESS,
31+
shippingAddress))
32+
.collect(Collectors.toList());
33+
shopifyErrorCodes.addAll(shippingAddressErrorCodes);
34+
35+
if (shopifyErrorCodes.isEmpty()) {
36+
throw new IllegalArgumentException(String.format(NO_VALID_ERROR_CODES_FOUND_MESSAGE, responseBody));
37+
}
38+
} catch (final Exception e) {
39+
final ShopifyErrorCode shopifyErrorCode = new ShopifyErrorCode(ShopifyErrorCode.Type.UNKNOWN, responseBody);
40+
shopifyErrorCodes.add(shopifyErrorCode);
41+
LOGGER.warn(COULD_NOT_PARSE_ERROR_RESPONSE_MESSAGE, responseBody, e);
42+
}
43+
return shopifyErrorCodes;
44+
}
45+
46+
}
Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
11
package com.shopify.exceptions;
22

3+
import java.util.List;
4+
35
import javax.ws.rs.core.Response;
46

57
public class ShopifyErrorResponseException extends RuntimeException {
68

79
static final String MESSAGE = "Received unexpected Response Status Code of %d\nResponse Headers of:\n%s\nResponse Body of:\n%s";
8-
10+
private final int statusCode;
911
private static final long serialVersionUID = 5646635633348617058L;
12+
private final String responseBody;
13+
private final List<ShopifyErrorCode> shopifyErrorCodes;
1014

1115
public ShopifyErrorResponseException(final Response response) {
1216
super(buildMessage(response));
17+
response.bufferEntity();
18+
this.responseBody = response.readEntity(String.class);
19+
this.shopifyErrorCodes = ShopifyErrorCodeFactory.create(responseBody);
20+
this.statusCode = response.getStatus();
21+
}
22+
23+
public ShopifyErrorResponseException(final Response response, final String responseBody) {
24+
super(buildMessage(response, responseBody));
25+
response.bufferEntity();
26+
27+
this.responseBody = responseBody;
28+
this.shopifyErrorCodes = ShopifyErrorCodeFactory.create(responseBody);
29+
this.statusCode = response.getStatus();
30+
}
31+
32+
private static String buildMessage(final Response response, final String responseBody) {
33+
response.bufferEntity();
34+
return String.format(MESSAGE, response.getStatus(), response.getStringHeaders(), responseBody);
1335
}
1436

1537
private static String buildMessage(final Response response) {
@@ -18,4 +40,16 @@ private static String buildMessage(final Response response) {
1840
response.readEntity(String.class));
1941
}
2042

43+
public int getStatusCode() {
44+
return statusCode;
45+
}
46+
47+
public String getResponseBody() {
48+
return responseBody;
49+
}
50+
51+
public List<ShopifyErrorCode> getShopifyErrorCodes() {
52+
return shopifyErrorCodes;
53+
}
54+
2155
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.shopify.mappers;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.databind.AnnotationIntrospector;
5+
import com.fasterxml.jackson.databind.DeserializationFeature;
6+
import com.fasterxml.jackson.databind.MapperFeature;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
9+
import com.fasterxml.jackson.databind.type.TypeFactory;
10+
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
11+
12+
public class ShopifySdkObjectMapper {
13+
14+
private ShopifySdkObjectMapper() {
15+
}
16+
17+
public static ObjectMapper buildMapper() {
18+
final ObjectMapper mapper = new ObjectMapper();
19+
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
20+
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
21+
22+
final AnnotationIntrospector pair = AnnotationIntrospector.pair(
23+
new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()), new JacksonAnnotationIntrospector());
24+
mapper.setAnnotationIntrospector(pair);
25+
26+
mapper.enable(MapperFeature.USE_ANNOTATIONS);
27+
return mapper;
28+
}
29+
}

0 commit comments

Comments
 (0)