From 6fc835025144f8d235fb98a94b2e7070bdeba101 Mon Sep 17 00:00:00 2001 From: Zheng Feng Date: Thu, 9 Mar 2023 20:26:29 +0800 Subject: [PATCH] Support JDBC ObjectStore in narayana-jta extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yoann Rodière --- docs/src/main/asciidoc/transaction.adoc | 11 +++ extensions/narayana-jta/deployment/pom.xml | 4 + .../jta/deployment/NarayanaJtaProcessor.java | 56 +++++++++---- .../jta/runtime/NarayanaJtaProducers.java | 12 +-- .../jta/runtime/NarayanaJtaRecorder.java | 72 ++++++++++++++--- .../narayana/jta/runtime/ObjectStoreType.java | 6 ++ .../jta/runtime/QuarkusDataSource.java | 80 +++++++++++++++++++ .../jta/runtime/QuarkusRecoveryService.java | 17 ++++ .../TransactionManagerConfiguration.java | 69 +++++++++++++--- integration-tests/narayana-jta/pom.xml | 34 ++++++++ .../src/main/resources/application.properties | 4 +- .../narayana/jta/BaseTransactionTest.java | 12 +++ .../jta/JdbcObjectStoreTestProfile.java | 17 ++++ .../jta/TransactionJdbcObjectStoreIT.java | 7 ++ .../jta/TransactionJdbcObjectStoreTest.java | 17 ++++ .../narayana/jta/TransactionalTestCase.java | 7 +- 16 files changed, 370 insertions(+), 55 deletions(-) create mode 100644 extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/ObjectStoreType.java create mode 100644 extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusDataSource.java create mode 100644 extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusRecoveryService.java create mode 100644 integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/BaseTransactionTest.java create mode 100644 integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java create mode 100644 integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreIT.java create mode 100644 integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreTest.java diff --git a/docs/src/main/asciidoc/transaction.adoc b/docs/src/main/asciidoc/transaction.adoc index 319f66e814178..bd870f4c95f25 100644 --- a/docs/src/main/asciidoc/transaction.adoc +++ b/docs/src/main/asciidoc/transaction.adoc @@ -362,6 +362,17 @@ NOTE: The `event` object represents the transaction ID, and defines `toString()` TIP: In listener methods, you can access more information about the transaction in progress by accessing the `TransactionManager`, which is a CDI bean and can be ``@Inject``ed. +== Configuring transaction log to be stored in a DataSource + +The Narayana project has the capability to store the transaction logs into a JDBC Datasource; this should be our recommendation for users needing transaction recovery capabilities, especially when running in volatile containers. + +To enable this capability, you need to set `quarkus.transaction-manager.object-store.type` to `jdbc` explicitly. Also, you can specify a datasource name to be used for the transaction log storage by setting `quarkus.transaction-manager.object-store.datasource`. It will use the default datasource configuration if not specified. + +If you enable `quarkus.transaction-manager.object-store.create-table`, the transaction log table will be created automatically if it does not exist. + +NOTE: When enabling this capability, the transaction node identifier must be set through `quarkus.transaction-manager.node-name`. + + == Why always having a transaction manager? Does it work everywhere I want to?:: diff --git a/extensions/narayana-jta/deployment/pom.xml b/extensions/narayana-jta/deployment/pom.xml index b9818008a7973..ced79bdff9fb4 100644 --- a/extensions/narayana-jta/deployment/pom.xml +++ b/extensions/narayana-jta/deployment/pom.xml @@ -29,6 +29,10 @@ io.quarkus quarkus-narayana-jta + + io.quarkus + quarkus-agroal-spi + io.quarkus quarkus-junit5-internal 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 e34a05a00bc05..f99c1cfffdab3 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 @@ -2,6 +2,9 @@ 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 jakarta.annotation.Priority; @@ -13,6 +16,8 @@ import com.arjuna.ats.arjuna.recovery.TransactionStatusConnectionManager; import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple; import com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore; +import com.arjuna.ats.internal.arjuna.objectstore.jdbc.JDBCImple_driver; +import com.arjuna.ats.internal.arjuna.objectstore.jdbc.JDBCStore; import com.arjuna.ats.internal.arjuna.recovery.AtomicActionExpiryScanner; import com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule; import com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner; @@ -30,19 +35,23 @@ import com.arjuna.ats.jta.common.JTAEnvironmentBean; import com.arjuna.common.util.propertyservice.PropertiesFactory; +import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem; import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem.ContextConfiguratorBuildItem; import io.quarkus.arc.deployment.CustomScopeBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.deployment.Feature; import io.quarkus.deployment.IsTest; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem; @@ -76,6 +85,7 @@ public NativeImageSystemPropertyBuildItem nativeImageSystemPropertyBuildItem() { @Record(RUNTIME_INIT) @Produce(NarayanaInitBuildItem.class) public void build(NarayanaJtaRecorder recorder, + CombinedIndexBuildItem indexBuildItem, BuildProducer additionalBeans, BuildProducer reflectiveClass, BuildProducer runtimeInit, @@ -95,21 +105,25 @@ public void build(NarayanaJtaRecorder recorder, runtimeInit.produce(new RuntimeInitializedClassBuildItem(JTAActionStatusServiceXAResourceOrphanFilter.class.getName())); runtimeInit.produce(new RuntimeInitializedClassBuildItem(AtomicActionExpiryScanner.class.getName())); - reflectiveClass.produce(ReflectiveClassBuildItem.builder(JTAEnvironmentBean.class.getName(), - UserTransactionImple.class.getName(), - CheckedActionFactoryImple.class.getName(), - TransactionManagerImple.class.getName(), - TransactionSynchronizationRegistryImple.class.getName(), - ObjectStoreEnvironmentBean.class.getName(), - ShadowNoFileLockStore.class.getName(), - SocketProcessId.class.getName(), - AtomicActionRecoveryModule.class.getName(), - XARecoveryModule.class.getName(), - XAResourceRecord.class.getName(), - JTATransactionLogXAResourceOrphanFilter.class.getName(), - JTANodeNameXAResourceOrphanFilter.class.getName(), - JTAActionStatusServiceXAResourceOrphanFilter.class.getName(), - ExpiredTransactionStatusManagerScanner.class.getName()).build()); + indexBuildItem.getIndex().getAllKnownSubclasses(JDBCImple_driver.class).stream() + .map(impl -> ReflectiveClassBuildItem.builder(impl.name().toString()).build()) + .forEach(reflectiveClass::produce); + reflectiveClass.produce(ReflectiveClassBuildItem.builder(JTAEnvironmentBean.class, + UserTransactionImple.class, + CheckedActionFactoryImple.class, + TransactionManagerImple.class, + TransactionSynchronizationRegistryImple.class, + ObjectStoreEnvironmentBean.class, + ShadowNoFileLockStore.class, + JDBCStore.class, + SocketProcessId.class, + AtomicActionRecoveryModule.class, + XARecoveryModule.class, + XAResourceRecord.class, + JTATransactionLogXAResourceOrphanFilter.class, + JTANodeNameXAResourceOrphanFilter.class, + JTAActionStatusServiceXAResourceOrphanFilter.class, + ExpiredTransactionStatusManagerScanner.class).build()); AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder(); builder.addBeanClass(TransactionalInterceptorSupports.class); @@ -135,6 +149,18 @@ public void build(NarayanaJtaRecorder recorder, recorder.setConfig(transactions); } + @BuildStep + @Record(RUNTIME_INIT) + @Consume(NarayanaInitBuildItem.class) + @Consume(SyntheticBeansRuntimeInitBuildItem.class) + public void startRecoveryService(NarayanaJtaRecorder recorder, + List jdbcDataSourceBuildItems, TransactionManagerConfiguration transactions) { + Map namedDataSources = new HashMap<>(); + + jdbcDataSourceBuildItems.forEach(i -> namedDataSources.put(i.isDefault(), i.getName())); + recorder.startRecoveryService(transactions, namedDataSources); + } + @BuildStep(onlyIf = IsTest.class) void testTx(BuildProducer generatedBeanBuildItemBuildProducer, BuildProducer additionalBeans) { diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java index ea456aad98cda..b784d5b92e6df 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java @@ -7,18 +7,19 @@ import jakarta.transaction.TransactionSynchronizationRegistry; import jakarta.transaction.UserTransaction; +import org.jboss.logging.Logger; import org.jboss.tm.JBossXATerminator; import org.jboss.tm.XAResourceRecoveryRegistry; import org.jboss.tm.usertx.UserTransactionRegistry; import com.arjuna.ats.internal.jbossatx.jta.jca.XATerminator; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple; -import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; import io.quarkus.arc.Unremovable; @Dependent public class NarayanaJtaProducers { + private static final Logger log = Logger.getLogger(NarayanaJtaProducers.class); @Produces @ApplicationScoped @@ -41,13 +42,8 @@ public jakarta.transaction.TransactionManager transactionManager() { @Produces @Singleton - public XAResourceRecoveryRegistry xaResourceRecoveryRegistry(TransactionManagerConfiguration config) { - RecoveryManagerService recoveryManagerService = new RecoveryManagerService(); - if (config.enableRecovery) { - recoveryManagerService.create(); - recoveryManagerService.start(); - } - return recoveryManagerService; + public XAResourceRecoveryRegistry xaResourceRecoveryRegistry() { + return QuarkusRecoveryService.getInstance(); } @Produces 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 ac859f3502391..a3edd9972cc38 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 @@ -1,7 +1,10 @@ package io.quarkus.narayana.jta.runtime; import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Properties; import org.jboss.logging.Logger; @@ -13,6 +16,7 @@ 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; @@ -20,6 +24,7 @@ import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.runtime.configuration.ConfigurationException; @Recorder public class NarayanaJtaRecorder { @@ -72,12 +77,12 @@ public void disableTransactionStatusManager() { } public void setConfig(final TransactionManagerConfiguration transactions) { - BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class) - .setObjectStoreDir(transactions.objectStoreDirectory); - BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore") - .setObjectStoreDir(transactions.objectStoreDirectory); - BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore") - .setObjectStoreDir(transactions.objectStoreDirectory); + List objectStores = Arrays.asList(null, "communicationStore", "stateStore"); + if (transactions.objectStore.type.equals(ObjectStoreType.File_System)) { + objectStores.forEach(name -> setObjectStoreDir(name, transactions)); + } else if (transactions.objectStore.type.equals(ObjectStoreType.JDBC)) { + objectStores.forEach(name -> setJDBCObjectStore(name, transactions)); + } BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) .setRecoveryModuleClassNames(transactions.recoveryModules); BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) @@ -86,15 +91,56 @@ public void setConfig(final TransactionManagerConfiguration transactions) { .setXaResourceOrphanFilterClassNames(transactions.xaResourceOrphanFilters); } + private void setObjectStoreDir(String name, TransactionManagerConfiguration config) { + BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, name).setObjectStoreDir(config.objectStore.directory); + } + + private void setJDBCObjectStore(String name, TransactionManagerConfiguration config) { + final ObjectStoreEnvironmentBean instance = BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, name); + instance.setObjectStoreType(JDBCStore.class.getName()); + instance.setJdbcDataSource(new QuarkusDataSource(config.objectStore.datasource)); + instance.setCreateTable(config.objectStore.createTable); + instance.setDropTable(config.objectStore.dropTable); + instance.setTablePrefix(config.objectStore.tablePrefix); + } + + public void startRecoveryService(final TransactionManagerConfiguration transactions, Map dataSources) { + if (transactions.objectStore.type.equals(ObjectStoreType.JDBC)) { + 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.")); + } 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.")); + } + } + if (transactions.enableRecovery) { + QuarkusRecoveryService.getInstance().create(); + QuarkusRecoveryService.getInstance().start(); + } + } + public void handleShutdown(ShutdownContext context, TransactionManagerConfiguration transactions) { - context.addLastShutdownTask(new Runnable() { - @Override - public void run() { - if (transactions.enableRecovery) { - RecoveryManager.manager().terminate(true); - } - TransactionReaper.terminate(false); + context.addLastShutdownTask(() -> { + if (transactions.enableRecovery) { + RecoveryManager.manager().terminate(true); } + TransactionReaper.terminate(false); }); } } diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/ObjectStoreType.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/ObjectStoreType.java new file mode 100644 index 0000000000000..dd01810fd8744 --- /dev/null +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/ObjectStoreType.java @@ -0,0 +1,6 @@ +package io.quarkus.narayana.jta.runtime; + +public enum ObjectStoreType { + File_System, + JDBC +} diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusDataSource.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusDataSource.java new file mode 100644 index 0000000000000..dfbf97fce0073 --- /dev/null +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusDataSource.java @@ -0,0 +1,80 @@ +package io.quarkus.narayana.jta.runtime; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Optional; +import java.util.logging.Logger; + +import javax.sql.DataSource; + +import jakarta.enterprise.inject.literal.NamedLiteral; + +import io.quarkus.arc.Arc; + +public class QuarkusDataSource implements DataSource { + private final Optional dsName; + private volatile DataSource datasource; + + public QuarkusDataSource(Optional dsName) { + this.dsName = dsName; + } + + private DataSource getDataSource() { + if (datasource == null) { + if (dsName.isEmpty()) { + datasource = Arc.container().instance(DataSource.class).get(); + } else { + datasource = Arc.container().instance(DataSource.class, NamedLiteral.of(dsName.get())).get(); + } + } + + return datasource; + } + + @Override + public Connection getConnection() throws SQLException { + return getDataSource().getConnection(); + } + + @Override + public Connection getConnection(final String user, final String passwd) throws SQLException { + return getDataSource().getConnection(user, passwd); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return getDataSource().getLogWriter(); + } + + @Override + public void setLogWriter(final PrintWriter writer) throws SQLException { + getDataSource().setLogWriter(writer); + } + + @Override + public void setLoginTimeout(final int timeout) throws SQLException { + getDataSource().setLoginTimeout(timeout); + } + + @Override + public int getLoginTimeout() throws SQLException { + return getDataSource().getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return getDataSource().getParentLogger(); + } + + @Override + public T unwrap(final Class aClass) throws SQLException { + return getDataSource().unwrap(aClass); + } + + @Override + public boolean isWrapperFor(final Class aClass) throws SQLException { + return getDataSource().isWrapperFor(aClass); + } +} 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 new file mode 100644 index 0000000000000..18c5e80438c10 --- /dev/null +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/QuarkusRecoveryService.java @@ -0,0 +1,17 @@ +package io.quarkus.narayana.jta.runtime; + +import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; + +public class QuarkusRecoveryService { + private static RecoveryManagerService recoveryManagerService; + + public static RecoveryManagerService getInstance() { + if (recoveryManagerService == null) { + recoveryManagerService = new RecoveryManagerService(); + } + return recoveryManagerService; + } + + private QuarkusRecoveryService() { + } +} diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java index be182bd3d76cc..86f580ae3a6e4 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java @@ -2,7 +2,9 @@ import java.time.Duration; import java.util.List; +import java.util.Optional; +import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -13,25 +15,17 @@ @ConfigRoot(phase = ConfigPhase.RUN_TIME) public final class TransactionManagerConfiguration { /** - * The node name used by the transaction manager + * The node name used by the transaction manager. */ @ConfigItem(defaultValue = "quarkus") public String nodeName; /** - * The default transaction timeout + * The default transaction timeout. */ @ConfigItem(defaultValue = "60") public Duration defaultTransactionTimeout; - /** - * The directory name of location of the transaction logs. - * If the value is not absolute then the directory is relative - * to the user.dir system property. - */ - @ConfigItem(defaultValue = "ObjectStore") - public String objectStoreDirectory; - /** * Start the recovery service on startup. */ @@ -39,23 +33,72 @@ public final class TransactionManagerConfiguration { public boolean enableRecovery; /** - * The list of recovery modules + * The list of recovery modules. */ @ConfigItem(defaultValue = "com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule," + "com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule") public List recoveryModules; /** - * The list of expiry scanners + * The list of expiry scanners. */ @ConfigItem(defaultValue = "com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner") public List expiryScanners; /** - * The list of orphan filters + * The list of orphan filters. */ @ConfigItem(defaultValue = "com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter," + "com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter," + "com.arjuna.ats.internal.jta.recovery.arjunacore.JTAActionStatusServiceXAResourceOrphanFilter") public List xaResourceOrphanFilters; + + /** + * The object store configuration. + */ + @ConfigItem + public ObjectStoreConfig objectStore; +} + +@ConfigGroup +class ObjectStoreConfig { + /** + * The name of the directory where the transaction logs will be stored when using the {@code file-system} object store. + * If the value is not absolute then the directory is relative + * to the user.dir system property. + */ + @ConfigItem(defaultValue = "ObjectStore") + public String directory; + + /** + * The type of object store. + */ + @ConfigItem(defaultValue = "file-system") + public ObjectStoreType type; + + /** + * The name of the datasource where the transaction logs will be stored when using the {@code jdbc} object store. + *

+ * If undefined, it will use the default datasource. + */ + @ConfigItem + public Optional datasource = Optional.empty(); + + /** + * Whether to create the table if it does not exist. + */ + @ConfigItem(defaultValue = "false") + public boolean createTable; + + /** + * Whether to drop the table on startup. + */ + @ConfigItem(defaultValue = "false") + public boolean dropTable; + + /** + * The prefix to apply to the table. + */ + @ConfigItem(defaultValue = "quarkus_") + public String tablePrefix; } diff --git a/integration-tests/narayana-jta/pom.xml b/integration-tests/narayana-jta/pom.xml index d993384757fb8..3138ba3c0642b 100644 --- a/integration-tests/narayana-jta/pom.xml +++ b/integration-tests/narayana-jta/pom.xml @@ -12,6 +12,10 @@ Quarkus - Integration Tests - Narayana JTA CDI + + io.quarkus + quarkus-agroal + io.quarkus quarkus-narayana-jta @@ -20,6 +24,10 @@ io.quarkus quarkus-resteasy + + io.quarkus + quarkus-jdbc-h2 + io.quarkus quarkus-junit5 @@ -37,6 +45,19 @@ + + io.quarkus + quarkus-agroal-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-narayana-jta-deployment @@ -63,6 +84,19 @@ + + io.quarkus + quarkus-jdbc-h2-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/narayana-jta/src/main/resources/application.properties b/integration-tests/narayana-jta/src/main/resources/application.properties index 221d51ba83af1..d08ca5fe0cda6 100644 --- a/integration-tests/narayana-jta/src/main/resources/application.properties +++ b/integration-tests/narayana-jta/src/main/resources/application.properties @@ -8,4 +8,6 @@ quarkus.console.color=false quarkus.log.category."com.arjuna".level=INFO quarkus.log.category."io.quarkus".level=INFO -quarkus.transaction-manager.object-store-directory=target/tx-object-store \ No newline at end of file +quarkus.datasource.db-kind=h2 + +quarkus.transaction-manager.object-store.directory=target/tx-object-store diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/BaseTransactionTest.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/BaseTransactionTest.java new file mode 100644 index 0000000000000..e6ed7afef9323 --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/BaseTransactionTest.java @@ -0,0 +1,12 @@ +package io.quarkus.narayana.jta; + +import static org.hamcrest.core.Is.is; + +import io.restassured.RestAssured; + +public class BaseTransactionTest { + + public void runTest() { + RestAssured.when().get("/status").then().body(is("0")); + } +} 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 new file mode 100644 index 0000000000000..88fa090fa3a3b --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java @@ -0,0 +1,17 @@ +package io.quarkus.narayana.jta; + +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTestProfile; + +public class JdbcObjectStoreTestProfile implements QuarkusTestProfile { + @Override + 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.enable-recovery", "true"); + return props; + } +} diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreIT.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreIT.java new file mode 100644 index 0000000000000..c649e3ee5e754 --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreIT.java @@ -0,0 +1,7 @@ +package io.quarkus.narayana.jta; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class TransactionJdbcObjectStoreIT extends TransactionJdbcObjectStoreTest { +} diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreTest.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreTest.java new file mode 100644 index 0000000000000..fc26432aec1ea --- /dev/null +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionJdbcObjectStoreTest.java @@ -0,0 +1,17 @@ +package io.quarkus.narayana.jta; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestHTTPEndpoint(TransactionalResource.class) +@TestProfile(JdbcObjectStoreTestProfile.class) +public class TransactionJdbcObjectStoreTest extends BaseTransactionTest { + @Test + public void test() { + runTest(); + } +} diff --git a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionalTestCase.java b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionalTestCase.java index acb77cca6a461..3ec45306b9a63 100644 --- a/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionalTestCase.java +++ b/integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/TransactionalTestCase.java @@ -1,19 +1,16 @@ package io.quarkus.narayana.jta; -import static org.hamcrest.core.Is.is; - import org.junit.jupiter.api.Test; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; -import io.restassured.RestAssured; @QuarkusTest @TestHTTPEndpoint(TransactionalResource.class) -public class TransactionalTestCase { +public class TransactionalTestCase extends BaseTransactionTest { @Test public void test() { - RestAssured.when().get("/status").then().body(is("0")); + runTest(); } }