Skip to content

Commit 6f466dc

Browse files
authored
Merge pull request #34996 from michalvavrik/feature/narayana-warn-when-not-disabled
Narayana JTA: do not allow to use datasource XA transactions with JDBC object store
2 parents 9ff6c6a + c3ae13e commit 6f466dc

File tree

9 files changed

+128
-36
lines changed

9 files changed

+128
-36
lines changed

extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,9 @@ void generateDataSourceBeans(AgroalRecorder recorder,
267267
return;
268268
}
269269

270-
for (Map.Entry<String, DataSourceSupport.Entry> entry : getDataSourceSupport(aggregatedBuildTimeConfigBuildItems,
271-
sslNativeConfig,
272-
capabilities).entries.entrySet()) {
270+
for (AggregatedDataSourceBuildTimeConfigBuildItem aggregatedBuildTimeConfigBuildItem : aggregatedBuildTimeConfigBuildItems) {
273271

274-
String dataSourceName = entry.getKey();
272+
String dataSourceName = aggregatedBuildTimeConfigBuildItem.getName();
275273

276274
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
277275
.configure(AgroalDataSource.class)
@@ -284,7 +282,7 @@ void generateDataSourceBeans(AgroalRecorder recorder,
284282
// are created after runtime configuration has been set up
285283
.createWith(recorder.agroalDataSourceSupplier(dataSourceName, dataSourcesRuntimeConfig));
286284

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

300298
jdbcDataSource.produce(new JdbcDataSourceBuildItem(dataSourceName,
301-
entry.getValue().resolvedDbKind,
302-
entry.getValue().dbVersion,
303-
entry.getValue().isDefault));
299+
aggregatedBuildTimeConfigBuildItem.getDbKind(),
300+
aggregatedBuildTimeConfigBuildItem.getDataSourceConfig().dbVersion,
301+
aggregatedBuildTimeConfigBuildItem.getJdbcConfig().transactions != TransactionIntegration.DISABLED,
302+
aggregatedBuildTimeConfigBuildItem.isDefault()));
304303
}
305304
}
306305

