feat(quartz): add clustered jobs support

Fixes #3520
This commit is contained in:
Manyanda Chitimbo
2019-11-21 15:44:46 +01:00
parent 3d79b16a86
commit e7a7a58781
29 changed files with 1097 additions and 175 deletions

View File

@@ -106,6 +106,11 @@
<artifactId>quarkus-agroal-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-artemis-core</artifactId>

View File

@@ -348,6 +348,7 @@ stages:
modules:
- kogito
- kubernetes-client
- quartz
name: misc_3
- template: native-build-steps.yaml

View File

@@ -26,6 +26,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-jta-deployment</artifactId>

View File

@@ -14,6 +14,7 @@
<name>Quarkus - Agroal</name>
<packaging>pom</packaging>
<modules>
<module>spi</module>
<module>deployment</module>
<module>runtime</module>
</modules>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-agroal-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-agroal-spi</artifactId>
<name>Quarkus - Agroal - SPI</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,58 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-quartz-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
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">
<parent>
<artifactId>quarkus-quartz-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-quartz-deployment</artifactId>
<name>Quarkus - Scheduler Quartz - Deployment</name>
<artifactId>quarkus-quartz-deployment</artifactId>
<name>Quarkus - Quartz - Deployment</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-quartz</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-quartz</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,20 @@
package io.quarkus.quartz.deployment;
import java.util.Optional;
import io.quarkus.builder.item.SimpleBuildItem;
/**
* Holds the SQL driver dialect {@link org.quartz.impl.jdbcjobstore.StdJDBCDelegate driver delegate} to use.
*/
final class QuartzJDBCDriverDialectBuildItem extends SimpleBuildItem {
private final Optional<String> driver;
public QuartzJDBCDriverDialectBuildItem(Optional<String> driver) {
this.driver = driver;
}
public Optional<String> getDriver() {
return driver;
}
}

View File

