Skip to content

Commit f4add24

Browse files
authored
Merge pull request #882 from commercetools/request-policies
Add request specific policies
2 parents 05a5008 + 83f6d62 commit f4add24

File tree

11 files changed

+432
-23
lines changed

11 files changed

+432
-23
lines changed

commercetools/commercetools-sdk-java-api/src/integrationTest/java/commercetools/TimeoutTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import com.commercetools.api.defaultconfig.ApiRootBuilder;
1010
import com.commercetools.api.defaultconfig.ServiceRegion;
1111
import com.commercetools.api.models.category.Category;
12+
import com.commercetools.api.models.product.ProductProjectionPagedSearchResponse;
1213
import commercetools.utils.CommercetoolsTestUtils;
1314

15+
import io.vrap.rmf.base.client.ApiHttpMethod;
1416
import io.vrap.rmf.base.client.ApiHttpResponse;
1517
import io.vrap.rmf.base.client.http.HttpStatusCode;
1618
import io.vrap.rmf.base.client.oauth2.ClientCredentials;
@@ -93,4 +95,48 @@ public void timeoutWithRetryTimeout() {
9395
.getBody();
9496
});
9597
}
98+
99+
@Test
100+
public void requestPolicies() {
101+
String projectKey = CommercetoolsTestUtils.getProjectKey();
102+
103+
ProjectApiRoot b = ApiRootBuilder.of()
104+
.defaultClient(ClientCredentials.of()
105+
.withClientId(CommercetoolsTestUtils.getClientId())
106+
.withClientSecret(CommercetoolsTestUtils.getClientSecret())
107+
.build(),
108+
ServiceRegion.GCP_EUROPE_WEST1)
109+
.withTelemetryMiddleware((request, next) -> next.apply(request).thenApply((response) -> {
110+
try {
111+
Thread.sleep(2000); // ensure timeout
112+
}
113+
catch (InterruptedException e) {
114+
throw new RuntimeException(e);
115+
}
116+
return response;
117+
}))
118+
.withRequestPolicies(policies -> policies
119+
.withRequestMatching(apiHttpRequest -> apiHttpRequest.getMethod().equals(ApiHttpMethod.POST),
120+
policyBuilder -> policyBuilder.withTimeout(Duration.ofSeconds(10)))
121+
.withRequestMatching(apiHttpRequest -> apiHttpRequest.getMethod().equals(ApiHttpMethod.GET),
122+
policyBuilder -> policyBuilder.withTimeout(Duration.ofSeconds(1)))
123+
.withAllOtherRequests(policyBuilder -> policyBuilder.withTimeout(Duration.ofSeconds(60))))
124+
.build(projectKey);
125+
126+
Assertions.assertThatExceptionOfType(TimeoutExceededException.class).isThrownBy(() -> {
127+
Category category = b.categories()
128+
.withId("fdbaf4ea-fbc9-4fea-bac4-1d7e6c1995b3")
129+
.get()
130+
.executeBlocking()
131+
.getBody();
132+
});
133+
134+
ProductProjectionPagedSearchResponse searchResponse = b.productProjections()
135+
.search()
136+
.post()
137+
.executeBlocking()
138+
.getBody();
139+
140+
Assertions.assertThat(searchResponse).isNotNull();
141+
}
96142
}

