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 379d994768448..b4f92ee56e739 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/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 a4ce15b61814f..907cfa0a380cc 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 @@ -5,8 +5,10 @@ import java.util.ArrayList; import java.util.List; +import org.quartz.impl.jdbcjobstore.JobStoreSupport; +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.SimpleThreadPool; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -19,10 +21,13 @@ import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; 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 @@ -40,31 +45,45 @@ AdditionalBeanBuildItem beans() { } @BuildStep - List reflectiveClasses() { + List reflectiveClasses(QuartzBuildTimeConfig config) { List reflectiveClasses = new ArrayList<>(); - reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName())); + StoreType storeType = config.store.type; + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleThreadPool.class.getName())); - reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, RAMJobStore.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, false, AbstractTrigger.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleTriggerImpl.class.getName())); + reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, config.store.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) { + public void logCleanup(BuildProducer logCleanupFilter, QuartzBuildTimeConfig config) { logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.impl.StdSchedulerFactory", "Quartz scheduler version:", // no need to log if it's the default "Using default implementation for", - "Quartz scheduler 'DefaultQuartzScheduler'")); + "Quartz scheduler 'QuarkusQuartzScheduler'")); logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.QuartzScheduler", "Quartz Scheduler v", "JobFactory set to:", "Scheduler meta-data:", // no need to log if it's the default - "Scheduler DefaultQuartzScheduler")); + "Scheduler QuarkusQuartzScheduler")); - logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.simpl.RAMJobStore", - "RAMJobStore initialized.")); + StoreType storeType = config.store.type; + logCleanupFilter.produce(new LogCleanupFilterBuildItem(storeType.clazz, storeType.name + " initialized.", "Handling")); logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.SchedulerSignalerImpl", "Initialized Scheduler Signaller of type")); @@ -72,9 +91,10 @@ public void logCleanup(BuildProducer logCleanupFilter @BuildStep @Record(RUNTIME_INIT) - public void build(QuartzRuntimeConfig runtimeConfig, QuartzRecorder recorder, BeanContainerBuildItem beanContainer, + public void build(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig, QuartzRecorder recorder, + BeanContainerBuildItem beanContainer, BuildProducer serviceStart) { - recorder.initialize(runtimeConfig, beanContainer.getValue()); + recorder.initialize(runtimeConfig, buildTimeConfig, beanContainer.getValue()); // 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/clustered/NoDefaultDataSourceTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/clustered/NoDefaultDataSourceTest.java new file mode 100644 index 0000000000000..dba2fc9ca5a9e --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/clustered/NoDefaultDataSourceTest.java @@ -0,0 +1,32 @@ +package io.quarkus.quartz.test.clustered; + +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.quartz.test.SimpleJobs; +import io.quarkus.test.QuarkusUnitTest; + +public class NoDefaultDataSourceTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setExpectedException(IllegalStateException.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleJobs.class) + .addAsResource(new StringAsset("simpleJobs.cron=0/1 * * * * ?\nsimpleJobs.every=1s" + + "\nquarkus.quartz.store.type=db\n"), + "application.properties")); + + @Test + public void shouldFailBecauseOfMissingDataSource() { + /** + * Should not reach here + */ + Assertions.fail(); + } + +} diff --git a/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/clustered/NoNamedDataSourceTest.java b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/clustered/NoNamedDataSourceTest.java new file mode 100644 index 0000000000000..071dd3613289b --- /dev/null +++ b/extensions/quartz/deployment/src/test/java/io/quarkus/quartz/test/clustered/NoNamedDataSourceTest.java @@ -0,0 +1,32 @@ +package io.quarkus.quartz.test.clustered; + +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.quartz.test.SimpleJobs; +import io.quarkus.test.QuarkusUnitTest; + +public class NoNamedDataSourceTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setExpectedException(IllegalStateException.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleJobs.class) + .addAsResource(new StringAsset("simpleJobs.cron=0/1 * * * * ?\nsimpleJobs.every=1s" + + "\nquarkus.quartz.store.type=db\nquarkus.quartz.store.datasource=named-datasource\n"), + "application.properties")); + + @Test + public void shouldFailBecauseOfMissingDataSource() { + /** + * Should not reach here + */ + Assertions.fail(); + } + +} 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..aeb75318b54ad --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuarkusQuartzConnectionPoolProvider.java @@ -0,0 +1,66 @@ +package io.quarkus.quartz.runtime; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.quartz.utils.PoolingConnectionProvider; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; + +public class QuarkusQuartzConnectionPoolProvider implements PoolingConnectionProvider { + private DataSource 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(DataSource.class); + } else { + instanceHandle = container.instance(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 it will be closed inside Agroal extension ? + } + + @Override + public void initialize() { + + } + + static void setDataSourceName(String dataSourceName) { + QuarkusQuartzConnectionPoolProvider.dataSourceName = dataSourceName; + } +} 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..053df07b944eb --- /dev/null +++ b/extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java @@ -0,0 +1,67 @@ +package io.quarkus.quartz.runtime; + +import java.util.Optional; + +import org.quartz.impl.jdbcjobstore.DriverDelegate; + +import io.quarkus.runtime.annotations.ConfigGroup; +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; + + /** + * Scheduler job store configuration + */ + @ConfigItem + public SchedulerJobStoreConfig store; + + @ConfigGroup + public static class SchedulerJobStoreConfig { + /** + * The Quartz {@link DriverDelegate} class to use. + *

+ * Optionally needed when using the `db` store type {@link SchedulerJobStoreConfig#type}. + *

+ * The delegate class must correspond to the type of driver in use. + *
+ * Configuration + * of JDBC-Store + */ + @ConfigItem(name = "driver.delegate", defaultValue = "org.quartz.impl.jdbcjobstore.StdJDBCDelegate") + public Optional driver; + + /** + * The name of the datasource to use. + * Optionally needed when using the `db` store type {@link SchedulerJobStoreConfig#type}. + * If not specified default to using the default datasource. + */ + @ConfigItem(name = "datasource") + public Optional dataSource; + + /** + * 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 driver. + * + */ + @ConfigItem(defaultValue = "ram") + public StoreType type; + } +} \ No newline at end of file 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..6990ed987c96f 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 @@ -6,9 +6,9 @@ @Recorder public class QuartzRecorder { - public void initialize(QuartzRuntimeConfig runtimeConfig, BeanContainer container) { + public void initialize(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig, BeanContainer container) { QuartzSupport support = container.instance(QuartzSupport.class); - support.initialize(runtimeConfig); + support.initialize(runtimeConfig, buildTimeConfig); } } 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 2ca2cd3515b80..13f1a92c26dcb 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..2b35823af79a4 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(); @@ -161,7 +148,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 +195,78 @@ 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, "QuarkusQuartzScheduler"); + 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); + QuartzBuildTimeConfig.SchedulerJobStoreConfig store = buildTimeConfig.store; + props.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, store.type.clazz); + + if (store.type == StoreType.DB) { + String dataSource = buildTimeConfig.store.dataSource.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", store.driver.get()); + props.put(StdSchedulerFactory.PROP_DATASOURCE_PREFIX + "." + dataSource + ".connectionProvider.class", + QuarkusQuartzConnectionPoolProvider.class.getName()); + if (buildTimeConfig.clustered.isPresent()) { + props.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_ID, String.valueOf(System.nanoTime())); // instance ID need to be unique + props.put(StdSchedulerFactory.PROP_JOB_STORE_PREFIX + ".isClustered", + buildTimeConfig.clustered.get().booleanValue()); + 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..923b90270e581 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 @@ -6,13 +6,18 @@ public class QuartzSupport { private QuartzRuntimeConfig runtimeConfig; + private QuartzBuildTimeConfig buildTimeConfig; - void initialize(QuartzRuntimeConfig runtimeConfig) { + void initialize(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig) { this.runtimeConfig = runtimeConfig; + this.buildTimeConfig = buildTimeConfig; } public QuartzRuntimeConfig getRuntimeConfig() { return runtimeConfig; } + public QuartzBuildTimeConfig getBuildTimeConfig() { + return buildTimeConfig; + } } 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..be8c193c30616 --- /dev/null +++ b/integration-tests/quartz/src/main/resources/application.properties @@ -0,0 +1,16 @@ +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 + +quarkus.quartz.store.type=db +quarkus.quartz.clustered=true +quarkus.quartz.store.driver.delegate=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate + +# 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); + } + +}