From c018788846b8c1a29b4f5e19c51c3fd6df771fda Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 10 Nov 2021 19:14:54 +0000 Subject: [PATCH] Test JTA auto-configured from JNDI Closes gh-28594 --- .../spring-boot-autoconfigure/build.gradle | 5 +- .../jta/JtaAutoConfigurationTests.java | 119 ++++++++++++++++++ .../src/test/resources/jndi.properties | 5 + .../src/test/resources/simple-jndi | 0 .../spring-boot-parent/build.gradle | 7 ++ 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/jndi.properties create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/resources/simple-jndi diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 46202f3eab..7175bc57ee 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -73,7 +73,9 @@ dependencies { optional("org.aspectj:aspectjweaver") optional("org.eclipse.jetty:jetty-webapp") optional("org.eclipse.jetty:jetty-reactive-httpclient") - optional("org.eclipse.jetty.websocket:javax-websocket-server-impl") + optional("org.eclipse.jetty.websocket:javax-websocket-server-impl") { + exclude(group: "org.eclipse.jetty", module: "jetty-jndi") + } optional("org.ehcache:ehcache") optional("org.elasticsearch.client:elasticsearch-rest-client") optional("org.elasticsearch.client:elasticsearch-rest-high-level-client") @@ -153,6 +155,7 @@ dependencies { testImplementation("ch.qos.logback:logback-classic") testImplementation("commons-fileupload:commons-fileupload") testImplementation("com.atomikos:transactions-jms") + testImplementation("com.github.h-thurow:simple-jndi") testImplementation("com.ibm.db2:jcc") testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.squareup.okhttp3:mockwebserver") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java index af4960257c..b03560c8e3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java @@ -21,12 +21,18 @@ import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; import javax.jms.ConnectionFactory; import javax.jms.TemporaryQueue; import javax.jms.XAConnection; import javax.jms.XAConnectionFactory; import javax.jms.XASession; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; import javax.sql.DataSource; import javax.sql.XADataSource; import javax.transaction.TransactionManager; @@ -38,7 +44,19 @@ import com.atomikos.icatch.jta.UserTransactionManager; import com.atomikos.jms.AtomikosConnectionFactoryBean; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.osjava.sj.loader.JndiLoader; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; @@ -53,6 +71,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.jta.JtaTransactionManager; +import org.springframework.transaction.jta.UserTransactionAdapter; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -80,6 +99,31 @@ class JtaAutoConfigurationTests { if (this.context != null) { this.context.close(); } + + } + + @ParameterizedTest + @ExtendWith(JndiExtension.class) + @MethodSource("transactionManagerJndiEntries") + void transactionManagerFromJndi(JndiEntry jndiEntry, InitialContext initialContext) throws NamingException { + jndiEntry.register(initialContext); + this.context = new AnnotationConfigApplicationContext(JtaAutoConfiguration.class); + JtaTransactionManager transactionManager = this.context.getBean(JtaTransactionManager.class); + if (jndiEntry.value instanceof UserTransaction) { + assertThat(transactionManager.getUserTransaction()).isEqualTo(jndiEntry.value); + assertThat(transactionManager.getTransactionManager()).isNull(); + } + else { + assertThat(transactionManager.getUserTransaction()).isInstanceOf(UserTransactionAdapter.class); + assertThat(transactionManager.getTransactionManager()).isEqualTo(jndiEntry.value); + } + } + + static List transactionManagerJndiEntries() { + return Arrays.asList(Arguments.of(new JndiEntry("java:comp/UserTransaction", UserTransaction.class)), + Arguments.of(new JndiEntry("java:appserver/TransactionManager", TransactionManager.class)), + Arguments.of(new JndiEntry("java:pm/TransactionManager", TransactionManager.class)), + Arguments.of(new JndiEntry("java:/TransactionManager", TransactionManager.class))); } @Test @@ -292,4 +336,79 @@ class JtaAutoConfigurationTests { } + private static final class JndiEntry { + + private final String name; + + private final Class type; + + private final Object value; + + private JndiEntry(String name, Class type) { + this.name = name; + this.type = type; + this.value = mock(type); + } + + private void register(InitialContext initialContext) throws NamingException { + String[] components = this.name.split("/"); + String subcontextName = components[0]; + String entryName = components[1]; + Context javaComp = initialContext.createSubcontext(subcontextName); + JndiLoader loader = new JndiLoader(initialContext.getEnvironment()); + Properties properties = new Properties(); + properties.setProperty(entryName + "/type", this.type.getName()); + properties.put(entryName + "/valueToConvert", this.value); + loader.load(properties, javaComp); + } + + @Override + public String toString() { + return this.name; + } + + } + + private static final class JndiExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + Namespace namespace = Namespace.create(getClass(), context.getUniqueId()); + context.getStore(namespace).getOrComputeIfAbsent(InitialContext.class, (k) -> createInitialContext(), + InitialContext.class); + } + + private InitialContext createInitialContext() { + try { + return new InitialContext(); + } + catch (Exception ex) { + throw new RuntimeException(); + } + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + Namespace namespace = Namespace.create(getClass(), context.getUniqueId()); + InitialContext initialContext = context.getStore(namespace).remove(InitialContext.class, + InitialContext.class); + initialContext.removeFromEnvironment("org.osjava.sj.jndi.ignoreClose"); + initialContext.close(); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return InitialContext.class.isAssignableFrom(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + Namespace namespace = Namespace.create(getClass(), extensionContext.getUniqueId()); + return extensionContext.getStore(namespace).get(InitialContext.class); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/jndi.properties b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/jndi.properties new file mode 100644 index 0000000000..8d34127a66 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/jndi.properties @@ -0,0 +1,5 @@ +java.naming.factory.initial = org.osjava.sj.SimpleJndiContextFactory +org.osjava.sj.delimiter = / +org.osjava.sj.jndi.shared = true +org.osjava.sj.root = src/test/resources/simple-jndi +org.osjava.sj.jndi.ignoreClose = true \ No newline at end of file diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/simple-jndi b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/simple-jndi new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-project/spring-boot-parent/build.gradle b/spring-boot-project/spring-boot-parent/build.gradle index 6f665394a2..a132ec9137 100644 --- a/spring-boot-project/spring-boot-parent/build.gradle +++ b/spring-boot-project/spring-boot-parent/build.gradle @@ -126,6 +126,13 @@ bom { ] } } + library("Simple JNDI", "0.23.0") { + group("com.github.h-thurow") { + modules = [ + "simple-jndi" + ] + } + } library("Sisu", "2.6.0") { group("org.sonatype.sisu") { modules = [