Skip to content

Change how Generated Clients are registered. #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* @Get
* @InstrumentServerContext
* void helloWorld(long id) {
* Server resolver.currentRequest()
* Context = resolver.currentRequest()
* ...
* }
*
Expand Down
18 changes: 18 additions & 0 deletions http-api/src/main/java/io/avaje/http/api/spi/MetaData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.avaje.http.api.spi;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;

import java.lang.annotation.*;

/**
* For internal use, holds metadata on generated client interfaces for use by code generation (Java
* annotation processing).
*/
@Target(TYPE)
@Retention(CLASS)
public @interface MetaData {

/** The generated HttpClient interfaces. */
Class<?>[] value();
}
1 change: 1 addition & 0 deletions http-api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

exports io.avaje.http.api;
exports io.avaje.http.api.context;
exports io.avaje.http.api.spi;
}
8 changes: 7 additions & 1 deletion http-client/src/main/java/io/avaje/http/client/DHttpApi.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.avaje.http.client;

import io.avaje.applog.AppLog;
import io.avaje.http.client.HttpClient.GeneratedComponent;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -25,9 +26,14 @@ final class DHttpApi {

@SuppressWarnings("rawtypes")
void init() {
for (HttpApiProvider apiProvider : ServiceLoader.load(HttpApiProvider.class)) {
for (final HttpApiProvider apiProvider : ServiceLoader.load(HttpApiProvider.class)) {
addProvider(apiProvider);
}

for (final GeneratedComponent apiProvider : ServiceLoader.load(GeneratedComponent.class)) {
apiProvider.register(providerMap);
}

log.log(DEBUG, "providers for {0}", providerMap.keySet());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,43 @@ public <T> T create(Class<T> clientInterface) {
if (!clientInterface.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
HttpApiProvider<T> apiProvider = DHttpApi.get(clientInterface);
final HttpApiProvider<T> apiProvider = DHttpApi.get(clientInterface);
if (apiProvider != null) {
return apiProvider.provide(this);
}
try {
Class<?> implementationClass = implementationClass(clientInterface);
Constructor<?> constructor = implementationClass.getConstructor(HttpClientContext.class);
final Class<?> implementationClass = implementationClass(clientInterface);
final Constructor<?> constructor = implementationClass.getConstructor(HttpClient.class);
return (T) constructor.newInstance(this);
} catch (Exception e) {
String cn = implementationClassName(clientInterface, "HttpClient");
} catch (final Exception e) {
return constructReflectively(clientInterface);
}
}

@SuppressWarnings("unchecked")
private <T> T constructReflectively(Class<T> clientInterface) {
try {
final Class<?> implementationClass = implementationClass(clientInterface);
final Constructor<?> constructor = implementationClass.getConstructor(HttpClientContext.class);
return (T) constructor.newInstance(this);
} catch (final Exception e) {
final String cn = implementationClassName(clientInterface, "HttpClient");
throw new IllegalStateException("Failed to create http client service " + cn, e);
}
}

private Class<?> implementationClass(Class<?> clientInterface) throws ClassNotFoundException {
try {
return Class.forName(implementationClassName(clientInterface, "HttpClient"));
} catch (ClassNotFoundException e) {
} catch (final ClassNotFoundException e) {
// try the older generated client suffix
return Class.forName(implementationClassName(clientInterface, "$HttpClient"));
}
}

private <T> String implementationClassName(Class<T> clientInterface, String suffix) {
String packageName = clientInterface.getPackageName();
String simpleName = clientInterface.getSimpleName();
final String packageName = clientInterface.getPackageName();
final String simpleName = clientInterface.getSimpleName();
return packageName + ".httpclient." + simpleName + suffix;
}

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

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

@Override
public byte[] decodeContent(String encoding, byte[] body) {
if (encoding.equals("gzip")) {
if ("gzip".equals(encoding)) {
return GzipUtil.gzipDecode(body);
}
// todo: register decoders with context and use them
return body;
}

@Override
public byte[] decodeContent(HttpResponse<byte[]> httpResponse) {
String encoding = getContentEncoding(httpResponse);
final String encoding = getContentEncoding(httpResponse);
return encoding == null ? httpResponse.body() : decodeContent(encoding, httpResponse.body());
}

String firstHeader(HttpHeaders headers, String... names) {
final Map<String, List<String>> map = headers.map();
for (String key : names) {
for (final String key : names) {
final List<String> values = map.get(key);
if (values != null && !values.isEmpty()) {
return values.get(0);
Expand All @@ -253,9 +265,9 @@ String firstHeader(HttpHeaders headers, String... names) {
<T> HttpResponse<T> send(HttpRequest.Builder requestBuilder, HttpResponse.BodyHandler<T> bodyHandler) {
try {
return httpClient.send(requestBuilder.build(), bodyHandler);
} catch (IOException e) {
} catch (final IOException e) {
throw new HttpException(499, e);
} catch (InterruptedException e) {
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new HttpException(499, e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package io.avaje.http.client;

import java.util.Map;

/**
* Provides http client implementations for an interface.
*
* @param <T> The interface type
*/
@FunctionalInterface
public interface HttpApiProvider<T> {

/**
* Return the interface type this API implements.
*/
Class<T> type();
/** Return the interface type this API implements. */
default Class<T> type() {
throw new UnsupportedOperationException();
}

/**
* Return the provided implementation of the API.
*/
/** Return the provided implementation of the API. */
T provide(HttpClient client);

}
17 changes: 13 additions & 4 deletions http-client/src/main/java/io/avaje/http/client/HttpClient.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package io.avaje.http.client;

import io.avaje.inject.BeanScope;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.ProxySelector;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.Executor;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;

import io.avaje.inject.BeanScope;

/**
* The HTTP client context that we use to build and process requests.
*
Expand Down Expand Up @@ -356,4 +358,11 @@ interface State {
interface Metrics extends HttpClientContext.Metrics {

}

/** Components register Generated Client interface Providers */
@FunctionalInterface
interface GeneratedComponent {

void register(Map<Class<?>, HttpApiProvider<?>> providerMap);
}
}
1 change: 1 addition & 0 deletions http-client/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module io.avaje.http.client {

uses io.avaje.http.client.HttpApiProvider;
uses io.avaje.http.client.HttpClient.GeneratedComponent;

requires transitive java.net.http;
requires transitive io.avaje.applog;
Expand Down
44 changes: 37 additions & 7 deletions http-generator-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,59 @@

<properties>
<java.version>11</java.version>
<avaje.prisms.version>1.9</avaje.prisms.version>
</properties>

<dependencies>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-prisms</artifactId>
<version>${avaje.prisms.version}</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-http-generator-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-http-client</artifactId>
<scope>test</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-http-api</artifactId>
<version>${project.version}</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>11</source>
<target>11</target>
<!-- Turn off annotation processing for building -->
<compilerArgument>-proc:none</compilerArgument>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-prisms</artifactId>
<version>${avaje.prisms.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<useModulePath>false</useModulePath>
</configuration>
</plugin>
</plugins>
Expand Down
Loading