Skip to content

Commit

Permalink
Merge pull request quarkusio#44064 from manovotn/quartzDriverDelegate
Browse files Browse the repository at this point in the history
Quartz - add configuration option for custom JDBC delegate option
  • Loading branch information
mkouba authored Oct 25, 2024
2 parents 75fddfc + 1730f70 commit 5100063
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;

import jakarta.inject.Singleton;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobListener;
Expand Down Expand Up @@ -46,6 +49,7 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
Expand All @@ -70,6 +74,11 @@
public class QuartzProcessor {

private static final DotName JOB = DotName.createSimple(Job.class.getName());
private static final DotName DELEGATE_POSTGRESQL = DotName.createSimple(QuarkusPostgreSQLDelegate.class.getName());
private static final DotName DELEGATE_DB2V8 = DotName.createSimple(QuarkusDBv8Delegate.class.getName());
private static final DotName DELEGATE_HSQLDB = DotName.createSimple(QuarkusHSQLDBDelegate.class.getName());
private static final DotName DELEGATE_MSSQL = DotName.createSimple(QuarkusMSSQLDelegate.class.getName());
private static final DotName DELEGATE_STDJDBC = DotName.createSimple(QuarkusStdJDBCDelegate.class.getName());

@BuildStep
FeatureBuildItem feature() {
Expand Down Expand Up @@ -103,8 +112,7 @@ NativeImageProxyDefinitionBuildItem connectionProxy(QuartzBuildTimeConfig config

@BuildStep
QuartzJDBCDriverDialectBuildItem driver(List<JdbcDataSourceBuildItem> jdbcDataSourceBuildItems,
QuartzBuildTimeConfig config,
Capabilities capabilities) {
QuartzBuildTimeConfig config, Capabilities capabilities, CombinedIndexBuildItem indexBuildItem) {
if (!config.storeType.isDbStore()) {
if (config.clustered) {
throw new ConfigurationException("Clustered jobs configured with unsupported job store option");
Expand All @@ -118,19 +126,52 @@ QuartzJDBCDriverDialectBuildItem driver(List<JdbcDataSourceBuildItem> jdbcDataSo
"The Agroal extension is missing and it is required when a Quartz JDBC store is used.");
}

Optional<JdbcDataSourceBuildItem> selectedJdbcDataSourceBuildItem = jdbcDataSourceBuildItems.stream()
.filter(i -> config.dataSourceName.isPresent() ? config.dataSourceName.get().equals(i.getName())
: i.isDefault())
.findFirst();

if (!selectedJdbcDataSourceBuildItem.isPresent()) {
String message = String.format(
"JDBC Store configured but the '%s' datasource is not configured properly. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource",
config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default");
throw new ConfigurationException(message);
Optional<String> driverDelegate = config.driverDelegate;
if (driverDelegate.isPresent()) {
// user-specified custom delegate
IndexView indexView = indexBuildItem.getIndex();
ClassInfo customDelegate = indexView.getClassByName(driverDelegate.get());
if (customDelegate == null) {
String message = String.format(
"Custom JDBC delegate implementation class '%s' was not found in Jandex index. " +
"Make sure the dependency containing this class has proper marker file enabling discovery. " +
"Alternatively, you can index a dependency using IndexDependencyBuildItem.",
driverDelegate.get());
throw new ConfigurationException(message);
} else {
// any custom implementation needs to be a subclass of known Quarkus delegate
boolean implementsKnownDelegate = false;
for (DotName knownImplementation : Set.of(DELEGATE_MSSQL, DELEGATE_POSTGRESQL, DELEGATE_DB2V8, DELEGATE_STDJDBC,
DELEGATE_HSQLDB)) {
for (ClassInfo classInfo : indexView.getAllKnownSubclasses(knownImplementation)) {
if (classInfo.name().equals(customDelegate.name())) {
implementsKnownDelegate = true;
break;
}
}
}
if (!implementsKnownDelegate) {
String message = String.format(
"Custom JDBC delegate implementation with name '%s' needs to be a subclass of one of the existing Quarkus delegates such as io.quarkus.quartz.runtime.jdbc.QuarkusPostgreSQLDelegate.",
driverDelegate.get());
throw new ConfigurationException(message);
}
}
} else {
Optional<JdbcDataSourceBuildItem> selectedJdbcDataSourceBuildItem = jdbcDataSourceBuildItems.stream()
.filter(i -> config.dataSourceName.isPresent() ? config.dataSourceName.get().equals(i.getName())
: i.isDefault())
.findFirst();

if (!selectedJdbcDataSourceBuildItem.isPresent()) {
String message = String.format(
"JDBC Store configured but the '%s' datasource is not configured properly. You can configure your datasource by following the guide available at: https://quarkus.io/guides/datasource",
config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default");
throw new ConfigurationException(message);
}
driverDelegate = Optional.of(guessDriver(selectedJdbcDataSourceBuildItem));
}

return new QuartzJDBCDriverDialectBuildItem(Optional.of(guessDriver(selectedJdbcDataSourceBuildItem)));
return new QuartzJDBCDriverDialectBuildItem(driverDelegate);
}

private String guessDriver(Optional<JdbcDataSourceBuildItem> jdbcDataSource) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.quartz.test.customDelegate;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.function.Consumer;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
import io.quarkus.quartz.test.SimpleJobs;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.test.QuarkusUnitTest;

public class DelegateNotASubclassTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
// add a mock pretending to provide Agroal Capability to pass our validation
.addBuildChainCustomizer(new Consumer<>() {
@Override
public void accept(BuildChainBuilder buildChainBuilder) {
buildChainBuilder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(
new CapabilityBuildItem(Capability.AGROAL, "fakeProvider"));
}
}).produces(CapabilityBuildItem.class).build();
}
})
.assertException(t -> {
assertEquals(ConfigurationException.class, t.getClass());
Assertions.assertTrue(t.getMessage().contains(
"Custom JDBC delegate implementation with name 'io.quarkus.quartz.test.customDelegate.InvalidDelegate' needs to be a subclass"));
})
.withApplicationRoot((jar) -> jar
.addClasses(SimpleJobs.class, InvalidDelegate.class)
.addAsResource(new StringAsset(
"quarkus.quartz.driver-delegate=io.quarkus.quartz.test.customDelegate.InvalidDelegate\nquarkus.quartz.store-type=jdbc-cmt"),
"application.properties"));

@Test
public void shouldFailIfNotASubclass() {
Assertions.fail();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.quartz.test.customDelegate;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.function.Consumer;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
import io.quarkus.quartz.test.SimpleJobs;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.test.QuarkusUnitTest;

public class DelegateNotIndexedTest {

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
// add a mock pretending to provide Agroal Capability to pass our validation
.addBuildChainCustomizer(new Consumer<>() {
@Override
public void accept(BuildChainBuilder buildChainBuilder) {
buildChainBuilder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(
new CapabilityBuildItem(Capability.AGROAL, "fakeProvider"));
}
}).produces(CapabilityBuildItem.class).build();
}
})
.assertException(t -> {
assertEquals(ConfigurationException.class, t.getClass());
Assertions.assertTrue(t.getMessage().contains(
"Custom JDBC delegate implementation class 'org.acme.DoesNotExist' was not found in Jandex index"));
})
.withApplicationRoot((jar) -> jar
.addClasses(SimpleJobs.class)
.addAsResource(new StringAsset(
"quarkus.quartz.driver-delegate=org.acme.DoesNotExist\nquarkus.quartz.store-type=jdbc-cmt"),
"application.properties"));

@Test
public void shouldFailWhenNotIndexed() {
Assertions.fail();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.quarkus.quartz.test.customDelegate;

// dummy class representing an invalid JDBC delegate by not subclassing a known one
public class InvalidDelegate {
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ public class QuartzBuildTimeConfig {
@ConfigItem
public Optional<String> selectWithLockSql;

/**
* Allows users to specify fully qualified class name for a custom JDBC driver delegate.
* <p>
* This property is optional and leaving it empty will result in Quarkus automatically choosing appropriate default
* driver delegate implementation.
* <p>
* Note that any custom implementation has to be a subclass of existing Quarkus implementation such as
* {@link io.quarkus.quartz.runtime.jdbc.QuarkusPostgreSQLDelegate} or
* {@link io.quarkus.quartz.runtime.jdbc.QuarkusMSSQLDelegate}
*/
@ConfigItem
public Optional<String> driverDelegate;

/**
* Instructs JDBCJobStore to serialize JobDataMaps in the BLOB column.
* <p>
Expand Down

0 comments on commit 5100063

Please sign in to comment.