@@ -2,13 +2,27 @@ package io.quarkus.quartz.deployment;
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.quartz.core.QuartzSchedulerThread;
import org.quartz.core.SchedulerSignalerImpl;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.jdbcjobstore.AttributeRestoringConnectionInvocationHandler;
import org.quartz.impl.jdbcjobstore.HSQLDBDelegate;
import org.quartz.impl.jdbcjobstore.JobStoreSupport;
import org.quartz.impl.jdbcjobstore.MSSQLDelegate;
import org.quartz.impl.jdbcjobstore.PostgreSQLDelegate;
import org.quartz.impl.jdbcjobstore.StdJDBCDelegate;
import org.quartz.impl.triggers.AbstractTrigger;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.quartz.simpl.CascadingClassLoadHelper;
import org.quartz.simpl.RAMJobStore;
import org.quartz.simpl.SimpleInstanceIdGenerator;
import org.quartz.simpl.SimpleThreadPool;
import io.quarkus.agroal.deployment.DataSourceDriverBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.deployment.Capabilities;
@@ -17,18 +31,22 @@ import io.quarkus.deployment.annotations.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 +58,118 @@ public class QuartzProcessor {
}
@BuildStep
List<ReflectiveClassBuildItem> reflectiveClasses() {
NativeImageProxyDefinitionBuildItem connectionProxy(QuartzBuildTimeConfig config) {
if (config.storeType.equals(StoreType.DB)) {
return new NativeImageProxyDefinitionBuildItem(Connection.class.getName());
}
return null;
}
@BuildStep
QuartzJDBCDriverDialectBuildItem driver(Optional<DataSourceDriverBuildItem> dataSourceDriver,
QuartzBuildTimeConfig config) {
if (config.storeType == StoreType.RAM) {
if (config.clustered) {
throw new ConfigurationError("Clustered jobs configured with unsupported job store option");
}
return new QuartzJDBCDriverDialectBuildItem(Optional.empty());
}
if (!dataSourceDriver.isPresent()) {
String message = String.format(
"JDBC Store configured but '%s' datasource is not configured properly. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource-guide",
config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default");
throw new ConfigurationError(message);
}
return new QuartzJDBCDriverDialectBuildItem(Optional.of(guessDriver(dataSourceDriver)));
}
private String guessDriver(Optional<DataSourceDriverBuildItem> 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<ReflectiveClassBuildItem> reflectiveClasses(QuartzBuildTimeConfig config,
QuartzJDBCDriverDialectBuildItem driverDialect) {
List<ReflectiveClassBuildItem> reflectiveClasses = new ArrayList<>();
reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName()));
StoreType storeType = config.storeType;
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleThreadPool.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, RAMJobStore.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleInstanceIdGenerator.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(false, false, CascadingClassLoadHelper.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, true, storeType.clazz));
if (storeType.equals(StoreType.DB)) {
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, JobStoreSupport.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, true, Connection.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, AbstractTrigger.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, SimpleTriggerImpl.class.getName()));
reflectiveClasses.add(new ReflectiveClassBuildItem(true, false, driverDialect.getDriver().get()));
reflectiveClasses
.add(new ReflectiveClassBuildItem(true, true, "io.quarkus.quartz.runtime.QuartzScheduler$InvokerJob"));
reflectiveClasses
.add(new ReflectiveClassBuildItem(true, false, QuarkusQuartzConnectionPoolProvider.class.getName()));
}
return reflectiveClasses;
}
@BuildStep
public void logCleanup(BuildProducer<LogCleanupFilterBuildItem> logCleanupFilter) {
logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.impl.StdSchedulerFactory",
public List<LogCleanupFilterBuildItem> logCleanup(QuartzBuildTimeConfig config) {
StoreType storeType = config.storeType;
List<LogCleanupFilterBuildItem> 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<ServiceStartBuildItem> serviceStart) {
recorder.initialize(runtimeConfig, beanContainer.getValue());
public void build(QuartzRuntimeConfig runtimeConfig, QuartzBuildTimeConfig buildTimeConfig, QuartzRecorder recorder,
BeanContainerBuildItem beanContainer,
BuildProducer<ServiceStartBuildItem> serviceStart, QuartzJDBCDriverDialectBuildItem driverDialect) {
recorder.initialize(runtimeConfig, buildTimeConfig, beanContainer.getValue(), driverDialect.getDriver());
// Make sure that StartupEvent is fired after the init
serviceStart.produce(new ServiceStartBuildItem("quartz"));
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,28 @@
package io.quarkus.quartz.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.deployment.configuration.ConfigurationError;
import io.quarkus.test.QuarkusUnitTest;
public class UnsupportedClusteredJobConfigurationTest {
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setExpectedException(ConfigurationError.class)
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(SimpleJobs.class)
.addAsResource(new StringAsset(
"quarkus.quartz.store-type=ram\nquarkus.quartz.clustered=true"),
"application.properties"));
@Test
public void shouldFailWhenConfiguringClusteredJobWithRamStore() {
Assertions.fail();
}
}

View File

@@ -1,71 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-quartz-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-quartz</artifactId>
<name>Quarkus - Scheduler Quartz - Runtime</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.substratevm</groupId>
<artifactId>svm</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java6</artifactId>
</exclusion>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<!--
TODO: Make this an optional dependency?
Graal compiler not happy with broken classpaths
<optional>true</optional>
-->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
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">
<parent>
<artifactId>quarkus-quartz-parent</artifactId>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-quartz</artifactId>
<name>Quarkus - Quartz - Runtime</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.substratevm</groupId>
<artifactId>svm</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java6</artifactId>
</exclusion>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<!--
TODO: Make this an optional dependency?
Graal compiler not happy with broken classpaths
<optional>true</optional>
-->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,78 @@
package io.quarkus.quartz.runtime;
import java.sql.Connection;
import java.sql.SQLException;
import javax.enterprise.util.AnnotationLiteral;
import javax.sql.DataSource;
import org.quartz.utils.PoolingConnectionProvider;
import io.agroal.api.AgroalDataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InstanceHandle;
public class QuarkusQuartzConnectionPoolProvider implements PoolingConnectionProvider {
private AgroalDataSource dataSource;
private static String dataSourceName;
public QuarkusQuartzConnectionPoolProvider() {
final ArcContainer container = Arc.container();
final InstanceHandle<AgroalDataSource> instanceHandle;
final boolean useDefaultDataSource = "QUARKUS_QUARTZ_DEFAULT_DATASOURCE".equals(dataSourceName);
if (useDefaultDataSource) {
instanceHandle = container.instance(AgroalDataSource.class);
} else {
instanceHandle = container.instance(AgroalDataSource.class, new DataSourceLiteral(dataSourceName));
}
if (instanceHandle.isAvailable()) {
this.dataSource = instanceHandle.get();
} else {
String message = String.format(
"JDBC Store configured but '%s' datasource is missing. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource",
useDefaultDataSource ? "default" : dataSourceName);
throw new IllegalStateException(message);
}
}
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void shutdown() {
// Do nothing as the connection will be closed inside the Agroal extension
}
@Override
public void initialize() {
}
static void setDataSourceName(String dataSourceName) {
QuarkusQuartzConnectionPoolProvider.dataSourceName = dataSourceName;
}
private static class DataSourceLiteral extends AnnotationLiteral<io.quarkus.agroal.DataSource>
implements io.quarkus.agroal.DataSource {
private String name;
public DataSourceLiteral(String name) {
this.name = name;
}
@Override
public String value() {
return name;
}
}
}

View File

@@ -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)
public class QuartzBuildTimeConfig {
/**
* Enable cluster mode or not.
* <p>
* If enabled make sure to set the appropriate cluster properties.
*/
@ConfigItem
public boolean clustered;
/**
* The type of store to use.
* <p>
* When using the `db` store type configuration value make sure that you have the datasource configured.
* See <a href="https://quarkus.io/guides/datasource"> Configuring your datasource</a> for more information.
* <p>
* To create Quartz tables, you can perform a schema migration via the <a href="https://quarkus.io/guides/flyway"> Flyway
* extension</a> using a SQL script matching your database picked from <a href=
* "https://github.com/quartz-scheduler/quartz/blob/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore">Quartz
* repository</a>.
*/
@ConfigItem(defaultValue = "ram")
public StoreType storeType;
/**
* The name of the datasource to use.
* <p>
* Optionally needed when using the `db` store type.
* If not specified, defaults to using the default datasource.
*/
@ConfigItem(name = "datasource")
public Optional<String> dataSourceName;
}

View File

@@ -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<String> driverDialect) {
QuartzSupport support = container.instance(QuartzSupport.class);
support.initialize(runtimeConfig);
support.initialize(runTimeConfig, buildTimeConfig, driverDialect);
}
}

