I am trying to implement a custom scope/context, similar to @RequestScoped
, but I want to be able to manually toggle it, similar to how QuarkusTransaction.begin()
and QuarkusTransaction.commit()
toggle @TransactionScoped
scope.
I looked through the Quarkus's source code, and could not really figure out how to do so. Initially, I wanted to use the approach described here: https://rpestano.wordpress.com/2013/06/30/cdi-custom-scope/, and use events and observables on my scope:
public class CustomScopeContext implements Context, Serializable {
private Logger log = Logger.getLogger(getClass().getSimpleName());
private CustomScopeContextHolder customScopeContextHolder;
public CustomScopeContext() {
log.info("Init");
this.customScopeContextHolder = CustomScopeContextHolder.getInstance();
}
// ...
public void destroy(@Observes KillEvent killEvent) {
if (customScopeContextHolder.getBeans().containsKey(killEvent.getBeanType())) {
customScopeContextHolder.destroyBean(customScopeContextHolder.getBean(killEvent.getBeanType()));
}
}
}
This does not work in Quarkus, because in order for Quarkus to receive events, a class should be defined as a bean, but by doing so via @ApplicationScoped
and an appropriate build-item, each time it received my event, it had a completely different empty CustomScopeContextHolder
.
From the Quarkus's source code, it seems that I need to use ManagedContext
to implement contexts that can be manually controlled, but the problem is that its activate()
and deactivate()
methods accept no parameters, so there is no way to identify which scope to start/stop. I tried using ThreadLocal<Map<Contextual<?>, ContextualInstance<?>>> instances
that seemed to have worked, but I wonder if ThreadLocal
behaves appropriately in Quarkus. So far, there were no issues, but I am not sure if it is going to just be a floating bug at some point. (From what I found, precisely because of issues with ThreadLocal
, other CDI frameworks, like Weld have their own classes org.jboss.weld.context.BoundContext
for classes that rely on ThreadLocal
state)
Additionally, I found a CurrentManagedContext
that is the base class that RequestContext
and SessionContext
use under the hood in Quarkus, but it also lacks proper documentations, so I could not figure out how to use it; furthermore, it is a part of the arc.impl
package, so I am afraid that the Quarkus is free to break these classes without any warning.
So, how can I implement a scope that can be toggled manually via some functions, for example:
MyBean.java
@MyScoped
public class MyBean{
private String value;
// ...
}
MyService.java
@ApplicationScoped
public class MyService{
@Inject
MyBean myBean;
public void foo() {
// myBean unavailable
MyScope.init();
// myBean#1 is available
myBean.setValue("foo");
System.out.println(myBean.getValue()); // prints "foo"
MyScope.deinit();
// myBean unavailable
// Will throw an exception:
// System.out.println(myBean.getValue());
MyScope.init();
// myBean#2 is available
myBean.setValue("bar");
System.out.println(myBean.getValue()); // prints "bar"
MyScope.deinit();
}
}
This does not work in Quarkus, because in order for Quarkus to receive events, a class should be defined as a bean
This should be true not only for Quarkus but other implementations as well. A context object, while maintained by CDI container, is not a CDI bean.
Below code assumes that you know how to register the custom scope/context in CDI/Quarkus and that you want to activate/deactivate based on certain CDI events you are firing/observing.
One way way I can think of is to activate/deactivate this context from any other helper bean monitoring the events you are looking for - Quarkus/ArC keeps track of the Context
instance and you can ask for it. For this to work, your context will need to implement io.quarkus.arc.InjectableContext
which is an extended API Quarkus uses for contexts and which isn't going to change without warnings. Here's how you can get context object:
List<InjectableContext> contextImpls = Arc.container().getContexts(MyScoped.class);
if (contextImpls.size() != 1) {
// throw some error
}
InjectableContext myContextImpl = contextImpls.iterator().next();
From here you can call any method on your context. Let's say your context has (among other things) following methods:
public class CustomScopeContext implements InjectableContext {
public void activateContext() {
// do any work needed for context activation
}
public void deactivateContext() {
// do any work needed for context deactivation/destruction
}
}
You could then have a CDI bean observing the events you are looking for and manipulating your context. Here's a snippet:
@ApplicationScoped
public class ContextManipulatingBean {
public void destroy(@Observes KillEvent killEvent) {
((CustomScopeContext)Arc.container().getContexts(MyScoped.class).iterator().next()).deactivateContext();
}
}
}
For an example of a custom context implementing InjectableContext
you can take a look at TransactionContext
used by JTA (Narayana).