+ 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 {
+}