Home > OS >  Tomcat context is empty when accessed via executor and runnable
Tomcat context is empty when accessed via executor and runnable

Time:02-08

Hello I have a web application running on apache-tomee-plus-8.0.1. My problem is about getting an Environment variable from a runnable in a custom executor. The variable is defined in /conf/context.xml:

<?xml version="1.0" encoding="UTF-8"?>

<Context>

    <!-- Default set of monitored resources. If one of these changes, the    -->
    <!-- web application will be reloaded.                                   -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <!-- disable the scan  in order to avoid errors at startup due to ora18n.jar-->
    <JarScanner scanManifest="false"  scanClassPath="false" scanBootstrapClassPath="false"></JarScanner>

    <!-- base64 from user:pass -->  
    <Environment name="myCreds"
    value="toto" type="java.lang.String" />
        
    
    
</Context>

The function I use to get the variable "myCreds"

 private static String getCredentialsFromContext() throws NamingException {
        Context initialContext = new InitialContext();
        Context environmentContext = (Context) initialContext.lookup("java:comp/env");
        return (String) environmentContext.lookup("myCreds");
    }

This function is called via a JAX-RS endpoint which is used to start long background maintenance tasks of the server application. The progress of the background task is then available on another endpoint. If I do

    @GET
    @Path("/testOK")
    public static String testOK() {
          return getCredentialsFromContext(); // works fine
    }

But when I use an executor, the lookup fails with javax.naming.NameNotFoundException: Name [comp/env] is not bound in this Context. Unable to find [comp].

    private static ExecutorService index_executor;
    @GET
    @Path("/testKO")
    public static Response testKO() {
          if (index_executor == null){
               index_executor = Executors.newFixedThreadPool(5);
          }
          index_executor.submit(new Runnable() {

            @Override
            public void run() {
                System.out.println(getCredentialsFromContext()); // FAIL
            }
        });
        return Response.ok.build()  
    }

It looks like the InitialContext is not the same when called from the runnable. I would like to avoid to pass through args the value of "myCreds". I tried to move the declaration of "myCreds" in the context.xml of the webapp but it didn't help. Using JNDIContext also fails. Do you understand what is the problem and why the context is different?

Thanks :)

CodePudding user response:

JNDI lookups depend on some context information on the running thread, usually the context class loader.

On a Java EE/Jakarta EE server you should not spawn new (unmanaged) threads yourself, but use the ManagedExecutorService provided by the container. This service automatically propagates some kinds of contexts from the calling thread:

The types of contexts to be propagated from a contextualizing application component include JNDI naming context, classloader, and security information. Containers must support propagation of these context types.

(Jakarta Concurrency Specification, emphasis mine)

You can inject a ManagedExecutorService using a @Resource annotation:

    @Resource
    private ManagedExecutorService executorService;

Using a ManagedExecutorService works on Wildfly, but on TomEE there is IMHO a bug that prevents the propagation of the naming context: JAX-RS resources use CxfContainerClassLoader as context classloader, which wraps the real classloader, preventing it from propagating to the managed thread.

A workaround would consist in switching temporarily to the wrapped classloader:

        final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        if (tccl instanceof org.apache.openejb.util.classloader.Unwrappable) {
            final ClassLoader cl = ((org.apache.openejb.util.classloader.Unwrappable) tccl).unwrap();
            Thread.currentThread().setContextClassLoader(cl);
        }
        executorService.submit(...);
        Thread.currentThread().setContextClassLoader(tccl);

Edit: actually, it is enough to mark the JAX-RS resource as @Stateless for the correct propagation of the JNDI naming context.

  •  Tags:  
  • Related