From 3db2a69312c5c479cbe2ed75ae1bf183d95c5180 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Thu, 12 Dec 2019 01:33:27 +1300 Subject: [PATCH] #1885 - ENH: Add ModuleInfoLoader ... to support automatic registration of entity classes (via generated code using querybean generator) --- src/main/java/io/ebean/config/ModuleInfo.java | 21 ++++++++ .../io/ebean/config/ModuleInfoLoader.java | 19 +++++++ .../java/io/ebean/config/ServerConfig.java | 42 +++++++++++---- .../server/core/DefaultContainer.java | 53 ++++++++++++------- .../io/ebean/config/ServerConfigTest.java | 4 ++ 5 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 src/main/java/io/ebean/config/ModuleInfo.java create mode 100644 src/main/java/io/ebean/config/ModuleInfoLoader.java diff --git a/src/main/java/io/ebean/config/ModuleInfo.java b/src/main/java/io/ebean/config/ModuleInfo.java new file mode 100644 index 000000000..b4e6b95a3 --- /dev/null +++ b/src/main/java/io/ebean/config/ModuleInfo.java @@ -0,0 +1,21 @@ +package io.ebean.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used by generated code to hold entity classes to register with Ebean. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ModuleInfo { + + /** + * Returns the entity classes with db name prefix. + *

