Skip to content

Commit

Permalink
rewrite the jdbc configuration validation
Browse files Browse the repository at this point in the history
  • Loading branch information
dcdh committed Mar 4, 2024
1 parent 1759a83 commit 7ea41c4
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 49 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.version>3.7.4</quarkus.version>
<quarkus.version>3.8.1</quarkus.version>
<shedlock.version>5.11.0</shedlock.version>
</properties>
<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: "Distributed lock for your scheduled tasks"
metadata:
keywords:
- "shedlock"
- "provider"
- "inmemory"
# guide: https://quarkiverse.github.io/quarkiverse-docs/shedlock/dev/ # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
categories:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package io.quarkiverse.shedlock.providers.jdbc.deployment;

import java.util.List;
import java.util.stream.Stream;

import jakarta.inject.Singleton;

import org.jboss.jandex.DotName;

import io.quarkiverse.shedlock.providers.jdbc.runtime.JdbcConfig;
import io.quarkiverse.shedlock.providers.jdbc.runtime.JdbcLockProviderInitializer;
import io.quarkiverse.shedlock.providers.jdbc.runtime.JdbcSchedulerLock;
import io.quarkiverse.shedlock.providers.jdbc.runtime.JdbcSchedulerLockInterceptor;
import io.quarkiverse.shedlock.providers.jdbc.runtime.*;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;

