From f955067d2b8490b437a8fd3a36d43d2547e83c30 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 16 Aug 2021 15:19:50 +1000 Subject: [PATCH] Add ability to clean the DB in DevUI Fixes #583 Fixes #19303 --- .../agroal/deployment/AgroalProcessor.java | 4 + .../agroal/runtime/AgroalRecorder.java | 1 - .../devservices/DevUIDatasourceProcessor.java | 34 ++++ .../main/resources/dev-templates/clean.html | 27 +++ .../resources/dev-templates/embedded.html | 3 + extensions/datasource/runtime/pom.xml | 10 + .../datasource/runtime/DatabaseRecorder.java | 31 ++++ .../runtime/DatabaseSchemaProvider.java | 11 ++ .../flyway/runtime/FlywaySchemaProvider.java | 23 +++ ....datasource.runtime.DatabaseSchemaProvider | 1 + .../orm/deployment/HibernateOrmProcessor.java | 2 + ...rnateSchemaRecreateDevConsoleTestCase.java | 37 ++++ .../hibernate/orm/MyEntityTestResource.java | 19 ++ .../deployment/src/test/resources/import.sql | 1 + .../orm/runtime/HibernateOrmRecorder.java | 8 +- .../schema/SchemaManagementIntegrator.java | 173 ++++++++++++++++++ ....datasource.runtime.DatabaseSchemaProvider | 1 + .../runtime/LiquibaseSchemaProvider.java | 67 +++++++ ....datasource.runtime.DatabaseSchemaProvider | 1 + 19 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevUIDatasourceProcessor.java create mode 100644 extensions/datasource/deployment/src/main/resources/dev-templates/clean.html create mode 100644 extensions/datasource/deployment/src/main/resources/dev-templates/embedded.html create mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseRecorder.java create mode 100644 extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseSchemaProvider.java create mode 100644 extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java create mode 100644 extensions/flyway/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider create mode 100644 extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateSchemaRecreateDevConsoleTestCase.java create mode 100644 extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/schema/SchemaManagementIntegrator.java create mode 100644 extensions/hibernate-orm/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider create mode 100644 extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseSchemaProvider.java create mode 100644 extensions/liquibase/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 91d268ba571053..a3be91ebf015e6 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -1,5 +1,7 @@ package io.quarkus.agroal.deployment; +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; + import java.sql.Driver; import java.util.ArrayList; import java.util.HashMap; @@ -7,6 +9,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.stream.Collectors; import javax.enterprise.inject.Default; import javax.inject.Singleton; @@ -48,6 +51,7 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java index 7ed7348330160c..a953583dc83304 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java @@ -55,5 +55,4 @@ public DataSourcesExcludedFromHealthChecks get() { } }; } - } diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevUIDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevUIDatasourceProcessor.java new file mode 100644 index 00000000000000..d608338bfe0945 --- /dev/null +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevUIDatasourceProcessor.java @@ -0,0 +1,34 @@ +package io.quarkus.datasource.deployment.devservices; + +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; +import io.quarkus.datasource.runtime.DatabaseRecorder; +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; +import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; + +public class DevUIDatasourceProcessor { + + @BuildStep + public DevConsoleTemplateInfoBuildItem devConsoleInfo( + DataSourcesBuildTimeConfig dataSourceBuildTimeConfig) { + List names = new ArrayList<>(); + names.add(""); + names.addAll(dataSourceBuildTimeConfig.namedDataSources.keySet()); + Collections.sort(names); + return new DevConsoleTemplateInfoBuildItem("dbs", names); + } + + @BuildStep(onlyIf = IsDevelopment.class) + @Record(value = STATIC_INIT, optional = true) + DevConsoleRouteBuildItem devConsoleCleanDatabaseHandler(DatabaseRecorder recorder) { + return new DevConsoleRouteBuildItem("clean", "POST", recorder.devConsoleCleanDatabaseHandler()); + } +} diff --git a/extensions/datasource/deployment/src/main/resources/dev-templates/clean.html b/extensions/datasource/deployment/src/main/resources/dev-templates/clean.html new file mode 100644 index 00000000000000..d667072017f00a --- /dev/null +++ b/extensions/datasource/deployment/src/main/resources/dev-templates/clean.html @@ -0,0 +1,27 @@ +{#include main} + {#title}Clean Databases{/title} + {#body} + + + + + + + + + {#for db in info:dbs} + + + + {/for} + +
DatasourceActions
+ {db} + +
+ + +
+
+ {/body} +{/include} \ No newline at end of file diff --git a/extensions/datasource/deployment/src/main/resources/dev-templates/embedded.html b/extensions/datasource/deployment/src/main/resources/dev-templates/embedded.html new file mode 100644 index 00000000000000..5782d51593a6b7 --- /dev/null +++ b/extensions/datasource/deployment/src/main/resources/dev-templates/embedded.html @@ -0,0 +1,3 @@ + + + Reset Databases diff --git a/extensions/datasource/runtime/pom.xml b/extensions/datasource/runtime/pom.xml index 4cb3c34c536226..eae8d29226ad36 100644 --- a/extensions/datasource/runtime/pom.xml +++ b/extensions/datasource/runtime/pom.xml @@ -17,6 +17,16 @@ io.quarkus quarkus-core + + io.vertx + vertx-web + true + + + io.quarkus + quarkus-vertx-http-dev-console-runtime-spi + true + io.quarkus quarkus-arc diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseRecorder.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseRecorder.java new file mode 100644 index 00000000000000..a770aa7c2cb0ee --- /dev/null +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseRecorder.java @@ -0,0 +1,31 @@ +package io.quarkus.datasource.runtime; + +import java.util.ServiceLoader; + +import io.quarkus.devconsole.runtime.spi.DevConsolePostHandler; +import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; +import io.vertx.ext.web.RoutingContext; + +@Recorder +public class DatabaseRecorder { + + public Handler devConsoleCleanDatabaseHandler() { + // the usual issue of Vert.x hanging on to the first TCCL and setting it on all its threads + final ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + return new DevConsolePostHandler() { + + @Override + protected void handlePostAsync(RoutingContext event, MultiMap form) throws Exception { + String name = form.get("name"); + ServiceLoader dbs = ServiceLoader.load(DatabaseSchemaProvider.class, + Thread.currentThread().getContextClassLoader()); + for (DatabaseSchemaProvider i : dbs) { + i.resetDatabase(name); + } + flashMessage(event, "Action invoked"); + } + }; + } +} diff --git a/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseSchemaProvider.java b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseSchemaProvider.java new file mode 100644 index 00000000000000..d1b1034b37115a --- /dev/null +++ b/extensions/datasource/runtime/src/main/java/io/quarkus/datasource/runtime/DatabaseSchemaProvider.java @@ -0,0 +1,11 @@ +package io.quarkus.datasource.runtime; + +/** + * A service interface that can be used to reset the database for dev and test mode. + */ +public interface DatabaseSchemaProvider { + + void resetDatabase(String dbName); + + void resetAllDatabases(); +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java new file mode 100644 index 00000000000000..b5026a133c9eac --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywaySchemaProvider.java @@ -0,0 +1,23 @@ +package io.quarkus.flyway.runtime; + +import io.quarkus.datasource.runtime.DatabaseSchemaProvider; + +public class FlywaySchemaProvider implements DatabaseSchemaProvider { + @Override + public void resetDatabase(String dbName) { + for (FlywayContainer i : FlywayRecorder.FLYWAY_CONTAINERS) { + if (i.getDataSourceName().equals(dbName)) { + i.getFlyway().clean(); + i.getFlyway().migrate(); + } + } + } + + @Override + public void resetAllDatabases() { + for (FlywayContainer i : FlywayRecorder.FLYWAY_CONTAINERS) { + i.getFlyway().clean(); + i.getFlyway().migrate(); + } + } +} diff --git a/extensions/flyway/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider b/extensions/flyway/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider new file mode 100644 index 00000000000000..60c52a7bd78e0b --- /dev/null +++ b/extensions/flyway/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider @@ -0,0 +1 @@ +io.quarkus.flyway.runtime.FlywaySchemaProvider diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 7dd7c391e24dc9..214456677d76fc 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -121,6 +121,7 @@ import io.quarkus.hibernate.orm.runtime.devconsole.HibernateOrmDevConsoleIntegrator; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor; import io.quarkus.hibernate.orm.runtime.proxies.PreGeneratedProxies; +import io.quarkus.hibernate.orm.runtime.schema.SchemaManagementIntegrator; import io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver; import io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver; import io.quarkus.panache.common.deployment.HibernateEnhancersRegisteredBuildItem; @@ -459,6 +460,7 @@ public void build(RecorderContext recorderContext, HibernateOrmRecorder recorder } if (launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT) { integratorClasses.add(HibernateOrmDevConsoleIntegrator.class); + integratorClasses.add(SchemaManagementIntegrator.class); } Map> integrationStaticDescriptors = HibernateOrmIntegrationStaticConfiguredBuildItem diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateSchemaRecreateDevConsoleTestCase.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateSchemaRecreateDevConsoleTestCase.java new file mode 100644 index 00000000000000..0ffe0aeaa7d402 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateSchemaRecreateDevConsoleTestCase.java @@ -0,0 +1,37 @@ +package io.quarkus.hibernate.orm; + +import static org.hamcrest.Matchers.is; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class HibernateSchemaRecreateDevConsoleTestCase { + @RegisterExtension + final static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyEntity.class, MyEntityTestResource.class) + .addAsResource("application.properties") + .addAsResource("import.sql")); + + @Test + public void testCleanDatabase() { + RestAssured.when().get("/my-entity/count").then().body(is("2")); + RestAssured.when().get("/my-entity/add").then().body(is("MyEntity:added")); + RestAssured.when().get("/my-entity/count").then().body(is("3")); + RestAssured.with() + .redirects().follow(false).formParam("name", "").post("q/dev/io.quarkus.quarkus-datasource/clean") + .then() + .statusCode(303); + RestAssured.when().get("/my-entity/count").then().body(is("2")); + + } + + private void assertBodyIs(String expectedBody) { + RestAssured.when().get("/my-entity/2").then().body(is(expectedBody)); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/MyEntityTestResource.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/MyEntityTestResource.java index 871e2b818e3542..a2827a372d7274 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/MyEntityTestResource.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/MyEntityTestResource.java @@ -2,6 +2,7 @@ import javax.inject.Inject; import javax.persistence.EntityManager; +import javax.transaction.Transactional; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -25,4 +26,22 @@ public String getName(@PathParam("id") long id) { return "no entity"; } + + @GET + @Path("/add") + @Produces(MediaType.TEXT_PLAIN) + @Transactional + public String add() { + MyEntity entity = new MyEntity(); + entity.setName("added"); + em.persist(entity); + return entity.toString(); + } + + @GET + @Path("/count") + @Produces(MediaType.TEXT_PLAIN) + public int count() { + return em.createQuery("from MyEntity").getResultList().size(); + } } diff --git a/extensions/hibernate-orm/deployment/src/test/resources/import.sql b/extensions/hibernate-orm/deployment/src/test/resources/import.sql index 861dca425e30cf..fec2bf0f4cefed 100644 --- a/extensions/hibernate-orm/deployment/src/test/resources/import.sql +++ b/extensions/hibernate-orm/deployment/src/test/resources/import.sql @@ -1,2 +1,3 @@ INSERT INTO MyEntity(id, name) VALUES(1, 'default sql load script entity'); INSERT INTO MyEntity(id, name) VALUES(2, 'import.sql load script entity'); +alter sequence myEntitySeq restart with 3; \ No newline at end of file diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java index 49a8c8b809f42f..3e404089403789 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRecorder.java @@ -21,6 +21,7 @@ import io.quarkus.hibernate.orm.runtime.boot.QuarkusPersistenceUnitDefinition; import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeDescriptor; import io.quarkus.hibernate.orm.runtime.proxies.PreGeneratedProxies; +import io.quarkus.hibernate.orm.runtime.schema.SchemaManagementIntegrator; import io.quarkus.hibernate.orm.runtime.session.ForwardingSession; import io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver; import io.quarkus.runtime.annotations.Recorder; @@ -55,6 +56,12 @@ public void setupPersistenceProvider(HibernateOrmRuntimeConfig hibernateOrmRunti public BeanContainerListener initMetadata(List parsedPersistenceXmlDescriptors, Scanner scanner, Collection> additionalIntegrators, PreGeneratedProxies proxyDefinitions) { + SchemaManagementIntegrator.clearDsMap(); + for (QuarkusPersistenceUnitDefinition i : parsedPersistenceXmlDescriptors) { + if (i.getDataSource().isPresent()) { + SchemaManagementIntegrator.mapDatasource(i.getDataSource().get(), i.getName()); + } + } return new BeanContainerListener() { @Override public void created(BeanContainer beanContainer) { @@ -119,5 +126,4 @@ protected Session delegate() { } }; } - } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/schema/SchemaManagementIntegrator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/schema/SchemaManagementIntegrator.java new file mode 100644 index 00000000000000..85f75063f62a0b --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/schema/SchemaManagementIntegrator.java @@ -0,0 +1,173 @@ +package io.quarkus.hibernate.orm.runtime.schema; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.boot.Metadata; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; +import org.hibernate.tool.schema.SourceType; +import org.hibernate.tool.schema.TargetType; +import org.hibernate.tool.schema.spi.CommandAcceptanceException; +import org.hibernate.tool.schema.spi.ExceptionHandler; +import org.hibernate.tool.schema.spi.ExecutionOptions; +import org.hibernate.tool.schema.spi.SchemaDropper; +import org.hibernate.tool.schema.spi.SchemaManagementTool; +import org.hibernate.tool.schema.spi.ScriptSourceInput; +import org.hibernate.tool.schema.spi.ScriptTargetOutput; +import org.hibernate.tool.schema.spi.SourceDescriptor; +import org.hibernate.tool.schema.spi.TargetDescriptor; +import org.jboss.logging.Logger; + +import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.datasource.runtime.DatabaseSchemaProvider; +import io.quarkus.runtime.LaunchMode; + +public class SchemaManagementIntegrator implements Integrator, DatabaseSchemaProvider { + + private static final Logger log = Logger.getLogger(SchemaManagementIntegrator.class); + + private static final Map metadataMap = new ConcurrentHashMap<>(); + private static final Map datasourceToPuMap = new ConcurrentHashMap<>(); + + @Override + public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + metadataMap.put(defaultName(sessionFactory), new Holder(metadata, sessionFactory, serviceRegistry)); + } + + @Override + public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { + metadataMap.remove(defaultName(sessionFactory)); + } + + public static void clearDsMap() { + datasourceToPuMap.clear(); + } + + public static void mapDatasource(String datasource, String pu) { + datasourceToPuMap.put(datasource, pu); + } + + static String defaultName(SessionFactoryImplementor sf) { + String name = sf.getName(); + if (name != null) { + return name; + } + Object prop = sf.getProperties().get("hibernate.ejb.persistenceUnitName"); + if (prop != null) { + return prop.toString(); + } + return DataSourceUtil.DEFAULT_DATASOURCE_NAME; + } + + public static void recreateDatabases() { + if (!LaunchMode.current().isDevOrTest()) { + throw new IllegalStateException("Can only be used in dev or test mode"); + } + for (String val : metadataMap.keySet()) { + recreateDatabase(val); + } + } + + public static void recreateDatabase(String name) { + if (!LaunchMode.current().isDevOrTest()) { + throw new IllegalStateException("Can only be used in dev or test mode"); + } + Holder val = metadataMap.get(name); + + Object prop = val.sessionFactory.getProperties().get("javax.persistence.schema-generation.database.action"); + if (prop != null && !(prop.toString().equals("none"))) { + //if this is none we assume another framework is doing this (e.g. flyway) + SchemaManagementTool schemaManagementTool = val.sessionFactory.getServiceRegistry().getService(SchemaManagementTool.class); + SchemaDropper schemaDropper = schemaManagementTool.getSchemaDropper(new HashMap()); + schemaDropper. + schemaDropper + .doDrop(val.metadata, new SimpleExecutionOptions(), new SimpleSourceDescriptor(), + new SimpleTargetDescriptor()); + schemaManagementTool.getSchemaCreator(new HashMap()) + .doCreation(val.metadata, new SimpleExecutionOptions(), new SimpleSourceDescriptor(), + new SimpleTargetDescriptor()); + } + //we still clear caches though + val.sessionFactory.getCache().evictAll(); + val.sessionFactory.getCache().evictQueries(); + } + + @Override + public void resetDatabase(String dbName) { + String name = datasourceToPuMap.get(dbName); + if (name == null) { + //not a hibernate DS + return; + } + recreateDatabase(name); + } + + @Override + public void resetAllDatabases() { + recreateDatabases(); + } + + static class Holder { + final Metadata metadata; + final SessionFactoryImplementor sessionFactory; + final SessionFactoryServiceRegistry serviceRegistry; + + Holder(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { + this.metadata = metadata; + this.sessionFactory = sessionFactory; + this.serviceRegistry = serviceRegistry; + } + } + + private static class SimpleExecutionOptions implements ExecutionOptions { + @Override + public Map getConfigurationValues() { + return Collections.emptyMap(); + } + + @Override + public boolean shouldManageNamespaces() { + return false; + } + + @Override + public ExceptionHandler getExceptionHandler() { + return new ExceptionHandler() { + @Override + public void handleException(CommandAcceptanceException exception) { + log.error("Failed to recreate schema", exception); + } + }; + } + } + + private static class SimpleSourceDescriptor implements SourceDescriptor { + @Override + public SourceType getSourceType() { + return SourceType.METADATA; + } + + @Override + public ScriptSourceInput getScriptSourceInput() { + return null; + } + } + + private static class SimpleTargetDescriptor implements TargetDescriptor { + @Override + public EnumSet getTargetTypes() { + return EnumSet.of(TargetType.DATABASE); + } + + @Override + public ScriptTargetOutput getScriptTargetOutput() { + return null; + } + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider b/extensions/hibernate-orm/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider new file mode 100644 index 00000000000000..d6954febfd8a5d --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider @@ -0,0 +1 @@ +io.quarkus.hibernate.orm.runtime.schema.SchemaManagementIntegrator \ No newline at end of file diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseSchemaProvider.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseSchemaProvider.java new file mode 100644 index 00000000000000..f58433983327f9 --- /dev/null +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseSchemaProvider.java @@ -0,0 +1,67 @@ +package io.quarkus.liquibase.runtime; + +import javax.enterprise.inject.Any; +import javax.enterprise.inject.UnsatisfiedResolutionException; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InjectableInstance; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.datasource.runtime.DatabaseSchemaProvider; +import io.quarkus.liquibase.LiquibaseFactory; +import liquibase.Liquibase; +import liquibase.exception.LiquibaseException; + +public class LiquibaseSchemaProvider implements DatabaseSchemaProvider { + @Override + public void resetDatabase(String dbName) { + try { + InjectableInstance liquibaseFactoryInstance = Arc.container() + .select(LiquibaseFactory.class, Any.Literal.INSTANCE); + if (liquibaseFactoryInstance.isUnsatisfied()) { + return; + } + for (InstanceHandle liquibaseFactoryHandle : liquibaseFactoryInstance.handles()) { + try { + LiquibaseFactory liquibaseFactory = liquibaseFactoryHandle.get(); + if (liquibaseFactory.getDataSourceName().equals(dbName)) { + doReset(liquibaseFactory); + } + } catch (UnsatisfiedResolutionException e) { + //ignore, the DS is not configured + } + } + } catch (Exception e) { + throw new IllegalStateException("Error starting Liquibase", e); + } + } + + @Override + public void resetAllDatabases() { + try { + InjectableInstance liquibaseFactoryInstance = Arc.container() + .select(LiquibaseFactory.class, Any.Literal.INSTANCE); + if (liquibaseFactoryInstance.isUnsatisfied()) { + return; + } + for (InstanceHandle liquibaseFactoryHandle : liquibaseFactoryInstance.handles()) { + try { + LiquibaseFactory liquibaseFactory = liquibaseFactoryHandle.get(); + doReset(liquibaseFactory); + } catch (UnsatisfiedResolutionException e) { + //ignore, the DS is not configured + } + } + } catch (Exception e) { + throw new IllegalStateException("Error starting Liquibase", e); + } + } + + public void doReset(LiquibaseFactory liquibaseFactory) throws LiquibaseException { + try (Liquibase liquibase = liquibaseFactory.createLiquibase()) { + liquibase.dropAll(); + } + try (Liquibase liquibase = liquibaseFactory.createLiquibase()) { + liquibase.update(liquibaseFactory.createContexts(), liquibaseFactory.createLabels()); + } + } +} diff --git a/extensions/liquibase/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider b/extensions/liquibase/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider new file mode 100644 index 00000000000000..23d7c6452f4998 --- /dev/null +++ b/extensions/liquibase/runtime/src/main/resources/META-INF/services/io.quarkus.datasource.runtime.DatabaseSchemaProvider @@ -0,0 +1 @@ +io.quarkus.liquibase.runtime.LiquibaseSchemaProvider \ No newline at end of file