Context Management

Managing the built in contexts

Weld allows you to easily manage the built in contexts by injecting them and calling lifecycle methods. Weld defines two types of context, managed and unmanaged. Managed contexts can be activated (allowing bean instances to be retrieved from the context), invalidated (scheduling bean instances for destruction) and deactivated (stopping bean instances from being retrieved, and if the context has been invalidated, causing the bean instances to be destroyed). Unmanaged contexts are always active; some may offer the ability to destroy instances.

Managed contexts can either be bound or unbound. An unbound context is scoped to the thread in which it is activated (instances placed in the context in one thread are not visible in other threads), and is destroyed upon invalidation and deactivation. Bound contexts are attached to some external data store (such as the HTTP Session or a manually propagated map) by associating the data store with the context before calling activate, and dissociating the data store after calling activate.

Tip
Weld automatically controls context lifecycle in many scenarios such as HTTP requests, EJB remote invocations, and MDB invocations. Many of the extensions for CDI offer context lifecycle for other environments, it’s worth checking to see if there is a suitable extension before deciding to manage the context yourself.

Weld provides a number of built in contexts, which are shown in Available Contexts in Weld.

Table 1. Available Contexts in Weld
Scope Qualifiers Context Notes

@Dependent

@Default

DependentContext

The dependent context is unbound and unmanaged

@RequestScoped

@Unbound

RequestContext

An unbound request context, useful for testing

@RequestScoped

@Bound

@Default

RequestContext

BoundRequestContext

A request context bound to a manually propagated map, useful for testing or non-Servlet environments

@RequestScoped

@Http

@Default

RequestContext

HttpRequestContext

A request context bound to a Servlet request, used for any Servlet based request context

@RequestScoped

@Ejb

@Default

RequestContext

EjbRequestContext

A request context bound to a an interceptor’s invocation context, used for EJB invocations outside of Servlet requests

@ConversationScoped

@Bound

@Default

ConversationContext

BoundConversationContext

A conversation context bound to two manually propagated maps (one which represents the request and one which represents the session), useful for testing or non-Servlet environments

@ConversationScoped

@Http

@Default

ConversationContext

HttpConversationContext

A conversation context bound to a Servlet request, used for any Servlet based conversation context

@SessionScoped

@Bound

@Default

SessionContext

BoundSessionContext

A session context bound to a manually propagated map, useful for testing or non-Servlet environments

@SessionScoped

@Http

@Default

SessionContext

HttpSessionContext

A session context bound to a Servlet request, used for any Servlet based session context

@ApplicationScoped

@Default

ApplicationContext

An application context backed by an application scoped singleton, it is unmanaged and unbound but does offer an option to destroy all entries

@SingletonScoped

@Default

SingletonContext

A singleton context backed by an application scoped singleton, it is unmanaged and unbound but does offer an option to destroy all entries

Unmanaged contexts offer little of interest in a discussion about managing context lifecycles, so from here on in we will concentrate on the managed contexts (unmanaged contexts of course play a vital role in the functioning of your application and Weld!). As you can see from the table above, the managed contexts offer a number of different implementations for the same scope; in general, each flavor of context for a scope has the same API. We’ll walk through a number of common lifecycle management scenarios below; armed with this knowledge, and the Javadoc, you should be able to work with any of the context implementations Weld offers.

We’ll start simple with the BoundRequestContext, which you might use to provide the request scope outside of a Servlet request or EJB Invocation.

   /* Inject the BoundRequestContext. */
   /* Alternatively, you could look this up from the BeanManager */
   @Inject BoundRequestContext requestContext;

   ...

   /* Start the request, providing a data store which will last the lifetime of the request */
   public void startRequest(Map<String, Object> requestDataStore) {
      // Associate the store with the context and activate the context
      requestContext.associate(requestDataStore);
      requestContext.activate();
   }

   /* End the request, providing the same data store as was used to start the request */
   public void endRequest(Map<String, Object> requestDataStore) {
      try {
         /* Invalidate the request (all bean instances will be scheduled for destruction) */
         requestContext.invalidate();
         /* Deactivate the request, causing all bean instances to be destroyed (as the context is invalid) */
         requestContext.deactivate();
      } finally {
         /* Ensure that whatever happens we dissociate to prevent any memory leaks */
         requestContext.dissociate(requestDataStore);
      }
   }

The bound session context works in much the same way, excepting that invalidating and deactivating the session context causes the any conversations in the session to be destroyed as well. The HTTP session context and HTTP request context also work similarly, and might be of use if you find yourself creating threads from an HTTP request). The HTTP session context additionally offers a method which can immediately destroy the context.

Note
Weld’s session contexts are "lazy" and don’t require a session to actually exist until a bean instance must be written.