Expand All @@ -37,26 +41,50 @@ void produceBeans(final BuildProducer<AdditionalBeanBuildItem> additionalBeanBui
@BuildStep
List<ValidationErrorBuildItem> validateDataSourcesDefinitionsWhenJdbcShedLockIsUsed(
final ApplicationIndexBuildItem applicationIndexBuildItem,
final JdbcConfig jdbcConfig,
final List<JdbcDataSourceBuildItem> jdbcDataSourceBuildItems) {
final List<String> dataSourceNames = getDataSourcesNameFromJdbcSchedulerLocks(applicationIndexBuildItem);
return dataSourceNames
.stream()
.filter(shedLockDataSourceName -> jdbcDataSourceBuildItems.stream().map(JdbcDataSourceBuildItem::getName)
.noneMatch(jdbcDataSourceName -> jdbcDataSourceName.equals(shedLockDataSourceName)))
.map(missingDataSource -> new ValidationErrorBuildItem(
new IllegalStateException(String.format(
"A missing datasource '%s' has been defined for ShedLock. Please fixe the ShedLock configuration or add the datasource",
missingDataSource))))
.toList();
}

@BuildStep
@Record(ExecutionTime.STATIC_INIT)
List<SyntheticBeanBuildItem> registerJdbcLockProviderInitializer(
final ApplicationIndexBuildItem applicationIndexBuildItem,
final DataSourceNameRecorder dataSourceNameRecorder) {
final List<String> dataSourceNames = getDataSourcesNameFromJdbcSchedulerLocks(applicationIndexBuildItem);
List<SyntheticBeanBuildItem> list = dataSourceNames.stream()
.map(dataSourceName -> SyntheticBeanBuildItem.configure(DataSourceName.class)
.scope(Singleton.class)
.identifier(dataSourceName)
.supplier(dataSourceNameRecorder.dataSourceNameSupplier(dataSourceName))
.unremovable()
.done())
.toList();
return list;
}

private List<String> getDataSourcesNameFromJdbcSchedulerLocks(ApplicationIndexBuildItem applicationIndexBuildItem) {
final DotName jdbcSchedulerLock = DotName.createSimple(JdbcSchedulerLock.class);
final boolean shouldCheckDataSourcesBecauseLockIsUsed = applicationIndexBuildItem.getIndex()
.getKnownClasses().stream()
.anyMatch(classInfo -> classInfo.hasAnnotation(jdbcSchedulerLock) || classInfo.methods().stream()
.anyMatch(methodInfo -> methodInfo.hasAnnotation(jdbcSchedulerLock)));
if (shouldCheckDataSourcesBecauseLockIsUsed) {
return jdbcConfig.dataSources()
.keySet()
.stream()
.filter(shedLockDataSourceName -> jdbcDataSourceBuildItems.stream().map(JdbcDataSourceBuildItem::getName)
.noneMatch(jdbcDataSourceName -> jdbcDataSourceName.equals(shedLockDataSourceName)))
.map(missingDataSource -> new ValidationErrorBuildItem(
new IllegalStateException(String.format(
"A missing datasource '%s' has been defined for ShedLock. Please fixe the ShedLock configuration or add the datasource",
missingDataSource))))
.toList();
} else {
return List.of();
}
return Stream.concat(
applicationIndexBuildItem.getIndex().getKnownClasses()
.stream().filter(classInfo -> classInfo.hasAnnotation(jdbcSchedulerLock))
.map(classInfo -> classInfo.annotation(jdbcSchedulerLock)),
applicationIndexBuildItem.getIndex().getKnownClasses().stream()
.flatMap(classInfo -> classInfo.methods().stream())
.filter(methodInfo -> methodInfo.hasAnnotation(jdbcSchedulerLock))
.map(methodInfo -> methodInfo.annotation(jdbcSchedulerLock)))
.map(annotationInstance -> annotationInstance.value("dataSourceName"))
.map(dataSourceName -> dataSourceName != null ? dataSourceName.asString()
: DataSourceUtil.DEFAULT_DATASOURCE_NAME)
.distinct()
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkiverse.shedlock.providers.jdbc.deployment;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkiverse.shedlock.providers.jdbc.runtime.JdbcSchedulerLock;

@ApplicationScoped
public class LockableServiceUsingUnknownDataSource {
@JdbcSchedulerLock(dataSourceName = "unknownDataSource")
void execute() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ShouldFailWhenUsedDataSourceIsNotPresentTest {
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(LockableService.class)
.addClasses(LockableServiceUsingUnknownDataSource.class)
.addAsResource(new StringAsset("quarkus.shedlock.defaults-lock-at-most-for=PT30S\n" +
"quarkus.shedlock.jdbc.unknownDataSource.table-name=myShedLockTableName"),
"application.properties"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.quarkiverse.shedlock.providers.jdbc.runtime;

public interface DataSourceName {
String name();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkiverse.shedlock.providers.jdbc.runtime;

import java.util.function.Supplier;

import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class DataSourceNameRecorder {
public Supplier<DataSourceName> dataSourceNameSupplier(final String dataSourceName) {
return () -> (DataSourceName) () -> dataSourceName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.Instance;

import io.agroal.api.AgroalDataSource;
import io.quarkiverse.shedlock.common.runtime.SchedulerLockInterceptorBase;
import io.quarkus.agroal.DataSource;
import io.quarkus.arc.Arc;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
Expand All @@ -18,34 +22,42 @@
@ApplicationScoped
public class JdbcLockProviderInitializer {
private final JdbcConfig jdbcConfig;
private final List<DataSourceName> dataSourcesName;

public JdbcLockProviderInitializer(final JdbcConfig jdbcConfig) {
public JdbcLockProviderInitializer(final JdbcConfig jdbcConfig,
final Instance<DataSourceName> dataSourcesName) {
this.jdbcConfig = Objects.requireNonNull(jdbcConfig);
this.dataSourcesName = Objects.requireNonNull(dataSourcesName).stream().toList();
}

void createTable(@Observes StartupEvent startupEvent) {
jdbcConfig.dataSources().forEach((dataSourceName, dataSourceConfig) -> {
final AgroalDataSource agroalDataSource = Arc.container()
.select(AgroalDataSource.class,
DataSourceUtil.DEFAULT_DATASOURCE_NAME.equals(dataSourceName) ? new Default.Literal()
: new DataSource.DataSourceLiteral(dataSourceName))
.get();
final String databaseCreationSql = """
CREATE TABLE IF NOT EXISTS %s (
name VARCHAR(255),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
""";
try (final Connection connection = agroalDataSource.getConnection()) {
final PreparedStatement preparedStatement = connection
.prepareStatement(String.format(databaseCreationSql, dataSourceConfig.tableName()));
preparedStatement.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
dataSourcesName
.stream().map(DataSourceName::name)
.forEach(dataSourceName -> {
final AgroalDataSource agroalDataSource = Arc.container()
.select(AgroalDataSource.class,
DataSourceUtil.DEFAULT_DATASOURCE_NAME.equals(dataSourceName) ? new Default.Literal()
: new DataSource.DataSourceLiteral(dataSourceName))
.get();
final String databaseCreationSql = """
CREATE TABLE IF NOT EXISTS %s (
name VARCHAR(255),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
""";
try (final Connection connection = agroalDataSource.getConnection()) {
final String tableName = Optional.ofNullable(jdbcConfig.dataSources().get(dataSourceName))
.map(JdbcConfig.DataSourceConfig::tableName)
.orElse(SchedulerLockInterceptorBase.SHED_LOCK);
final PreparedStatement preparedStatement = connection
.prepareStatement(String.format(databaseCreationSql, tableName));
preparedStatement.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: "Distributed lock for your scheduled tasks"
metadata:
keywords:
- "shedlock"
- "provider"
- "jdbc"
# guide: https://quarkiverse.github.io/quarkiverse-docs/shedlock/dev/ # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
categories:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: "Distributed lock for your scheduled tasks"
metadata:
keywords:
- "shedlock"
- "provider"
- "mongo"
# guide: https://quarkiverse.github.io/quarkiverse-docs/shedlock/dev/ # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
categories:
Expand Down

0 comments on commit 7ea41c4

Please sign in to comment.