From a7f865cfa1e2e3575da3ef0dfb1e90dbb2cb10d3 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Thu, 21 Nov 2019 15:44:46 +0100 Subject: [PATCH] feat(quartz): add clustered jobs support Fixes #3520 --- bom/deployment/pom.xml | 5 + ci-templates/stages.yml | 1 + extensions/agroal/deployment/pom.xml | 4 + extensions/agroal/pom.xml | 1 + extensions/agroal/spi/pom.xml | 23 ++ .../deployment/DataSourceDriverBuildItem.java | 0 .../DataSourceInitializedBuildItem.java | 0 extensions/quartz/deployment/pom.xml | 104 ++++---- .../QuartzJDBCDriverDialectBuildItem.java | 20 ++ .../quartz/deployment/QuartzProcessor.java | 129 ++++++++-- .../quartz/test/MissingDataSourceTest.java | 26 ++ ...upportedClusteredJobConfigurationTest.java | 27 ++ extensions/quartz/runtime/pom.xml | 133 +++++----- .../QuarkusQuartzConnectionPoolProvider.java | 78 ++++++ .../quartz/runtime/QuartzBuildTimeConfig.java | 41 +++ .../quartz/runtime/QuartzRecorder.java | 7 +- ...meConfig.java => QuartzRunTimeConfig.java} | 4 +- .../quartz/runtime/QuartzScheduler.java | 128 +++++++--- .../quarkus/quartz/runtime/QuartzSupport.java | 23 +- .../io/quarkus/quartz/runtime/StoreType.java | 17 ++ .../runtime/graal/QuartzSubstitutions.java | 28 +++ integration-tests/pom.xml | 1 + integration-tests/quartz/pom.xml | 131 ++++++++++ .../io/quarkus/it/quartz/CountResource.java | 20 ++ .../java/io/quarkus/it/quartz/Counter.java | 29 +++ .../src/main/resources/application.properties | 17 ++ .../db/migration/V1.0.1__QuarkusQuartz.sql | 238 ++++++++++++++++++ .../io/quarkus/it/quartz/QuartzITCase.java | 8 + .../io/quarkus/it/quartz/QuartzTestCase.java | 28 +++ .../io/quarkus/it/quartz/TestResources.java | 8 + 30 files changed, 1100 insertions(+), 179 deletions(-) create mode 100644 extensions/agroal/spi/pom.xml rename extensions/agroal/{deployment => spi}/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java (100%) rename extensions/agroal/{deployment => spi}/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java (100%) create mode 100644 extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java create mode 100644 extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java rename extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/{QuartzRuntimeConfig.java => QuartzRunTimeConfig.java} (84%) create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java create mode 100644 integration-tests/quartz/pom.xml create mode 100644 integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java create mode 100644 integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java create mode 100644 integration-tests/quartz/src/main/resources/application.properties create mode 100644 integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql create mode 100644 integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java create mode 100644 integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java create mode 100644 integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index d971194fab1a34..d68c2b20840117 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -106,6 +106,11 @@ quarkus-agroal-deployment ${project.version} + + io.quarkus + quarkus-agroal-spi + ${project.version} + io.quarkus quarkus-artemis-core diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 31fe1ac3153c36..91db7326e170d0 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -342,6 +342,7 @@ stages: modules: - kogito - kubernetes-client + - quartz name: misc_3 - template: native-build-steps.yaml diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index 03167b34a1efdc..92d20fa9b228a0 100644 --- a/extensions/agroal/deployment/pom.xml +++ b/extensions/agroal/deployment/pom.xml @@ -26,6 +26,10 @@ io.quarkus quarkus-agroal + + io.quarkus + quarkus-agroal-spi + io.quarkus quarkus-narayana-jta-deployment diff --git a/extensions/agroal/pom.xml b/extensions/agroal/pom.xml index 47b4f6984e30db..4eb57e49b3eb2b 100644 --- a/extensions/agroal/pom.xml +++ b/extensions/agroal/pom.xml @@ -14,6 +14,7 @@ Quarkus - Agroal pom + spi deployment runtime diff --git a/extensions/agroal/spi/pom.xml b/extensions/agroal/spi/pom.xml new file mode 100644 index 00000000000000..dca7328ae8c8b1 --- /dev/null +++ b/extensions/agroal/spi/pom.xml @@ -0,0 +1,23 @@ + + + + quarkus-agroal-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-agroal-spi + Quarkus - Agroal - SPI + + + + io.quarkus + quarkus-core-deployment + + + + diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java similarity index 100% rename from extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java rename to extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java similarity index 100% rename from extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java rename to extensions/agroal/spi/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java diff --git a/extensions/quartz/deployment/pom.xml b/extensions/quartz/deployment/pom.xml index 5e0e82e4636f58..ce55f3ac36e3b0 100644 --- a/extensions/quartz/deployment/pom.xml +++ b/extensions/quartz/deployment/pom.xml @@ -1,58 +1,62 @@ - - quarkus-quartz-parent - io.quarkus - 999-SNAPSHOT - ../ - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + quarkus-quartz-parent + io.quarkus + 999-SNAPSHOT + ../ + - 4.0.0 + 4.0.0 - quarkus-quartz-deployment - Quarkus - Scheduler Quartz - Deployment + quarkus-quartz-deployment + Quarkus - Quartz - Deployment - - - io.quarkus - quarkus-core-deployment - - - io.quarkus - quarkus-arc-deployment - - - io.quarkus - quarkus-scheduler-deployment - - - io.quarkus - quarkus-quartz - + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-scheduler-deployment + + + io.quarkus + quarkus-agroal-spi + + + io.quarkus + quarkus-quartz + - - io.quarkus - quarkus-junit5-internal - test - - + + io.quarkus + quarkus-junit5-internal + test + + - - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + diff --git a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java new file mode 100644 index 00000000000000..0fa3375a54c19e --- /dev/null +++ b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzJDBCDriverDialectBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.quartz.deployment; + +import java.util.Optional; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Holds the SQL driver dialect {@link org.quartz.impl.jdbcjobstore.StdJDBCDelegate driver delegate} to use. + */ +final class QuartzJDBCDriverDialectBuildItem extends SimpleBuildItem { + private final Optional driver; + + public QuartzJDBCDriverDialectBuildItem(Optional driver) { + this.driver = driver; + } + + public Optional getDriver() { + return driver; + } +} diff --git a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java index 220de421ecee09..964b48e1a2695f 100644 --- a/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java +++ b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java @@ -2,13 +2,27 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import java.sql.Connection; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import org.quartz.core.QuartzSchedulerThread; +import org.quartz.core.SchedulerSignalerImpl; +import org.quartz.impl.StdSchedulerFactory; +import org.quartz.impl.jdbcjobstore.AttributeRestoringConnectionInvocationHandler; +import org.quartz.impl.jdbcjobstore.HSQLDBDelegate; +import org.quartz.impl.jdbcjobstore.JobStoreSupport; +import org.quartz.impl.jdbcjobstore.MSSQLDelegate; +import org.quartz.impl.jdbcjobstore.PostgreSQLDelegate; +import org.quartz.impl.jdbcjobstore.StdJDBCDelegate; +import org.quartz.impl.triggers.AbstractTrigger; +import org.quartz.impl.triggers.SimpleTriggerImpl; import org.quartz.simpl.CascadingClassLoadHelper; -import org.quartz.simpl.RAMJobStore; +import org.quartz.simpl.SimpleInstanceIdGenerator; import org.quartz.simpl.SimpleThreadPool; +import io.quarkus.agroal.deployment.DataSourceDriverBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.Capabilities; @@ -17,18 +31,22 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.configuration.ConfigurationError; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; +import io.quarkus.quartz.runtime.QuarkusQuartzConnectionPoolProvider; +import io.quarkus.quartz.runtime.QuartzBuildTimeConfig; import io.quarkus.quartz.runtime.QuartzRecorder; -import io.quarkus.quartz.runtime.QuartzRuntimeConfig; +import io.quarkus.quartz.runtime.QuartzRunTimeConfig; import io.quarkus.quartz.runtime.QuartzScheduler; import io.quarkus.quartz.runtime.QuartzSupport; +import io.quarkus.quartz.runtime.StoreType; /** * @author Martin Kouba */ public class QuartzProcessor { - @BuildStep CapabilityBuildItem capability() { return new CapabilityBuildItem(Capabilities.QUARTZ); @@ -40,41 +58,118 @@ AdditionalBeanBuildItem beans() { } @BuildStep - List reflectiveClasses() { + NativeImageProxyDefinitionBuildItem connectionProxy(QuartzBuildTimeConfig config) { + if (config.storeType.equals(StoreType.DB)) { + return new NativeImageProxyDefinitionBuildItem(Connection.class.getName()); + } + return null; + } + + @BuildStep + QuartzJDBCDriverDialectBuildItem driver(Optional dataSourceDriver, + QuartzBuildTimeConfig config) { + if (config.storeType == StoreType.RAM) { + if (config.clustered) { + throw new ConfigurationError("Clustered jobs configured with unsupported job store option"); + } + + return new QuartzJDBCDriverDialectBuildItem(Optional.empty()); + } + + if (!dataSourceDriver.isPresent()) { + String message = String.format( + "JDBC Store configured but '%s' datasource is not configured properly. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource-guide", + config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default"); + throw new ConfigurationError(message); + } + + return new QuartzJDBCDriverDialectBuildItem(Optional.of(guessDriver(dataSourceDriver))); + } + + private String guessDriver(Optional dataSourceDriver) { + if (!dataSourceDriver.isPresent()) { + return StdJDBCDelegate.class.getName(); + } + + String resolvedDriver = dataSourceDriver.get().getDriver(); + if (resolvedDriver.contains("postgresql")) { + return PostgreSQLDelegate.class.getName(); + } + if (resolvedDriver.contains("org.h2.Driver")) { + return HSQLDBDelegate.class.getName(); + } + + if (resolvedDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerResource")) { + return MSSQLDelegate.class.getName(); + } + + return StdJDBCDelegate.class.getName(); + + } + + @BuildStep + List reflectiveClasses(QuartzBuildTimeConfig config, + QuartzJDBCDriverDialectBuildItem driverDialect) { List reflectiveClasses = new ArrayList<>(); - reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName())); + StoreType storeType = config.storeType; + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleThreadPool.class.getName())); - reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, RAMJobStore.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleInstanceIdGenerator.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, true, storeType.clazz)); + + if (storeType.equals(StoreType.DB)) { + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, JobStoreSupport.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, true, Connection.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, AbstractTrigger.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleTriggerImpl.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, driverDialect.getDriver().get())); + reflectiveClasses + .add(new ReflectiveClassBuildItem(true, true, "io.quarkus.quartz.runtime.QuartzScheduler$InvokerJob")); + reflectiveClasses + .add(new ReflectiveClassBuildItem(true, false, QuarkusQuartzConnectionPoolProvider.class.getName())); + } + return reflectiveClasses; } @BuildStep - public void logCleanup(BuildProducer logCleanupFilter) { - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.impl.StdSchedulerFactory", + public List logCleanup(QuartzBuildTimeConfig config) { + StoreType storeType = config.storeType; + List logCleanUps = new ArrayList<>(); + logCleanUps.add(new LogCleanupFilterBuildItem(StdSchedulerFactory.class.getName(), "Quartz scheduler version:", "Using default implementation for", "Quartz scheduler 'QuarkusQuartzScheduler'")); - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.QuartzScheduler", + logCleanUps.add(new LogCleanupFilterBuildItem(org.quartz.core.QuartzScheduler.class.getName(), "Quartz Scheduler v", "JobFactory set to:", "Scheduler meta-data:", "Scheduler QuarkusQuartzScheduler")); - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.simpl.RAMJobStore", - "RAMJobStore initialized.")); - - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.SchedulerSignalerImpl", + logCleanUps.add(new LogCleanupFilterBuildItem(storeType.clazz, storeType.name + " initialized.", "Handling", + "Using db table-based data access locking", "JDBCJobStore threads will inherit ContextClassLoader of thread", + "Couldn't rollback jdbc connection", "Database connection shutdown unsuccessful")); + logCleanUps.add(new LogCleanupFilterBuildItem(SchedulerSignalerImpl.class.getName(), "Initialized Scheduler Signaller of type")); + logCleanUps.add(new LogCleanupFilterBuildItem(QuartzSchedulerThread.class.getName(), + "QuartzSchedulerThread Inheriting ContextClassLoader")); + logCleanUps.add(new LogCleanupFilterBuildItem(SimpleThreadPool.class.getName(), + "Job execution threads will use class loader of thread")); + + logCleanUps.add(new LogCleanupFilterBuildItem(AttributeRestoringConnectionInvocationHandler.class.getName(), + "Failed restore connection's original")); + return logCleanUps; } @BuildStep @Record(RUNTIME_INIT) - public void build(QuartzRuntimeConfig runtimeConfig, QuartzRecorder recorder, BeanContainerBuildItem beanContainer, - BuildProducer serviceStart) { - recorder.initialize(runtimeConfig, beanContainer.getValue()); + public void build(QuartzRunTimeConfig runTimeConfig, QuartzBuildTimeConfig buildTimeConfig, QuartzRecorder recorder, + BeanContainerBuildItem beanContainer, + BuildProducer serviceStart, QuartzJDBCDriverDialectBuildItem driverDialect) { + recorder.initialize(runTimeConfig, buildTimeConfig, beanContainer.getValue(), driverDialect.getDriver()); // Make sure that StartupEvent is fired after the init serviceStart.produce(new ServiceStartBuildItem("quartz")); } - } diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java new file mode 100644 index 00000000000000..04a1694bdfbc9d --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/MissingDataSourceTest.java @@ -0,0 +1,26 @@ +package io.quarkus.quartz.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.test.QuarkusUnitTest; + +public class MissingDataSourceTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setExpectedException(ConfigurationError.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleJobs.class) + .addAsResource(new StringAsset("quarkus.quartz.store-type=db"), "application.properties")); + + @Test + public void shouldFailAndNotReachHere() { + Assertions.fail(); + } +} diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java new file mode 100644 index 00000000000000..3ef21c00a655ad --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/UnsupportedClusteredJobConfigurationTest.java @@ -0,0 +1,27 @@ +package io.quarkus.quartz.test; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.test.QuarkusUnitTest; + +public class UnsupportedClusteredJobConfigurationTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setExpectedException(ConfigurationError.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleJobs.class) + .addAsResource(new StringAsset("quarkus.quartz.store-type=ram\nquarkus.quartz.clustered=true"), + "application.properties")); + + @Test + public void shouldFailWhenConfiguringClusteredJobWithRamStore() { + Assertions.fail(); + } +} diff --git a/extensions/quartz/runtime/pom.xml b/extensions/quartz/runtime/pom.xml index cfc00d070b7b5b..af327d6c856127 100644 --- a/extensions/quartz/runtime/pom.xml +++ b/extensions/quartz/runtime/pom.xml @@ -1,71 +1,76 @@ - - quarkus-quartz-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + quarkus-quartz-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 - quarkus-quartz - Quarkus - Scheduler Quartz - Runtime - - - io.quarkus - quarkus-scheduler - - - com.oracle.substratevm - svm - - - org.quartz-scheduler - quartz - - - com.zaxxer - HikariCP-java6 - - - com.mchange - c3p0 - - - - - jakarta.transaction - jakarta.transaction-api - - + quarkus-quartz + Quarkus - Quartz - Runtime + + + io.quarkus + quarkus-agroal + true + + + io.quarkus + quarkus-scheduler + + + com.oracle.substratevm + svm + + + org.quartz-scheduler + quartz + + + com.zaxxer + HikariCP-java6 + + + com.mchange + c3p0 + + + + + jakarta.transaction + jakarta.transaction-api + + - + - - - - io.quarkus - quarkus-bootstrap-maven-plugin - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java new file mode 100644 index 00000000000000..e39fd122baec5b --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java @@ -0,0 +1,78 @@ +package io.quarkus.quartz.runtime; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.enterprise.util.AnnotationLiteral; +import javax.sql.DataSource; + +import org.quartz.utils.PoolingConnectionProvider; + +import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; + +public class QuarkusQuartzConnectionPoolProvider implements PoolingConnectionProvider { + private AgroalDataSource dataSource; + private static String dataSourceName; + + public QuarkusQuartzConnectionPoolProvider() { + final ArcContainer container = Arc.container(); + final InstanceHandle instanceHandle; + final boolean useDefaultDataSource = "QUARKUS_QUARTZ_DEFAULT_DATASOURCE".equals(dataSourceName); + if (useDefaultDataSource) { + instanceHandle = container.instance(AgroalDataSource.class); + } else { + instanceHandle = container.instance(AgroalDataSource.class, new DataSourceLiteral(dataSourceName)); + } + if (instanceHandle.isAvailable()) { + this.dataSource = instanceHandle.get(); + } else { + String message = String.format( + "JDBC Store configured but '%s' datasource is missing. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource", + useDefaultDataSource ? "default" : dataSourceName); + throw new IllegalStateException(message); + } + } + + @Override + public DataSource getDataSource() { + return dataSource; + } + + @Override + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + @Override + public void shutdown() { + // Do nothing as the connection will be closed inside the Agroal extension + } + + @Override + public void initialize() { + + } + + static void setDataSourceName(String dataSourceName) { + QuarkusQuartzConnectionPoolProvider.dataSourceName = dataSourceName; + } + + private static class DataSourceLiteral extends AnnotationLiteral + implements io.quarkus.agroal.DataSource { + + private String name; + + public DataSourceLiteral(String name) { + this.name = name; + } + + @Override + public String value() { + return name; + } + + } +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java new file mode 100644 index 00000000000000..82d863dee448ba --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java @@ -0,0 +1,41 @@ +package io.quarkus.quartz.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED, name = "quartz") +public class QuartzBuildTimeConfig { + /** + * Enable cluster mode or not. + *