The conversation context offers a few more options, which we will walk through here.

   @Inject BoundConversationContext conversationContext;

   ...

   /* Start a transient conversation */
   /* Provide a data store which will last the lifetime of the request */
   /* and one that will last the lifetime of the session */
   public void startTransientConversation(Map<String, Object> requestDataStore,
                                          Map<String, Object> sessionDataStore) {
      resumeOrStartConversation(requestDataStore, sessionDataStore, null);
   }

   /* Start a transient conversation (if cid is null) or resume a non-transient */
   /* conversation. Provide a data store which will last the lifetime of the request */
   /* and one that will last the lifetime of the session */
   public void resumeOrStartConversation(Map<String, Object> requestDataStore,
                                         Map<String, Object> sessionDataStore,
                                         String cid) {
      /* Associate the stores with the context and activate the context */
      * BoundRequest just wraps the two datastores */
      conversationContext.associate(new MutableBoundRequest(requestDataStore, sessionDataStore));
      // Pass the cid in
      conversationContext.activate(cid);
   }

   /* End the conversations, providing the same data store as was used to start */
   /* the request. Any transient conversations will be destroyed, any newly-promoted */
   /* conversations will be placed into the session */
   public void endOrPassivateConversation(Map<String, Object> requestDataStore,
                                          Map<String, Object> sessionDataStore) {
      try {
         /* Invalidate the conversation (all transient conversations will be scheduled for destruction) */
         conversationContext.invalidate();
         /* Deactivate the conversation, causing all transient conversations to be destroyed */
         conversationContext.deactivate();
      } finally {
        /* Ensure that whatever happens we dissociate to prevent memory leaks*/
         conversationContext.dissociate(new MutableBoundRequest(requestDataStore, sessionDataStore));
      }
   }

The conversation context also offers a number of properties which control the behavior of conversation expiration (after this period of inactivity the conversation will be ended and destroyed by the container), and the duration of lock timeouts (the conversation context ensures that a single thread is accessing any bean instances by locking access, if a lock can’t be obtained after a certain time Weld will error rather than continue to wait for the lock). Additionally, you can alter the name of the parameter used to transfer the conversation id (by default, cid).

Weld also introduces the notion of a ManagedConversation, which extends the Conversation interface with the ability to lock, unlock and touch (update the last used timestamp) a conversation. Finally, all non-transient conversations in a session can be obtained from the conversation context, as can the current conversation.

Note
Weld’s conversations are not assigned ids until they become non-transient.

Propagating built-in contexts

By context propagation we understand a scenario in which you want to capture a collection of contextual instances bound to certain context in one thread and provide them as context state in another thread. Starting with Weld 3.1.0.Final, this kind of context propagation is possible.

Note
Context propagation comes with some additional requirements on user code and may not work for every scenario!

First of all, what contexts are affected and how:

  • Application context

    • Works out of the box, no propagation needed

  • Singleton context

    • Works out of the box, no propagation needed

  • Dependent context

    • By nature of this context, this cannot be propagated

  • Request, session, conversation contexts

    • These can be manually propagated if desired

In order to achieve context propagation you generally need the following steps:

  • Obtain collection of contextual instances from current thread

  • In another thread, obtain a reference to given context and activate it

  • Feed this newly activated context the instances you previously obtained

  • Perform your tasks

  • Clean up the context by deactivating it

New API methods supporting context propagation

There are several new things in Weld API allowing for this. Firstly, all contexts supporting propagation now implement org.jboss.weld.context.WeldAlterableContext, an interface extending jakarta.enterprise.context.spi.AlterableContext. Methods on WeldAlterableContext allow to capture current context state, returning a collection of all contextual instances, as well as clear and set the context state by feeding it a collection of contextual instances.

public interface WeldAlterableContext extends AlterableContext {
    default <T> Collection<ContextualInstance<T>> getAllContextualInstances();
    default <T> void clearAndSet(Collection<ContextualInstance<T>> setOfInstances);
}

In order to get hold of these contexts, the best approach is to use WeldManager, an injectable bean providing some capabilities on top of what BeanManager has. Following WeldManager methods are useful for context propagation:

public interface WeldManager extends BeanManager {

    // excerpt of interface methods is shortened here
    boolean isContextActive(Class<? extends Annotation> scopeType);
    Collection<Class<? extends Annotation>> getScopes();
    default Collection<Context> getActiveContexts() {
        return getScopes().stream()
            .filter(this::isContextActive)
            .map(this::getContext)
            .collect(Collectors.toSet());
    }
    default Collection<WeldAlterableContext> getActiveWeldAlterableContexts() {
        return getScopes().stream()
            .filter(this::isContextActive)
            .map(this::getContext)
            .filter(t -> t instanceof WeldAlterableContext)
            .map(t -> (WeldAlterableContext) t)
            .collect(Collectors.toSet());
    }
}

