Issue #12094 restore classloader association during comp/env creation (#12107)

* Issue #12094 restore classloader association during comp/env creation
This commit is contained in:
Jan Bartel 2024-08-04 16:48:43 +10:00 committed by GitHub
parent e1d03b4c68
commit 54fde0e8be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 243 additions and 10 deletions

View File

@ -93,9 +93,31 @@ public class ContextFactory implements ObjectFactory
{
Context ctx = null;
//If the thread context classloader is set, then try its hierarchy to find a matching context
//See if there is a classloader already set to use for finding the comp
//naming Context
ClassLoader loader = (ClassLoader)__threadClassLoader.get();
if (loader != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Using threadlocal classloader");
try (AutoLock l = __lock.lock())
{
ctx = getContextForClassLoader(loader);
if (ctx == null)
{
ctx = newNamingContext(obj, loader, env, name, nameCtx);
__contextMap.put(loader, ctx);
if (LOG.isDebugEnabled())
LOG.debug("Made context {} for classloader {}", name.get(0), loader);
}
return ctx;
}
}
//If the thread context classloader is set, then try it and its
//classloader hierarchy to find a matching naming Context
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
ClassLoader loader = tccl;
loader = tccl;
if (loader != null)
{
if (LOG.isDebugEnabled())
@ -121,7 +143,7 @@ public class ContextFactory implements ObjectFactory
}
//If trying thread context classloader hierarchy failed, try the
//classloader associated with the current context
//classloader associated with the current ContextHandler
if (ContextHandler.getCurrentContext() != null)
{
if (LOG.isDebugEnabled())
@ -189,6 +211,18 @@ public class ContextFactory implements ObjectFactory
}
}
public static ClassLoader associateClassLoader(final ClassLoader loader)
{
ClassLoader prev = (ClassLoader)__threadClassLoader.get();
__threadClassLoader.set(loader);
return prev;
}
public static void disassociateClassLoader()
{
__threadClassLoader.set(null);
}
public static void dump(Appendable out, String indent) throws IOException
{
try (AutoLock l = __lock.lock())

View File

@ -23,6 +23,7 @@ module org.eclipse.jetty.ee10.plus
// Only required if using Transaction.
requires static transitive jakarta.transaction;
requires org.eclipse.jetty.jndi;
exports org.eclipse.jetty.ee10.plus.jndi;
exports org.eclipse.jetty.ee10.plus.webapp;

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.ee10.webapp.MetaInfConfiguration;
import org.eclipse.jetty.ee10.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.ee10.webapp.WebXmlConfiguration;
import org.eclipse.jetty.jndi.ContextFactory;
import org.eclipse.jetty.plus.jndi.EnvEntry;
import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
import org.eclipse.jetty.util.jndi.NamingDump;
@ -123,6 +124,7 @@ public class EnvConfiguration extends AbstractConfiguration
//get rid of any bindings for comp/env for webapp
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
ContextFactory.associateClassLoader(context.getClassLoader());
try
{
Context ic = new InitialContext();
@ -147,6 +149,7 @@ public class EnvConfiguration extends AbstractConfiguration
}
finally
{
ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
@ -218,14 +221,26 @@ public class EnvConfiguration extends AbstractConfiguration
{
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(wac.getClassLoader());
//ensure that we create a unique comp/env context for this webapp based off
//its classloader
ContextFactory.associateClassLoader(wac.getClassLoader());
try
{
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.createSubcontext("env");
WebAppClassLoader.runWithServerClassAccess(() ->
{
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.createSubcontext("env");
return null;
});
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(oldLoader);
}
}

View File

@ -15,19 +15,31 @@ package org.eclipse.jetty.ee10.plus.webapp;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.ee10.webapp.Configuration;
import org.eclipse.jetty.ee10.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Isolated;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Isolated("jndi entries")
public class EnvConfigurationTest
{
Server _server;
@ -73,4 +85,79 @@ public class EnvConfigurationTest
assertNotNull(NamingEntryUtil.lookupNamingEntry(context, "peach"));
assertNull(NamingEntryUtil.lookupNamingEntry(context, "cabbage"));
}
@Test
public void testCompEnvCreation() throws Exception
{
EnvConfiguration envConfigurationA = new EnvConfiguration();
EnvConfiguration envConfigurationB = new EnvConfiguration();
WebAppContext webappA = null;
WebAppContext webappB = null;
try
{
webappA = new WebAppContext();
webappA.setConfigurations(new Configuration[]{new PlusConfiguration(), new EnvConfiguration()});
webappA.setClassLoader(new WebAppClassLoader(Thread.currentThread().getContextClassLoader(), webappA));
//ensure that a java:comp/env Context was created for webappA
envConfigurationA.preConfigure(webappA);
Context namingContextA = getCompEnvFor(webappA);
webappB = new WebAppContext();
webappB.setConfigurations(new Configuration[]{new PlusConfiguration(), new EnvConfiguration()});
webappB.setClassLoader(new WebAppClassLoader(Thread.currentThread().getContextClassLoader(), webappB));
//ensure that a different java:comp/env Context was created for webappB
envConfigurationB.preConfigure(webappB);
Context namingContextB = getCompEnvFor(webappB);
assertThat(namingContextA, is(not(namingContextB)));
}
catch (Throwable t)
{
t.printStackTrace();
}
finally
{
envConfigurationA.deconfigure(webappA);
envConfigurationB.deconfigure(webappB);
}
}
@Test
public void testPriorCompCreation() throws Exception
{
//pre-create java:comp on the app classloader
new InitialContext().lookup("java:comp");
//test that each webapp still gets its own naming Context
testCompEnvCreation();
}
/**
* Find the java:comp/env naming Context for the given webapp
* @param webapp the WebAppContext whose naming comp/env Context to find
* @return the comp/env naming Context specific to the given WebAppContext
* @throws NamingException
*/
private Context getCompEnvFor(WebAppContext webapp)
throws NamingException
{
if (webapp == null)
return null;
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Context namingContext = null;
try
{
Thread.currentThread().setContextClassLoader(webapp.getClassLoader());
InitialContext ic = new InitialContext();
namingContext = (Context)ic.lookup("java:comp/env");
return namingContext;
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
}

View File

@ -24,6 +24,7 @@ module org.eclipse.jetty.ee9.plus
// Only required if using Transaction.
requires static transitive jakarta.transaction;
requires org.eclipse.jetty.jndi;
exports org.eclipse.jetty.ee9.plus.jndi;
exports org.eclipse.jetty.ee9.plus.webapp;

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.ee9.webapp.MetaInfConfiguration;
import org.eclipse.jetty.ee9.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee9.webapp.WebAppContext;
import org.eclipse.jetty.ee9.webapp.WebXmlConfiguration;
import org.eclipse.jetty.jndi.ContextFactory;
import org.eclipse.jetty.plus.jndi.EnvEntry;
import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
import org.eclipse.jetty.util.IO;
@ -139,6 +140,7 @@ public class EnvConfiguration extends AbstractConfiguration
//get rid of any bindings for comp/env for webapp
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
ContextFactory.associateClassLoader(context.getClassLoader());
try
{
Context ic = new InitialContext();
@ -163,6 +165,7 @@ public class EnvConfiguration extends AbstractConfiguration
}
finally
{
ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(oldLoader);
IO.close(_resourceFactory);
_resourceFactory = null;
@ -236,14 +239,27 @@ public class EnvConfiguration extends AbstractConfiguration
{
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(wac.getClassLoader());
//ensure that we create a unique comp/env context for this webapp based off
//its classloader
ContextFactory.associateClassLoader(wac.getClassLoader());
try
{
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.createSubcontext("env");
WebAppClassLoader.runWithServerClassAccess(() ->
{
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.createSubcontext("env");
return null;
});
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
ContextFactory.disassociateClassLoader();
Thread.currentThread().setContextClassLoader(oldLoader);
}
}

View File

@ -15,20 +15,29 @@ package org.eclipse.jetty.ee9.plus.webapp;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.eclipse.jetty.ee9.webapp.Configuration;
import org.eclipse.jetty.ee9.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee9.webapp.WebAppContext;
import org.eclipse.jetty.plus.jndi.NamingEntryUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.jndi.NamingUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Isolated;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Isolated("jndi entries")
public class EnvConfigurationTest
{
Server _server;
@ -74,4 +83,74 @@ public class EnvConfigurationTest
assertNotNull(NamingEntryUtil.lookupNamingEntry(context, "peach"));
assertNull(NamingEntryUtil.lookupNamingEntry(context, "cabbage"));
}
@Test
public void testCompEnvCreation() throws Exception
{
EnvConfiguration envConfigurationA = new EnvConfiguration();
EnvConfiguration envConfigurationB = new EnvConfiguration();
WebAppContext webappA = null;
WebAppContext webappB = null;
try
{
webappA = new WebAppContext();
webappA.setConfigurations(new Configuration[]{new PlusConfiguration(), new EnvConfiguration()});
webappA.setClassLoader(new WebAppClassLoader(Thread.currentThread().getContextClassLoader(), webappA));
//ensure that a java:comp/env Context was created for webappA
envConfigurationA.preConfigure(webappA);
Context namingContextA = getCompEnvFor(webappA);
webappB = new WebAppContext();
webappB.setConfigurations(new Configuration[]{new PlusConfiguration(), new EnvConfiguration()});
webappB.setClassLoader(new WebAppClassLoader(Thread.currentThread().getContextClassLoader(), webappB));
//ensure that a different java:comp/env Context was created for webappB
envConfigurationB.preConfigure(webappB);
Context namingContextB = getCompEnvFor(webappB);
assertThat(namingContextA, is(not(namingContextB)));
}
finally
{
envConfigurationA.deconfigure(webappA);
envConfigurationB.deconfigure(webappB);
}
}
@Test
public void testPriorCompCreation() throws Exception
{
//pre-create java:comp on the app classloader
new InitialContext().lookup("java:comp");
//test that each webapp still gets its own naming Context
testCompEnvCreation();
}
/**
* Find the java:comp/env naming Context for the given webapp
* @param webapp the WebAppContext whose naming comp/env Context to find
* @return the comp/env naming Context specific to the given WebAppContext
* @throws NamingException
*/
private Context getCompEnvFor(WebAppContext webapp)
throws NamingException
{
if (webapp == null)
return null;
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
Context namingContext = null;
try
{
Thread.currentThread().setContextClassLoader(webapp.getClassLoader());
InitialContext ic = new InitialContext();
namingContext = (Context)ic.lookup("java:comp/env");
return namingContext;
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
}