+ * If enabled make sure to set the appropriate cluster properties. + */ + @ConfigItem + public boolean clustered; + + /** + * The type of store to use. + *

+ * When using the `db` store type configuration value make sure that you have the datasource configured. + * See Configuring your datasource for more information. + *

+ * To create Quartz tables, you can perform a schema migration via the Flyway + * extension using a SQL script matching your database picked from Quartz + * repository. + */ + @ConfigItem(defaultValue = "ram") + public StoreType storeType; + + /** + * The name of the datasource to use. + *

+ * Optionally needed when using the `db` store type. + * If not specified, defaults to using the default datasource. + */ + @ConfigItem(name = "datasource") + public Optional dataSourceName; +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java index d694e8fe5ac1ae..aa806bb102f275 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRecorder.java @@ -1,14 +1,17 @@ package io.quarkus.quartz.runtime; +import java.util.Optional; + import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.annotations.Recorder; @Recorder public class QuartzRecorder { - public void initialize(QuartzRuntimeConfig runtimeConfig, BeanContainer container) { + public void initialize(QuartzRunTimeConfig runTimeConfig, QuartzBuildTimeConfig buildTimeConfig, BeanContainer container, + Optional driverDialect) { QuartzSupport support = container.instance(QuartzSupport.class); - support.initialize(runtimeConfig); + support.initialize(runTimeConfig, buildTimeConfig, driverDialect); } } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRunTimeConfig.java similarity index 84% rename from extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java rename to extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRunTimeConfig.java index 344fe0b65a88ac..2461e62bcfa476 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRuntimeConfig.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzRunTimeConfig.java @@ -4,8 +4,8 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.RUN_TIME) -public class QuartzRuntimeConfig { +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "quartz") +public class QuartzRunTimeConfig { /** * The size of scheduler thread pool. This will initialize the number of worker threads in the pool. diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java index 1198bc60441a3e..2fcda0954d8077 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzScheduler.java @@ -9,6 +9,8 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.BeforeDestroyed; import javax.enterprise.event.Observes; import javax.inject.Singleton; @@ -17,8 +19,8 @@ import org.quartz.CronScheduleBuilder; import org.quartz.Job; import org.quartz.JobBuilder; +import org.quartz.JobDetail; import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; import org.quartz.ScheduleBuilder; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; @@ -55,7 +57,6 @@ public class QuartzScheduler implements Scheduler { private final Map invokers; public QuartzScheduler(SchedulerSupport schedulerSupport, QuartzSupport quartzSupport, Config config) { - if (schedulerSupport.getScheduledMethods().isEmpty()) { this.triggerNameSequence = null; this.scheduler = null; @@ -66,21 +67,7 @@ public QuartzScheduler(SchedulerSupport schedulerSupport, QuartzSupport quartzSu this.invokers = new HashMap<>(); try { - Properties props = new Properties(); - props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_ID, "QuarkusQuartzScheduler"); - props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "QuarkusQuartzScheduler"); - props.put(StdSchedulerFactory.PROP_SCHED_WRAP_JOB_IN_USER_TX, false); - props.put(StdSchedulerFactory.PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD, - true); - props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); - props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", - "" + quartzSupport.getRuntimeConfig().threadCount); - props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadPriority", - "" + quartzSupport.getRuntimeConfig().threadPriority); - props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".misfireThreshold", "60000"); - props.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, "org.quartz.simpl.RAMJobStore"); - props.put(StdSchedulerFactory.PROP_SCHED_RMI_EXPORT, false); - props.put(StdSchedulerFactory.PROP_SCHED_RMI_PROXY, false); + Properties props = getSchedulerConfigurationProperties(quartzSupport); SchedulerFactory schedulerFactory = new StdSchedulerFactory(props); scheduler = schedulerFactory.getScheduler(); @@ -109,8 +96,9 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler scheduler) thr for (Scheduled scheduled : method.getSchedules()) { String name = triggerNameSequence.getAndIncrement() + "_" + method.getInvokerClassName(); JobBuilder jobBuilder = JobBuilder.newJob(InvokerJob.class) - .withIdentity(name, Scheduler.class.getName()).usingJobData(INVOKER_KEY, - method.getInvokerClassName()); + .withIdentity(name, Scheduler.class.getName()) + .usingJobData(INVOKER_KEY, method.getInvokerClassName()) + .requestRecovery(); ScheduleBuilder scheduleBuilder; String cron = scheduled.cron().trim(); @@ -161,8 +149,13 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler scheduler) thr .plusMillis(scheduled.delayUnit().toMillis(scheduled.delay())).toEpochMilli())); } - scheduler.scheduleJob(jobBuilder.build(), triggerBuilder.build()); - LOGGER.debugf("Scheduled business method %s with config %s", method.getMethodDescription(), scheduled); + JobDetail job = jobBuilder.build(); + if (scheduler.checkExists(job.getKey())) { + scheduler.deleteJob(job.getKey()); + } + scheduler.scheduleJob(job, triggerBuilder.build()); + LOGGER.debugf("Scheduled business method %s with config %s", method.getMethodDescription(), + scheduled); } } } catch (SchedulerException e) { @@ -204,21 +197,76 @@ void start(@Observes StartupEvent startupEvent) { } } + /** + * Need to gracefully shutdown the scheduler making sure that all triggers have been + * released before datasource shutdown. + * + * @param event ignored + */ + void destroy(@BeforeDestroyed(ApplicationScoped.class) Object event) { // + if (scheduler != null) { + try { + scheduler.shutdown(true); // gracefully shutdown + } catch (SchedulerException e) { + LOGGER.warnf("Unable to gracefully shutdown the scheduler", e); + } + } + } + @PreDestroy void destroy() { if (scheduler != null) { try { - scheduler.shutdown(); + if (!scheduler.isShutdown()) { + scheduler.shutdown(false); // force shutdown + } } catch (SchedulerException e) { - LOGGER.warnf("Unable to shutdown scheduler", e); + LOGGER.warnf("Unable to shutdown the scheduler", e); } } } + private Properties getSchedulerConfigurationProperties(QuartzSupport quartzSupport) { + Properties props = new Properties(); + QuartzBuildTimeConfig buildTimeConfig = quartzSupport.getBuildTimeConfig(); + props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_ID, "AUTO"); + props.put("org.quartz.scheduler.skipUpdateCheck", "true"); + props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "QuarkusQuartzScheduler"); + props.put(StdSchedulerFactory.PROP_SCHED_WRAP_JOB_IN_USER_TX, "false"); + props.put(StdSchedulerFactory.PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD, "true"); + props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); + props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadCount", + "" + quartzSupport.getRunTimeConfig().threadCount); + props.put(StdSchedulerFactory.PROP_THREAD_POOL_PREFIX + ".threadPriority", + "" + quartzSupport.getRunTimeConfig().threadPriority); + props.put(StdSchedulerFactory.PROP_SCHED_RMI_EXPORT, "false"); + props.put(StdSchedulerFactory.PROP_SCHED_RMI_PROXY, "false"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, buildTimeConfig.storeType.clazz); + + if (buildTimeConfig.storeType == StoreType.DB) { + String dataSource = buildTimeConfig.dataSourceName.orElse("QUARKUS_QUARTZ_DEFAULT_DATASOURCE"); + QuarkusQuartzConnectionPoolProvider.setDataSourceName(dataSource); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".useProperties", "true"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".misfireThreshold", "60000"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".tablePrefix", "QRTZ_"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".dataSource", dataSource); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".driverDelegateClass", + quartzSupport.getDriverDialect().get()); + props.put(StdSchedulerFactory.PROP_DATASOURCE_PREFIX + "." + dataSource + ".connectionProvider.class", + QuarkusQuartzConnectionPoolProvider.class.getName()); + if (buildTimeConfig.clustered) { + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".isClustered", "true"); + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".clusterCheckinInterval", "20000"); // 20 seconds + } + } + + return props; + } + class InvokerJob implements Job { @Override - public void execute(JobExecutionContext context) throws JobExecutionException { + public void execute(JobExecutionContext context) { Trigger trigger = new Trigger() { @Override @@ -239,23 +287,25 @@ public String getId() { } }; String invokerClass = context.getJobDetail().getJobDataMap().getString(INVOKER_KEY); - invokers.get(invokerClass).invoke(new ScheduledExecution() { - - @Override - public Trigger getTrigger() { - return trigger; - } + ScheduledInvoker scheduledInvoker = invokers.get(invokerClass); + if (scheduledInvoker != null) { // could be null from previous runs + scheduledInvoker.invoke(new ScheduledExecution() { + @Override + public Trigger getTrigger() { + return trigger; + } - @Override - public Instant getScheduledFireTime() { - return context.getScheduledFireTime().toInstant(); - } + @Override + public Instant getScheduledFireTime() { + return context.getScheduledFireTime().toInstant(); + } - @Override - public Instant getFireTime() { - return context.getFireTime().toInstant(); - } - }); + @Override + public Instant getFireTime() { + return context.getFireTime().toInstant(); + } + }); + } } } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java index 53a908533ef6ed..532cad9e50cff0 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzSupport.java @@ -1,18 +1,31 @@ package io.quarkus.quartz.runtime; +import java.util.Optional; + import javax.inject.Singleton; @Singleton public class QuartzSupport { - private QuartzRuntimeConfig runtimeConfig; + private QuartzRunTimeConfig runTimeConfig; + private QuartzBuildTimeConfig buildTimeConfig; + private Optional driverDialect; + + void initialize(QuartzRunTimeConfig runTimeConfig, QuartzBuildTimeConfig buildTimeConfig, Optional driverDialect) { + this.runTimeConfig = runTimeConfig; + this.buildTimeConfig = buildTimeConfig; + this.driverDialect = driverDialect; + } - void initialize(QuartzRuntimeConfig runtimeConfig) { - this.runtimeConfig = runtimeConfig; + public QuartzRunTimeConfig getRunTimeConfig() { + return runTimeConfig; } - public QuartzRuntimeConfig getRuntimeConfig() { - return runtimeConfig; + public QuartzBuildTimeConfig getBuildTimeConfig() { + return buildTimeConfig; } + public Optional getDriverDialect() { + return driverDialect; + } } diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java new file mode 100644 index 00000000000000..fb43eebbc3289b --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java @@ -0,0 +1,17 @@ +package io.quarkus.quartz.runtime; + +import org.quartz.impl.jdbcjobstore.JobStoreTX; +import org.quartz.simpl.RAMJobStore; + +public enum StoreType { + RAM(RAMJobStore.class.getName(), RAMJobStore.class.getSimpleName()), + DB(JobStoreTX.class.getName(), JobStoreTX.class.getSimpleName()); + + public String name; + public String clazz; + + StoreType(String clazz, String name) { + this.clazz = clazz; + this.name = name; + } +} diff --git a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java index c408fb2f1fcd2e..07cc9ced71ee5a 100644 --- a/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/graal/QuartzSubstitutions.java @@ -1,8 +1,11 @@ package io.quarkus.quartz.runtime.graal; +import java.io.ByteArrayOutputStream; import java.rmi.RemoteException; +import java.sql.ResultSet; import org.quartz.core.RemotableQuartzScheduler; +import org.quartz.impl.jdbcjobstore.StdJDBCDelegate; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -37,5 +40,30 @@ protected RemotableQuartzScheduler getRemoteScheduler() { } +@TargetClass(StdJDBCDelegate.class) +final class Target_org_quartz_impl_jdbc_jobstore_StdJDBCDelegate { + + /** + * Activate the usage of {@link java.util.Properties} to avoid Object serialization + * which is not supported by GraalVM - see https://github.com/oracle/graal/issues/460 + * + * @return true + */ + @Substitute + protected boolean canUseProperties() { + return true; + } + + @Substitute + protected ByteArrayOutputStream serializeObject(Object obj) { + throw new IllegalStateException("Object serialization not supported."); // should not reach here + } + + @Substitute + protected Object getObjectFromBlob(ResultSet rs, String colName) { + throw new IllegalStateException("Object serialization not supported."); // should not reach here + } +} + final class QuartzSubstitutions { } diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 7fc88094f682fb..4b5bdea0b3a70a 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -86,6 +86,7 @@ elytron-security-jdbc vertx-graphql jpa-without-entity + quartz diff --git a/integration-tests/quartz/pom.xml b/integration-tests/quartz/pom.xml new file mode 100644 index 00000000000000..d0581b8d737f91 --- /dev/null +++ b/integration-tests/quartz/pom.xml @@ -0,0 +1,131 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-quartz + Quarkus - Integration Tests - Quartz + The Quartz integration test module + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-quartz + + + + io.quarkus + quarkus-agroal + + + + io.quarkus + quarkus-flyway + + + + io.quarkus + quarkus-jdbc-h2 + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-h2 + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + diff --git a/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java new file mode 100644 index 00000000000000..fc0aef6fc913a0 --- /dev/null +++ b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/CountResource.java @@ -0,0 +1,20 @@ +package io.quarkus.it.quartz; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/scheduler/count") +public class CountResource { + + @Inject + Counter counter; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Integer getCount() { + return counter.get(); + } +} diff --git a/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java new file mode 100644 index 00000000000000..f5aa6745f023e2 --- /dev/null +++ b/integration-tests/quartz/src/main/java/io/quarkus/it/quartz/Counter.java @@ -0,0 +1,29 @@ +package io.quarkus.it.quartz; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.scheduler.Scheduled; + +@ApplicationScoped +public class Counter { + + AtomicInteger counter; + + @PostConstruct + void init() { + counter = new AtomicInteger(); + } + + public int get() { + return counter.get(); + } + + @Scheduled(cron = "0/1 * * * * ?") + void increment() { + counter.incrementAndGet(); + } + +} \ No newline at end of file diff --git a/integration-tests/quartz/src/main/resources/application.properties b/integration-tests/quartz/src/main/resources/application.properties new file mode 100644 index 00000000000000..6107a8ad8a3a90 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# datasource configuration +quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.max-size=8 +quarkus.datasource.min-size=2 + +# Quartz configuration +quarkus.quartz.store-type=db +quarkus.quartz.clustered=true + +# flyway to create Quartz tables +quarkus.flyway.connect-retries=10 +quarkus.flyway.table=flyway_quarkus_history +quarkus.flyway.migrate-at-start=true +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.baseline-version=1.0 +quarkus.flyway.baseline-description=Quartz diff --git a/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql b/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql new file mode 100644 index 00000000000000..b362b7e53fc071 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql @@ -0,0 +1,238 @@ +CREATE TABLE QRTZ_CALENDARS ( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR (200) NOT NULL , + CALENDAR IMAGE NOT NULL +); + +CREATE TABLE QRTZ_CRON_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + CRON_EXPRESSION VARCHAR (120) NOT NULL , + TIME_ZONE_ID VARCHAR (80) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR (95) NOT NULL , + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + INSTANCE_NAME VARCHAR (200) NOT NULL , + FIRED_TIME BIGINT NOT NULL , + SCHED_TIME BIGINT NOT NULL , + PRIORITY INTEGER NOT NULL , + STATE VARCHAR (16) NOT NULL, + JOB_NAME VARCHAR (200) NULL , + JOB_GROUP VARCHAR (200) NULL , + IS_NONCONCURRENT BOOLEAN NULL , + REQUESTS_RECOVERY BOOLEAN NULL +); + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR (200) NOT NULL +); + +CREATE TABLE QRTZ_SCHEDULER_STATE ( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR (200) NOT NULL , + LAST_CHECKIN_TIME BIGINT NOT NULL , + CHECKIN_INTERVAL BIGINT NOT NULL +); + +CREATE TABLE QRTZ_LOCKS ( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR (40) NOT NULL +); + +CREATE TABLE QRTZ_JOB_DETAILS ( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR (200) NOT NULL , + JOB_GROUP VARCHAR (200) NOT NULL , + DESCRIPTION VARCHAR (250) NULL , + JOB_CLASS_NAME VARCHAR (250) NOT NULL , + IS_DURABLE BOOLEAN NOT NULL , + IS_NONCONCURRENT BOOLEAN NOT NULL , + IS_UPDATE_DATA BOOLEAN NOT NULL , + REQUESTS_RECOVERY BOOLEAN NOT NULL , + JOB_DATA IMAGE NULL +); + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + REPEAT_COUNT BIGINT NOT NULL , + REPEAT_INTERVAL BIGINT NOT NULL , + TIMES_TRIGGERED BIGINT NOT NULL +); + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INTEGER NULL, + INT_PROP_2 INTEGER NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 BOOLEAN NULL, + BOOL_PROP_2 BOOLEAN NULL +); + +CREATE TABLE QRTZ_BLOB_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + BLOB_DATA IMAGE NULL +); + +CREATE TABLE QRTZ_TRIGGERS ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR (200) NOT NULL , + TRIGGER_GROUP VARCHAR (200) NOT NULL , + JOB_NAME VARCHAR (200) NOT NULL , + JOB_GROUP VARCHAR (200) NOT NULL , + DESCRIPTION VARCHAR (250) NULL , + NEXT_FIRE_TIME BIGINT NULL , + PREV_FIRE_TIME BIGINT NULL , + PRIORITY INTEGER NULL , + TRIGGER_STATE VARCHAR (16) NOT NULL , + TRIGGER_TYPE VARCHAR (8) NOT NULL , + START_TIME BIGINT NOT NULL , + END_TIME BIGINT NULL , + CALENDAR_NAME VARCHAR (200) NULL , + MISFIRE_INSTR SMALLINT NULL , + JOB_DATA IMAGE NULL +); + +ALTER TABLE QRTZ_CALENDARS ADD + CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY + ( + SCHED_NAME, + CALENDAR_NAME + ); + +ALTER TABLE QRTZ_CRON_TRIGGERS ADD + CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_FIRED_TRIGGERS ADD + CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + ENTRY_ID + ); + +ALTER TABLE QRTZ_PAUSED_TRIGGER_GRPS ADD + CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_SCHEDULER_STATE ADD + CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY + ( + SCHED_NAME, + INSTANCE_NAME + ); + +ALTER TABLE QRTZ_LOCKS ADD + CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY + ( + SCHED_NAME, + LOCK_NAME + ); + +ALTER TABLE QRTZ_JOB_DETAILS ADD + CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY + ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ); + +ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD + CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD + CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_TRIGGERS ADD + CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ); + +ALTER TABLE QRTZ_CRON_TRIGGERS ADD + CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + + +ALTER TABLE QRTZ_SIMPLE_TRIGGERS ADD + CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + +ALTER TABLE QRTZ_SIMPROP_TRIGGERS ADD + CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS FOREIGN KEY + ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) REFERENCES QRTZ_TRIGGERS ( + SCHED_NAME, + TRIGGER_NAME, + TRIGGER_GROUP + ) ON DELETE CASCADE; + + +ALTER TABLE QRTZ_TRIGGERS ADD + CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS FOREIGN KEY + ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ) REFERENCES QRTZ_JOB_DETAILS ( + SCHED_NAME, + JOB_NAME, + JOB_GROUP + ); + +COMMIT; diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java new file mode 100644 index 00000000000000..075e767cfb937c --- /dev/null +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzITCase.java @@ -0,0 +1,8 @@ +package io.quarkus.it.quartz; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class QuartzITCase extends QuartzTestCase { + +} diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java new file mode 100644 index 00000000000000..758d80e6e3ab2e --- /dev/null +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/QuartzTestCase.java @@ -0,0 +1,28 @@ +package io.quarkus.it.quartz; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.response.Response; + +@QuarkusTest +public class QuartzTestCase { + + @Test + public void testCount() throws InterruptedException { + // Wait at least 1 second + Thread.sleep(1000); + Response response = given() + .when().get("/scheduler/count"); + String body = response.asString(); + int count = Integer.valueOf(body); + assertTrue(count > 0); + response + .then() + .statusCode(200); + } + +} diff --git a/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java new file mode 100644 index 00000000000000..ecc74f854233f0 --- /dev/null +++ b/integration-tests/quartz/src/test/java/io/quarkus/it/quartz/TestResources.java @@ -0,0 +1,8 @@ +package io.quarkus.it.quartz; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +}