Repository: avaje/avaje-inject
Pull request #734 (2024-11-15 20:43:19)

Mark Generated Avaje HttpClient implementations as Closeable
We already know that they are autocloseable, must we really go through the ceremony of `@Bean(closeable=true)`?

- use http annotation prisms
- generated Avaje Http Client implementations are now registered as closeable if `HttpClient` is detected to be a subtype of AutoCloseable


Patch:
diff --git a/inject-generator/pom.xml b/inject-generator/pom.xml
index 4e247f26..acb45065 100644
--- a/inject-generator/pom.xml
+++ b/inject-generator/pom.xml
@@ -39,6 +39,15 @@
       <artifactId>avaje-spi-service</artifactId>
       <version>2.8</version>
     </dependency>
+    
+    <dependency>
+      <groupId>io.avaje</groupId>
+      <artifactId>avaje-http-api</artifactId>
+      <version>2.8</version>
+      <optional>true</optional>
+      <scope>provided</scope>
+    </dependency>
+    
     <!-- test dependencies -->
     <dependency>
       <groupId>io.avaje</groupId>
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java b/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java
index 4df599e2..2942541c 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java
@@ -18,10 +18,6 @@ final class Constants {
   static final String QUALIFIER = "jakarta.inject.Qualifier";
   static final String NAMED = "jakarta.inject.Named";
 
-  static final String PATH = "io.avaje.http.api.Path";
-  static final String CONTROLLER = "io.avaje.http.api.Controller";
-  static final String HTTP_GENERATED = "io.avaje.http.api.Generated";
-
   static final String AT_SINGLETON = "@Singleton";
   static final String AT_PROXY = "@Proxy";
   static final String AT_GENERATED = "@Generated(\"io.avaje.inject.generator\")";
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java b/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java
index cc865203..056e7100 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java
@@ -15,7 +15,7 @@ final class IncludeAnnotations {
 
   static {
     EXCLUDED_ANNOTATIONS.add(Constants.KOTLIN_METADATA);
-    EXCLUDED_ANNOTATIONS.add(Constants.PATH);
+    EXCLUDED_ANNOTATIONS.add(PathPrism.PRISM_TYPE);
   }
 
   /**
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java b/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
index 10afc6ef..673bdbae 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
@@ -34,7 +34,7 @@ import static io.avaje.inject.generator.ProcessingContext.*;
   AssistFactoryPrism.PRISM_TYPE,
   ComponentPrism.PRISM_TYPE,
   Constants.TESTSCOPE,
-  Constants.CONTROLLER,
+  ControllerPrism.PRISM_TYPE,
   ExternalPrism.PRISM_TYPE,
   FactoryPrism.PRISM_TYPE,
   ImportPrism.PRISM_TYPE,
@@ -150,7 +150,7 @@ public final class InjectProcessor extends AbstractProcessor {
 
     readImported(importedElements(roundEnv));
 
-    maybeElements(roundEnv, Constants.CONTROLLER).ifPresent(this::readBeans);
+    maybeElements(roundEnv, ControllerPrism.PRISM_TYPE).ifPresent(this::readBeans);
     maybeElements(roundEnv, ProxyPrism.PRISM_TYPE).ifPresent(this::readBeans);
     maybeElements(roundEnv, AssistFactoryPrism.PRISM_TYPE).ifPresent(this::readAssisted);
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
index 17cecb50..732e216a 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
@@ -3,6 +3,7 @@ package io.avaje.inject.generator;
 import static io.avaje.inject.generator.APContext.logWarn;
 import static io.avaje.inject.generator.APContext.types;
 import static io.avaje.inject.generator.ProcessingContext.asElement;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -50,10 +51,21 @@ final class TypeExtendsReader {
     this.baseTypeIsInterface = baseType.getKind() == ElementKind.INTERFACE;
     this.publicAccess = baseType.getModifiers().contains(Modifier.PUBLIC);
     this.proxyBean = proxyBean;
-    this.controller = hasAnnotation(Constants.CONTROLLER);
+    this.controller = ControllerPrism.isPresent(baseType);
+    this.closeable = closeableClient(baseType);
     this.autoProvide = autoProvide();
   }
 
+  /**
+   * generated Avaje Http Clients are autoCloseable on JDK 21+
+   */
+  private boolean closeableClient(TypeElement baseType) {
+    return ClientPrism.isPresent(baseType)
+      && APContext.typeElement("io.avaje.http.client.HttpClient").getInterfaces().stream()
+      .map(Object::toString)
+      .anyMatch(AutoCloseable.class.getCanonicalName()::equals);
+  }
+
   private boolean autoProvide() {
     return publicAccess
       && !controller
@@ -61,16 +73,6 @@ final class TypeExtendsReader {
       && !ProxyPrism.isPresent(baseType);
   }
 
-  private boolean hasAnnotation(String annotationFQN) {
-    for (final var m : baseType.getAnnotationMirrors()) {
-      final CharSequence mfqn = ((TypeElement) m.getAnnotationType().asElement()).getQualifiedName();
-      if (annotationFQN.contentEquals(mfqn)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   UType baseType() {
     return baseUType;
   }
@@ -124,7 +126,7 @@ final class TypeExtendsReader {
       // http controller, or request scoped controller via BeanFactory
       return List.of();
     }
-    if (hasAnnotation(Constants.HTTP_GENERATED)) {
+    if (HttpGeneratedPrism.isPresent(baseType)) {
       // http route
       return providesTypes.stream()
         .filter(ut -> ROUTE_TYPES.contains(ut.mainType()))
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java b/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java
index 8218a294..3401e29f 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java
@@ -31,6 +31,10 @@
 @GeneratePrism(Scope.class)
 @GeneratePrism(Secondary.class)
 @GeneratePrism(io.avaje.spi.ServiceProvider.class)
+@GeneratePrism(io.avaje.http.api.Client.class)
+@GeneratePrism(io.avaje.http.api.Controller.class)
+@GeneratePrism(io.avaje.http.api.Path.class)
+@GeneratePrism(name = "HttpGeneratedPrism", value = io.avaje.http.api.Generated.class)
 package io.avaje.inject.generator;
 
 import io.avaje.inject.*;
diff --git a/inject-generator/src/main/java/module-info.java b/inject-generator/src/main/java/module-info.java
index c931420c..4dbf08f8 100644
--- a/inject-generator/src/main/java/module-info.java
+++ b/inject-generator/src/main/java/module-info.java
@@ -5,6 +5,7 @@ module io.avaje.inject.generator {
   requires io.avaje.inject.aop;
   requires io.avaje.inject.events;
 
+  requires static io.avaje.http.api;
   requires static io.avaje.prism;
   requires static io.avaje.spi;
 


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/IncludeAnnotationsTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/IncludeAnnotationsTest.java
index 5ebf901b..48557ffd 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/IncludeAnnotationsTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/IncludeAnnotationsTest.java
@@ -34,7 +34,7 @@ public class IncludeAnnotationsTest {
     assertFalse(include(Constants.FACTORY));
     assertFalse(include(Constants.PRIMARY));
     assertFalse(include(Constants.SECONDARY));
-    assertFalse(include(Constants.PATH));
+    assertFalse(include(PathPrism.PRISM_TYPE));
   }
 
 }