Skip to content

Commit 43b56bc

Browse files
authored
#327: fix error handling for completable future (#328)
* #324: extended tests to cover default error handler * #328: improve JavaDoc
1 parent fadab9c commit 43b56bc

File tree

7 files changed

+219
-69
lines changed

7 files changed

+219
-69
lines changed

modules/http-client/src/main/java/com/devonfw/module/httpclient/common/impl/AbstractAsyncServiceHttpClient.java

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -50,30 +50,7 @@ protected <R> void doCall(ServiceClientInvocation<S> invocation, Consumer<R> res
5050
HttpRequest request = createRequest(invocation);
5151
CompletableFuture<HttpResponse<String>> future = this.client.getHttpClient().sendAsync(request,
5252
BodyHandlers.ofString());
53-
future.thenAccept(response -> handleResponse(response, startTime, invocation, resultHandler));
54-
}
55-
56-
@SuppressWarnings({ "rawtypes", "unchecked" })
57-
private void handleResponse(HttpResponse<?> response, long startTime, ServiceClientInvocation<S> invocation,
58-
Consumer resultHandler) {
59-
60-
Throwable error = null;
61-
String service = invocation.getServiceDescription(response.uri().toString());
62-
try {
63-
int statusCode = response.statusCode();
64-
if (statusCode >= 400) {
65-
error = createError(response, invocation, service);
66-
this.errorHandler.accept(error);
67-
} else {
68-
Object result = createResult(response, invocation);
69-
resultHandler.accept(result);
70-
}
71-
} catch (Throwable t) {
72-
this.errorHandler.accept(t);
73-
error = t;
74-
} finally {
75-
ServiceClientPerformanceLogger.log(startTime, service, response.statusCode(), error);
76-
}
53+
future.thenAccept(response -> handleResponse(response, startTime, invocation, resultHandler, getErrorHandler()));
7754
}
7855

