Skip to content

Commit 4bf12e6

Browse files
authored
Merge pull request #218 from SentryMan/client
Change how Generated Clients are registered.
2 parents 71d2701 + 4e77fdd commit 4bf12e6

File tree

23 files changed

+624
-84
lines changed

23 files changed

+624
-84
lines changed

http-api/src/main/java/io/avaje/http/api/InstrumentServerContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* @Get
1919
* @InstrumentServerContext
2020
* void helloWorld(long id) {
21-
* Server resolver.currentRequest()
21+
* Context = resolver.currentRequest()
2222
* ...
2323
* }
2424
*
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.avaje.http.api.spi;
2+
3+
import static java.lang.annotation.ElementType.TYPE;
4+
import static java.lang.annotation.RetentionPolicy.CLASS;
5+
6+
import java.lang.annotation.*;
7+
8+
/**
9+
* For internal use, holds metadata on generated client interfaces for use by code generation (Java
10+
* annotation processing).
11+
*/
12+
@Target(TYPE)
13+
@Retention(CLASS)
14+
public @interface MetaData {
15+
16+
/** The generated HttpClient interfaces. */
17+
Class<?>[] value();
18+
}

http-api/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
exports io.avaje.http.api;
44
exports io.avaje.http.api.context;
5+
exports io.avaje.http.api.spi;
56
}

http-client/src/main/java/io/avaje/http/client/DHttpApi.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.http.client;
22

33
import io.avaje.applog.AppLog;
4+
import io.avaje.http.client.HttpClient.GeneratedComponent;
45