+ * The db name prefix is added to entity classes for non default databases. + */ + String[] entities() default {}; +} diff --git a/src/main/java/io/ebean/config/ModuleInfoLoader.java b/src/main/java/io/ebean/config/ModuleInfoLoader.java new file mode 100644 index 000000000..d32f80630 --- /dev/null +++ b/src/main/java/io/ebean/config/ModuleInfoLoader.java @@ -0,0 +1,19 @@ +package io.ebean.config; + +import java.util.List; + +/** + * Loads and returns entity classes to register with Ebean databases. + */ +public interface ModuleInfoLoader { + + /** + * Return the entity classes to register with the default DB. + */ + List> entityClasses(); + + /** + * Return entity classes to register for a named DB (not default DB). + */ + List> entityClassesFor(String dbName); +} diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java index 625c6e3dd..53127965d 100644 --- a/src/main/java/io/ebean/config/ServerConfig.java +++ b/src/main/java/io/ebean/config/ServerConfig.java @@ -132,6 +132,15 @@ public class ServerConfig { private TenantCatalogProvider tenantCatalogProvider; + /** + * When true will load entity classes via ModuleInfoLoader. + *

+ * NB: ModuleInfoLoader implementations are generated by querybean generator. + * Having this on and registering entity classes means we don't need to manually + * write that code or use classpath scanning to find entity classes. + */ + private boolean loadModuleInfo; + /** * List of interesting classes such as entities, embedded, ScalarTypes, * Listeners, Finders, Controllers etc. @@ -2276,12 +2285,18 @@ public class ServerConfig { * server. */ public void addClass(Class cls) { - if (classes == null) { - classes = new ArrayList<>(); - } classes.add(cls); } + /** + * Register all the classes (typically entity classes). + */ + public void addAll(List> classList) { + if (classList != null && !classList.isEmpty()) { + classes.addAll(classList); + } + } + /** * Add a package to search for entities via class path search. *

@@ -2289,9 +2304,6 @@ public class ServerConfig { *

*/ public void addPackage(String packageName) { - if (packages == null) { - packages = new ArrayList<>(); - } packages.add(packageName); } @@ -2838,6 +2850,7 @@ public class ServerConfig { } loadDocStoreSettings(p); + loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo); maxCallStack = p.getInt("maxCallStack", maxCallStack); dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown); dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions); @@ -2962,20 +2975,20 @@ public class ServerConfig { return classes; } - List> classes = new ArrayList<>(); + List> classList = new ArrayList<>(); String[] split = StringHelper.splitNames(classNames); for (String cn : split) { if (!"class".equalsIgnoreCase(cn)) { try { - classes.add(Class.forName(cn)); + classList.add(Class.forName(cn)); } catch (ClassNotFoundException e) { String msg = "Error registering class [" + cn + "] from [" + classNames + "]"; throw new RuntimeException(msg, e); } } } - return classes; + return classList; } private List getSearchList(String searchNames, List defaultValue) { @@ -3259,6 +3272,17 @@ public class ServerConfig { this.dumpMetricsOptions = dumpMetricsOptions; } + /** + * Return true if entity classes should be loaded and registered via ModuleInfoLoader. + *

+ * When false we either register entity classes via application code or use classpath + * scanning to find and register entity classes. + *

+ */ + public boolean isAutoLoadModuleInfo() { + return loadModuleInfo && classes.isEmpty(); + } + public enum UuidVersion { VERSION4, VERSION1, diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java b/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java index 8be384042..a90f4869b 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.core; import io.ebean.config.ContainerConfig; +import io.ebean.config.ModuleInfoLoader; import io.ebean.config.ServerConfig; import io.ebean.config.ServerConfigProvider; import io.ebean.config.TenantMode; @@ -85,11 +86,7 @@ public class DefaultContainer implements SpiContainer { public SpiEbeanServer createServer(ServerConfig serverConfig) { synchronized (this) { - if (serverConfig.isDefaultServer()) { - for (ServerConfigProvider configProvider : ServiceLoader.load(ServerConfigProvider.class)) { - configProvider.apply(serverConfig); - } - } + applyConfigServices(serverConfig); setNamingConvention(serverConfig); BootupClasses bootupClasses = getBootupClasses(serverConfig); @@ -120,7 +117,6 @@ public class DefaultContainer implements SpiContainer { // executor and l2 caching service setup early (used during server construction) SpiBackgroundExecutor executor = createBackgroundExecutor(serverConfig); - InternalConfiguration c = new InternalConfiguration(online, clusterManager, executor, serverConfig, bootupClasses); DefaultServer server = new DefaultServer(c, c.cacheManager()); @@ -128,24 +124,45 @@ public class DefaultContainer implements SpiContainer { // generate and run DDL if required // if there are any other tasks requiring action in their plugins, do them as well if (!DbOffline.isGenerateMigration()) { - server.executePlugins(online); - - // initialise prior to registering with clusterManager - server.initialise(); - if (online) { - if (clusterManager.isClustering()) { - // register the server once it has been created - clusterManager.registerServer(server); - } - } - // start any services after registering with clusterManager - server.start(); + startServer(online, server); } DbOffline.reset(); return server; } } + private void applyConfigServices(ServerConfig config) { + if (config.isDefaultServer()) { + for (ServerConfigProvider configProvider : ServiceLoader.load(ServerConfigProvider.class)) { + configProvider.apply(config); + } + if (config.isAutoLoadModuleInfo()) { + // auto register entity classes (default db) + for (ModuleInfoLoader loader : ServiceLoader.load(ModuleInfoLoader.class)) { + config.addAll(loader.entityClasses()); + } + } + } else if (config.isAutoLoadModuleInfo()) { + // auto register entity classes (other named db) + for (ModuleInfoLoader loader : ServiceLoader.load(ModuleInfoLoader.class)) { + config.addAll(loader.entityClassesFor(config.getName())); + } + } + } + + private void startServer(boolean online, DefaultServer server) { + server.executePlugins(online); + // initialise prior to registering with clusterManager + server.initialise(); + if (online) { + if (clusterManager.isClustering()) { + clusterManager.registerServer(server); + } + } + // start any services after registering with clusterManager + server.start(); + } + /** * Get the entities, scalarTypes, Listeners etc combining the class registered * ones with the already created instances. diff --git a/src/test/java/io/ebean/config/ServerConfigTest.java b/src/test/java/io/ebean/config/ServerConfigTest.java index 2279a864e..c9c20d6d5 100644 --- a/src/test/java/io/ebean/config/ServerConfigTest.java +++ b/src/test/java/io/ebean/config/ServerConfigTest.java @@ -71,6 +71,7 @@ public class ServerConfigTest { props.setProperty("idGeneratorAutomatic", "true"); props.setProperty("enabledL2Regions", "r0,users,orgs"); props.setProperty("caseSensitiveCollation", "false"); + props.setProperty("loadModuleInfo", "true"); serverConfig.loadFromProperties(props); @@ -79,6 +80,8 @@ public class ServerConfigTest { assertTrue(serverConfig.isNotifyL2CacheInForeground()); assertTrue(serverConfig.isDbOffline()); assertTrue(serverConfig.isAutoReadOnlyDataSource()); + assertTrue(serverConfig.isAutoLoadModuleInfo()); + assertTrue(serverConfig.isIdGeneratorAutomatic()); assertFalse(serverConfig.getPlatformConfig().isCaseSensitiveCollation()); @@ -134,6 +137,7 @@ public class ServerConfigTest { assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime()); assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate()); assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation()); + assertFalse(serverConfig.isAutoLoadModuleInfo()); } @Test