View File

@@ -9,6 +9,8 @@ import java.util.Properties;
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.jboss.logging.Logger;
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<String, ScheduledInvoker> invokers;
public QuartzScheduler(SchedulerSupport schedulerSupport, QuartzSupport quartzSupport, Config config) {
if (schedulerSupport.getScheduledMethods().isEmpty()) {
this.triggerNameSequence = null;
this.scheduler = null;
@@ -66,21 +67,7 @@ public class QuartzScheduler implements Scheduler {
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 class QuartzScheduler implements Scheduler {
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 class QuartzScheduler implements Scheduler {
.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 @@ public class QuartzScheduler implements Scheduler {
}
}
/**
* 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 class QuartzScheduler implements Scheduler {
}
};
String invokerClass = context.getJobDetail().getJobDataMap().getString(INVOKER_KEY);
invokers.get(invokerClass).invoke(new ScheduledExecution() {
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 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();
}
});
}
}
}

View File

@@ -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<String> driverDialect;
void initialize(QuartzRuntimeConfig runtimeConfig) {
this.runtimeConfig = runtimeConfig;
void initialize(QuartzRuntimeConfig runTimeConfig, QuartzBuildTimeConfig buildTimeConfig, Optional<String> driverDialect) {
this.runtimeConfig = runTimeConfig;
this.buildTimeConfig = buildTimeConfig;
this.driverDialect = driverDialect;
}
public QuartzRuntimeConfig getRuntimeConfig() {
return runtimeConfig;
}
public QuartzBuildTimeConfig getBuildTimeConfig() {
return buildTimeConfig;
}
public Optional<String> getDriverDialect() {
return driverDialect;
}
}

View File

@@ -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;
}
}

View File

@@ -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 @@ final class Target_org_quartz_impl_RemoteScheduler {
}
@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 {
}

View File

@@ -86,6 +86,7 @@
<module>elytron-security-jdbc</module>
<module>vertx-graphql</module>
<module>jpa-without-entity</module>
<module>quartz</module>
</modules>
<build>

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-integration-tests-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-integration-test-quartz</artifactId>
<name>Quarkus - Integration Tests - Quartz</name>
<description>The Quartz integration test module</description>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-quartz</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native-image</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemProperties>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<id>native-image</id>
<goals>
<goal>native-image</goal>
</goals>
<configuration>
<cleanupServer>true</cleanupServer>
<enableHttpUrlHandler>true</enableHttpUrlHandler>
<graalvmHome>${graalvmHome}</graalvmHome>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -0,0 +1,8 @@
package io.quarkus.it.quartz;
import io.quarkus.test.junit.NativeImageTest;
@NativeImageTest
public class QuartzITCase extends QuartzTestCase {
}

View File

@@ -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);
}
}

View File

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