From 58bdb988ca98e764a275af866374f1ca3ac2b9a0 Mon Sep 17 00:00:00 2001 From: Manyanda Chitimbo Date: Mon, 18 Nov 2019 12:43:14 +0100 Subject: [PATCH] feat(quartz): add clustered jobs support Fixes #3520 --- bom/deployment/pom.xml | 5 + ci-templates/jvm-build-steps.yaml | 2 +- ci-templates/native-build-steps.yaml | 2 +- ci-templates/stages.yml | 4 +- extensions/agroal/deployment/pom.xml | 8 + extensions/agroal/pom.xml | 1 + extensions/agroal/spi/pom.xml | 23 ++ .../deployment/DataSourceDriverBuildItem.java | 0 .../DataSourceInitializedBuildItem.java | 0 extensions/quartz/deployment/pom.xml | 104 ++++----- .../quartz/deployment/QuartzProcessor.java | 130 +++++++++-- .../quartz/test/MissingDataSourceTest.java | 26 +++ extensions/quartz/runtime/pom.xml | 7 +- .../QuarkusQuartzConnectionPoolProvider.java | 84 +++++++ .../quartz/runtime/QuartzBuildTimeConfig.java | 42 ++++ .../quartz/runtime/QuartzRecorder.java | 7 +- .../quartz/runtime/QuartzRuntimeConfig.java | 2 +- .../quartz/runtime/QuartzScheduler.java | 124 +++++++---- .../quarkus/quartz/runtime/QuartzSupport.java | 15 +- .../io/quarkus/quartz/runtime/StoreType.java | 17 ++ .../runtime/graal/QuartzSubstitutions.java | 28 +++ integration-tests/pom.xml | 1 + integration-tests/quartz/README.md | 13 ++ integration-tests/quartz/pom.xml | 174 +++++++++++++++ .../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 | 207 ++++++++++++++++++ .../io/quarkus/it/quartz/QuartzITCase.java | 8 + .../io/quarkus/it/quartz/QuartzTestCase.java | 28 +++ 30 files changed, 1018 insertions(+), 110 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/test/java/io/quarkus/quartz/test/MissingDataSourceTest.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 create mode 100644 extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/StoreType.java create mode 100644 integration-tests/quartz/README.md 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 diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index 4892fd35d11e8..3122af60f158f 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/jvm-build-steps.yaml b/ci-templates/jvm-build-steps.yaml index 0cde1f96232cd..0600484dea05f 100644 --- a/ci-templates/jvm-build-steps.yaml +++ b/ci-templates/jvm-build-steps.yaml @@ -29,5 +29,5 @@ steps: goals: 'install' mavenOptions: $(MAVEN_OPTS) jdkVersionOption: ${{ parameters.jdk }} - options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dtest-mysql -Dtest-dynamodb -Dtest-vault -Dno-format ${{ parameters.extraf }}' + options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dtest-mysql -Dtest-dynamodb -Dtest-vault -Dtest-quartz -Dno-format ${{ parameters.extraf }}' diff --git a/ci-templates/native-build-steps.yaml b/ci-templates/native-build-steps.yaml index 68c9206056df9..6e557eb4b89d8 100644 --- a/ci-templates/native-build-steps.yaml +++ b/ci-templates/native-build-steps.yaml @@ -49,4 +49,4 @@ jobs: inputs: goals: 'install' mavenOptions: $(MAVEN_OPTS) - options: '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dnative-image.xmx=6g -Dnative -Dno-format' + options: '-pl integration-tests/${{ join('',integration-tests/'', parameters.modules) }} -B --settings azure-mvn-settings.xml -Dquarkus.native.container-build=true -Dtest-postgresql -Dtest-elasticsearch -Dtest-keycloak -Ddocker-keycloak -Dtest-dynamodb -Dtest-mysql -Dtest-vault -Dtest-quartz -Dnative-image.xmx=6g -Dnative -Dno-format' diff --git a/ci-templates/stages.yml b/ci-templates/stages.yml index 31fe1ac3153c3..1f6bb6010b593 100644 --- a/ci-templates/stages.yml +++ b/ci-templates/stages.yml @@ -186,7 +186,9 @@ stages: parameters: poolSettings: ${{parameters.poolSettings}} expectUseVMs: ${{parameters.expectUseVMs}} - modules: main + modules: + - main + - quartz name: main postgres: true diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index 03167b34a1efd..235f977cdc7d9 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 @@ -46,6 +50,10 @@ quarkus-test-h2 test + + io.quarkus + quarkus-agroal-spi + diff --git a/extensions/agroal/pom.xml b/extensions/agroal/pom.xml index 47b4f6984e30d..4eb57e49b3eb2 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 0000000000000..dca7328ae8c8b --- /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 5e0e82e4636f5..dc8194724e74f 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 - Scheduler 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/QuartzProcessor.java b/extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java index 220de421ecee0..fca18dd9760a3 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,33 +2,52 @@ 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.builder.item.SimpleBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; 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.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 +59,122 @@ 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) { + 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.driver.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.driver); // Make sure that StartupEvent is fired after the init serviceStart.produce(new ServiceStartBuildItem("quartz")); } + private final static class QuartzJDBCDriverDialectBuildItem extends SimpleBuildItem { + private final Optional driver; + + public QuartzJDBCDriverDialectBuildItem(Optional driver) { + this.driver = driver; + } + } } 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 0000000000000..04a1694bdfbc9 --- /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/runtime/pom.xml b/extensions/quartz/runtime/pom.xml index cfc00d070b7b5..bd6425d4715f2 100644 --- a/extensions/quartz/runtime/pom.xml +++ b/extensions/quartz/runtime/pom.xml @@ -15,8 +15,13 @@ io.quarkus - quarkus-scheduler + quarkus-agroal + true + + io.quarkus + quarkus-scheduler + com.oracle.substratevm svm 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 0000000000000..94cffd9e7d4dc --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java @@ -0,0 +1,84 @@ +package io.quarkus.quartz.runtime; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +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() { + InstanceHandle instanceHandle; + ArcContainer container = Arc.container(); + 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-guide", + useDefaultDataSource ? "default" : dataSourceName); + throw new IllegalStateException(message); + } + } + + @SuppressWarnings("unused") + public QuarkusQuartzConnectionPoolProvider(Properties properties) { + this(); + } + + @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 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 0000000000000..f967276044940 --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java @@ -0,0 +1,42 @@ +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(name = "quartz", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class QuartzBuildTimeConfig { + /** + * Enable cluster mode or not. + *

