diff --git a/blackbox-test-inject/src/main/java/io/ebean/Database.java b/blackbox-test-inject/src/main/java/io/ebean/Database.java new file mode 100644 index 000000000..47416c4f3 --- /dev/null +++ b/blackbox-test-inject/src/main/java/io/ebean/Database.java @@ -0,0 +1,15 @@ +package io.ebean; + +/** + * Simulate the Ebean io.ebean.Database interface with a shutdown() method. + *

+ * For "Graceful Shutdown" we desire this to be shutdown last. + */ +public interface Database { + + /** Shutdown including underlying DataSources. We want this to occur LAST. */ + void shutdown(); + + /** Test only method */ + boolean isShutdown(); +} diff --git a/blackbox-test-inject/src/main/java/io/ebean/MyDatabase.java b/blackbox-test-inject/src/main/java/io/ebean/MyDatabase.java new file mode 100644 index 000000000..6d4c10310 --- /dev/null +++ b/blackbox-test-inject/src/main/java/io/ebean/MyDatabase.java @@ -0,0 +1,16 @@ +package io.ebean; + +public class MyDatabase implements Database { + + public boolean shutdownCalled = false; + + @Override + public void shutdown() { + shutdownCalled = true; + } + + @Override + public boolean isShutdown() { + return shutdownCalled; + } +} diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java b/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java index 57631e304..b8cd7cbfe 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java @@ -1,6 +1,8 @@ package org.example.myapp.config; import io.avaje.inject.*; +import io.ebean.Database; +import io.ebean.MyDatabase; import jakarta.inject.Inject; import jakarta.inject.Named; import org.example.myapp.HelloData; @@ -14,6 +16,12 @@ public class AppConfig { public static AtomicBoolean BEAN_AUTO_CLOSED = new AtomicBoolean(); + /** Because this is a io.ebean.Database, we know it should be shutdown() last */ + @Bean // Effectively default to @Bean(destroyMethod = "shutdown", destroyPriority = Integer.MAX_VALUE) + Database database() { + return new MyDatabase(); + } + @PreDestroy(priority = 999) void close() { MyDestroyOrder.add("AppConfig"); diff --git a/blackbox-test-inject/src/test/java/org/example/myapp/DefaultDestroyTest.java b/blackbox-test-inject/src/test/java/org/example/myapp/DefaultDestroyTest.java new file mode 100644 index 000000000..9e3168662 --- /dev/null +++ b/blackbox-test-inject/src/test/java/org/example/myapp/DefaultDestroyTest.java @@ -0,0 +1,22 @@ +package org.example.myapp; + +import io.avaje.inject.BeanScope; +import io.ebean.Database; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class DefaultDestroyTest { + + @Test + void expect_ebeanDatabase_hasShutdownByDefault() { + try (BeanScope beanScope = BeanScope.builder().build()) { + Database database = beanScope.get(Database.class); + + assertThat(database.isShutdown()).isFalse(); + beanScope.close(); + + assertThat(database.isShutdown()).isTrue(); + } + } +} diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java index cc4119270..dfc31234e 100644 --- a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java +++ b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java @@ -3,10 +3,7 @@ import javax.lang.model.element.*; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import static io.avaje.inject.generator.Constants.CONDITIONAL_DEPENDENCY; import static io.avaje.inject.generator.ProcessingContext.asElement; @@ -15,6 +12,13 @@ final class MethodReader { private static final String CODE_COMMENT_BUILD_FACTORYBEAN = " /**\n * Create and register %s via factory bean method %s#%s().\n */"; + private static final Map DEFAULT_DESTROY_METHODS = Map.of( + "io.ebean.Database", "shutdown" + ); + private static final Map DEFAULT_DESTROY_PRIORITY = Map.of( + "io.ebean.Database", Integer.MAX_VALUE + ); + private final TypeExtendsInjection factory; private final ExecutableElement element; private final String factoryType; @@ -85,8 +89,8 @@ final class MethodReader { this.factoryShortName = Util.shortName(factoryType); this.isVoid = Util.isVoid(mainType); String initMethod = bean == null ? null : bean.initMethod(); - String destroyMethod = bean == null ? null : bean.destroyMethod(); - this.destroyPriority = bean == null ? null : bean.destroyPriority(); + String destroyMethod = bean == null ? null : defaultDestroyMethod(bean.destroyMethod()); + this.destroyPriority = bean == null ? null : defaultDestroyPriority(bean.destroyPriority()); this.beanCloseable = bean != null && bean.autoCloseable(); // for multiRegister we desire a qualifier name such that builder.isBeanAbsent() uses it and allows // other beans of the same type to also register, otherwise it defaults to slightly confusing behaviour @@ -113,6 +117,20 @@ final class MethodReader { } } + private String defaultDestroyMethod(String destroyMethod) { + if (!destroyMethod.isEmpty()) { + return destroyMethod; + } + return DEFAULT_DESTROY_METHODS.get(returnTypeRaw); + } + + private Integer defaultDestroyPriority(Integer destroyPriority) { + if (destroyPriority != null && destroyPriority != 1000) { + return destroyPriority; + } + return DEFAULT_DESTROY_PRIORITY.get(returnTypeRaw); + } + @Override public String toString() { return "MethodReader{" +