Skip to content

Commit 8e0c376

Browse files
committed
Redefine Jakarta module.
1 parent 0798632 commit 8e0c376

11 files changed

Lines changed: 1197 additions & 228 deletions

File tree

httpclient5-jakarta-rest-client/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@
5050
<groupId>jakarta.ws.rs</groupId>
5151
<artifactId>jakarta.ws.rs-api</artifactId>
5252
</dependency>
53+
<dependency>
54+
<groupId>org.apache.httpcomponents.core5</groupId>
55+
<artifactId>httpcore5-jackson2</artifactId>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.fasterxml.jackson.core</groupId>
59+
<artifactId>jackson-databind</artifactId>
60+
</dependency>
5361
<dependency>
5462
<groupId>org.junit.jupiter</groupId>
5563
<artifactId>junit-jupiter</artifactId>

httpclient5-jakarta-rest-client/src/main/java/org/apache/hc/client5/http/rest/ClientResourceMethod.java

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,12 @@ static final class ParamInfo {
5959

6060
private final ParamSource source;
6161
private final String name;
62-
private final Class<?> type;
6362
private final String defaultValue;
6463

6564
ParamInfo(final ParamSource paramSource, final String paramName,
66-
final Class<?> paramType, final String defValue) {
65+
final String defValue) {
6766
this.source = paramSource;
6867
this.name = paramName;
69-
this.type = paramType;
7068
this.defaultValue = defValue;
7169
}
7270

@@ -78,10 +76,6 @@ String getName() {
7876
return name;
7977
}
8078

81-
Class<?> getType() {
82-
return type;
83-
}
84-
8579
String getDefaultValue() {
8680
return defaultValue;
8781
}
@@ -196,8 +190,7 @@ static List<ClientResourceMethod> scan(final Class<?> iface) {
196190

197191
final ParamInfo[] params = scanParameters(m);
198192
validatePathParams(m, combinedPath, params);
199-
validateReturnType(m);
200-
validateBodyTypes(m, params);
193+
validateConsumes(m, cons, params);
201194
final String strippedPath = stripRegex(combinedPath);
202195
result.add(new ClientResourceMethod(m, verb, strippedPath, prod, cons, params));
203196
}
@@ -215,12 +208,11 @@ private static String resolveHttpMethod(final Method m) {
215208
}
216209

217210
private static ParamInfo[] scanParameters(final Method m) {
218-
final Class<?>[] types = m.getParameterTypes();
219211
final Annotation[][] annotations = m.getParameterAnnotations();
220-
final ParamInfo[] result = new ParamInfo[types.length];
212+
final ParamInfo[] result = new ParamInfo[annotations.length];
221213
int bodyCount = 0;
222-
for (int i = 0; i < types.length; i++) {
223-
result[i] = resolveParam(types[i], annotations[i]);
214+
for (int i = 0; i < annotations.length; i++) {
215+
result[i] = resolveParam(annotations[i]);
224216
if (result[i].getSource() == ParamSource.BODY) {
225217
bodyCount++;
226218
}
@@ -233,8 +225,7 @@ private static ParamInfo[] scanParameters(final Method m) {
233225
return result;
234226
}
235227

236-
private static ParamInfo resolveParam(final Class<?> type,
237-
final Annotation[] annotations) {
228+
private static ParamInfo resolveParam(final Annotation[] annotations) {
238229
String defVal = null;
239230
for (final Annotation a : annotations) {
240231
if (a instanceof DefaultValue) {
@@ -243,16 +234,16 @@ private static ParamInfo resolveParam(final Class<?> type,
243234
}
244235
for (final Annotation a : annotations) {
245236
if (a instanceof PathParam) {
246-
return new ParamInfo(ParamSource.PATH, ((PathParam) a).value(), type, defVal);
237+
return new ParamInfo(ParamSource.PATH, ((PathParam) a).value(), defVal);
247238
}
248239
if (a instanceof QueryParam) {
249-
return new ParamInfo(ParamSource.QUERY, ((QueryParam) a).value(), type, defVal);
240+
return new ParamInfo(ParamSource.QUERY, ((QueryParam) a).value(), defVal);
250241
}
251242
if (a instanceof HeaderParam) {
252-
return new ParamInfo(ParamSource.HEADER, ((HeaderParam) a).value(), type, defVal);
243+
return new ParamInfo(ParamSource.HEADER, ((HeaderParam) a).value(), defVal);
253244
}
254245
}
255-
return new ParamInfo(ParamSource.BODY, null, type, null);
246+
return new ParamInfo(ParamSource.BODY, null, null);
256247
}
257248

258249
private static void validatePathParams(final Method m, final String path,
@@ -280,27 +271,17 @@ private static void validatePathParams(final Method m, final String path,
280271
}
281272
}
282273

283-
private static void validateReturnType(final Method m) {
284-
final Class<?> rt = m.getReturnType();
285-
if (rt != void.class && rt != Void.class
286-
&& rt != String.class && rt != byte[].class) {
287-
throw new IllegalStateException("Method " + m.getName()
288-
+ ": return type " + rt.getName() + " is not supported;"
289-
+ " only void, String and byte[] are allowed");
274+
private static void validateConsumes(final Method m,
275+
final String[] consumes,
276+
final ParamInfo[] params) {
277+
if (consumes.length <= 1) {
278+
return;
290279
}
291-
}
292-
293-
private static void validateBodyTypes(final Method m,
294-
final ParamInfo[] params) {
295280
for (final ParamInfo pi : params) {
296281
if (pi.getSource() == ParamSource.BODY) {
297-
final Class<?> bt = pi.getType();
298-
if (bt != String.class && bt != byte[].class) {
299-
throw new IllegalStateException("Method " + m.getName()
300-
+ ": body parameter type " + bt.getName()
301-
+ " is not supported;"
302-
+ " only String and byte[] are allowed");
303-
}
282+
throw new IllegalStateException("Method " + m.getName()
283+
+ " has a request body and multiple @Consumes"
284+
+ " values; exactly one is required");
304285
}
305286
}
306287
}

httpclient5-jakarta-rest-client/src/main/java/org/apache/hc/client5/http/rest/RestClientBuilder.java

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,37 +33,43 @@
3333
import java.util.List;
3434
import java.util.Map;
3535

36-
import org.apache.hc.client5.http.classic.HttpClient;
36+
import com.fasterxml.jackson.databind.ObjectMapper;
37+
38+
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
3739
import org.apache.hc.core5.util.Args;
3840

3941
/**
4042
* Builds type-safe REST client proxies from Jakarta REST annotated interfaces. The proxy
41-
* translates each method call into an HTTP request executed through the classic
42-
* {@link HttpClient} transport.
43+
* translates each method call into an HTTP request executed through the async
44+
* {@link CloseableHttpAsyncClient} transport, which supports both HTTP/1.1 and HTTP/2.
4345
*
4446
* <p>Minimal usage:</p>
4547
* <pre>
46-
* try (CloseableHttpClient client = HttpClients.createDefault()) {
48+
* try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
49+
* client.start();
4750
* UserApi api = RestClientBuilder.newBuilder()
48-
* .baseUri("http://api.example.com")
51+
* .baseUri("<a href="http://api.example.com">...</a>")
4952
* .httpClient(client)
5053
* .build(UserApi.class);
5154
* String json = api.getUser(42);
5255
* }
5356
* </pre>
5457
*
5558
* <p>Both {@code baseUri} and {@code httpClient} are required. The caller owns the
56-
* client lifecycle.</p>
59+
* client lifecycle, including the call to {@code start()} before use.</p>
5760
*
58-
* <p>Methods may return {@code String}, {@code byte[]}, or {@code void}. Non-2xx
59-
* responses throw {@link RestClientResponseException}.</p>
61+
* <p>Methods may return {@code String}, {@code byte[]}, {@code void}, or any type
62+
* deserializable by the configured Jackson {@link ObjectMapper}. Request bodies may be
63+
* {@code String}, {@code byte[]}, or any type serializable by the ObjectMapper.
64+
* Non-2xx responses throw {@link RestClientResponseException}.</p>
6065
*
6166
* @since 5.7
6267
*/
6368
public final class RestClientBuilder {
6469

6570
private URI baseUri;
66-
private HttpClient httpClient;
71+
private CloseableHttpAsyncClient httpClient;
72+
private ObjectMapper objectMapper;
6773

6874
private RestClientBuilder() {
6975
}
@@ -102,21 +108,36 @@ public RestClientBuilder baseUri(final URI uri) {
102108
}
103109

104110
/**
105-
* Sets the {@link HttpClient} to use for requests. The caller owns the client
106-
* lifecycle.
111+
* Sets the async HTTP client to use for requests. The caller owns the client
112+
* lifecycle, including the call to {@link CloseableHttpAsyncClient#start()}.
107113
*
108-
* @param client the HTTP client, must not be {@code null}.
114+
* @param client the async HTTP client, must not be {@code null}.
109115
* @return this builder for chaining.
116+
* @since 5.7
110117
*/
111-
public RestClientBuilder httpClient(final HttpClient client) {
118+
public RestClientBuilder httpClient(final CloseableHttpAsyncClient client) {
112119
Args.notNull(client, "HTTP client");
113120
this.httpClient = client;
114121
return this;
115122
}
116123

124+
/**
125+
* Sets the Jackson {@link ObjectMapper} for JSON serialization and deserialization.
126+
* If not set, a default ObjectMapper is used.
127+
*
128+
* @param mapper the object mapper, must not be {@code null}.
129+
* @return this builder for chaining.
130+
* @since 5.7
131+
*/
132+
public RestClientBuilder objectMapper(final ObjectMapper mapper) {
133+
Args.notNull(mapper, "Object mapper");
134+
this.objectMapper = mapper;
135+
return this;
136+
}
137+
117138
/**
118139
* Scans the given interface for Jakarta REST annotations and creates a proxy that
119-
* implements it by dispatching HTTP requests through the configured client.
140+
* implements it by dispatching HTTP requests through the configured async client.
120141
*
121142
* @param <T> the interface type.
122143
* @param iface the Jakarta REST annotated interface class.
@@ -140,18 +161,22 @@ public <T> T build(final Class<T> iface) {
140161

141162
final List<ClientResourceMethod> methods = ClientResourceMethod.scan(iface);
142163
if (methods.isEmpty()) {
143-
throw new IllegalStateException("No Jakarta REST methods found on " + iface.getName());
164+
throw new IllegalStateException(
165+
"No Jakarta REST methods found on " + iface.getName());
144166
}
145167
final Map<Method, ClientResourceMethod> methodMap =
146168
new HashMap<>(methods.size());
147169
for (final ClientResourceMethod rm : methods) {
148170
methodMap.put(rm.getMethod(), rm);
149171
}
150172

173+
final ObjectMapper mapper = objectMapper != null
174+
? objectMapper : new ObjectMapper();
175+
151176
return (T) Proxy.newProxyInstance(
152177
iface.getClassLoader(),
153178
new Class<?>[]{iface},
154-
new RestInvocationHandler(httpClient, baseUri, methodMap));
179+
new RestInvocationHandler(httpClient, baseUri, methodMap, mapper));
155180
}
156181

157182
}

0 commit comments

Comments
 (0)