commercetools/commercetools-sdk-java-api/src/integrationTest/java/commercetools/utils/CommercetoolsTestUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ public class CommercetoolsTestUtils {
2121
static {
2222
ApiRootBuilder builder = ApiRootBuilder.ofEnvironmentVariables()
2323
.addConcurrentModificationMiddleware()
24-
.withPolicies(
25-
policyBuilder -> policyBuilder.withRetry(b -> b.maxRetries(5).statusCodes(singletonList(503))))
24+
.withRequestPolicies(policyBuilder -> policyBuilder.withAllOtherRequests(
25+
request -> request.withRetry(b -> b.maxRetries(5).statusCodes(singletonList(503)))))
2626
.withErrorMiddleware(ErrorMiddleware.ExceptionMode.UNWRAP_COMPLETION_EXCEPTION);
2727
projectApiRoot = builder.buildProjectRoot();
2828
}

commercetools/commercetools-sdk-java-api/src/main/java/com/commercetools/api/defaultconfig/ApiRootBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,14 @@ public ApiRootBuilder withPolicies(final PolicyBuilder policyBuilder) {
321321
return with(clientBuilder -> clientBuilder.withPolicies(policyBuilder));
322322
}
323323

324+
public ApiRootBuilder withRequestPolicies(final Function<RequestPolicyBuilder, RequestPolicyBuilder> fn) {
325+
return with(clientBuilder -> clientBuilder.withRequestPolicies(fn));
326+
}
327+
328+
public ApiRootBuilder withRequestPolicies(final RequestPolicyBuilder policyBuilder) {
329+
return with(clientBuilder -> clientBuilder.withRequestPolicies(policyBuilder));
330+
}
331+
324332
public ApiRootBuilder withPolicyMiddleware(final PolicyMiddleware policyMiddleware) {
325333
return with(clientBuilder -> clientBuilder.withPolicyMiddleware(policyMiddleware));
326334
}

commercetools/internal-docs/src/test/java/example/ExamplesTest.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@
5252
import org.junit.jupiter.api.Test;
5353
import org.slf4j.event.Level;
5454

55-
import dev.failsafe.Failsafe;
56-
import dev.failsafe.FailsafeExecutor;
5755
import reactor.netty.http.HttpProtocol;
5856

5957
public class ExamplesTest {
@@ -121,18 +119,14 @@ public void customUrls() {
121119
}
122120

123121
public void timeoutMiddleware() {
124-
dev.failsafe.Timeout<ApiHttpResponse<byte[]>> timeout = dev.failsafe.Timeout
125-
.<ApiHttpResponse<byte[]>> builder(Duration.ofSeconds(10))
126-
.build();
127-
FailsafeExecutor<ApiHttpResponse<byte[]>> failsafeExecutor = Failsafe.with(timeout);
128-
129122
ProjectApiRoot apiRoot = ApiRootBuilder.of()
130123
.defaultClient(ClientCredentials.of()
131124
.withClientId("your-client-id")
132125
.withClientSecret("your-client-secret")
133126
.build(),
134127
ServiceRegion.GCP_EUROPE_WEST1)
135-
.addMiddleware((request, next) -> failsafeExecutor.getStageAsync(() -> next.apply(request)))
128+
.withRequestPolicies(
129+
policies -> policies.withAllOtherRequests(request -> request.withTimeout(Duration.ofSeconds(10))))
136130
.build("my-project");
137131
}
138132

@@ -401,8 +395,12 @@ public void retry() {
401395
ProjectApiRoot apiRoot = ApiRootBuilder.of()
402396
.defaultClient(ClientCredentials.of().withClientId("clientId").withClientSecret("clientSecret").build(),
403397
ServiceRegion.GCP_EUROPE_WEST1)
404-
.withPolicies(policies -> policies.withRetry(builder -> builder.maxRetries(5)
405-
.statusCodes(Arrays.asList(BAD_GATEWAY_502, SERVICE_UNAVAILABLE_503, GATEWAY_TIMEOUT_504))))
398+
.withRequestPolicies(
399+
policies -> policies
400+
.withAllOtherRequests(
401+
request -> request.withRetry(builder -> builder.maxRetries(5)
402+
.statusCodes(Arrays.asList(BAD_GATEWAY_502, SERVICE_UNAVAILABLE_503,
403+
GATEWAY_TIMEOUT_504)))))
406404
.build("my-project");
407405
}
408406

@@ -590,7 +588,8 @@ public void httpConcurrentLimitation() {
590588
public void queueConcurrentLimitation() {
591589
ApiRootBuilder.of()
592590
// ...
593-
.withPolicies(policies -> policies.withBulkhead(64, Duration.ofSeconds(10)))
591+
.withRequestPolicies(policies -> policies
592+
.withAllOtherRequests(request -> request.withBulkhead(64, Duration.ofSeconds(10))))
594593
.build();
595594
}
596595

commercetools/internal-docs/src/test/java/example/MigrationV2.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ public void retry() {
4747
.defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl(),
4848
ClientCredentials.of().withClientId("clientId").withClientSecret("clientSecret").build(),
4949
ServiceRegion.GCP_EUROPE_WEST1.getOAuthTokenUrl())
50-
.withPolicies(policies -> policies.withRetry(builder -> builder
51-
.statusCodes(Arrays.asList(BAD_GATEWAY_502, SERVICE_UNAVAILABLE_503, GATEWAY_TIMEOUT_504))))
50+
.withRequestPolicies(policies -> policies
51+
.withAllOtherRequests(request -> request.withRetry(builder -> builder.statusCodes(
52+
Arrays.asList(BAD_GATEWAY_502, SERVICE_UNAVAILABLE_503, GATEWAY_TIMEOUT_504)))))
5253
.build("projectKey");
5354

5455
final CategoryPagedQueryResponse body = projectClient.categories().get().executeBlocking().getBody();

rmf/rmf-java-base/src/main/java/io/vrap/rmf/base/client/ClientBuilder.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class ClientBuilder implements Builder<ApiHttpClient> {
4141
private Supplier<OAuthMiddleware> oAuthMiddleware;
4242
private Supplier<RetryRequestMiddleware> retryMiddleware;
4343
private PolicyBuilder policyBuilder;
44+
private RequestPolicyBuilder requestPolicyBuilder;
4445
private Supplier<PolicyMiddleware> policyMiddleware;
4546
private Supplier<QueueRequestMiddleware> queueMiddleware;
4647
private Supplier<Middleware> correlationIdMiddleware;
@@ -1275,6 +1276,24 @@ public ClientBuilder withPolicies(Function<PolicyBuilder, PolicyBuilder> fn) {
12751276
return this;
12761277
}
12771278

1279+
/**
1280+
* add middleware for safe handling of failed requests
1281+
* @param requestPolicyBuilder the policy builder
1282+
* @return ClientBuilder instance
1283+
*/
1284+
public ClientBuilder withRequestPolicies(RequestPolicyBuilder requestPolicyBuilder) {
1285+
this.requestPolicyBuilder = requestPolicyBuilder;
1286+
this.policyMiddleware = requestPolicyBuilder::build;
1287+
return this;
1288+
}
1289+
1290+
public ClientBuilder withRequestPolicies(Function<RequestPolicyBuilder, RequestPolicyBuilder> fn) {
1291+
this.requestPolicyBuilder = fn
1292+
.apply(Optional.ofNullable(requestPolicyBuilder).orElse(RequestPolicyBuilder.of()));
1293+
this.policyMiddleware = requestPolicyBuilder::build;
1294+
return this;
1295+
}
1296+
12781297
/**
12791298
* add middleware for safe handling of failed requests
12801299
* @param policyMiddleware {@link PolicyMiddleware} to be used

rmf/rmf-java-base/src/main/java/io/vrap/rmf/base/client/http/PolicyBuilder.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010

1111
import io.vrap.rmf.base.client.*;
1212

13-
import org.slf4j.Logger;
14-
import org.slf4j.LoggerFactory;
15-
1613
import dev.failsafe.*;
1714
import dev.failsafe.spi.Scheduler;
1815

@@ -21,6 +18,8 @@
2118
*
2219
* <p>The PolicyBuilder allows the combination of different policies for failing requests.</p>
2320
*
21+
* <p>In case you need different policies based on the request use the {@link RequestPolicyBuilder} instead</p>
22+
*
2423
* <p>The order of policies matters. For example applying a {@link #withTimeout(Duration) timeout} before
2524
* {@link #withRetry(RetryPolicyBuilder)} retry} will time out across all requests whereas applying a timeout after the retry count
2625
* against every single request even the retried ones.</p>
@@ -47,11 +46,6 @@
4746
*/
4847
public class PolicyBuilder {
4948

50-
static final String loggerName = ClientBuilder.COMMERCETOOLS + ".retry";
51-
52-
private static final InternalLogger logger = InternalLogger.getLogger(loggerName);
53-
private static final Logger classLogger = LoggerFactory.getLogger(PolicyMiddleware.class);
54-
5549
private final List<Policy<ApiHttpResponse<byte[]>>> policies;
5650

5751
private final Scheduler scheduler;
@@ -61,6 +55,10 @@ public PolicyBuilder() {
6155
this.scheduler = Scheduler.DEFAULT;
6256
}
6357

58+
List<Policy<ApiHttpResponse<byte[]>>> getPolicies() {
59+
return policies;
60+
}
61+
6462
public PolicyBuilder(List<Policy<ApiHttpResponse<byte[]>>> policies) {
6563
this.policies = policies;
6664
this.scheduler = Scheduler.DEFAULT;

rmf/rmf-java-base/src/main/java/io/vrap/rmf/base/client/http/PolicyMiddleware.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
package io.vrap.rmf.base.client.http;
33

44
import java.util.List;
5+
import java.util.Map;
56
import java.util.concurrent.ExecutorService;
67
import java.util.concurrent.ScheduledExecutorService;
8+
import java.util.function.Predicate;
79

10+
import io.vrap.rmf.base.client.ApiHttpRequest;
811
import io.vrap.rmf.base.client.ApiHttpResponse;
912

1013
import dev.failsafe.Policy;
@@ -28,4 +31,23 @@ public static PolicyMiddleware of(final ExecutorService scheduler,
2831
final List<Policy<ApiHttpResponse<byte[]>>> policies) {
2932
return new PolicyMiddlewareImpl(Scheduler.of(scheduler), policies);
3033
}
34+
35+
public static PolicyMiddleware of(
36+
final List<Map.Entry<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>>> policies,
37+
final Scheduler scheduler) {
38+
return new RequestPolicyMiddlewareImpl(scheduler, policies);
39+
}
40+
41+
public static PolicyMiddleware of(
42+
final List<Map.Entry<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>>> policies,
43+
final ScheduledExecutorService scheduler) {
44+
return new RequestPolicyMiddlewareImpl(Scheduler.of(scheduler), policies);
45+
}
46+
47+
public static PolicyMiddleware of(
48+
final List<Map.Entry<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>>> policies,
49+
final ExecutorService scheduler) {
50+
return new RequestPolicyMiddlewareImpl(Scheduler.of(scheduler), policies);
51+
}
52+
3153
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
2+
package io.vrap.rmf.base.client.http;
3+
4+
import java.time.Duration;
5+
import java.util.*;
6+
import java.util.concurrent.ExecutorService;
7+
import java.util.concurrent.ScheduledExecutorService;
8+
import java.util.function.Function;
9+
import java.util.function.Predicate;
10+
11+
import io.vrap.rmf.base.client.ApiHttpRequest;
12+
import io.vrap.rmf.base.client.ApiHttpResponse;
13+
14+
import dev.failsafe.Policy;
15+
import dev.failsafe.spi.Scheduler;
16+
17+
/**
18+
* <h2>RequestPolicyBuilder</h2>
19+
*
20+
* <p>The RequestPolicyBuilder allows the combination of different policies for failing requests and apply them to matching
21+
* requests.</p>
22+
*
23+
* <p>The order of policies matters. For example applying a {@link PolicyBuilder#withTimeout(Duration) timeout} before
24+
* {@link PolicyBuilder#withRetry(RetryPolicyBuilder)} retry} will time out across all requests whereas applying a timeout after the retry count
25+
* against every single request even the retried ones.</p>
26+
*
27+
* <h3 id="retry">Retry</h3>
28+
*
29+
* <h4>Retrying on HTTP status codes</h4>
30+
*
31+
* {@include.example io.vrap.rmf.base.client.http.RequestPolicyMiddlewareTest#retryConfigurationStatusCodes()}
32+
*
33+
* <h3>Retrying specific exceptions</h3>
34+
*
35+
* {@include.example io.vrap.rmf.base.client.http.RequestPolicyMiddlewareTest#retryConfigurationExceptions()}
36+
*
37+
* <h3 id="timeout">Timeout</h3>
38+
*
39+
* {@include.example io.vrap.rmf.base.client.http.RequestPolicyMiddlewareTest#timeoutConfiguration()}
40+
*
41+
* <h3 id="bulkhead">Bulkhead</h3>
42+
*
43+
* <p>Implementation of a Queue to limit the number of concurrent requests handled by the client</p>
44+
*
45+
* {@include.example io.vrap.rmf.base.client.http.RequestPolicyMiddlewareTest#queueConfiguration()}
46+
*/
47+
public class RequestPolicyBuilder {
48+
49+
private final Map<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>> policies;
50+
51+
private final Scheduler scheduler;
52+
53+
public RequestPolicyBuilder() {
54+
this.policies = new LinkedHashMap<>();
55+
this.scheduler = Scheduler.DEFAULT;
56+
}
57+
58+
public RequestPolicyBuilder(final Map<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>> policies) {
59+
this.policies = policies;
60+
this.scheduler = Scheduler.DEFAULT;
61+
}
62+
63+
public RequestPolicyBuilder(final Scheduler scheduler,
64+
final Map<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>> policies) {
65+
this.policies = policies;
66+
this.scheduler = scheduler;
67+
}
68+
69+
public RequestPolicyBuilder withScheduler(final ScheduledExecutorService scheduler) {
70+
return new RequestPolicyBuilder(Scheduler.of(scheduler), policies);
71+
}
72+
73+
public RequestPolicyBuilder withScheduler(final ExecutorService scheduler) {
74+
return new RequestPolicyBuilder(Scheduler.of(scheduler), policies);
75+
}
76+
77+
public RequestPolicyBuilder withScheduler(final Scheduler scheduler) {
78+
return new RequestPolicyBuilder(scheduler, policies);
79+
}
80+
81+
public RequestPolicyBuilder withRequestMatching(final Predicate<ApiHttpRequest> predicate,
82+
Function<PolicyBuilder, PolicyBuilder> fn) {
83+
Map<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>> policiesCopy = new LinkedHashMap<>(
84+
policies);
85+
policiesCopy.put(predicate, fn.apply(PolicyBuilder.of()).getPolicies());
86+
return new RequestPolicyBuilder(scheduler, policiesCopy);
87+
}
88+
89+
public RequestPolicyBuilder withAllOtherRequests(Function<PolicyBuilder, PolicyBuilder> fn) {
90+
Map<Predicate<ApiHttpRequest>, List<Policy<ApiHttpResponse<byte[]>>>> policiesCopy = new LinkedHashMap<>(
91+
policies);
92+
policiesCopy.put(apiHttpRequest -> true, fn.apply(PolicyBuilder.of()).getPolicies());
93+
return new RequestPolicyBuilder(scheduler, policiesCopy);
94+
}
95+
96+
public PolicyMiddleware build() {
97+
return PolicyMiddleware.of(new ArrayList<>(policies.entrySet()), scheduler);
98+
}
99+
100+
public static RequestPolicyBuilder of() {
101+
return new RequestPolicyBuilder();
102+
}
103+
}

0 commit comments

Comments
 (0)