Is it possible to activate the Session Scope and Conversation Scope from the existing Request Scope?

Matt Fellows

I have an @EJB injected bean TransactionCompleteJob. This bean has an @Asynchronous method on it asyncCompleteTransaction(Integer transactionId).

When I try to make use of other injected beans and entities that are either session scoped or conversation scoped within this method I end up getting an error:

WELD-001303: No active contexts for scope type javax.enterprise.context.ConversationScoped

So I Injected weld's BoundConversationScope, BoundSessionScope, and BoundRequestScope and activated them, generating an empty map for the request data, and an empty map for the session data, as specified by jboss weld documentation:

The problem is that when activating the request scope I get another error message:

WELD-001304: More than one context active for scope type javax.enterprise.context.RequestScoped

I've tried not activating the request scope, but I seem to end up with a resource leak of anything which was on the actual request scope, specifically I have a request scoped JPA EntityManager. In particular once the process finishes I see another message:

WELD-000019: Error destroying an instance org.hibernate.jpa.internal.EntityManagerImpl@5df070be of Producer Method [EntityManager] with qualifiers [@RequestScopey @Any] declared as [[BackedAnnotatedMethod] @Produces @RequestScoped @RequestScopey public packagename.entitymanager.EntityManagerProducer.createRequestScopedEntityManager()]

How can I start a Request Scope Context when I have one already active? Or start a Session Scope Context and Conversation Scope Context which tie in to the existing Request Scope Context? Alternatively, are there any better ways to get around this issue?

EDIT:

Is there any way to get hold of the RequestScope from weld so I can deactivate it, before starting my own? Or a way to Asynchronously start my TransactionCompleteJob asynchronously, without injecting it and calling the @Asynchronous method?

Xavier Dury

I had more or less the same problem but took a different approach: I had a @ConversationScoped EntityManager injected in my repositories but then I needed to do some batch processing where no ConversationContext was available and got exceptions when using my repositories. Instead of trying to activate the ConversationContext where it was not meant to be used, I ended implementing 2 new contexts (+ 1 interceptor):

  • the first one was a ThreadContext (@ThreadScoped) which stored everything in a Map in a ThreadLocal (which is nice for async processing) + 1 method interceptor (@ThreadContextual) to be used on my async/batch methods to activate this context for the time of the invocation.
  • the second one was a bit more complicated: it was some sort of dynamic context which delegated to the first active context in that order: ThreadContext, (NonTransient)ConversationContext, (NonTransient)ViewContext (@ViewScoped from JSF 2.2), RequestContext. I called this context UnitOfWorkContext with the corresponding @UnitOfWorkScoped annotation. I annotated the (few) beans that needed to live in that context (for me, it was only the @Produces method for myEntityManager).

It can seem difficult to implement all of this but it's not, the code was pretty small. If needed, I will paste my code in 2-3 days as I don't have access to it for the moment.

UPDATE: Here is the code for the 2nd context:

The following interface is used as a complement to Context.isActive(). Sometimes, even if a context is active, it does not mean I want to use it, see below for an example.

public interface DynamicContextActivation {

    boolean isActive(Context context);
}

The following annotation should be put on your new scope

@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
public @interface DynamicScope {

    class DefaultActivation implements DynamicContextActivation {

        public boolean isActive(Context context) {
            return true;
        }
    }

    Class<? extends Annotation>[] value();

    Class<? extends DynamicContextActivation> activation() default DefaultActivation.class;
}

Implementation of dynamic context

public class DynamicContext implements AlterableContext {

    private final BeanManager beanManager;
    private final DynamicContextActivation activation;
    private final Class<? extends Annotation> scope;
    private final Class<? extends Annotation>[] scopes;

    public DynamicContext(BeanManager beanManager, DynamicContextActivation activation, Class<? extends Annotation> scope, Class<? extends Annotation>[] scopes) {
        this.beanManager = beanManager;
        this.activation = activation;
        this.scope = scope;
        this.scopes = scopes;
    }

