Skip to content

Commit 2d390fb

Browse files
committed
CA-4834 Merges feature/Update-Order into develop
2 parents 078b763 + 638005e commit 2d390fb

18 files changed

+1421
-426
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-SNAPSHOT</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: 39 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;
@@ -67,6 +61,8 @@
6761
import com.shopify.model.ShopifyOrderRisk;
6862
import com.shopify.model.ShopifyOrderRisksRoot;
6963
import com.shopify.model.ShopifyOrderRoot;
64+
import com.shopify.model.ShopifyOrderShippingAddressUpdateRequest;
65+
import com.shopify.model.ShopifyOrderUpdateRoot;
7066
import com.shopify.model.ShopifyOrdersRoot;
7167
import com.shopify.model.ShopifyProduct;
7268
import com.shopify.model.ShopifyProductCreationRequest;
@@ -171,6 +167,7 @@ public class ShopifySdk {
171167
private long minimumRequestRetryRandomDelayMilliseconds;
172168
private long maximumRequestRetryRandomDelayMilliseconds;
173169
private long maximumRequestRetryTimeoutMilliseconds;
170+
private final ShopifySdkRetryListener shopifySdkRetryListener = new ShopifySdkRetryListener();
174171

175172
private static final Client CLIENT = buildClient();
176173

@@ -623,6 +620,16 @@ public ShopifyOrder createOrder(final ShopifyOrderCreationRequest shopifyOrderCr
623620
return shopifyOrderRootResponse.getOrder();
624621
}
625622

623+
public ShopifyOrder updateOrderShippingAddress(
624+
final ShopifyOrderShippingAddressUpdateRequest shopifyOrderUpdateRequest) {
625+
final ShopifyOrderUpdateRoot shopifyOrderRoot = new ShopifyOrderUpdateRoot();
626+
shopifyOrderRoot.setOrder(shopifyOrderUpdateRequest);
627+
final Response response = put(getWebTarget().path(ORDERS).path(shopifyOrderUpdateRequest.getId()),
628+
shopifyOrderRoot);
629+
final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class);
630+
return shopifyOrderRootResponse.getOrder();
631+
}
632+
626633
public ShopifyFulfillment cancelFulfillment(final String orderId, final String fulfillmentId) {
627634
final Response response = post(
628635
getWebTarget().path(ORDERS).path(orderId).path(FULFILLMENTS).path(fulfillmentId).path(CANCEL),
@@ -811,6 +818,7 @@ private <T> Response put(final WebTarget webTarget, final T object) {
811818
}
812819

813820
private Response handleResponse(final Response response, final Status... expectedStatus) {
821+
814822
if ((response.getHeaders() != null) && response.getHeaders().containsKey(DEPRECATED_REASON_HEADER)) {
815823
LOGGER.error(DEPRECATED_SHOPIFY_CALL_ERROR_MESSAGE, response.getLocation(), response.getStatus(),
816824
response.getStringHeaders());
@@ -820,7 +828,8 @@ private Response handleResponse(final Response response, final Status... expecte
820828
if (expectedStatusCodes.contains(response.getStatus())) {
821829
return response;
822830
}
823-
throw new ShopifyErrorResponseException(response);
831+
832+
throw new ShopifyErrorResponseException(response, shopifySdkRetryListener.getResponseBody());
824833
}
825834

826835
private List<Integer> getExpectedStatusCodes(final Status... expectedStatus) {
@@ -842,7 +851,7 @@ private Retryer<Response> buildResponseRetyer() {
842851
TimeUnit.MILLISECONDS, maximumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS))
843852
.withStopStrategy(
844853
StopStrategies.stopAfterDelay(maximumRequestRetryTimeoutMilliseconds, TimeUnit.MILLISECONDS))
845-
.withRetryListener(new ShopifySdkRetryListener()).build();
854+
.withRetryListener(shopifySdkRetryListener).build();
846855
}
847856

848857
private static boolean shouldRetryResponse(final Response response) {
@@ -859,9 +868,10 @@ private static boolean isServerError(final Response response) {
859868
}
860869

861870
private static boolean hasNotBeenSaved(final Response response) {
871+
response.bufferEntity();
862872
if ((UNPROCESSABLE_ENTITY_STATUS_CODE == response.getStatus()) && response.hasEntity()) {
863-
response.bufferEntity();
864873
final String shopifyErrorResponse = response.readEntity(String.class);
874+
LOGGER.debug(shopifyErrorResponse);
865875
return shopifyErrorResponse.contains(COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE);
866876
}
867877
return false;
@@ -909,43 +919,38 @@ private WebTarget getWebTarget() {
909919
}
910920

911921
private static Client buildClient() {
912-
final ObjectMapper mapper = buildMapper();
922+
final ObjectMapper mapper = ShopifySdkObjectMapper.buildMapper();
913923
final JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
914924
provider.setMapper(mapper);
915925

916926
return ClientBuilder.newClient().register(JacksonFeature.class).register(provider);
917927
}
918928

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 {
929+
public class ShopifySdkRetryListener implements RetryListener {
933930

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 {}";
931+
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";
935932
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{}";
936933

934+
private String responseBody;
935+
937936
@Override
938937
public <V> void onRetry(final Attempt<V> attempt) {
939-
if (LOGGER.isWarnEnabled() && attempt.hasResult()) {
938+
if (attempt.hasResult()) {
940939
final Response response = (Response) attempt.getResult();
940+
941941
response.bufferEntity();
942-
if (shouldRetryResponse(response) && !hasExceededRateLimit(response)) {
942+
this.responseBody = response.readEntity(String.class);
943+
944+
if (LOGGER.isWarnEnabled() && !hasExceededRateLimit(response) && shouldRetryResponse(response)) {
945+
943946
final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(
944947
attempt.getDelaySinceFirstAttempt());
945948
LOGGER.warn(RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE, delaySinceFirstAttemptInSeconds,
946949
attempt.getAttemptNumber(), response.getLocation(), response.getStatus(),
947-
response.getStringHeaders(), response.readEntity(String.class));
950+
response.getStringHeaders(), this.responseBody);
951+
948952
}
953+
949954
} else if (LOGGER.isWarnEnabled() && attempt.hasException()) {
950955

951956
final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds(
@@ -955,9 +960,14 @@ public <V> void onRetry(final Attempt<V> attempt) {
955960
}
956961
}
957962

963+
public String getResponseBody() {
964+
return responseBody;
965+
}
966+
958967
private long convertMillisecondsToSeconds(final long milliiseconds) {
959968
return TimeUnit.SECONDS.convert(milliiseconds, TimeUnit.MILLISECONDS);
960969
}
970+
961971
}
962972

963973
}
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)