+ * If enabled make sure to set the appropriate cluster properties. + */ + @ConfigItem + public Optional clustered; + + /** + * The type of store to use. Possible values are: `ram`, `db`. + *

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

+ * The Quarkus scheduler does not create the necessary scheduling tables in database automatically. + * To create Quartz tables, visit Quartz + * table creation scripts and pick a script file corresponding to your database in use. + * + */ + @ConfigItem(defaultValue = "ram") + public StoreType storeType; + + /** + * The name of the datasource to use. + *

+ * Optionally needed when using the `db` store type {@link #storeType}. + * If not specified default 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 d694e8fe5ac1a..613e0d0a7b3c9 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 index 344fe0b65a88a..458c333738fd9 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,7 +4,7 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.RUN_TIME) +@ConfigRoot(name = "quartz", phase = ConfigPhase.RUN_TIME) public class QuartzRuntimeConfig { /** 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 1198bc60441a3..9272c0f43600b 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,7 +149,11 @@ public Job newJob(TriggerFiredBundle bundle, org.quartz.Scheduler scheduler) thr .plusMillis(scheduled.delayUnit().toMillis(scheduled.delay())).toEpochMilli())); } - scheduler.scheduleJob(jobBuilder.build(), triggerBuilder.build()); + 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); } } @@ -204,21 +196,77 @@ 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 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); } } } + 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.isPresent()) { + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".isClustered", + String.valueOf(buildTimeConfig.clustered.get())); + 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 53a908533ef6e..56e0495bdb5ae 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 QuartzBuildTimeConfig buildTimeConfig; + private Optional driverDialect; - void initialize(QuartzRuntimeConfig runtimeConfig) { + void initialize(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig, Optional driverDialect) { this.runtimeConfig = runtimeConfig; + this.buildTimeConfig = buildTimeConfig; + this.driverDialect = driverDialect; } 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 0000000000000..fb43eebbc3289 --- /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 c408fb2f1fcd2..07cc9ced71ee5 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 7fc88094f682f..4b5bdea0b3a70 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/README.md b/integration-tests/quartz/README.md new file mode 100644 index 0000000000000..686b34739710e --- /dev/null +++ b/integration-tests/quartz/README.md @@ -0,0 +1,13 @@ +# Quartz datastore example + +## Running the tests + +By default, the tests of this module are disabled. + +To run the tests in a standard JVM, you have to start your own PostgreSQL server and it needs to listen on the default port and have a database called `hibernate_orm_test` accessible to the user `hibernate_orm_test` with the password `hibernate_orm_test`. + +You can then run the tests as follows (either with `-Dnative` or not): + +``` +mvn clean install -Dtest-quartz +``` \ No newline at end of file diff --git a/integration-tests/quartz/pom.xml b/integration-tests/quartz/pom.xml new file mode 100644 index 0000000000000..9e843677eab78 --- /dev/null +++ b/integration-tests/quartz/pom.xml @@ -0,0 +1,174 @@ + + + + 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-jdbc-postgresql + + + + io.quarkus + quarkus-agroal + + + + io.quarkus + quarkus-flyway + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-h2 + test + + + io.rest-assured + rest-assured + test + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + + + + + test-quartz + + + test-quartz + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + + + + native-image-it-quartz + + + 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 + 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 0000000000000..fc0aef6fc913a --- /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 0000000000000..f5aa6745f023e --- /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 0000000000000..3a4b82d161f96 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# datasource configuration +quarkus.datasource.url=jdbc:postgresql://localhost/hibernate_orm_test +quarkus.datasource.username=hibernate_orm_test +quarkus.datasource.password=hibernate_orm_test +quarkus.datasource.driver=org.postgresql.Driver + +# 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 0000000000000..417eb703f6538 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/db/migration/V1.0.1__QuarkusQuartz.sql @@ -0,0 +1,207 @@ +-- Thanks to Patrick Lightbody for submitting this... +-- +-- In your Quartz properties file, you'll need to set +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate + +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +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 BOOL NOT NULL, + IS_NONCONCURRENT BOOL NOT NULL, + IS_UPDATE_DATA BOOL NOT NULL, + REQUESTS_RECOVERY BOOL NOT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +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 BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) + REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +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, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +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), + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +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 INT NULL, + INT_PROP_2 INT 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 BOOL NULL, + BOOL_PROP_2 BOOL NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +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 BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CALENDARS +( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR(200) NOT NULL, + CALENDAR BYTEA NOT NULL, + PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) +); + + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) +); + +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 BOOL NULL, + REQUESTS_RECOVERY BOOL NULL, + PRIMARY KEY (SCHED_NAME, ENTRY_ID) +); + +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, + PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) +); + +CREATE TABLE QRTZ_LOCKS +( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR(40) NOT NULL, + PRIMARY KEY (SCHED_NAME, LOCK_NAME) +); + +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY + ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP + ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C + ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME + ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_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 0000000000000..075e767cfb937 --- /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 0000000000000..758d80e6e3ab2 --- /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); + } + +}