Skip to content

Commit de86ca7

Browse files
Merge pull request #588 from bcgov/fix/GRAD2-2925
Changes to allow retry on IO & WebClientRequest exceptions.
2 parents cc565b8 + ba7c309 commit de86ca7

File tree

4 files changed

+99
-5
lines changed

4 files changed

+99
-5
lines changed

api/src/main/java/ca/bc/gov/educ/api/batchgraduation/rest/RESTService.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
import org.springframework.stereotype.Component;
1111
import org.springframework.web.reactive.function.BodyInserters;
1212
import org.springframework.web.reactive.function.client.WebClient;
13+
import org.springframework.web.reactive.function.client.WebClientRequestException;
1314
import org.springframework.web.reactive.function.client.WebClientResponseException;
1415
import reactor.core.publisher.Mono;
1516

17+
import java.io.IOException;
1618
import java.time.Duration;
1719

1820
@Component
@@ -27,6 +29,7 @@ public class RESTService {
2729

2830
private final WebClient batchWebClient;
2931

32+
3033
@Autowired
3134
public RESTService(@Qualifier("batchClient") WebClient batchWebClient, WebClient webClient, final EducGradBatchGraduationApiConstants constants) {
3235
this.constants = constants;
@@ -61,7 +64,7 @@ public <T> T get(String url, Class<T> clazz, String accessToken) {
6164
- do not retry if 4xx errors happens like 404, 401, 403 etc.
6265
*/
6366
.retryWhen(reactor.util.retry.Retry.backoff(constants.getDefaultRetryMaxAttempts(), Duration.ofSeconds(constants.getDefaultRetryWaitDurationSeconds()))
64-
.filter(ServiceException.class::isInstance)
67+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
6568
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
6669
throw new ServiceException(getErrorMessage(url, ERROR_MESSAGE2), HttpStatus.SERVICE_UNAVAILABLE.value());
6770
}))
@@ -93,7 +96,7 @@ public <T> T get(String url, Class<T> clazz) {
9396
- do not retry if 4xx errors happens like 404, 401, 403 etc.
9497
*/
9598
.retryWhen(reactor.util.retry.Retry.backoff(constants.getDefaultRetryMaxAttempts(), Duration.ofSeconds(constants.getDefaultRetryWaitDurationSeconds()))
96-
.filter(ServiceException.class::isInstance)
99+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
97100
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
98101
throw new ServiceException(getErrorMessage(url, ERROR_MESSAGE2), HttpStatus.SERVICE_UNAVAILABLE.value());
99102
}))
@@ -132,7 +135,7 @@ public <T> T post(String url, Object body, Class<T> clazz, String accessToken) {
132135
clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_MESSAGE1), clientResponse.statusCode().value())))
133136
.bodyToMono(clazz)
134137
.retryWhen(reactor.util.retry.Retry.backoff(constants.getDefaultRetryMaxAttempts(), Duration.ofSeconds(constants.getDefaultRetryWaitDurationSeconds()))
135-
.filter(ServiceException.class::isInstance)
138+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
136139
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
137140
throw new ServiceException(getErrorMessage(url, ERROR_MESSAGE2), HttpStatus.SERVICE_UNAVAILABLE.value());
138141
}))
@@ -159,7 +162,7 @@ public <T> T post(String url, Object body, Class<T> clazz) {
159162
clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_MESSAGE1), clientResponse.statusCode().value())))
160163
.bodyToMono(clazz)
161164
.retryWhen(reactor.util.retry.Retry.backoff(constants.getDefaultRetryMaxAttempts(), Duration.ofSeconds(constants.getDefaultRetryWaitDurationSeconds()))
162-
.filter(ServiceException.class::isInstance)
165+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
163166
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
164167
throw new ServiceException(getErrorMessage(url, ERROR_MESSAGE2), HttpStatus.SERVICE_UNAVAILABLE.value());
165168
}))
@@ -200,7 +203,7 @@ public <T> T put(String url, Object body, Class<T> clazz) {
200203
clientResponse -> Mono.error(new ServiceException(getErrorMessage(url, ERROR_MESSAGE1), clientResponse.statusCode().value())))
201204
.bodyToMono(clazz)
202205
.retryWhen(reactor.util.retry.Retry.backoff(constants.getDefaultRetryMaxAttempts(), Duration.ofSeconds(constants.getDefaultRetryWaitDurationSeconds()))
203-
.filter(ServiceException.class::isInstance)
206+
.filter(ex -> ex instanceof ServiceException || ex instanceof IOException || ex instanceof WebClientRequestException)
204207
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
205208
throw new ServiceException(getErrorMessage(url, ERROR_MESSAGE2), HttpStatus.SERVICE_UNAVAILABLE.value());
206209
}))

