Skip to content

Commit b4d7212

Browse files
authored
Merge pull request #65 from geoffcm/PLAT-3484
Jersey does not currently support Status Codes like `429:Too Many Requests` which is problematic. Code has been added to detect cases where Jersey does not translate the Status Code correctly and deserialise any response payload. Furthermore, a response of `429` has been translated into a `RateLimitingException` given it forms one of the six error groupings of the Campaign Monitor API.
2 parents 6a25463 + 6b6c87c commit b4d7212

5 files changed

Lines changed: 101 additions & 12 deletions

File tree

HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# createsend-java history
22

3+
## v7.0.1 - 2 December 2022
4+
5+
* Added support for rate limiting errors
6+
37
## v7.0.0 - 14 December 2021
48

59
* Upgrades to Createsend API v3.3 which includes new breaking changes

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ install.dependsOn ':build'
77
defaultTasks 'clean', 'install'
88

99
sourceCompatibility = 1.7
10-
version = '7.0.0'
10+
version = '7.0.1'
1111
group = 'com.createsend'
1212

1313
def localMavenRepo = 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath

src/com/createsend/util/JerseyClientImpl.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@
3535
import com.createsend.util.exceptions.CreateSendHttpException;
3636
import com.createsend.util.exceptions.ExpiredOAuthTokenException;
3737
import com.createsend.util.exceptions.NotFoundException;
38+
import com.createsend.util.exceptions.RateLimitingException;
3839
import com.createsend.util.exceptions.ServerErrorException;
3940
import com.createsend.util.exceptions.UnauthorisedException;
4041
import com.createsend.util.jersey.AuthorisedResourceFactory;
4142
import com.createsend.util.jersey.JsonProvider;
4243
import com.createsend.util.jersey.ResourceFactory;
4344
import com.createsend.util.jersey.UnauthorisedResourceFactory;
4445
import com.createsend.util.jersey.UserAgentFilter;
46+
import com.fasterxml.jackson.databind.ObjectMapper;
4547
import com.sun.jersey.api.client.Client;
4648
import com.sun.jersey.api.client.ClientResponse;
4749
import com.sun.jersey.api.client.GenericType;
@@ -80,6 +82,7 @@ public class JerseyClientImpl implements JerseyClient {
8082
}
8183

8284
private ErrorDeserialiser<String> defaultDeserialiser = new ErrorDeserialiser<String>(){};
85+
private ObjectMapper mapper = new ObjectMapper();
8386
private ResourceFactory authorisedResourceFactory;
8487
private AuthenticationDetails authDetails;
8588

