Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction manager-related backports and fixes #35242

Merged
merged 5 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,9 @@ void generateDataSourceBeans(AgroalRecorder recorder,
return;
}

for (Map.Entry<String, DataSourceSupport.Entry> entry : getDataSourceSupport(aggregatedBuildTimeConfigBuildItems,
sslNativeConfig,
capabilities).entries.entrySet()) {
for (AggregatedDataSourceBuildTimeConfigBuildItem aggregatedBuildTimeConfigBuildItem : aggregatedBuildTimeConfigBuildItems) {

String dataSourceName = entry.getKey();
String dataSourceName = aggregatedBuildTimeConfigBuildItem.getName();

SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(AgroalDataSource.class)
Expand All @@ -282,7 +280,7 @@ void generateDataSourceBeans(AgroalRecorder recorder,
// are created after runtime configuration has been set up
.supplier(recorder.agroalDataSourceSupplier(dataSourceName, dataSourcesRuntimeConfig));

if (entry.getValue().isDefault) {
if (aggregatedBuildTimeConfigBuildItem.isDefault()) {
configurator.addQualifier(Default.class);
} else {
// this definitely not ideal, but 'elytron-jdbc-security' uses it (although it could be easily changed)
Expand All @@ -296,9 +294,10 @@ void generateDataSourceBeans(AgroalRecorder recorder,
syntheticBeanBuildItemBuildProducer.produce(configurator.done());

jdbcDataSource.produce(new JdbcDataSourceBuildItem(dataSourceName,
entry.getValue().resolvedDbKind,
entry.getValue().dbVersion,
entry.getValue().isDefault));
aggregatedBuildTimeConfigBuildItem.getDbKind(),
aggregatedBuildTimeConfigBuildItem.getDataSourceConfig().dbVersion,
aggregatedBuildTimeConfigBuildItem.getJdbcConfig().transactions != TransactionIntegration.DISABLED,
aggregatedBuildTimeConfigBuildItem.isDefault()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ public final class JdbcDataSourceBuildItem extends MultiBuildItem {
private final String dbKind;

private final Optional<String> dbVersion;

private final boolean transactionIntegrationEnabled;

private final boolean isDefault;

public JdbcDataSourceBuildItem(String name, String kind, Optional<String> dbVersion, boolean isDefault) {
public JdbcDataSourceBuildItem(String name, String kind, Optional<String> dbVersion,
boolean transactionIntegrationEnabled, boolean isDefault) {
this.name = name;
this.dbKind = kind;
this.dbVersion = dbVersion;
this.transactionIntegrationEnabled = transactionIntegrationEnabled;
this.isDefault = isDefault;
}

Expand All @@ -38,6 +43,10 @@ public Optional<String> getDbVersion() {
return dbVersion;
}

public boolean isTransactionIntegrationEnabled() {
return transactionIntegrationEnabled;
}

public boolean isDefault() {
return isDefault;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import jakarta.annotation.Priority;
import jakarta.interceptor.Interceptor;
Expand Down Expand Up @@ -44,6 +46,7 @@
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.IsTest;
import io.quarkus.deployment.annotations.BuildProducer;
Expand Down Expand Up @@ -155,10 +158,16 @@ public void build(NarayanaJtaRecorder recorder,
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
public void startRecoveryService(NarayanaJtaRecorder recorder,
List<JdbcDataSourceBuildItem> jdbcDataSourceBuildItems, TransactionManagerConfiguration transactions) {
Map<Boolean, String> namedDataSources = new HashMap<>();

jdbcDataSourceBuildItems.forEach(i -> namedDataSources.put(i.isDefault(), i.getName()));
recorder.startRecoveryService(transactions, namedDataSources);
Map<String, String> configuredDataSourcesConfigKeys = jdbcDataSourceBuildItems.stream()
.map(j -> j.getName())
.collect(Collectors.toMap(Function.identity(),
n -> DataSourceUtil.dataSourcePropertyKey(n, "jdbc.transactions")));
Set<String> dataSourcesWithTransactionIntegration = jdbcDataSourceBuildItems.stream()
.filter(j -> j.isTransactionIntegrationEnabled())
.map(j -> j.getName())
.collect(Collectors.toSet());

recorder.startRecoveryService(transactions, configuredDataSourcesConfigKeys, dataSourcesWithTransactionIntegration);
}

@BuildStep(onlyIf = IsTest.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.narayana.quarkus;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.util.List;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.Version;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.runtime.util.ExceptionUtil;
import io.quarkus.test.QuarkusUnitTest;

public class TransactionJdbcObjectStoreValidationFailureTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource("jdbc-object-store-validation.properties", "application.properties"))
.setForcedDependencies(List.of(Dependency.of("io.quarkus", "quarkus-jdbc-h2", Version.getVersion())))
.assertException(t -> {
Throwable rootCause = ExceptionUtil.getRootCause(t);
if (rootCause instanceof ConfigurationException) {
assertTrue(rootCause.getMessage().contains(
"The Narayana JTA extension is configured to use the datasource 'test' but that datasource is not configured."));
} else {
fail(t);
}
});

@Test
public void test() {
// needs to be there in order to run test
Assertions.fail("Application was supposed to fail.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
quarkus.transaction-manager.object-store.type=jdbc
quarkus.transaction-manager.object-store.datasource=test
quarkus.datasource.test.db-kind=h2
quarkus.datasource.test.jdbc.url=jdbc:h2:mem:default
quarkus.datasource.test.jdbc.transactions=xa

# we must not start database as CI is also executed on Windows without (Docker) Linux containers
quarkus.datasource.devservices.enabled=false
quarkus.devservices.enabled=false
4 changes: 4 additions & 0 deletions extensions/narayana-jta/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mutiny</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-datasource-common</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-context-propagation-jta</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.jboss.logging.Logger;

Expand All @@ -15,13 +16,13 @@
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
import com.arjuna.ats.arjuna.coordinator.TxControl;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.ats.internal.arjuna.objectstore.jdbc.JDBCStore;
import com.arjuna.ats.jta.common.JTAEnvironmentBean;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.common.internal.util.propertyservice.BeanPopulator;
import com.arjuna.common.util.propertyservice.PropertiesFactory;

import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigurationException;
Expand Down Expand Up @@ -104,29 +105,47 @@ private void setJDBCObjectStore(String name, TransactionManagerConfiguration con
instance.setTablePrefix(config.objectStore.tablePrefix);
}

public void startRecoveryService(final TransactionManagerConfiguration transactions, Map<Boolean, String> dataSources) {
public void startRecoveryService(final TransactionManagerConfiguration transactions,
Map<String, String> configuredDataSourcesConfigKeys,
Set<String> dataSourcesWithTransactionIntegration) {

if (transactions.objectStore.type.equals(ObjectStoreType.JDBC)) {
final String objectStoreDataSourceName;
if (transactions.objectStore.datasource.isEmpty()) {
dataSources.keySet().stream().filter(i -> i).findFirst().orElseThrow(
() -> new ConfigurationException(
"The Narayana JTA extension does not have a datasource configured,"
+ " so it defaults to the default datasource,"
+ " but that datasource is not configured."
+ " To solve this, either configure the default datasource,"
+ " referring to https://quarkus.io/guides/datasource for guidance,"
+ " or configure the datasource to use in the Narayana JTA extension "
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource."));
if (!DataSourceUtil.hasDefault(configuredDataSourcesConfigKeys.keySet())) {
throw new ConfigurationException(
"The Narayana JTA extension does not have a datasource configured as the JDBC object store,"
+ " so it defaults to the default datasource,"
+ " but that datasource is not configured."
+ " To solve this, either configure the default datasource,"
+ " referring to https://quarkus.io/guides/datasource for guidance,"
+ " or configure the datasource to use in the Narayana JTA extension "
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource.");
}
objectStoreDataSourceName = DataSourceUtil.DEFAULT_DATASOURCE_NAME;
} else {
String dsName = transactions.objectStore.datasource.get();
dataSources.values().stream().filter(i -> i.equals(dsName)).findFirst()
.orElseThrow(() -> new ConfigurationException(
"The Narayana JTA extension is configured to use the datasource '"
+ dsName
+ "' but that datasource is not configured."
+ " To solve this, either configure datasource " + dsName
+ " referring to https://quarkus.io/guides/datasource for guidance,"
+ " or configure another datasource to use in the Narayana JTA extension "
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource."));
objectStoreDataSourceName = transactions.objectStore.datasource.get();

if (!configuredDataSourcesConfigKeys.keySet().contains(objectStoreDataSourceName)) {
throw new ConfigurationException(
"The Narayana JTA extension is configured to use the datasource '"
+ objectStoreDataSourceName
+ "' but that datasource is not configured."
+ " To solve this, either configure datasource " + objectStoreDataSourceName
+ " referring to https://quarkus.io/guides/datasource for guidance,"
+ " or configure another datasource to use in the Narayana JTA extension "
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource.");
}
}
if (dataSourcesWithTransactionIntegration.contains(objectStoreDataSourceName)) {
throw new ConfigurationException(String.format(
"The Narayana JTA extension is configured to use the '%s' JDBC "
+ "datasource as the transaction log storage, "
+ "but that datasource does not have transaction capabilities disabled. "
+ "To solve this, please set '%s=disabled', or configure another datasource "
+ "with disabled transaction capabilities as the JDBC object store. "
+ "Please refer to the https://quarkus.io/guides/transaction#jdbcstore for more information.",
objectStoreDataSourceName, configuredDataSourcesConfigKeys.get(objectStoreDataSourceName)));
}
}
if (transactions.enableRecovery) {
Expand All @@ -136,10 +155,19 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti
}

public void handleShutdown(ShutdownContext context, TransactionManagerConfiguration transactions) {
context.addLastShutdownTask(() -> {
context.addShutdownTask(() -> {
if (transactions.enableRecovery) {
RecoveryManager.manager().terminate(true);
try {
QuarkusRecoveryService.getInstance().stop();
} catch (Exception e) {
// the recovery manager throws IllegalStateException if it has already been shutdown
log.warn("The recovery manager has already been shutdown", e);
} finally {
QuarkusRecoveryService.getInstance().destroy();
}
}
});
context.addLastShutdownTask(() -> {
TransactionReaper.terminate(false);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,11 @@ public void create() {
}
xaResources.clear();
}

@Override
public void destroy() {
super.destroy();
isCreated = false;
recoveryManagerService = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ quarkus.log.category."io.quarkus".level=INFO
quarkus.datasource.db-kind=h2

quarkus.transaction-manager.object-store.directory=target/tx-object-store

quarkus.datasource.test.jdbc.transactions=disabled
quarkus.datasource.test.db-kind=h2
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ public Map<String, String> getConfigOverrides() {
HashMap<String, String> props = new HashMap<>();
props.put("quarkus.transaction-manager.object-store.type", "jdbc");
props.put("quarkus.transaction-manager.object-store.create-table", "true");
props.put("quarkus.transaction-manager.object-store.datasource", "test");
props.put("quarkus.transaction-manager.enable-recovery", "true");

props.put("quarkus.datasource.test.db-kind", "h2");
props.put("quarkus.datasource.test.jdbc.url", "jdbc:h2:mem:default");
props.put("quarkus.datasource.test.jdbc.transactions", "xa");

return props;
}
Expand Down