diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java index 9c69e33fd..31064ee3c 100644 --- a/ebean-api/src/main/java/io/ebean/Database.java +++ b/ebean-api/src/main/java/io/ebean/Database.java @@ -1741,4 +1741,26 @@ public interface Database { */ void truncate(Class>... tables); + /** + * Runs the DDL manually. Can be used, if database is offline or if you use a + * TenantMode that does not support DDL run on startup. + *
+ * Note: runDdl is normally executed at startup automatically, if
+ * ebean.db.runDdl=true is set. Calling this method, will not check
+ * that flag.
+ *
+ * Note: runDdl is normally executed at startup automatically, if
+ * ebean.db.migration.run=true is set. Calling this method, will not check
+ * that flag.
+ *
- * Run based on on property settings for ebean.ddl.generate and ebean.ddl.run etc. + * Run based on on property settings for ebean.ddl.generate */ - void execute(boolean online); + void generateDdl(); + /** + * Runs the created DDL files. + *
+ * Run based on on property settings for ebean.ddl.run etc. + */ + void runDdl(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 1cdabc9f8..1ba14d8e2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -246,7 +246,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ public void executePlugins(boolean online) { if (!config.isDocStoreOnly()) { - ddlGenerator.execute(online); + ddlGenerator.generateDdl(); } for (Plugin plugin : serverPlugins) { plugin.online(online); @@ -359,27 +359,54 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { serverCacheManager.enabledRegions(config.getEnabledL2Regions()); } + /** * Start any services after registering with the ClusterManager. */ public void start() { - if (config.isRunMigration() && TenantMode.DB != config.getTenantMode()) { - final AutoMigrationRunner migrationRunner = ServiceUtil.service(AutoMigrationRunner.class); - if (migrationRunner == null) { - throw new IllegalStateException("No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?"); + + // check, if DB is available to run DDL and migration + boolean dbAvailable = !config.isDocStoreOnly() + && !config.isDbOffline() + && config.getDataSource() != null + && config.getTenantMode().isDdlEnabled(); + + if (dbAvailable) { + if (config.isDdlRun()) { + runDdl(); } - final String dbSchema = config.getDbSchema(); - if (dbSchema != null) { - migrationRunner.setDefaultDbSchema(dbSchema); + if (config.isRunMigration()) { + runMigration(); } - migrationRunner.setName(config.getName()); - migrationRunner.setPlatform(config.getDatabasePlatform().getPlatform().base().name().toLowerCase()); - migrationRunner.loadProperties(config.getProperties()); - migrationRunner.run(config.getDataSource()); + } else if (config.isDdlRun() || config.isRunMigration()) { + log.warn("There was a request to run DDL or migration, but this is currently not possible, as the database is not available"); } startQueryPlanCapture(); } + @Override + public void runDdl() { + ddlGenerator.runDdl(); + } + + @Override + public void runMigration() { + final AutoMigrationRunner migrationRunner = ServiceUtil.service(AutoMigrationRunner.class); + if (migrationRunner == null) { + throw new IllegalStateException( + "No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?"); + } + + final String dbSchema = config.getDbSchema(); + if (dbSchema != null) { + migrationRunner.setDefaultDbSchema(dbSchema); + } + migrationRunner.setName(config.getName()); + migrationRunner.setPlatform(config.getDatabasePlatform().getPlatform().base().name().toLowerCase()); + migrationRunner.loadProperties(config.getProperties()); + migrationRunner.run(dataSource()); + } + private void startQueryPlanCapture() { if (config.isQueryPlanCapture()) { long secs = config.getQueryPlanCapturePeriodSecs(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index 151a7031f..72747f22a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -610,8 +610,13 @@ public final class InternalConfiguration { } @Override - public void execute(boolean online) { - if (online && ddlRun) { + public void generateDdl() { + // do nothing + } + + @Override + public void runDdl() { + if (ddlRun) { CoreLog.log.error("Configured to run DDL but ebean-ddl-generator is not in the classpath (or ebean-test in the test classpath?)"); } } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java index 69a4af033..71c4ff4f2 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java @@ -41,7 +41,6 @@ public class DdlGenerator implements SpiDdlGenerator { private final SpiEbeanServer server; private final boolean generateDdl; - private final boolean runDdl; private final boolean extraDdl; private final boolean createOnly; private final boolean jaxbPresent; @@ -67,14 +66,7 @@ public class DdlGenerator implements SpiDdlGenerator { final DatabasePlatform databasePlatform = server.databasePlatform(); this.platform = databasePlatform.getPlatform(); this.platformName = platform.base().name(); - if (!config.getTenantMode().isDdlEnabled() && config.isDdlRun()) { - log.warn("DDL can't be run on startup with TenantMode " + config.getTenantMode()); - this.runDdl = false; - this.ddlAutoCommit = false; - } else { - this.runDdl = config.isDdlRun(); - this.ddlAutoCommit = databasePlatform.isDdlAutoCommit(); - } + this.ddlAutoCommit = databasePlatform.isDdlAutoCommit(); this.scriptTransform = createScriptTransform(config); this.baseDir = initBaseDir(); } @@ -89,18 +81,11 @@ public class DdlGenerator implements SpiDdlGenerator { return new File("."); } - @Override - public void execute(boolean online) { - generateDdl(); - if (online) { - runDdl(); - } - } - /** * Generate the DDL drop and create scripts if the properties have been set. */ - protected void generateDdl() { + @Override + public void generateDdl() { if (generateDdl) { if (!createOnly) { writeDrop(getDropFileName()); @@ -112,16 +97,15 @@ public class DdlGenerator implements SpiDdlGenerator { /** * Run the DDL drop and DDL create scripts if properties have been set. */ - protected void runDdl() { - if (runDdl) { - Connection connection = null; - try { - connection = obtainConnection(); - runDdlWith(connection); - } finally { - JdbcClose.rollback(connection); - JdbcClose.close(connection); - } + @Override + public void runDdl() { + Connection connection = null; + try { + connection = obtainConnection(); + runDdlWith(connection); + } finally { + JdbcClose.rollback(connection); + JdbcClose.close(connection); } } diff --git a/ebean-test/src/test/java/io/ebean/config/TestServerOffline.java b/ebean-test/src/test/java/io/ebean/config/TestServerOffline.java new file mode 100644 index 000000000..bed9ab01e --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/config/TestServerOffline.java @@ -0,0 +1,151 @@ +package io.ebean.config; + + +import io.ebean.Database; +import io.ebean.DatabaseFactory; +import io.ebean.annotation.ForPlatform; +import io.ebean.annotation.Platform; +import io.ebean.datasource.DataSourceAlert; +import io.ebean.datasource.DataSourceInitialiseException; + +import org.junit.jupiter.api.Test; +import org.tests.model.basic.EBasicVer; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class TestServerOffline { + + @Test + @ForPlatform({Platform.H2}) + public void testOffline_default() throws SQLException { + + String url = "jdbc:h2:mem:testoffline1"; + try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) { + Properties props = props(url); + DatabaseConfig config = config(props); + + assertThatThrownBy(() -> DatabaseFactory.create(config)) + .isInstanceOf(DataSourceInitialiseException.class); + } + + } + + private static class LazyDatasourceInitializer implements DataSourceAlert { + + public Database server; + + private boolean initialized; + + @Override + public void dataSourceUp(DataSource dataSource) { + if (!initialized) { + initDatabase(); + } + } + + public synchronized void initDatabase() { + if (!initialized) { + server.runDdl(); + server.runMigration(); + initialized = true; + } + } + + @Override + public void dataSourceDown(DataSource dataSource, SQLException reason) {} + + @Override + public void dataSourceWarning(DataSource dataSource, String msg) {} + + } + + @Test + @ForPlatform({Platform.H2}) + public void testOffline_recovery() throws SQLException { + + String url = "jdbc:h2:mem:testoffline3"; + try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) { + + Properties props = props(url); + + // to bring up ebean without a database, we must disable varous things + // that happen on startup + props.setProperty("datasource.h2_offline.failOnStart", "false"); + props.setProperty("ebean.h2_offline.skipDataSourceCheck", "true"); + props.setProperty("ebean.h2_offline.ddl.run", "false"); + DatabaseConfig config = config(props); + + LazyDatasourceInitializer alert = new LazyDatasourceInitializer() ; + config.getDataSourceConfig().setAlert(alert); + config.getDataSourceConfig().setHeartbeatFreqSecs(1); + + Database h2Offline = DatabaseFactory.create(config); + alert.server = h2Offline; + assertThat(h2Offline).isNotNull(); + // DB is online now in offline mode + + // Accessing the DB will throw a PE + assertThatThrownBy(() -> alert.initDatabase()) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Failed to obtain connection to run DDL"); + + assertThatThrownBy(() -> h2Offline.find(EBasicVer.class).findCount()).isInstanceOf(PersistenceException.class); + + // so - reset the password so that the server can reconnect + try (Statement stmt = bootup.createStatement()) { + stmt.execute("alter user sa set password 'sa'"); + } + + assertThat(alert.initialized).isFalse(); + + // next access to ebean should bring DS online + h2Offline.find(EBasicVer.class).findCount(); + assertThat(alert.initialized).isTrue(); + + // check if server is working (ie ddl was run) + EBasicVer bean = new EBasicVer("foo"); + h2Offline.save(bean); + assertThat(h2Offline.find(EBasicVer.class).findCount()).isEqualTo(1); + h2Offline.delete(bean); + } + } + + private Properties props(String url) { + + Properties props = new Properties(); + + props.setProperty("datasource.h2_offline.username", "sa"); + props.setProperty("datasource.h2_offline.password", "sa"); + props.setProperty("datasource.h2_offline.url", url); + props.setProperty("datasource.h2_offline.driver", "org.h2.Driver"); + + props.setProperty("ebean.h2_offline.databasePlatformName", "h2"); + props.setProperty("ebean.h2_offline.ddl.extra", "false"); + + props.setProperty("ebean.h2_offline.ddl.generate", "true"); + props.setProperty("ebean.h2_offline.ddl.run", "true"); + + return props; + } + + private DatabaseConfig config(Properties props) { + DatabaseConfig config = new DatabaseConfig(); + config.setName("h2_offline"); + config.loadFromProperties(props); + config.setDefaultServer(false); + config.setRegister(false); + config.getClasses().add(EBasicVer.class); + return config; + } + +} diff --git a/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java index 2cf23625b..ba823c057 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java +++ b/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java @@ -642,4 +642,14 @@ public class TDSpiServer implements SpiServer { public void loadBean(EntityBeanIntercept ebi) { } + + @Override + public void runDdl() { + + } + + @Override + public void runMigration() { + + } }