api/src/test/java/ca/bc/gov/educ/api/batchgraduation/util/RESTServiceGetTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ca.bc.gov.educ.api.batchgraduation.exception.ServiceException;
44
import ca.bc.gov.educ.api.batchgraduation.rest.RESTService;
5+
import io.netty.channel.ConnectTimeoutException;
56
import org.junit.Assert;
67
import org.junit.Before;
78
import org.junit.Test;
@@ -13,11 +14,14 @@
1314
import org.springframework.beans.factory.annotation.Qualifier;
1415
import org.springframework.boot.test.context.SpringBootTest;
1516
import org.springframework.boot.test.mock.mockito.MockBean;
17+
import org.springframework.http.HttpHeaders;
18+
import org.springframework.http.HttpMethod;
1619
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1720
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1821
import org.springframework.test.context.ActiveProfiles;
1922
import org.springframework.test.context.junit4.SpringRunner;
2023
import org.springframework.web.reactive.function.client.WebClient;
24+
import org.springframework.web.reactive.function.client.WebClientRequestException;
2125
import reactor.core.publisher.Mono;
2226

2327
import java.util.function.Consumer;
@@ -112,4 +116,35 @@ public void testGetOverride_Given4xxErrorFromService_ExpectServiceError(){
112116
this.restService.get(TEST_URL_403, String.class);
113117
}
114118

119+
@Test(expected = ServiceException.class)
120+
public void testGet_Given5xxErrorFromService_ExpectConnectionError(){
121+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
122+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
123+
124+
Throwable cause = new RuntimeException("Simulated cause");
125+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
126+
restService.get(TEST_URL_503, String.class);
127+
}
128+
129+
@Test(expected = ServiceException.class)
130+
public void testGet_Given5xxErrorFromService_ExpectWebClientRequestError(){
131+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
132+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
133+
134+
Throwable cause = new RuntimeException("Simulated cause");
135+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.GET, null, new HttpHeaders())));
136+
restService.get(TEST_URL_503, String.class);
137+
}
138+
139+
@Test(expected = ServiceException.class)
140+
public void testGetWithToken_Given5xxErrorFromService_ExpectWebClientRequestError(){
141+
when(requestBodyUriMock.uri(TEST_URL_503)).thenReturn(requestBodyMock);
142+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
143+
144+
Throwable cause = new RuntimeException("Simulated cause");
145+
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.GET, null, new HttpHeaders())));
146+
restService.get(TEST_URL_503, String.class, "ABC");
147+
}
148+
149+
115150
}

api/src/test/java/ca/bc/gov/educ/api/batchgraduation/util/RESTServicePostTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ca.bc.gov.educ.api.batchgraduation.exception.ServiceException;
44
import ca.bc.gov.educ.api.batchgraduation.rest.RESTService;
5+
import io.netty.channel.ConnectTimeoutException;
56
import org.junit.Assert;
67
import org.junit.Before;
78
import org.junit.Test;
@@ -15,12 +16,15 @@
1516
import org.springframework.beans.factory.annotation.Qualifier;
1617
import org.springframework.boot.test.context.SpringBootTest;
1718
import org.springframework.boot.test.mock.mockito.MockBean;
19+
import org.springframework.http.HttpHeaders;
20+
import org.springframework.http.HttpMethod;
1821
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1922
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2023
import org.springframework.test.context.ActiveProfiles;
2124
import org.springframework.test.context.junit4.SpringRunner;
2225
import org.springframework.web.reactive.function.BodyInserter;
2326
import org.springframework.web.reactive.function.client.WebClient;
27+
import org.springframework.web.reactive.function.client.WebClientRequestException;
2428
import reactor.core.publisher.Mono;
2529