Example of context propagation

There is a concise example in a form of a test in our code showing how to propagate all built-in contexts. This doc only contains an excerpt from it, you can look here to get the full picture.

Following code shows a service that provides an extra thread onto which you offload a Callable<T> that uses beans from currently active context. The service activates contexts, propagates state from original thread, executes task and cleans up. Bound versions of Weld context implementations are used as on this new thread there is no actual HTTP request or session existing.

public class ContextPropagationService {

    private static final ExecutorService executor = Executors.newFixedThreadPool(1);

    public static <T> Future<T> propagateContextsAndSubmitTask(Callable<T> task) {
        // gather all the contexts we want to propagate and the instances in them
        Map<Class<? extends Annotation>, Collection<ContextualInstance<Object>>> scopeToContextualInstances = new HashMap<>();
        WeldManager get = CDI.current().select(WeldManager.class).get();
        for (WeldAlterableContext context : CDI.current().select(WeldManager.class).get().getActiveWeldAlterableContexts()) {
            scopeToContextualInstances.put(context.getScope(), context.getAllContextualInstances());
        }
        // We create a task wrapper which will make sure we have contexts propagated
        Callable<T> wrappedTask = new Callable<T>() {

            @Override
            public T call() throws Exception {
                // Get WeldManager,get instances of @Bound contexts for request, session and conversation scopes
                WeldManager weldManager = CDI.current().select(WeldManager.class).get();
                BoundRequestContext requestContext = weldManager.instance().select(BoundRequestContext.class, BoundLiteral.INSTANCE).get();
                BoundSessionContext sessionContext = weldManager.instance().select(BoundSessionContext.class, BoundLiteral.INSTANCE).get();
                BoundConversationContext conversationContext = weldManager.instance().select(BoundConversationContext.class, BoundLiteral.INSTANCE).get();

                // We will be using bound contexts, prepare backing structures for contexts
                Map<String, Object> sessionMap = new HashMap<>();
                Map<String, Object> requestMap = new HashMap<>();
                BoundRequest boundRequest = new MutableBoundRequest(requestMap, sessionMap);

                // activate contexts
                requestContext.associate(requestMap);
                requestContext.activate();
                sessionContext.associate(sessionMap);
                sessionContext.activate();
                conversationContext.associate(boundRequest);
                conversationContext.activate();

                // propagate all contexts that have some bean in them
                if (scopeToContextualInstances.get(requestContext.getScope()) != null) {
                    requestContext.clearAndSet(scopeToContextualInstances.get(requestContext.getScope()));
                }
                if (scopeToContextualInstances.get(sessionContext.getScope()) != null) {
                    sessionContext.clearAndSet(scopeToContextualInstances.get(sessionContext.getScope()));
                }
                if (scopeToContextualInstances.get(conversationContext.getScope()) != null) {
                    conversationContext.clearAndSet(scopeToContextualInstances.get(conversationContext.getScope()));
                }

                // now execute the actual original task
                T result = task.call();

                // cleanup, context deactivation, do not trigger @PreDestroy/@Disposes
                requestContext.deactivate();
                conversationContext.deactivate();
                sessionContext.deactivate();

                // all done, return
                return result;
            }
        };
        return executor.submit(wrappedTask);
    }
}

Pitfalls and drawbacks

There are several things that can possibly go wrong when propagating contexts. User code needs to be aware that propagation can happen and prepare their beans accordingly. For instance request scoped beans could now theoretically be accessed concurrently which wasn’t the case before.

@PreDestroy and @Disposes on your beans could cause inconsistent state based on how you perform the propagation. Since the same bean is now used in several threads, all of them can, in invalidating and deactivating contexts, trigger these methods but the bean will still exist in yet another thread. The example given above avoids calling context.invalidate() and only performs context.deactivate() - this avoids invoking @PreDestroy/@Disposes methods but could possibly lead to never invoking them if no thread does it. Note that this problem only concerns request, session and conversation beans where you manually need to activate/deactivate contexts. Application/singleton scoped bean would still work and their cleanup callbacks will only be invoked once during container shutdown.

There is currently no way to propagate any other contexts than those mentioned here. Custom scopes as well as scopes from other EE specifications have no support for this feature. The reason is that while technically any context implementing WeldAlterableContext can be used to obtain/set collection of contextual instances, there is no way of knowing how to activate custom contexts in different threads.

Last but not least, context propagator needs to be aware of context implementations existing in Weld, see Available Contexts in Weld. However, in a new thread some extra knowledge is required to activate the contexts. Bound versions (backed by a provided storage; a map in our case) were used, and those need to have a storage associated before activating them, hence the code such as requestContext.associate(requestMap). There is no need to use bound version though; propagators are free to choose from other context implementations.