From 001ddb76af7c5592d88e98a3525b8ab6b499c4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 25 Jul 2023 21:22:12 +0200 Subject: [PATCH 1/5] Prohibit using Narayana JDBC object store with Agroal XA (cherry picked from commit fd02a43d4b80fe60a3ad5bdaf1e54652c4796bdd) --- .../jta/deployment/NarayanaJtaProcessor.java | 34 +++++++++++++++- ...nJdbcObjectStoreValidationFailureTest.java | 40 +++++++++++++++++++ .../jdbc-object-store-validation.properties | 9 +++++ .../jta/runtime/NarayanaJtaRecorder.java | 20 ++++++++-- .../src/main/resources/application.properties | 3 ++ .../jta/JdbcObjectStoreTestProfile.java | 3 +- 6 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 extensions/narayana-jta/deployment/src/test/java/io/quarkus/narayana/quarkus/TransactionJdbcObjectStoreValidationFailureTest.java create mode 100644 extensions/narayana-jta/deployment/src/test/resources/jdbc-object-store-validation.properties diff --git a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java index f99c1cfffdab3..d50c060057215 100644 --- a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java +++ b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.narayana.jta.deployment; +import static io.quarkus.datasource.common.runtime.DataSourceUtil.dataSourcePropertyKeys; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import java.util.HashMap; @@ -52,6 +53,7 @@ import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; @@ -153,12 +155,40 @@ public void build(NarayanaJtaRecorder recorder, @Record(RUNTIME_INIT) @Consume(NarayanaInitBuildItem.class) @Consume(SyntheticBeansRuntimeInitBuildItem.class) - public void startRecoveryService(NarayanaJtaRecorder recorder, + public void startRecoveryService(NarayanaJtaRecorder recorder, ConfigurationBuildItem configurationBuildItem, List jdbcDataSourceBuildItems, TransactionManagerConfiguration transactions) { Map namedDataSources = new HashMap<>(); jdbcDataSourceBuildItems.forEach(i -> namedDataSources.put(i.isDefault(), i.getName())); - recorder.startRecoveryService(transactions, namedDataSources); + Map dsToConfigKey = getDsWithoutDisabledTransactions( + configurationBuildItem.getReadResult().getBuildTimeRunTimeValues(), namedDataSources); + + recorder.startRecoveryService(transactions, namedDataSources, dsToConfigKey); + } + + private static Map getDsWithoutDisabledTransactions(Map buildTimeRunTimeValues, + Map dataSources) { + Map dsToTransactionIntegration = new HashMap<>(); + for (String dataSourceName : dataSources.values()) { + List transactionsPropertyKeys = dataSourcePropertyKeys(dataSourceName, "jdbc.transactions"); + boolean propertyExplicitlyDefined = false; + for (String propertyKey : transactionsPropertyKeys) { + // get datasource 'io.quarkus.agroal.runtime.TransactionIntegration' + String transactionIntegration = buildTimeRunTimeValues.get(propertyKey); + if (transactionIntegration != null) { + propertyExplicitlyDefined = true; + if (!"disabled".equalsIgnoreCase(transactionIntegration)) { + dsToTransactionIntegration.put(dataSourceName, propertyKey); + break; + } + } + } + if (!propertyExplicitlyDefined) { + // by default transaction capabilities are enabled + dsToTransactionIntegration.put(dataSourceName, transactionsPropertyKeys.get(0)); + } + } + return dsToTransactionIntegration; } @BuildStep(onlyIf = IsTest.class) diff --git a/extensions/narayana-jta/deployment/src/test/java/io/quarkus/narayana/quarkus/TransactionJdbcObjectStoreValidationFailureTest.java b/extensions/narayana-jta/deployment/src/test/java/io/quarkus/narayana/quarkus/TransactionJdbcObjectStoreValidationFailureTest.java new file mode 100644 index 0000000000000..a0515c8128f91 --- /dev/null +++ b/extensions/narayana-jta/deployment/src/test/java/io/quarkus/narayana/quarkus/TransactionJdbcObjectStoreValidationFailureTest.java @@ -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."); + } +} diff --git a/extensions/narayana-jta/deployment/src/test/resources/jdbc-object-store-validation.properties b/extensions/narayana-jta/deployment/src/test/resources/jdbc-object-store-validation.properties new file mode 100644 index 0000000000000..b52fcbea70c8f --- /dev/null +++ b/extensions/narayana-jta/deployment/src/test/resources/jdbc-object-store-validation.properties @@ -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 diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java index a3edd9972cc38..22108ab19c5a4 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java @@ -104,11 +104,13 @@ private void setJDBCObjectStore(String name, TransactionManagerConfiguration con instance.setTablePrefix(config.objectStore.tablePrefix); } - public void startRecoveryService(final TransactionManagerConfiguration transactions, Map dataSources) { + public void startRecoveryService(final TransactionManagerConfiguration transactions, Map dataSources, + Map dsWithInvalidTransactionsToConfigKey) { if (transactions.objectStore.type.equals(ObjectStoreType.JDBC)) { + final String dsName; if (transactions.objectStore.datasource.isEmpty()) { - dataSources.keySet().stream().filter(i -> i).findFirst().orElseThrow( - () -> new ConfigurationException( + dsName = dataSources.entrySet().stream().filter(Map.Entry::getKey).map(Map.Entry::getValue).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." @@ -117,7 +119,7 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti + " 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.")); } else { - String dsName = transactions.objectStore.datasource.get(); + 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 '" @@ -128,6 +130,16 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti + " 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 (dsWithInvalidTransactionsToConfigKey.containsKey(dsName)) { + 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.", + dsName, dsWithInvalidTransactionsToConfigKey.get(dsName))); + } } if (transactions.enableRecovery) { QuarkusRecoveryService.getInstance().create(); diff --git a/integration-tests/narayana-jta/src/main/resources/application.properties b/integration-tests/narayana-jta/src/main/resources/application.properties index d08ca5fe0cda6..68faef6f4d1b0 100644 --- a/integration-tests/narayana-jta/src/main/resources/application.properties +++ b/integration-tests/narayana-jta/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java index f4aecafd2583c..b7e33ec40b9f4 100644 --- a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java @@ -11,11 +11,10 @@ public Map getConfigOverrides() { HashMap 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; } From e5cf29d1b90a6bb5a4d8832dfff9ba36d2e8e2e6 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 27 Jul 2023 13:42:07 +0200 Subject: [PATCH 2/5] Use the source of truth to publish JdbcDataSourceBuildItem Until now, we were using some transformed data, which wasn't very practical when we needed more information. (cherry picked from commit 6e44d77221d8a874edb8298c19832a3fb06794c1) --- .../quarkus/agroal/deployment/AgroalProcessor.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index 68b3407906052..ea8b3630cdf2a 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -266,11 +266,9 @@ void generateDataSourceBeans(AgroalRecorder recorder, return; } - for (Map.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) @@ -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) @@ -296,9 +294,9 @@ 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.isDefault())); } } From f94dfe8b76245c658a5259e3f7be141cc7bda4bf Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 27 Jul 2023 16:21:21 +0200 Subject: [PATCH 3/5] Simplify resolution of the config by pushing the info to the build item (cherry picked from commit c3ae13e1e887c030d5f011269e78552a5640d237) --- .../agroal/deployment/AgroalProcessor.java | 1 + .../agroal/spi/JdbcDataSourceBuildItem.java | 11 +++- .../jta/deployment/NarayanaJtaProcessor.java | 51 +++++------------ extensions/narayana-jta/runtime/pom.xml | 4 ++ .../jta/runtime/NarayanaJtaRecorder.java | 56 +++++++++++-------- 5 files changed, 62 insertions(+), 61 deletions(-) diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index ea8b3630cdf2a..94932382fa798 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -296,6 +296,7 @@ void generateDataSourceBeans(AgroalRecorder recorder, jdbcDataSource.produce(new JdbcDataSourceBuildItem(dataSourceName, aggregatedBuildTimeConfigBuildItem.getDbKind(), aggregatedBuildTimeConfigBuildItem.getDataSourceConfig().dbVersion, + aggregatedBuildTimeConfigBuildItem.getJdbcConfig().transactions != TransactionIntegration.DISABLED, aggregatedBuildTimeConfigBuildItem.isDefault())); } } diff --git a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/JdbcDataSourceBuildItem.java b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/JdbcDataSourceBuildItem.java index db2dfe2ebf3d8..dc12f4a86e40a 100644 --- a/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/JdbcDataSourceBuildItem.java +++ b/extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/JdbcDataSourceBuildItem.java @@ -17,12 +17,17 @@ public final class JdbcDataSourceBuildItem extends MultiBuildItem { private final String dbKind; private final Optional dbVersion; + + private final boolean transactionIntegrationEnabled; + private final boolean isDefault; - public JdbcDataSourceBuildItem(String name, String kind, Optional dbVersion, boolean isDefault) { + public JdbcDataSourceBuildItem(String name, String kind, Optional dbVersion, + boolean transactionIntegrationEnabled, boolean isDefault) { this.name = name; this.dbKind = kind; this.dbVersion = dbVersion; + this.transactionIntegrationEnabled = transactionIntegrationEnabled; this.isDefault = isDefault; } @@ -38,6 +43,10 @@ public Optional getDbVersion() { return dbVersion; } + public boolean isTransactionIntegrationEnabled() { + return transactionIntegrationEnabled; + } + public boolean isDefault() { return isDefault; } diff --git a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java index d50c060057215..396279af870c0 100644 --- a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java +++ b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java @@ -1,12 +1,13 @@ package io.quarkus.narayana.jta.deployment; -import static io.quarkus.datasource.common.runtime.DataSourceUtil.dataSourcePropertyKeys; 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; @@ -45,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; @@ -53,7 +55,6 @@ import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; @@ -155,40 +156,18 @@ public void build(NarayanaJtaRecorder recorder, @Record(RUNTIME_INIT) @Consume(NarayanaInitBuildItem.class) @Consume(SyntheticBeansRuntimeInitBuildItem.class) - public void startRecoveryService(NarayanaJtaRecorder recorder, ConfigurationBuildItem configurationBuildItem, + public void startRecoveryService(NarayanaJtaRecorder recorder, List jdbcDataSourceBuildItems, TransactionManagerConfiguration transactions) { - Map namedDataSources = new HashMap<>(); - - jdbcDataSourceBuildItems.forEach(i -> namedDataSources.put(i.isDefault(), i.getName())); - Map dsToConfigKey = getDsWithoutDisabledTransactions( - configurationBuildItem.getReadResult().getBuildTimeRunTimeValues(), namedDataSources); - - recorder.startRecoveryService(transactions, namedDataSources, dsToConfigKey); - } - - private static Map getDsWithoutDisabledTransactions(Map buildTimeRunTimeValues, - Map dataSources) { - Map dsToTransactionIntegration = new HashMap<>(); - for (String dataSourceName : dataSources.values()) { - List transactionsPropertyKeys = dataSourcePropertyKeys(dataSourceName, "jdbc.transactions"); - boolean propertyExplicitlyDefined = false; - for (String propertyKey : transactionsPropertyKeys) { - // get datasource 'io.quarkus.agroal.runtime.TransactionIntegration' - String transactionIntegration = buildTimeRunTimeValues.get(propertyKey); - if (transactionIntegration != null) { - propertyExplicitlyDefined = true; - if (!"disabled".equalsIgnoreCase(transactionIntegration)) { - dsToTransactionIntegration.put(dataSourceName, propertyKey); - break; - } - } - } - if (!propertyExplicitlyDefined) { - // by default transaction capabilities are enabled - dsToTransactionIntegration.put(dataSourceName, transactionsPropertyKeys.get(0)); - } - } - return dsToTransactionIntegration; + Map configuredDataSourcesConfigKeys = jdbcDataSourceBuildItems.stream() + .map(j -> j.getName()) + .collect(Collectors.toMap(Function.identity(), + n -> DataSourceUtil.dataSourcePropertyKey(n, "jdbc.transactions"))); + Set dataSourcesWithTransactionIntegration = jdbcDataSourceBuildItems.stream() + .filter(j -> j.isTransactionIntegrationEnabled()) + .map(j -> j.getName()) + .collect(Collectors.toSet()); + + recorder.startRecoveryService(transactions, configuredDataSourcesConfigKeys, dataSourcesWithTransactionIntegration); } @BuildStep(onlyIf = IsTest.class) diff --git a/extensions/narayana-jta/runtime/pom.xml b/extensions/narayana-jta/runtime/pom.xml index c0fc1bff3b037..86b14a5a6ba41 100644 --- a/extensions/narayana-jta/runtime/pom.xml +++ b/extensions/narayana-jta/runtime/pom.xml @@ -29,6 +29,10 @@ io.quarkus quarkus-mutiny + + io.quarkus + quarkus-datasource-common + io.smallrye smallrye-context-propagation-jta diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java index 22108ab19c5a4..38b1b1293ae94 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java @@ -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; @@ -22,6 +23,7 @@ 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; @@ -104,33 +106,39 @@ private void setJDBCObjectStore(String name, TransactionManagerConfiguration con instance.setTablePrefix(config.objectStore.tablePrefix); } - public void startRecoveryService(final TransactionManagerConfiguration transactions, Map dataSources, - Map dsWithInvalidTransactionsToConfigKey) { + public void startRecoveryService(final TransactionManagerConfiguration transactions, + Map configuredDataSourcesConfigKeys, + Set dataSourcesWithTransactionIntegration) { + if (transactions.objectStore.type.equals(ObjectStoreType.JDBC)) { - final String dsName; + final String objectStoreDataSourceName; if (transactions.objectStore.datasource.isEmpty()) { - dsName = dataSources.entrySet().stream().filter(Map.Entry::getKey).map(Map.Entry::getValue).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 { - 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 (dsWithInvalidTransactionsToConfigKey.containsKey(dsName)) { + 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, " @@ -138,7 +146,7 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti + "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.", - dsName, dsWithInvalidTransactionsToConfigKey.get(dsName))); + objectStoreDataSourceName, configuredDataSourcesConfigKeys.get(objectStoreDataSourceName))); } } if (transactions.enableRecovery) { From 9f7a935540277fec65546f5b61bf58e95e8658b3 Mon Sep 17 00:00:00 2001 From: Michael Musgrove Date: Wed, 2 Aug 2023 17:38:22 +0100 Subject: [PATCH 4/5] Ensure the narayana-jta extension fully shuts down the recovery manager (cherry picked from commit d99f0b52d202bc36a4731652052b706e096a412f) --- .../narayana/jta/runtime/NarayanaJtaRecorder.java | 10 ++++++++-- .../narayana/jta/runtime/QuarkusRecoveryService.java | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java index 38b1b1293ae94..c73a1cddb39ca 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java @@ -16,7 +16,6 @@ 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; @@ -158,7 +157,14 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti public void handleShutdown(ShutdownContext context, TransactionManagerConfiguration transactions) { context.addLastShutdownTask(() -> { 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(); + } } TransactionReaper.terminate(false); }); diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusRecoveryService.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusRecoveryService.java index 55ff7ae05fe22..923bcfe4625a1 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusRecoveryService.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusRecoveryService.java @@ -51,4 +51,11 @@ public void create() { } xaResources.clear(); } + + @Override + public void destroy() { + super.destroy(); + isCreated = false; + recoveryManagerService = null; + } } From acaf6104907934a1d25079ac30afadc06c4c4019 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 7 Aug 2023 16:21:55 +0200 Subject: [PATCH 5/5] Stop the recovery service while ArC is still around This is a band aid, really and I think we will need to take a step back and rethink how all this is started and shut down. --- .../io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java index c73a1cddb39ca..08ed5e00f0961 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java @@ -155,7 +155,7 @@ public void startRecoveryService(final TransactionManagerConfiguration transacti } public void handleShutdown(ShutdownContext context, TransactionManagerConfiguration transactions) { - context.addLastShutdownTask(() -> { + context.addShutdownTask(() -> { if (transactions.enableRecovery) { try { QuarkusRecoveryService.getInstance().stop(); @@ -166,6 +166,8 @@ public void handleShutdown(ShutdownContext context, TransactionManagerConfigurat QuarkusRecoveryService.getInstance().destroy(); } } + }); + context.addLastShutdownTask(() -> { TransactionReaper.terminate(false); }); }