2630
import java.util.function.Consumer;
@@ -109,4 +113,33 @@ public void testPostOverride_Given4xxErrorFromService_ExpectServiceError() {
109113
when(this.responseMock.onStatus(any(), any())).thenThrow(new ServiceException());
110114
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
111115
}
116+
117+
@Test(expected = ServiceException.class)
118+
public void testPost_Given5xxErrorFromService_ExpectConnectionError(){
119+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
120+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
121+
122+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
123+
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
124+
}
125+
126+
@Test(expected = ServiceException.class)
127+
public void testPost_Given5xxErrorFromService_ExpectWebClientRequestError(){
128+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
129+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
130+
131+
Throwable cause = new RuntimeException("Simulated cause");
132+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.POST, null, new HttpHeaders())));
133+
this.restService.post(TEST_URL, TEST_BODY, byte[].class);
134+
}
135+
136+
@Test(expected = ServiceException.class)
137+
public void testPostWithToken_Given5xxErrorFromService_ExpectWebClientRequestError(){
138+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
139+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
140+
141+
Throwable cause = new RuntimeException("Simulated cause");
142+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.POST, null, new HttpHeaders())));
143+
this.restService.post(TEST_URL, TEST_BODY, byte[].class, "ABC");
144+
}
112145
}

api/src/test/java/ca/bc/gov/educ/api/batchgraduation/util/RESTServicePutTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import ca.bc.gov.educ.api.batchgraduation.exception.ServiceException;
44
import ca.bc.gov.educ.api.batchgraduation.rest.RESTService;
5+
import io.netty.channel.ConnectTimeoutException;
56
import org.junit.Assert;
67
import org.junit.Before;
78
import org.junit.Test;
@@ -14,12 +15,15 @@
1415
import org.springframework.beans.factory.annotation.Qualifier;
1516
import org.springframework.boot.test.context.SpringBootTest;
1617
import org.springframework.boot.test.mock.mockito.MockBean;
18+
import org.springframework.http.HttpHeaders;
19+
import org.springframework.http.HttpMethod;
1720
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
1821
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1922
import org.springframework.test.context.ActiveProfiles;
2023
import org.springframework.test.context.junit4.SpringRunner;
2124
import org.springframework.web.reactive.function.BodyInserter;
2225
import org.springframework.web.reactive.function.client.WebClient;
26+
import org.springframework.web.reactive.function.client.WebClientRequestException;
2327
import reactor.core.publisher.Mono;
2428

2529
import java.util.function.Consumer;
@@ -90,4 +94,23 @@ public void testPut_Given4xxErrorFromService_ExpectServiceError() {
9094
this.restService.put(TEST_URL, TEST_BODY, byte[].class);
9195
}
9296

97+
@Test(expected = ServiceException.class)
98+
public void testPut_Given5xxErrorFromService_ExpectConnectionError(){
99+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
100+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
101+
102+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new ConnectTimeoutException("Connection closed")));
103+
this.restService.put(TEST_URL, TEST_BODY, byte[].class);
104+
}
105+
106+
@Test(expected = ServiceException.class)
107+
public void testPut_Given5xxErrorFromService_ExpectWebClientRequestError(){
108+
when(requestBodyUriMock.uri(TEST_URL)).thenReturn(requestBodyMock);
109+
when(requestBodyMock.retrieve()).thenReturn(responseMock);
110+
111+
Throwable cause = new RuntimeException("Simulated cause");
112+
when(responseMock.bodyToMono(byte[].class)).thenReturn(Mono.error(new WebClientRequestException(cause, HttpMethod.PUT, null, new HttpHeaders())));
113+
this.restService.put(TEST_URL, TEST_BODY, byte[].class);
114+
}
115+
93116
}

0 commit comments

Comments
 (0)