    public void destroy(Contextual<?> contextual) {
        Context context = getContext();
        if (context instanceof AlterableContext) {
            ((AlterableContext) context).destroy(contextual);
        }
    }

    public <T> T get(Contextual<T> contextual) {
        return getContext().get(contextual);
    }

    public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
        return getContext().get(contextual, creationalContext);
    }

    // Find the first active context
    private Context getContext() {
        for (Class<? extends Annotation> scope : this.scopes) {
            try {
                Context context = this.beanManager.getContext(scope);
                if (context.isActive() && this.activation.isActive(context)) {
                    return context;
                }
            } catch (ContextNotActiveException exception) {
                continue;
            }
        }
        return null;
    }

    public Class<? extends Annotation> getScope() {
        return this.scope;
    }

    public boolean isActive() {
        return getContext() != null;
    }
}

Extension that registers dynamic context automatically (add it to /META-INF/services/javax.enterprise.inject.spi.Extension)

public class DynamicContextExtension implements Extension {

    private final Set<Class<? extends Annotation>> scopes = new HashSet<>();

    public void processBean(@Observes ProcessBean<?> bean) {
        Class<? extends Annotation> scope = bean.getBean().getScope();
        if (scope.isAnnotationPresent(DynamicScope.class)) {
            this.scopes.add(scope);
        }
    }

    public void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
        for (Class<? extends Annotation> scope : scopes) {
            DynamicScope dynamicScope = scope.getAnnotation(DynamicScope.class);
            try {
                // TODO use a managed DynamicContextActivation instead of instantiating it here
                DynamicContextActivation activation = dynamicScope.activation().newInstance();
                Context context = new DynamicContext(beanManager, activation, scope, dynamicScope.value());
                afterBeanDiscovery.addContext(context);
            } catch (InstantiationException | IllegalAccessException exception) {
                afterBeanDiscovery.addDefinitionError(exception);
            }
        }
    }
}

This scopes delegates in order to ThreadScoped, (LongRunning)ConversationScoped, (NonTransient)ViewScoped, RequestScoped:

@Retention(RUNTIME)
@NormalScope(passivating = true) // must be true if any of the delegate context is passivation-capable
@DynamicScope(value = {ThreadScoped.class, ConversationScoped.class, ViewScoped.class, RequestScoped.class}, activation = UnitOfWorkActivation.class)
public @interface UnitOfWorkScoped {

    class UnitOfWorkActivation implements DynamicContextActivation {

        public boolean isActive(Context context) {
            if (context.getScope().equals(ConversationScoped.class)) {
                // I only want long-running conversations here because in JSF there
                // is always a transient conversation per request and it could take
                // precedence over all other scopes that come after it
                return !CDI.current().select(Conversation.class).get().isTransient();
            }
            if (context.getScope().equals(ViewScoped.class)) {
                // Storing things in view scope when the view is transient gives warnings
                return !FacesContext.getCurrentInstance().getViewRoot().isTransient();
            }
            return true;
        }
    }
}

An EntityManager producer which gives @UnitOfWorkScoped EntityManagers:

@Stateful // it could work without @Stateful (but Serializable) but I haven't tested enough
@UnitOfWorkScoped
public class EntityManagerProducer {

    @PersistenceContext(type = EXTENDED)
    private EntityManager entityManager;

    @Produces
    @UnitOfWorkScoped
    @TransactionAttribute(NOT_SUPPORTED)
    public EntityManager entityManager() {
        return entityManager;
    }
}

There is surely room for improvement so don't hesitate to give your feedback.

UPDATE 2: it would be nice to replace DynamicContextActivation with EL expressions

@Retention(RUNTIME)
@NormalScope(passivating = true)
@DynamicScope({
    @Scope(scope = ThreadScoped.class),
    @Scope(scope = ConversationScoped.class, ifExpression = "#{not javax.enterprise.context.conversation.transient}"),
    @Scope(scope = ViewScoped.class, ifExpression = "#{not facesContext.viewRoot.transient}"),
    @Scope(scope = RequestScoped.class)
})
public @interface UnitOfWorkScoped {}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related