@@ -407,9 +410,22 @@ private <T> CreateSendException handleErrorResponse(UniformInterfaceException ue
407410
ErrorDeserialiser<T> deserialiser) {
408411
ClientResponse response = ue.getResponse();
409412
ApiErrorResponse<T> apiResponse = null;
410-
413+
414+
int statusCode = response.getStatus();
411415
Status responseStatus = response.getClientResponseStatus();
412-
if(responseStatus == Status.BAD_REQUEST ||
416+
417+
// There are some HTTP Responses which Jersey does not handle correctly: both the HTTP
418+
// Status Code and response payloads.
419+
if (responseStatus == null) {
420+
try {
421+
// Use Jackson directly to deserialise the reponse payload
422+
apiResponse = mapper.readValue(response.getEntityInputStream(), ApiErrorResponse.class);
423+
} catch (Throwable t) { }
424+
425+
return handleUnknownStatusError(statusCode, apiResponse);
426+
}
427+
428+
if(responseStatus == Status.BAD_REQUEST ||
413429
responseStatus == Status.NOT_FOUND ||
414430
responseStatus == Status.UNAUTHORIZED ||
415431
responseStatus == Status.INTERNAL_SERVER_ERROR) {
@@ -419,16 +435,30 @@ private <T> CreateSendException handleErrorResponse(UniformInterfaceException ue
419435
}
420436

421437
if (apiResponse == null) {
422-
return handleUnknownError(responseStatus);
438+
return handleUnknownError(statusCode);
423439
} else if (apiResponse.error != null && apiResponse.error.length() > 0) {
424440
return handleOAuthErrorResponse(responseStatus, apiResponse);
425441
} else {
426-
return handleAPIErrorResponse(responseStatus, apiResponse);
442+
return handleAPIErrorResponse(responseStatus, apiResponse);
443+
}
444+
}
445+
446+
private <T> CreateSendException handleUnknownStatusError(int httpStatusCode, ApiErrorResponse<T> apiResponse) {
447+
boolean hasMessage = apiResponse != null && apiResponse.Message != null;
448+
449+
// Jersey does not yet handle HTTP Status Codes of 429:Too Many Requests
450+
if (httpStatusCode == 429) {
451+
return new RateLimitingException(httpStatusCode, hasMessage ? apiResponse.Message : null);
452+
}
453+
454+
if (hasMessage) {
455+
return new CreateSendHttpException(apiResponse.Message, httpStatusCode);
427456
}
457+
return new CreateSendHttpException(httpStatusCode);
428458
}
429459

430-
private <T> CreateSendException handleUnknownError(Status responseStatus) {
431-
return new CreateSendHttpException(responseStatus);
460+
private <T> CreateSendException handleUnknownError(int httpStatusCode) {
461+
return new CreateSendHttpException(httpStatusCode);
432462
}
433463

434464
private <T> CreateSendException handleAPIErrorResponse(
@@ -445,7 +475,7 @@ private <T> CreateSendException handleAPIErrorResponse(
445475
return new ExpiredOAuthTokenException(apiResponse.Code, apiResponse.Message);
446476
return new UnauthorisedException(apiResponse.Code, apiResponse.Message);
447477
default:
448-
return new CreateSendHttpException(responseStatus);
478+
return new CreateSendHttpException(responseStatus.getStatusCode());
449479
}
450480
}
451481

src/com/createsend/util/exceptions/CreateSendHttpException.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,45 @@
3030
public class CreateSendHttpException extends CreateSendException {
3131
private static final long serialVersionUID = 6026680795882633621L;
3232

33-
private ClientResponse.Status httpStatusCode;
33+
private int httpStatusCode;
3434
private int apiErrorCode;
3535
private String apiErrorMessage;
3636

37-
public CreateSendHttpException(Status httpStatusCode) {
37+
public CreateSendHttpException(int httpStatusCode) {
3838
super("The API call failed due to an unexpected HTTP error: " + httpStatusCode);
3939
this.httpStatusCode = httpStatusCode;
4040
this.apiErrorMessage = "";
4141
}
4242

43+
public CreateSendHttpException(String message, int httpStatusCode) {
44+
super(message);
45+
this.httpStatusCode = httpStatusCode;
46+
this.apiErrorMessage = apiErrorMessage;
47+
}
48+
4349
public CreateSendHttpException(String message, int httpStatusCode, int apiErrorCode, String apiErrorMessage) {
4450
super(message);
4551

46-
this.httpStatusCode = Status.fromStatusCode(httpStatusCode);
52+
this.httpStatusCode = httpStatusCode;
4753
this.apiErrorCode = apiErrorCode;
4854
this.apiErrorMessage = apiErrorMessage;
4955
}
5056

5157
/**
58+
* @return The HTTP Status code as an integer from the failed request
59+
*/
60+
public int getStatusCode() {
61+
return httpStatusCode;
62+
}
63+
64+
/**
65+
* This method will only interpret well know HTTP Status Codes. Status codes such as 429 may
66+
* not be correctly interpreted, in which case you can use {@link #getStatusCode}
67+
*
5268
* @return The HTTP Status code from the failed request
5369
*/
5470
public Status getHttpStatusCode() {
55-
return httpStatusCode;
71+
return Status.fromStatusCode(httpStatusCode);
5672
}
5773

5874
/**
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright (c) 2011 Toby Brain
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*/
22+
package com.createsend.util.exceptions;
23+
24+
/**
25+
* An exception raised when the Campaign Monitor API responds with a 429 Too Many Requests
26+
*/
27+
public class RateLimitingException extends CreateSendHttpException {
28+
private static final long serialVersionUID = -2724621705342365927L;
29+
30+
public RateLimitingException(int apiErrorCode, String message) {
31+
super(
32+
String.format(
33+
"The CreateSend API responded indicating that rate limiting occured%s",
34+
message == null ? "" : ": " + message),
35+
apiErrorCode,
36+
apiErrorCode,
37+
message == null ? "" : message);
38+
}
39+
}

0 commit comments

Comments
 (0)