Skip to content

Commit 7c387a7

Browse files
author
gdgate
authored
Merge pull request #780 from sadam21/ast-failover_retry
Add retry support when GD endpoint is unvailable Reviewed-by: Libor Ryšavý https://github.com/liry
2 parents 784a49b + 978f436 commit 7c387a7

File tree

11 files changed

+484
-22
lines changed

11 files changed

+484
-22
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ The *GoodData Java SDK* uses:
3737
* the *Slf4j API* version 1.7.*
3838
* the *Java Development Kit (JDK)* version 8 or later
3939

40+
##### Retry of failed API calls
41+
42+
You can retry your failed requests since version *2.34.0*. Turn it on by configuring
43+
[RetrySettings](https://github.com/gooddata/gooddata-java/blob/master/src/main/java/com/gooddata/retry/RetrySettings.java)
44+
and add [Spring retry](https://github.com/spring-projects/spring-retry) to your classpath:
45+
```xml
46+
<dependency>
47+
<groupId>org.springframework.retry</groupId>
48+
<artifactId>spring-retry</artifactId>
49+
<version>${spring.retry.version}</version>
50+
</dependency>
51+
```
52+
4053
### Logging
4154

4255
The *GoodData Java SDK* logs using `slf4j-api`. Please adjust your logging configuration for

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44

55
<artifactId>gooddata-java</artifactId>
6-
<version>2.33.1-SNAPSHOT</version>
6+
<version>2.34.0-SNAPSHOT</version>
77
<name>${project.artifactId}</name>
88
<description>GoodData Java SDK</description>
99
<url>https://github.com/gooddata/gooddata-java</url>
@@ -311,6 +311,13 @@
311311
<version>${spring.version}</version>
312312
<optional>true</optional>
313313
</dependency>
314+
<!-- Required only if you want to use retry of failed requests which is configured in RetrySettings. -->
315+
<dependency>
316+
<groupId>org.springframework.retry</groupId>
317+
<artifactId>spring-retry</artifactId>
318+
<version>1.2.4.RELEASE</version>
319+
<optional>true</optional>
320+
</dependency>
314321
<dependency>
315322
<groupId>com.fasterxml.jackson.core</groupId>
316323
<artifactId>jackson-core</artifactId>

src/main/java/com/gooddata/GoodData.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import com.gooddata.md.maintenance.ExportImportService;
1919
import com.gooddata.notification.NotificationService;
2020
import com.gooddata.projecttemplate.ProjectTemplateService;
21+
import com.gooddata.retry.RetrySettings;
22+
import com.gooddata.retry.GetServerErrorRetryStrategy;
23+
import com.gooddata.retry.RetryableRestTemplate;
2124
import com.gooddata.util.ResponseErrorHandler;
2225
import com.gooddata.authentication.LoginPasswordAuthentication;
2326
import com.gooddata.warehouse.WarehouseService;
@@ -38,6 +41,10 @@
3841
import org.springframework.context.annotation.Bean;
3942
import org.springframework.http.MediaType;
4043
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
44+
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
45+
import org.springframework.retry.backoff.FixedBackOffPolicy;
46+
import org.springframework.retry.policy.SimpleRetryPolicy;
47+
import org.springframework.retry.support.RetryTemplate;
4148
import org.springframework.util.StreamUtils;
4249
import org.springframework.web.client.RestTemplate;
4350

@@ -48,7 +55,6 @@
4855

4956
import static com.gooddata.util.Validate.notNull;
5057
import static java.util.Arrays.asList;
51-
import static java.util.Collections.singletonList;
5258
import static org.apache.http.util.VersionInfo.loadVersionInfo;
5359

5460
/**
@@ -216,7 +222,7 @@ protected GoodData(GoodDataEndpoint endpoint, Authentication authentication) {
216222
protected GoodData(GoodDataEndpoint endpoint, Authentication authentication, GoodDataSettings settings) {
217223
httpClient = authentication.createHttpClient(endpoint, createHttpClientBuilder(settings));
218224

219-
restTemplate = createRestTemplate(endpoint, httpClient);
225+
restTemplate = createRestTemplate(endpoint, httpClient, settings.getRetrySettings());
220226

221227
accountService = new AccountService(getRestTemplate(), settings);
222228
projectService = new ProjectService(getRestTemplate(), accountService, settings);
@@ -240,7 +246,7 @@ protected GoodData(GoodDataEndpoint endpoint, Authentication authentication, Goo
240246
lcmService = new LcmService(getRestTemplate(), settings);
241247
}
242248

243-
static RestTemplate createRestTemplate(GoodDataEndpoint endpoint, HttpClient httpClient) {
249+
static RestTemplate createRestTemplate(GoodDataEndpoint endpoint, HttpClient httpClient, RetrySettings retrySettings) {
244250
notNull(endpoint, "endpoint");
245251
notNull(httpClient, "httpClient");
246252

@@ -253,7 +259,12 @@ static RestTemplate createRestTemplate(GoodDataEndpoint endpoint, HttpClient htt
253259
presetHeaders.put("Accept", MediaType.APPLICATION_JSON_VALUE);
254260
presetHeaders.put(Header.GDC_VERSION, readApiVersion());
255261

256-
final RestTemplate restTemplate = new RestTemplate(factory);
262+
final RestTemplate restTemplate;
263+
if (retrySettings == null) {
264+
restTemplate = new RestTemplate(factory);
265+
} else {
266+
restTemplate = createRetryRestTemplate(retrySettings, factory);
267+
}
257268
restTemplate.setInterceptors(asList(
258269
new HeaderSettingRequestInterceptor(presetHeaders),
259270
new DeprecationWarningRequestInterceptor()));
@@ -263,6 +274,30 @@ static RestTemplate createRestTemplate(GoodDataEndpoint endpoint, HttpClient htt
263274
return restTemplate;
264275
}
265276

277+
private static RestTemplate createRetryRestTemplate(RetrySettings retrySettings, UriPrefixingClientHttpRequestFactory factory) {
278+
final RetryTemplate retryTemplate = new RetryTemplate();
279+
280+
if (retrySettings.getRetryCount() != null) {
281+
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(retrySettings.getRetryCount()));
282+
}
283+
284+
if (retrySettings.getRetryInitialInterval() != null) {
285+
if (retrySettings.getRetryMultiplier() != null) {
286+
final ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
287+
exponentialBackOffPolicy.setInitialInterval(retrySettings.getRetryInitialInterval());
288+
exponentialBackOffPolicy.setMultiplier(retrySettings.getRetryMultiplier());
289+
exponentialBackOffPolicy.setMaxInterval(retrySettings.getRetryMaxInterval());
290+
retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
291+
} else {
292+
final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
293+
backOffPolicy.setBackOffPeriod(retrySettings.getRetryInitialInterval());
294+
retryTemplate.setBackOffPolicy(backOffPolicy);
295+
}
296+
}
297+
298+
return new RetryableRestTemplate(factory, retryTemplate, new GetServerErrorRetryStrategy());
299+
}
300+
266301
private HttpClientBuilder createHttpClientBuilder(final GoodDataSettings settings) {
267302
final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
268303
connectionManager.setDefaultMaxPerRoute(settings.getMaxConnections());

src/main/java/com/gooddata/GoodDataSettings.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
*/
66
package com.gooddata;
77

8+
import com.gooddata.retry.RetrySettings;
89
import com.gooddata.util.GoodDataToStringBuilder;
910

11+
import java.util.Objects;
1012
import java.util.concurrent.TimeUnit;
1113

1214
import static org.springframework.util.Assert.isTrue;
@@ -26,6 +28,7 @@ public class GoodDataSettings {
2628
private int socketTimeout = secondsToMillis(60);
2729
private int pollSleep = secondsToMillis(5);
2830
private String userAgent;
31+
private RetrySettings retrySettings;
2932

3033

3134
/**
@@ -205,30 +208,36 @@ public void setUserAgent(String userAgent) {
205208
this.userAgent = userAgent;
206209
}
207210

211+
public RetrySettings getRetrySettings() {
212+
return retrySettings;
213+
}
214+
215+
/**
216+
* Set retry settings
217+
* @param retrySettings retry settings
218+
*/
219+
public void setRetrySettings(RetrySettings retrySettings) {
220+
this.retrySettings = retrySettings;
221+
}
222+
208223
@Override
209-
public boolean equals(final Object o) {
224+
public boolean equals(Object o) {
210225
if (this == o) return true;
211226
if (o == null || getClass() != o.getClass()) return false;
212-
213227
final GoodDataSettings that = (GoodDataSettings) o;
214-
215-
if (maxConnections != that.maxConnections) return false;
216-
if (connectionTimeout != that.connectionTimeout) return false;
217-
if (connectionRequestTimeout != that.connectionRequestTimeout) return false;
218-
if (socketTimeout != that.socketTimeout) return false;
219-
if (pollSleep != that.pollSleep) return false;
220-
return userAgent != null ? userAgent.equals(that.userAgent) : that.userAgent == null;
228+
return maxConnections == that.maxConnections
229+
&& connectionTimeout == that.connectionTimeout
230+
&& connectionRequestTimeout == that.connectionRequestTimeout
231+
&& socketTimeout == that.socketTimeout
232+
&& pollSleep == that.pollSleep
233+
&& Objects.equals(userAgent, that.userAgent)
234+
&& Objects.equals(retrySettings, that.retrySettings);
221235
}
222236

223237
@Override
224238
public int hashCode() {
225-
int result = maxConnections;
226-
result = 31 * result + connectionTimeout;
227-
result = 31 * result + connectionRequestTimeout;
228-
result = 31 * result + socketTimeout;
229-
result = 31 * result + pollSleep;
230-
result = 31 * result + (userAgent != null ? userAgent.hashCode() : 0);
231-
return result;
239+
return Objects.hash(maxConnections, connectionTimeout, connectionRequestTimeout, socketTimeout, pollSleep,
240+
userAgent, retrySettings);
232241
}
233242

234243
@Override
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (C) 2007-2019, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.retry;
7+
8+
import java.net.URI;
9+
import java.util.Collection;
10+
import java.util.Collections;
11+
12+
import static java.util.Arrays.asList;
13+
14+
/**
15+
* Allows retry for GET method and some HTTP 5XX states mentioned in {@link GetServerErrorRetryStrategy#RETRYABLE_STATES}.
16+
*/
17+
public class GetServerErrorRetryStrategy implements RetryStrategy {
18+
19+
public static final Collection<Integer> RETRYABLE_STATES = Collections.unmodifiableCollection(asList(500, 502, 503, 504, 507));
20+
public static final Collection<String> RETRYABLE_METHODS = Collections.unmodifiableCollection(asList("GET"));
21+
22+
@Override
23+
public boolean retryAllowed(String method, int statusCode, URI uri) {
24+
return RETRYABLE_STATES.contains(statusCode) && RETRYABLE_METHODS.contains(method);
25+
}
26+
27+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (C) 2007-2019, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.retry;
7+
8+
import java.util.Objects;
9+
10+
import static org.springframework.util.Assert.isTrue;
11+
12+
/**
13+
* Contains settings for HTTP requests retry.
14+
*/
15+
public class RetrySettings {
16+
17+
public Integer DEFAULT_RETRY_COUNT = 6;
18+
private Long DEFAULT_RETRY_INITIAL_INTERVAL = 1 * 1000l; // 1s
19+
private Long DEFAULT_RETRY_MAX_INTERVAL = 1 * 60 * 1000l; // 1min
20+
private Double DEFAULT_RETRY_MULTIPLIER = 2d;
21+
22+
private Integer retryCount = DEFAULT_RETRY_COUNT;
23+
private Long retryInitialInterval = DEFAULT_RETRY_INITIAL_INTERVAL;
24+
private Long retryMaxInterval = DEFAULT_RETRY_MAX_INTERVAL;
25+
private Double retryMultiplier = DEFAULT_RETRY_MULTIPLIER;
26+
27+
/**
28+
* Total retry count. Should be > 0. No retry if not set.
29+
* @return retry count
30+
*/
31+
public Integer getRetryCount() {
32+
return retryCount;
33+
}
34+
35+
public void setRetryCount(Integer retryCount) {
36+
isTrue(retryCount == null || retryCount > 0, "retryCount hast to be greater than 0");
37+
this.retryCount = retryCount;
38+
}
39+
40+
/**
41+
*
42+
* @return retry initial interval
43+
*/
44+
public Long getRetryInitialInterval() {
45+
return retryInitialInterval;
46+
}
47+
48+
public void setRetryInitialInterval(Long retryInitialInterval) {
49+
isTrue(retryInitialInterval > 0, "retryInitialInterval has to be greater than 0");
50+
this.retryInitialInterval = retryInitialInterval;
51+
}
52+
53+
/**
54+
*
55+
* @return maximum retry interval
56+
*/
57+
public Long getRetryMaxInterval() {
58+
return retryMaxInterval;
59+
}
60+
61+
public void setRetryMaxInterval(Long retryMaxInterval) {
62+
isTrue(retryMaxInterval > 0, "retryMaxInterval has to be greater than 0");
63+
this.retryMaxInterval = retryMaxInterval;
64+
}
65+
66+
/**
67+
* If set, exponential strategy is used. Every next retry interval will be computed as previos interval multiplied
68+
* by this number.
69+
* @return retry multiplier
70+
*/
71+
public Double getRetryMultiplier() {
72+
return retryMultiplier;
73+
}
74+
75+
public void setRetryMultiplier(Double retryMultiplier) {
76+
isTrue(retryMultiplier > 1.0, "retryMultiplier has to be greater than 1.0");
77+
this.retryMultiplier = retryMultiplier;
78+
}
79+
80+
@Override
81+
public boolean equals(Object o) {
82+
if (this == o) return true;
83+
if (o == null || getClass() != o.getClass()) return false;
84+
final RetrySettings that = (RetrySettings) o;
85+
return Objects.equals(retryCount, that.retryCount) &&
86+
Objects.equals(retryInitialInterval, that.retryInitialInterval) &&
87+
Objects.equals(retryMaxInterval, that.retryMaxInterval) &&
88+
Objects.equals(retryMultiplier, that.retryMultiplier);
89+
}
90+
91+
@Override
92+
public int hashCode() {
93+
return Objects.hash(retryCount, retryInitialInterval, retryMaxInterval, retryMultiplier);
94+
}
95+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (C) 2007-2019, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.retry;
7+
8+
import java.net.URI;
9+
10+
/**
11+
* Interface for describing retry strategy.
12+
*/
13+
public interface RetryStrategy {
14+
15+
/**
16+
* Method says if retry is allowed for given parameter combination.
17+
* @param method HTTP method
18+
* @param statusCode HTTP response code
19+
* @param uri requested URL
20+
* @return {@code true} it retry is allowed
21+
*/
22+
boolean retryAllowed(String method, int statusCode, URI uri);
23+
24+
}

0 commit comments

Comments
 (0)