Skip to content

Commit 5100063

Browse files
authored
Merge pull request #44064 from manovotn/quartzDriverDelegate
Quartz - add configuration option for custom JDBC delegate option
2 parents 75fddfc + 1730f70 commit 5100063

File tree

5 files changed

+179
-14
lines changed

5 files changed

+179
-14
lines changed

extensions/quartz/deployment/src/main/java/io/quarkus/quartz/deployment/QuartzProcessor.java

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
import java.util.List;
99
import java.util.Map;
1010
import java.util.Optional;
11+
import java.util.Set;
1112
import java.util.logging.Level;
1213

1314
import jakarta.inject.Singleton;
1415

16+
import org.jboss.jandex.ClassInfo;
1517
import org.jboss.jandex.DotName;
18+
import org.jboss.jandex.IndexView;
1619
import org.quartz.Job;
1720
import org.quartz.JobDataMap;
1821
import org.quartz.JobListener;
@@ -46,6 +49,7 @@
4649
import io.quarkus.deployment.annotations.BuildProducer;
4750
import io.quarkus.deployment.annotations.BuildStep;
4851
import io.quarkus.deployment.annotations.Record;
52+
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
4953
import io.quarkus.deployment.builditem.FeatureBuildItem;
5054
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
5155
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
@@ -70,6 +74,11 @@
7074
public class QuartzProcessor {
7175

7276
private static final DotName JOB = DotName.createSimple(Job.class.getName());
77+
private static final DotName DELEGATE_POSTGRESQL = DotName.createSimple(QuarkusPostgreSQLDelegate.class.getName());
78+
private static final DotName DELEGATE_DB2V8 = DotName.createSimple(QuarkusDBv8Delegate.class.getName());
79+
private static final DotName DELEGATE_HSQLDB = DotName.createSimple(QuarkusHSQLDBDelegate.class.getName());
80+
private static final DotName DELEGATE_MSSQL = DotName.createSimple(QuarkusMSSQLDelegate.class.getName());
81+
private static final DotName DELEGATE_STDJDBC = DotName.createSimple(QuarkusStdJDBCDelegate.class.getName());
7382

7483
@BuildStep
7584
FeatureBuildItem feature() {
@@ -103,8 +112,7 @@ NativeImageProxyDefinitionBuildItem connectionProxy(QuartzBuildTimeConfig config
103112

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

121-
Optional<JdbcDataSourceBuildItem> selectedJdbcDataSourceBuildItem = jdbcDataSourceBuildItems.stream()
122-
.filter(i -> config.dataSourceName.isPresent() ? config.dataSourceName.get().equals(i.getName())
123-
: i.isDefault())
124-
.findFirst();
125-
126-
if (!selectedJdbcDataSourceBuildItem.isPresent()) {
127-
String message = String.format(
128-
"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",
129-
config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default");
130-
throw new ConfigurationException(message);
129+
Optional<String> driverDelegate = config.driverDelegate;
130+
if (driverDelegate.isPresent()) {
131+
// user-specified custom delegate
132+
IndexView indexView = indexBuildItem.getIndex();
133+
ClassInfo customDelegate = indexView.getClassByName(driverDelegate.get());
134+
if (customDelegate == null) {
135+
String message = String.format(
136+
"Custom JDBC delegate implementation class '%s' was not found in Jandex index. " +
137+
"Make sure the dependency containing this class has proper marker file enabling discovery. " +
138+
"Alternatively, you can index a dependency using IndexDependencyBuildItem.",
139+
driverDelegate.get());
140+
throw new ConfigurationException(message);
141+
} else {
142+
// any custom implementation needs to be a subclass of known Quarkus delegate
143+
boolean implementsKnownDelegate = false;
144+
for (DotName knownImplementation : Set.of(DELEGATE_MSSQL, DELEGATE_POSTGRESQL, DELEGATE_DB2V8, DELEGATE_STDJDBC,
145+
DELEGATE_HSQLDB)) {
146+
for (ClassInfo classInfo : indexView.getAllKnownSubclasses(knownImplementation)) {
147+
if (classInfo.name().equals(customDelegate.name())) {
148+
implementsKnownDelegate = true;
149+
break;
150+
}
151+
}
152+
}
153+
if (!implementsKnownDelegate) {
154+
String message = String.format(
155+
"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.",
156+
driverDelegate.get());
157+
throw new ConfigurationException(message);
158+
}
159+
}
160+
} else {
161+
Optional<JdbcDataSourceBuildItem> selectedJdbcDataSourceBuildItem = jdbcDataSourceBuildItems.stream()
162+
.filter(i -> config.dataSourceName.isPresent() ? config.dataSourceName.get().equals(i.getName())
163+
: i.isDefault())
164+
.findFirst();
165+
166+
if (!selectedJdbcDataSourceBuildItem.isPresent()) {
167+
String message = String.format(
168+
"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",
169+
config.dataSourceName.isPresent() ? config.dataSourceName.get() : "default");
170+
throw new ConfigurationException(message);
171+
}
172+
driverDelegate = Optional.of(guessDriver(selectedJdbcDataSourceBuildItem));
131173
}
132-
133-
return new QuartzJDBCDriverDialectBuildItem(Optional.of(guessDriver(selectedJdbcDataSourceBuildItem)));
174+
return new QuartzJDBCDriverDialectBuildItem(driverDelegate);
134175
}
135176

136177
private String guessDriver(Optional<JdbcDataSourceBuildItem> jdbcDataSource) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.quarkus.quartz.test.customDelegate;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.function.Consumer;
6+
7+
import org.jboss.shrinkwrap.api.asset.StringAsset;
8+
import org.junit.jupiter.api.Assertions;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import io.quarkus.builder.BuildChainBuilder;
13+
import io.quarkus.builder.BuildContext;
14+
import io.quarkus.builder.BuildStep;
15+
import io.quarkus.deployment.Capability;
16+
import io.quarkus.deployment.builditem.CapabilityBuildItem;
17+
import io.quarkus.quartz.test.SimpleJobs;
18+
import io.quarkus.runtime.configuration.ConfigurationException;
19+
import io.quarkus.test.QuarkusUnitTest;
20+
21+
public class DelegateNotASubclassTest {
22+
23+
@RegisterExtension
24+
static final QuarkusUnitTest test = new QuarkusUnitTest()
25+
// add a mock pretending to provide Agroal Capability to pass our validation
26+
.addBuildChainCustomizer(new Consumer<>() {
27+
@Override
28+
public void accept(BuildChainBuilder buildChainBuilder) {
29+
buildChainBuilder.addBuildStep(new BuildStep() {
30+
@Override
31+
public void execute(BuildContext context) {
32+
context.produce(
33+
new CapabilityBuildItem(Capability.AGROAL, "fakeProvider"));
34+
}
35+
}).produces(CapabilityBuildItem.class).build();
36+
}
37+
})
38+
.assertException(t -> {
39+
assertEquals(ConfigurationException.class, t.getClass());
40+
Assertions.assertTrue(t.getMessage().contains(
41+
"Custom JDBC delegate implementation with name 'io.quarkus.quartz.test.customDelegate.InvalidDelegate' needs to be a subclass"));
42+
})
43+
.withApplicationRoot((jar) -> jar
44+
.addClasses(SimpleJobs.class, InvalidDelegate.class)
45+
.addAsResource(new StringAsset(
46+
"quarkus.quartz.driver-delegate=io.quarkus.quartz.test.customDelegate.InvalidDelegate\nquarkus.quartz.store-type=jdbc-cmt"),
47+
"application.properties"));
48+
49+
@Test
50+
public void shouldFailIfNotASubclass() {
51+
Assertions.fail();
52+
}
53+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.quarkus.quartz.test.customDelegate;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.function.Consumer;
6+
7+
import org.jboss.shrinkwrap.api.asset.StringAsset;
8+
import org.junit.jupiter.api.Assertions;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import io.quarkus.builder.BuildChainBuilder;
13+
import io.quarkus.builder.BuildContext;
14+
import io.quarkus.builder.BuildStep;
15+
import io.quarkus.deployment.Capability;
16+
import io.quarkus.deployment.builditem.CapabilityBuildItem;
17+
import io.quarkus.quartz.test.SimpleJobs;
18+
import io.quarkus.runtime.configuration.ConfigurationException;
19+
import io.quarkus.test.QuarkusUnitTest;
20+
21+
public class DelegateNotIndexedTest {
22+
23+
@RegisterExtension
24+
static final QuarkusUnitTest test = new QuarkusUnitTest()
25+
// add a mock pretending to provide Agroal Capability to pass our validation
26+
.addBuildChainCustomizer(new Consumer<>() {
27+
@Override
28+
public void accept(BuildChainBuilder buildChainBuilder) {
29+
buildChainBuilder.addBuildStep(new BuildStep() {
30+
@Override
31+
public void execute(BuildContext context) {
32+
context.produce(
33+
new CapabilityBuildItem(Capability.AGROAL, "fakeProvider"));
34+
}
35+
}).produces(CapabilityBuildItem.class).build();
36+
}
37+
})
38+
.assertException(t -> {
39+
assertEquals(ConfigurationException.class, t.getClass());
40+
Assertions.assertTrue(t.getMessage().contains(
41+
"Custom JDBC delegate implementation class 'org.acme.DoesNotExist' was not found in Jandex index"));
42+
})
43+
.withApplicationRoot((jar) -> jar
44+
.addClasses(SimpleJobs.class)
45+
.addAsResource(new StringAsset(
46+
"quarkus.quartz.driver-delegate=org.acme.DoesNotExist\nquarkus.quartz.store-type=jdbc-cmt"),
47+
"application.properties"));
48+
49+
@Test
50+
public void shouldFailWhenNotIndexed() {
51+
Assertions.fail();
52+
}
53+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.quarkus.quartz.test.customDelegate;
2+
3+
// dummy class representing an invalid JDBC delegate by not subclassing a known one
4+
public class InvalidDelegate {
5+
}

extensions/quartz/runtime/src/main/java/io/quarkus/quartz/runtime/QuartzBuildTimeConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ public class QuartzBuildTimeConfig {
7474
@ConfigItem
7575
public Optional<String> selectWithLockSql;
7676

77+
/**
78+
* Allows users to specify fully qualified class name for a custom JDBC driver delegate.
79+
* <p>
80+
* This property is optional and leaving it empty will result in Quarkus automatically choosing appropriate default
81+
* driver delegate implementation.
82+
* <p>
83+
* Note that any custom implementation has to be a subclass of existing Quarkus implementation such as
84+
* {@link io.quarkus.quartz.runtime.jdbc.QuarkusPostgreSQLDelegate} or
85+
* {@link io.quarkus.quartz.runtime.jdbc.QuarkusMSSQLDelegate}
86+
*/
87+
@ConfigItem
88+
public Optional<String> driverDelegate;
89+
7790
/**
7891
* Instructs JDBCJobStore to serialize JobDataMaps in the BLOB column.
7992
* <p>

0 commit comments

Comments
 (0)