56
import java.util.HashMap;
67
import java.util.Map;
@@ -25,9 +26,14 @@ final class DHttpApi {
2526

2627
@SuppressWarnings("rawtypes")
2728
void init() {
28-
for (HttpApiProvider apiProvider : ServiceLoader.load(HttpApiProvider.class)) {
29+
for (final HttpApiProvider apiProvider : ServiceLoader.load(HttpApiProvider.class)) {
2930
addProvider(apiProvider);
3031
}
32+
33+
for (final GeneratedComponent apiProvider : ServiceLoader.load(GeneratedComponent.class)) {
34+
apiProvider.register(providerMap);
35+
}
36+
3137
log.log(DEBUG, "providers for {0}", providerMap.keySet());
3238
}
3339

http-client/src/main/java/io/avaje/http/client/DHttpClientContext.java

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,43 @@ public <T> T create(Class<T> clientInterface) {
5858
if (!clientInterface.isInterface()) {
5959
throw new IllegalArgumentException("API declarations must be interfaces.");
6060
}
61-
HttpApiProvider<T> apiProvider = DHttpApi.get(clientInterface);
61+
final HttpApiProvider<T> apiProvider = DHttpApi.get(clientInterface);
6262
if (apiProvider != null) {
6363
return apiProvider.provide(this);
6464
}
6565
try {
66-
Class<?> implementationClass = implementationClass(clientInterface);
67-
Constructor<?> constructor = implementationClass.getConstructor(HttpClientContext.class);
66+
final Class<?> implementationClass = implementationClass(clientInterface);
67+
final Constructor<?> constructor = implementationClass.getConstructor(HttpClient.class);
6868
return (T) constructor.newInstance(this);
69-
} catch (Exception e) {
70-
String cn = implementationClassName(clientInterface, "HttpClient");
69+
} catch (final Exception e) {
70+
return constructReflectively(clientInterface);
71+
}
72+
}
73+
74+
@SuppressWarnings("unchecked")
75+
private <T> T constructReflectively(Class<T> clientInterface) {
76+
try {
77+
final Class<?> implementationClass = implementationClass(clientInterface);
78+
final Constructor<?> constructor = implementationClass.getConstructor(HttpClientContext.class);
79+
return (T) constructor.newInstance(this);
80+
} catch (final Exception e) {
81+
final String cn = implementationClassName(clientInterface, "HttpClient");
7182
throw new IllegalStateException("Failed to create http client service " + cn, e);
7283
}
7384
}
7485

7586
private Class<?> implementationClass(Class<?> clientInterface) throws ClassNotFoundException {
7687
try {
7788
return Class.forName(implementationClassName(clientInterface, "HttpClient"));
78-
} catch (ClassNotFoundException e) {
89+
} catch (final ClassNotFoundException e) {
7990
// try the older generated client suffix
8091
return Class.forName(implementationClassName(clientInterface, "$HttpClient"));
8192
}
8293
}
8394

8495
private <T> String implementationClassName(Class<T> clientInterface, String suffix) {
85-
String packageName = clientInterface.getPackageName();
86-
String simpleName = clientInterface.getSimpleName();
96+
final String packageName = clientInterface.getPackageName();
97+
final String simpleName = clientInterface.getSimpleName();
8798
return packageName + ".httpclient." + simpleName + suffix;
8899
}
89100

@@ -202,7 +213,7 @@ public BodyContent readErrorContent(boolean responseAsBytes, HttpResponse<?> htt
202213
if (body instanceof String) {
203214
return new BodyContent(contentType, ((String) body).getBytes(StandardCharsets.UTF_8));
204215
}
205-
String type = (body == null) ? "null" : body.getClass().toString();
216+
final String type = (body == null) ? "null" : body.getClass().toString();
206217
throw new IllegalStateException("Unable to translate response body to bytes? Maybe use HttpResponse directly instead? Response body type: " + type);
207218
}
208219

@@ -212,7 +223,7 @@ public BodyContent readContent(HttpResponse<byte[]> httpResponse) {
212223
if (body != null && body.length > 0) {
213224
metricResBytes.add(body.length);
214225
}
215-
byte[] bodyBytes = decodeContent(httpResponse);
226+
final byte[] bodyBytes = decodeContent(httpResponse);
216227
final String contentType = getContentType(httpResponse);
217228
return new BodyContent(contentType, bodyBytes);
218229
}
@@ -227,21 +238,22 @@ String getContentEncoding(HttpResponse<?> httpResponse) {
227238

228239
@Override
229240
public byte[] decodeContent(String encoding, byte[] body) {
230-
if (encoding.equals("gzip")) {
241+
if ("gzip".equals(encoding)) {
231242
return GzipUtil.gzipDecode(body);
232243
}
233244
// todo: register decoders with context and use them
234245
return body;
235246
}
236247

248+
@Override
237249
public byte[] decodeContent(HttpResponse<byte[]> httpResponse) {
238-
String encoding = getContentEncoding(httpResponse);
250+
final String encoding = getContentEncoding(httpResponse);
239251
return encoding == null ? httpResponse.body() : decodeContent(encoding, httpResponse.body());
240252
}
241253

242254
String firstHeader(HttpHeaders headers, String... names) {
243255
final Map<String, List<String>> map = headers.map();
244-
for (String key : names) {
256+
for (final String key : names) {
245257
final List<String> values = map.get(key);
246258
if (values != null && !values.isEmpty()) {
247259
return values.get(0);
@@ -253,9 +265,9 @@ String firstHeader(HttpHeaders headers, String... names) {
253265
<T> HttpResponse<T> send(HttpRequest.Builder requestBuilder, HttpResponse.BodyHandler<T> bodyHandler) {
254266
try {
255267
return httpClient.send(requestBuilder.build(), bodyHandler);
256-
} catch (IOException e) {
268+
} catch (final IOException e) {
257269
throw new HttpException(499, e);
258-
} catch (InterruptedException e) {
270+
} catch (final InterruptedException e) {
259271
Thread.currentThread().interrupt();
260272
throw new HttpException(499, e);
261273
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
package io.avaje.http.client;
22

3+
import java.util.Map;
4+
35
/**
46
* Provides http client implementations for an interface.
57
*
68
* @param <T> The interface type
79
*/
10+
@FunctionalInterface
811
public interface HttpApiProvider<T> {
912

10-
/**
11-
* Return the interface type this API implements.
12-
*/
13-
Class<T> type();
13+
/** Return the interface type this API implements. */
14+
default Class<T> type() {
15+
throw new UnsupportedOperationException();
16+
}
1417

15-
/**
16-
* Return the provided implementation of the API.
17-
*/
18+
/** Return the provided implementation of the API. */
1819
T provide(HttpClient client);
19-
2020
}

http-client/src/main/java/io/avaje/http/client/HttpClient.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package io.avaje.http.client;
22

3-
import io.avaje.inject.BeanScope;
4-
5-
import javax.net.ssl.SSLContext;
6-
import javax.net.ssl.SSLParameters;
73
import java.net.Authenticator;
84
import java.net.CookieHandler;
95
import java.net.ProxySelector;
106
import java.time.Duration;
7+
import java.util.Map;
118
import java.util.concurrent.Executor;
129

10+
import javax.net.ssl.SSLContext;
11+
import javax.net.ssl.SSLParameters;
12+
13+
import io.avaje.inject.BeanScope;
14+
1315
/**
1416
* The HTTP client context that we use to build and process requests.
1517
*
@@ -356,4 +358,11 @@ interface State {
356358
interface Metrics extends HttpClientContext.Metrics {
357359

358360
}
361+
362+
/** Components register Generated Client interface Providers */
363+
@FunctionalInterface
364+
interface GeneratedComponent {
365+
366+
void register(Map<Class<?>, HttpApiProvider<?>> providerMap);
367+
}
359368
}

http-client/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module io.avaje.http.client {
22

33
uses io.avaje.http.client.HttpApiProvider;
4+
uses io.avaje.http.client.HttpClient.GeneratedComponent;
45

56
requires transitive java.net.http;
67
requires transitive io.avaje.applog;

http-generator-client/pom.xml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,59 @@
1111

1212
<properties>
1313
<java.version>11</java.version>
14+
<avaje.prisms.version>1.9</avaje.prisms.version>
1415
</properties>
15-
1616
<dependencies>
17-
17+
<dependency>
18+
<groupId>io.avaje</groupId>
19+
<artifactId>avaje-prisms</artifactId>
20+
<version>${avaje.prisms.version}</version>
21+
<optional>true</optional>
22+
<scope>provided</scope>
23+
</dependency>
1824
<dependency>
1925
<groupId>io.avaje</groupId>
2026
<artifactId>avaje-http-generator-core</artifactId>
2127
<version>${project.version}</version>
2228
</dependency>
2329

30+
<dependency>
31+
<groupId>io.avaje</groupId>
32+
<artifactId>avaje-http-client</artifactId>
33+
<scope>test</scope>
34+
<version>${project.version}</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>io.avaje</groupId>
38+
<artifactId>avaje-http-api</artifactId>
39+
<version>${project.version}</version>
40+
<optional>true</optional>
41+
<scope>provided</scope>
42+
</dependency>
43+
2444
</dependencies>
2545

2646
<build>
2747
<plugins>
2848
<plugin>
2949
<groupId>org.apache.maven.plugins</groupId>
3050
<artifactId>maven-compiler-plugin</artifactId>
31-
<version>3.10.1</version>
3251
<configuration>
33-
<source>11</source>
34-
<target>11</target>
35-
<!-- Turn off annotation processing for building -->
36-
<compilerArgument>-proc:none</compilerArgument>
52+
<annotationProcessorPaths>
53+
<path>
54+
<groupId>io.avaje</groupId>
55+
<artifactId>avaje-prisms</artifactId>
56+
<version>${avaje.prisms.version}</version>
57+
</path>
58+
</annotationProcessorPaths>
59+
</configuration>
60+
</plugin>
61+
<plugin>
62+
<groupId>org.apache.maven.plugins</groupId>
63+
<artifactId>maven-surefire-plugin</artifactId>
64+
<version>${maven-surefire-plugin.version}</version>
65+
<configuration>
66+
<useModulePath>false</useModulePath>
3767
</configuration>
3868
</plugin>
3969
</plugins>

0 commit comments

Comments
 (0)