extensions/agroal/spi/src/main/java/io/quarkus/agroal/spi/JdbcDataSourceBuildItem.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ public final class JdbcDataSourceBuildItem extends MultiBuildItem {
1717
private final String dbKind;
1818

1919
private final Optional<String> dbVersion;
20+
21+
private final boolean transactionIntegrationEnabled;
22+
2023
private final boolean isDefault;
2124

22-
public JdbcDataSourceBuildItem(String name, String kind, Optional<String> dbVersion, boolean isDefault) {
25+
public JdbcDataSourceBuildItem(String name, String kind, Optional<String> dbVersion,
26+
boolean transactionIntegrationEnabled, boolean isDefault) {
2327
this.name = name;
2428
this.dbKind = kind;
2529
this.dbVersion = dbVersion;
30+
this.transactionIntegrationEnabled = transactionIntegrationEnabled;
2631
this.isDefault = isDefault;
2732
}
2833

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

46+
public boolean isTransactionIntegrationEnabled() {
47+
return transactionIntegrationEnabled;
48+
}
49+
4150
public boolean isDefault() {
4251
return isDefault;
4352
}

extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

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

5-
import java.util.HashMap;
65
import java.util.List;
76
import java.util.Map;
87
import java.util.Properties;
8+
import java.util.Set;
9+
import java.util.function.Function;
10+
import java.util.stream.Collectors;
911

1012
import jakarta.annotation.Priority;
1113
import jakarta.interceptor.Interceptor;
@@ -44,6 +46,7 @@
4446
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
4547
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
4648
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
49+
import io.quarkus.datasource.common.runtime.DataSourceUtil;
4750
import io.quarkus.deployment.Feature;
4851
import io.quarkus.deployment.IsTest;
4952
import io.quarkus.deployment.annotations.BuildProducer;
@@ -155,10 +158,16 @@ public void build(NarayanaJtaRecorder recorder,
155158
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
156159
public void startRecoveryService(NarayanaJtaRecorder recorder,
157160
List<JdbcDataSourceBuildItem> jdbcDataSourceBuildItems, TransactionManagerConfiguration transactions) {
158-
Map<Boolean, String> namedDataSources = new HashMap<>();
159-
160-
jdbcDataSourceBuildItems.forEach(i -> namedDataSources.put(i.isDefault(), i.getName()));
161-
recorder.startRecoveryService(transactions, namedDataSources);
161+
Map<String, String> configuredDataSourcesConfigKeys = jdbcDataSourceBuildItems.stream()
162+
.map(j -> j.getName())
163+
.collect(Collectors.toMap(Function.identity(),
164+
n -> DataSourceUtil.dataSourcePropertyKey(n, "jdbc.transactions")));
165+
Set<String> dataSourcesWithTransactionIntegration = jdbcDataSourceBuildItems.stream()
166+
.filter(j -> j.isTransactionIntegrationEnabled())
167+
.map(j -> j.getName())
168+
.collect(Collectors.toSet());
169+
170+
recorder.startRecoveryService(transactions, configuredDataSourcesConfigKeys, dataSourcesWithTransactionIntegration);
162171
}
163172

164173
@BuildStep(onlyIf = IsTest.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.quarkus.narayana.quarkus;
2+
3+
import static org.junit.jupiter.api.Assertions.assertTrue;
4+
import static org.junit.jupiter.api.Assertions.fail;
5+
6+
import java.util.List;
7+
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.Version;
13+
import io.quarkus.maven.dependency.Dependency;
14+
import io.quarkus.runtime.configuration.ConfigurationException;
15+
import io.quarkus.runtime.util.ExceptionUtil;
16+
import io.quarkus.test.QuarkusUnitTest;
17+
18+
public class TransactionJdbcObjectStoreValidationFailureTest {
19+
20+
@RegisterExtension
21+
static final QuarkusUnitTest config = new QuarkusUnitTest()
22+
.withApplicationRoot((jar) -> jar
23+
.addAsResource("jdbc-object-store-validation.properties", "application.properties"))
24+
.setForcedDependencies(List.of(Dependency.of("io.quarkus", "quarkus-jdbc-h2", Version.getVersion())))
25+
.assertException(t -> {
26+
Throwable rootCause = ExceptionUtil.getRootCause(t);
27+
if (rootCause instanceof ConfigurationException) {
28+
assertTrue(rootCause.getMessage().contains(
29+
"The Narayana JTA extension is configured to use the datasource 'test' but that datasource is not configured."));
30+
} else {
31+
fail(t);
32+
}
33+
});
34+
35+
@Test
36+
public void test() {
37+
// needs to be there in order to run test
38+
Assertions.fail("Application was supposed to fail.");
39+
}
40+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
quarkus.transaction-manager.object-store.type=jdbc
2+
quarkus.transaction-manager.object-store.datasource=test
3+
quarkus.datasource.test.db-kind=h2
4+
quarkus.datasource.test.jdbc.url=jdbc:h2:mem:default
5+
quarkus.datasource.test.jdbc.transactions=xa
6+
7+
# we must not start database as CI is also executed on Windows without (Docker) Linux containers
8+
quarkus.datasource.devservices.enabled=false
9+
quarkus.devservices.enabled=false

extensions/narayana-jta/runtime/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
<groupId>io.quarkus</groupId>
3030
<artifactId>quarkus-mutiny</artifactId>
3131
</dependency>
32+
<dependency>
33+
<groupId>io.quarkus</groupId>
34+
<artifactId>quarkus-datasource-common</artifactId>
35+
</dependency>
3236
<dependency>
3337
<groupId>io.smallrye</groupId>
3438
<artifactId>smallrye-context-propagation-jta</artifactId>

extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import java.util.Map;
88
import java.util.Properties;
9+
import java.util.Set;
910

1011
import org.jboss.logging.Logger;
1112

@@ -22,6 +23,7 @@
2223
import com.arjuna.common.internal.util.propertyservice.BeanPopulator;
2324
import com.arjuna.common.util.propertyservice.PropertiesFactory;
2425

26+
import io.quarkus.datasource.common.runtime.DataSourceUtil;
2527
import io.quarkus.runtime.ShutdownContext;
2628
import io.quarkus.runtime.annotations.Recorder;
2729
import io.quarkus.runtime.configuration.ConfigurationException;
@@ -104,29 +106,47 @@ private void setJDBCObjectStore(String name, TransactionManagerConfiguration con
104106
instance.setTablePrefix(config.objectStore.tablePrefix);
105107
}
106108

107-
public void startRecoveryService(final TransactionManagerConfiguration transactions, Map<Boolean, String> dataSources) {
109+
public void startRecoveryService(final TransactionManagerConfiguration transactions,
110+
Map<String, String> configuredDataSourcesConfigKeys,
111+
Set<String> dataSourcesWithTransactionIntegration) {
112+
108113
if (transactions.objectStore.type.equals(ObjectStoreType.JDBC)) {
114+
final String objectStoreDataSourceName;
109115
if (transactions.objectStore.datasource.isEmpty()) {
110-
dataSources.keySet().stream().filter(i -> i).findFirst().orElseThrow(
111-
() -> new ConfigurationException(
112-
"The Narayana JTA extension does not have a datasource configured,"
113-
+ " so it defaults to the default datasource,"
114-
+ " but that datasource is not configured."
115-
+ " To solve this, either configure the default datasource,"
116-
+ " referring to https://quarkus.io/guides/datasource for guidance,"
117-
+ " or configure the datasource to use in the Narayana JTA extension "
118-
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource."));
116+
if (!DataSourceUtil.hasDefault(configuredDataSourcesConfigKeys.keySet())) {
117+
throw new ConfigurationException(
118+
"The Narayana JTA extension does not have a datasource configured as the JDBC object store,"
119+
+ " so it defaults to the default datasource,"
120+
+ " but that datasource is not configured."
121+
+ " To solve this, either configure the default datasource,"
122+
+ " referring to https://quarkus.io/guides/datasource for guidance,"
123+
+ " or configure the datasource to use in the Narayana JTA extension "
124+
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource.");
125+
}
126+
objectStoreDataSourceName = DataSourceUtil.DEFAULT_DATASOURCE_NAME;
119127
} else {
120-
String dsName = transactions.objectStore.datasource.get();
121-
dataSources.values().stream().filter(i -> i.equals(dsName)).findFirst()
122-
.orElseThrow(() -> new ConfigurationException(
123-
"The Narayana JTA extension is configured to use the datasource '"
124-
+ dsName
125-
+ "' but that datasource is not configured."
126-
+ " To solve this, either configure datasource " + dsName
127-
+ " referring to https://quarkus.io/guides/datasource for guidance,"
128-
+ " or configure another datasource to use in the Narayana JTA extension "
129-
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource."));
128+
objectStoreDataSourceName = transactions.objectStore.datasource.get();
129+
130+
if (!configuredDataSourcesConfigKeys.keySet().contains(objectStoreDataSourceName)) {
131+
throw new ConfigurationException(
132+
"The Narayana JTA extension is configured to use the datasource '"
133+
+ objectStoreDataSourceName
134+
+ "' but that datasource is not configured."
135+
+ " To solve this, either configure datasource " + objectStoreDataSourceName
136+
+ " referring to https://quarkus.io/guides/datasource for guidance,"
137+
+ " or configure another datasource to use in the Narayana JTA extension "
138+
+ " by setting property 'quarkus.transaction-manager.object-store.datasource' to the name of a configured datasource.");
139+
}
140+
}
141+
if (dataSourcesWithTransactionIntegration.contains(objectStoreDataSourceName)) {
142+
throw new ConfigurationException(String.format(
143+
"The Narayana JTA extension is configured to use the '%s' JDBC "
144+
+ "datasource as the transaction log storage, "
145+
+ "but that datasource does not have transaction capabilities disabled. "
146+
+ "To solve this, please set '%s=disabled', or configure another datasource "
147+
+ "with disabled transaction capabilities as the JDBC object store. "
148+
+ "Please refer to the https://quarkus.io/guides/transaction#jdbcstore for more information.",
149+
objectStoreDataSourceName, configuredDataSourcesConfigKeys.get(objectStoreDataSourceName)));
130150
}
131151
}
132152
if (transactions.enableRecovery) {

integration-tests/narayana-jta/src/main/resources/application.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ quarkus.log.category."io.quarkus".level=INFO
1111
quarkus.datasource.db-kind=h2
1212

1313
quarkus.transaction-manager.object-store.directory=target/tx-object-store
14+
15+
quarkus.datasource.test.jdbc.transactions=disabled
16+
quarkus.datasource.test.db-kind=h2

integration-tests/narayana-jta/src/test/java/io/quarkus/narayana/jta/JdbcObjectStoreTestProfile.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ public Map<String, String> getConfigOverrides() {
1111
HashMap<String, String> props = new HashMap<>();
1212
props.put("quarkus.transaction-manager.object-store.type", "jdbc");
1313
props.put("quarkus.transaction-manager.object-store.create-table", "true");
14+
props.put("quarkus.transaction-manager.object-store.datasource", "test");
1415
props.put("quarkus.transaction-manager.enable-recovery", "true");
1516

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

2019
return props;
2120
}

0 commit comments

Comments
 (0)