Using EJBContext getContextData - is this safe?

10,815

Solution 1

I think in general the contract of the method is to enable the communication between interceptors + webservice contexts and beans. So the context should be available to all code, as long as no new invocation context is created. As such it should be absolutely thread-safe.

Section 12.6 of the EJB 3.1 spec says the following:

The InvocationContext object provides metadata that enables interceptor methods to control the behavior of the invocation chain. The contextual data is not sharable across separate business method invocations or lifecycle callback events. If interceptors are invoked as a result of the invocation on a web service endpoint, the map returned by getContextData will be the JAX-WS MessageContext

Furthermore, the getContextData method is described in 4.3.3:

The getContextData method enables a business method, lifecycle callback method, or timeout method to retrieve any interceptor/webservices context associated with its invocation.

In terms of actual implementation, JBoss AS does the following:

public Map<String, Object> getContextData() {
    return CurrentInvocationContext.get().getContextData();
}

Where the CurrentInvocationContext uses a stack based on a thread-local linked list to pop and push the current invocation context.

See org.jboss.ejb3.context.CurrentInvocationContext. The invocation context just lazily creates a simple HashMap, as is done in org.jboss.ejb3.interceptor.InvocationContextImpl

Glassfish does something similar. It also gets an invocation, and does this from an invocation manager, which also uses a stack based on a thread-local array list to pop and push these invocation contexts again.

The JavaDoc for the GlassFish implementation is especially interesting here:

This TLS variable stores an ArrayList. The ArrayList contains ComponentInvocation objects which represent the stack of invocations on this thread. Accesses to the ArrayList dont need to be synchronized because each thread has its own ArrayList.

Just as in JBoss AS, GlassFish too lazily creates a simple HashMap, in this case in com.sun.ejb.EjbInvocation. Interesting in the GlassFish case is that the webservice connection is easier to spot in the source.

Solution 2

I can't help you directly with your questions regarding EJBContext, since the getContextData method was added in JEE6 there is still not much documentation about it.

There is however another way to pass contextual data between EJBs, interceptors and lifecycle callbacks using the TransactionSynchronizationRegistry. The concept and sample code can be found in this blog post by Adam Bien.

javax.transaction.TransactionSynchronizationRegistry holds a Map-like structure and can be used to pass state inside a transaction. It works perfectly since the old J2EE 1.4 days and is thread-independent.

Because an Interceptor is executed in the same transaction as the ServiceFacade, the state can be even set in a @AroundInvoke method. The TransactionSynchronizationRegistry (TSR) can be directly injected into an Interceptor.

The example there uses @Resource injection to obtain the TransactionSynchronizationRegistry, but it can also be looked up from the InitialContext like this:

public static TransactionSynchronizationRegistry lookupTransactionSynchronizationRegistry() throws NamingException {
    InitialContext ic = new InitialContext();
    return (TransactionSynchronizationRegistry)ic.lookup("java:comp/TransactionSynchronizationRegistry");
}
Share:
10,815
wrschneider
Author by

wrschneider

Updated on June 16, 2022

Comments

  • wrschneider
    wrschneider almost 2 years

    I am planning to use EJBContext to pass some properties around from the application tier (specifically, a message-driven bean) to a persistence lifecycle callback that cannot directly be injected or passed parameters (session listener in EclipseLink, entity lifecycle callback, etc.), and that callback is getting the EJBContext via JNDI.

    This appears to work but are there any hidden gotchas, like thread safety or object lifespan that I'm missing? (Assume the property value being passed is immutable like String or Long.)

    Sample bean code

    @MessageDriven
    public class MDB implements MessageListener {
       private @Resource MessageDrivenContext context;
    
       public void onMessage(Message m) { 
          context.getContextData().put("property", "value");
       }
    }
    

    Then the callback that consumes the EJBContext

    public void callback() { 
       InitialContext ic = new InitialContext();
       EJBContext context = (EJBContext) ic.lookup("java:comp/EJBContext");
       String value = (String) context.getContextData().get("property");
    } 
    

    What I'm wondering is, can I be sure that the contextData map contents are only visible to the current invocation/thread? In other words, if two threads are running the callback method concurrently, and both look up an EJBContext from JNDI, they're actually getting different contextData map contents?

    And, how does that actually work - is the EJBContext returned from the JNDI lookup really a wrapper object around a ThreadLocal-like structure ultimately?

  • wrschneider
    wrschneider over 12 years
    Good suggestion. The pattern I'm using for EJBContext is nearly identical - injecting with @Resource in one place, putting objects in the map, and then looking up in JNDI somewhere else. So the question is whether the EJBContext object is also thread-independent like the TransactionSynchronizationRegistry
  • obe6
    obe6 over 8 years
    TransactionSynchronizationRegistry has one limit : it always needs a transaction, but in some cases is necessary to propagate informations without a transaction