7956
private Throwable createError(HttpResponse<?> response, ServiceClientInvocation<S> invocation, String service) {
@@ -107,7 +84,8 @@ protected Object handleUnsupportedBody(Object body) {
10784
/**
10885
* @param response the received {@link HttpResponse}.
10986
* @param invocation the {@link ServiceClientInvocation}.
110-
* @return the unmarshalled result object from the response body or {@code null} if no body was found or return type is {@code void}.
87+
* @return the unmarshalled result object from the response body or {@code null} if no body was found or return type
88+
* is {@code void}.
11189
* @throws IllegalStateException if the unmarshalling of the result failed.
11290
* @throws UnsupportedOperationException if the body type is not supported.
11391
*/
@@ -126,27 +104,30 @@ protected <R> CompletableFuture<R> doCall(ServiceClientInvocation<S> invocation)
126104
HttpRequest request = createRequest(invocation);
127105
CompletableFuture<HttpResponse<String>> future = this.client.getHttpClient().sendAsync(request,
128106
BodyHandlers.ofString());
129-
return future.thenApplyAsync(response -> handleResponse(response, startTime, invocation));
130-
107+
return future.thenApplyAsync(
108+
response -> handleResponse(response, startTime, invocation, null, ErrorHandlerThrowImmediately.get()));
131109
}
132110

133-
@SuppressWarnings({ "rawtypes", "unchecked" })
134-
private <R> R handleResponse(HttpResponse<?> response, long startTime, ServiceClientInvocation<S> invocation) {
111+
@SuppressWarnings({ "unchecked" })
112+
private <R> R handleResponse(HttpResponse<?> response, long startTime, ServiceClientInvocation<S> invocation,
113+
Consumer<R> resultHandler, Consumer<Throwable> errorHandler) {
135114

136115
Throwable error = null;
137116
String service = invocation.getServiceDescription(response.uri().toString());
138117
try {
139118
int statusCode = response.statusCode();
140119
if (statusCode >= 400) {
141120
error = createError(response, invocation, service);
142-
this.errorHandler.accept(error);
121+
errorHandler.accept(error);
143122
} else {
144-
145-
Object result = createResult(response, invocation);
146-
return (R) result;
123+
R result = (R) createResult(response, invocation);
124+
if (resultHandler != null) {
125+
resultHandler.accept(result);
126+
}
127+
return result;
147128
}
148129
} catch (Throwable t) {
149-
this.errorHandler.accept(t);
130+
errorHandler.accept(t);
150131
error = t;
151132
} finally {
152133
ServiceClientPerformanceLogger.log(startTime, service, response.statusCode(), error);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.devonfw.module.httpclient.common.impl;
2+
3+
import java.util.function.Consumer;
4+
5+
/**
6+
* {@link Consumer} used as error-handling to immediately throw received exceptions.
7+
*
8+
* @since 2020.12.001
9+
*/
10+
class ErrorHandlerThrowImmediately implements Consumer<Throwable> {
11+
12+
private static final ErrorHandlerThrowImmediately INSTANCE = new ErrorHandlerThrowImmediately();
13+
14+
@Override
15+
public void accept(Throwable t) {
16+
17+
if (t instanceof RuntimeException) {
18+
throw (RuntimeException) t;
19+
} else {
20+
throw new IllegalStateException(t);
21+
}
22+
}
23+
24+
/**
25+
* @return the singleton instance.
26+
*/
27+
public static ErrorHandlerThrowImmediately get() {
28+
29+
return INSTANCE;
30+
}
31+
32+
}

modules/service/src/main/java/com/devonfw/module/service/common/api/client/AsyncServiceClient.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
/**
77
* An {@link AsyncServiceClient} wraps a {@link #get() service client} allowing to do {@link #call(Object, Consumer)
8-
* asynchronous calls}. The following code is a simple example how to do an asynchronous service invocation:
8+
* asynchronous calls}. Only {@link ServiceClientFactory} is thread-safe but not instances of this interface so please
9+
* do not share clients between threads. The following code is a simple example how to do an asynchronous service
10+
* invocation:
911
*
1012
* <pre>
1113
* {@link ServiceClientFactory} factory = getServiceClientFactory();
@@ -39,7 +41,13 @@ public interface AsyncServiceClient<S> {
3941

4042
/**
4143
* @return the the {@link Consumer} callback {@link Consumer#accept(Object) accepting} a potential exception that
42-
* occurred during sending the request or receiving the response.
44+
* occurred during sending the request or receiving the response. <b>ATTENTION:</b> The error handler is only
45+
* used to report errors for {@link Consumer} usage (via {@link #call(Object, Consumer)} and
46+
* {@link #callVoid(Runnable, Consumer)}). When using {@link CompletableFuture} instead, errors will be
47+
* reported via the {@link CompletableFuture} itself. Please also note that due to design of
48+
* {@link CompletableFuture} the errors (like
49+
* {@link net.sf.mmm.util.exception.api.ServiceInvocationFailedException}) will be wrapped in a
50+
* {@link java.util.concurrent.ExecutionException}.
4351
*/
4452
Consumer<Throwable> getErrorHandler();
4553

@@ -53,23 +61,34 @@ public interface AsyncServiceClient<S> {
5361
* @param <R> type of the result of the service operation to call.
5462
* @param result the dummy result returned by the operation invoked on the actual {@link #get() service client}.
5563
* @param resultHandler the {@link Consumer} callback {@link Consumer#accept(Object) accepting} the actual result
56-
* asynchronously when available.
64+
* asynchronously when available. Errors will reported via {@link #getErrorHandler() errorHandler}.
5765
*/
5866
<R> void call(R result, Consumer<R> resultHandler);
5967

6068
/**
6169
* @param serviceInvoker the lambda function calling a void operation on the actual {@link #get() service client} -
6270
* e.g. {@code () -> client.get().myVoidFunction()}.
6371
* @param resultHandler the {@link Consumer} callback {@link Consumer#accept(Object) accepting} the actual result
64-
* asynchronously when available. May be {@code null} for fire and forget.
72+
* asynchronously when available. May be {@code null} for fire and forget. Errors will reported via
73+
* {@link #getErrorHandler() errorHandler}.
6574
*/
6675
void callVoid(Runnable serviceInvoker, Consumer<Void> resultHandler);
6776

6877
/**
6978
* @param <R> type of the result of the service operation to call.
7079
* @param result the dummy result returned by the operation invoked on the actual {@link #get() service client}.
71-
* @return a {@link CompletableFuture} to receive the result asynchronously.
80+
* @return a {@link CompletableFuture} to receive the result asynchronously. Also errors will reported via this
81+
* {@link CompletableFuture} - see {@link #getErrorHandler()} for details.
7282
*/
7383
<R> CompletableFuture<R> call(R result);
7484

85+
/**
86+
* @param serviceInvoker the lambda function calling a void operation on the actual {@link #get() service client} -
87+
* e.g. {@code () -> client.get().myVoidFunction()}.
88+
* @return a {@link CompletableFuture} to receive the result asynchronously. Also errors will reported via this
89+
* {@link CompletableFuture} - see {@link #getErrorHandler()} for details.
90+
* @see #callVoid(Runnable, Consumer)
91+
*/
92+
CompletableFuture<Void> callVoid(Runnable serviceInvoker);
93+
7594
}

modules/service/src/main/java/com/devonfw/module/service/common/api/client/ServiceClientFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
* </ul>
3535
* All these aspects can be configured via spring and customized with own implementations.
3636
*
37+
* <b>ATTENTION:</b> This {@link ServiceClientFactory} is thread-safe. However, the client objects returned by it are
38+
* not thread-safe and shall not be reused or shared between threads.
39+
*
3740
* @since 3.0.0
3841
*/
3942
public interface ServiceClientFactory {

modules/service/src/main/java/com/devonfw/module/service/common/base/client/async/AbstractAsyncServiceClient.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public abstract class AbstractAsyncServiceClient<S> implements AsyncServiceClien
2828
private final ServiceClientStub<S> stub;
2929

3030
/** The {@link #setErrorHandler(Consumer)} */
31-
protected Consumer<Throwable> errorHandler;
31+
private Consumer<Throwable> errorHandler;
3232

3333
/** The most recent invocation. */
3434
private ServiceClientInvocation<S> invocation;
@@ -87,6 +87,13 @@ public void callVoid(Runnable serviceInvoker, Consumer<Void> resultHandler) {
8787
}
8888
}
8989

90+
@Override
91+
public CompletableFuture<Void> callVoid(Runnable serviceInvoker) {
92+
93+
serviceInvoker.run();
94+
return call(null);
95+
}
96+
9097
private ServiceClientInvocation<S> getInvocation() {
9198

9299
this.invocation = this.stub.getInvocation();
@@ -125,19 +132,17 @@ private static int getLength(Object[] parameters) {
125132
return parameters.length;
126133
}
127134

128-
@SuppressWarnings("unchecked")
129135
@Override
130136
public <R> CompletableFuture<R> call(R result) {
131137

132-
ServiceClientInvocation<S> invocation = getInvocation();
133-
return doCall(invocation);
138+
return doCall(getInvocation());
134139
}
135140

136141
/**
137142
* @param <R> type of the return/result type.
138-
* @param invocation the {@link ServiceClientInvocation}.
143+
* @param serviceInvocation the {@link ServiceClientInvocation}.
139144
* @return a {@link CompletableFuture} to receive the result asynchronously.
140145
* @see #call(Object)
141146
*/
142-
protected abstract <R> CompletableFuture<R> doCall(ServiceClientInvocation<S> invocation);
147+
protected abstract <R> CompletableFuture<R> doCall(ServiceClientInvocation<S> serviceInvocation);
143148
}

0 commit comments

Comments
 (0)