Combine Resteasy async fail to inject SessionScoped and RequestScoped bean with CDI - java

I am working on a issue related to async job in Reasteasy (http://docs.jboss.org/resteasy/docs/2.3.1.GA/userguide/html_single/index.html#async_job_service).
I post a request adding ?asynch=true to the url, and the job is then run asynchronously, but when it runs, it works fine with #ApplicationScoped or #Singleton annotated bean, but it cannot access bean of class declared with #RequestScoped annotation and I always run into this error :
org.jboss.weld.context.ContextNotActiveException: WELD-001303 No active contexts for scope type javax.enterprise.context.RequestScoped
at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:664)
at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:77)
at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:87)
at com.examplecompany.exampleproject.multitenancy.org$jboss$weld$bean-flat-ManagedBean-class_com$examplecompany$exampleproject$multitenancy$PersistenceContext_$$WeldClientProxy.setDb(org$jboss$weld$bean-flat-ManagedBean-class_com$examplecompany$exampleproject$multitenancy$PersistenceContext$$_WeldClientProxy.java)
at com.examplecompany.exampleproject.auth.oauth.secure.OAuthDelegate.filterHttp(OAuthDelegate.java:115)
at com.examplecompany.exampleproject.auth.oauth.secure.AuthorizationInterceptor.preProcess(AuthorizationInterceptor.java:59)
at com.examplecompany.exampleproject.auth.oauth.secure.org$jboss$weld$bean-flat-ManagedBean-com$examplecompany$exampleproject$auth$oauth$secure$AuthorizationInterceptor$#javax$enterprise$context$ApplicationScoped()#javax$ws$rs$ext$Provider()#org$jboss$resteasy$annotations$interception$SecurityPrecedence()#org$jboss$resteasy$annotations$interception$ServerInterceptor()${com$examplecompany$exampleproject$auth$oauth$secure$AuthorizationInterceptor$oauthDelegate$#javax$inject$Inject()$$}_$$_WeldClientProxy.preProcess(org$jboss$weld$bean-flat-ManagedBean-com$examplecompany$exampleproject$auth$oauth$secure$AuthorizationInterceptor$#javax$enterprise$context$ApplicationScoped()#javax$ws$rs$ext$Provider()#org$jboss$resteasy$annotations$interception$SecurityPrecedence()#org$jboss$resteasy$annotations$interception$ServerInterceptor()${com$examplecompany$exampleproject$auth$oauth$secure$AuthorizationInterceptor$oauthDelegate$#javax$inject$Inject()$$}_$$_WeldClientProxy.java)
at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:247)
at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:222)
at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:211)
at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:525)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:502)
at org.jboss.resteasy.core.AsynchronousDispatcher.invokeSuper(AsynchronousDispatcher.java:227)
at org.jboss.resteasy.core.AsynchronousDispatcher$1.call(AsynchronousDispatcher.java:267)
at org.jboss.resteasy.core.AsynchronousDispatcher$1.call(AsynchronousDispatcher.java:259)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
This error do not occur if I post the same request not adding ?asynch=true.
I further investigated the issue and wrote the following lines in my code
try {
Context context = beanManager.getContext(RequestScoped.class);
} catch (ContextNotActiveException e) {
logger.info("Oops the context does not exists, we are in bad sh*t",e);
}
If I'm in async mode, the ContextNotActiveExceptionis always thrown, and the log have the same exception message WELD-001303 No active contexts for scope type javax.enterprise.context.RequestScoped.
So I guess that when the task is launched async mode, the two context Session and Request are not created, and thus my bean defined in these scope are inaccessible.
I raised a ticket into Resteasy Jira for this: https://issues.jboss.org/browse/RESTEASY-682

According the to spec, section 6.7.2, the SessionScope is not available for web service requests. You'll have to create a new persistent scope and use that. The problem with web service calls (JAXRS or JAXWS) and the session is that there is no guaranteed way to track the session from one request to the next. Clients aren't required to send back cookies, or use a request param. If your service requires and enforces this, then you'll have to either create a new scope and context backing, or use the CDI implementation's API to manually start and bind to the session scope.

Related

Spring Boot Request-scoped bean is re-used for multiple requests

Our Spring Boot web application defines a bean that, upon construction, writes a unique ID to the SLF4J MDC (mapped diagnostic context) in order to make it clear in the logs which request caused which log message.
Therefore the bean is request-scoped, i.e. we expect it to only exist as long as exactly one HTTP request is serviced and then it should be destroyed by Spring Boot.
Upon a new request, a new instance should be created, hence there should be a unique REQUEST_ID for every request.
#Component
#Scope(WebApplicationContext.SCOPE_REQUEST)
public class RequestContext {
public RequestContext() {
putInMdc(REQUEST_ID, UUID.randomUUID().toString());
}
...
However, what we observe is lots of unrelated log messages all having the same REQUEST_ID, which can only mean that the constructor of RequestContext is not called between those requests.
All the log messages belong to the same thread ID. I know that Tomcat usually re-uses threads for multiple requests, but shouldn't Spring Boot re-create a request scoped bean anyway as soon as the request is serviced?

share request context between threads

I have a Quarkus app with REST API and multiple services that are #ApplicationScoped, and one bean that is #RequestScoped (it has data related to JWT, cookies etc).
When the REST API is invoked, I want to do some work synchronously and then return an answer to the client but to keep doing async work with the same RequestScoped bean (context data).
I tried to propagate the RequestScoped bean to the thread that will handle the async task, but once the HTTP request is finished, the RequestScoped bean is getting deleted.
What is the correct way of doing this?
Basically I want to share some context across a flow that involves sync and async tasks

Java EE Request scope and Asynchronous

I am attempting to time an log performance using an interceptor.
#LogPerformance
#Interceptor
public class PerformanceInterceptor {
The times are stored in a request scoped bean. When the the request has finished the times are written to a log file.
When I changed several of the logged methods to be asynchronous, the logging for those operations stopped being output.
#Asynchronous
#LogPerformance
public Future<String> getString() {
I believe the new EJB thread is defining a new request scope. I can log output from the interceptor and see the request scoped object, but there are two different addresses attached to the objects. One address for the http thread and a different address for the EJB thread.
Is there a way to allow the interceptors from the async methods to write to the object in the http request scope? Is there another way to get the data back into the parent scope?
The container will propagate the javax.ejb.EJBContext to the thread that executes the asynchronous method.
You can inject this EJBContext into your interceptor and stash any state (such as your request scoped bean) that you want into it via javax.ejb.EJBContext.getContextData().

Using spring session scope without session

I have web application where I defined session scoped UserDetail. The problem is that I also have some Quartz jobs that should use UserDetail bean. When job is run I get:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.userDetails': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Inside job I need to inject some "technical user" details. Is it possible to properly handle situation when session is not present? Maybe any conditional injection?
EDIT 1
To clarify. I dont want to have session in my job service. This job modifies data and some audit log based on user data is saved to database. Normally user data comes from session but in job I need to provide some "static" technical user data. Any ideas?
Quartz does not know anything about "sessions", so if you want your background job to know the user that submitted the job, you have to pass that information to the job, likely in the DataMap of the trigger.

WARN: Could not register destruction callback

15:11:14,676 WARN FacesRequestAttributes:121 - Could not register destruction callback [org.springframework.beans.factory.support.DisposableBeanAdapter#1059fd6] for attribute 'purchaseController' because FacesRequestAttributes does not support such callbacks
This warn message appears in my log a lot. For every managed bean whenever it expires. It expires after a given time, because I'm using MyFaces Orchestra.
I have defined the org.springframework.web.context.request.RequestContextListener in my web.xml, and I don't have the spring jar only ones on my classpath (i.e. not a class-loading problem)
The docs of FacesRequestAttribute says:
NOTE: In contrast to ServletRequestAttributes, this variant does not support destruction callbacks for scoped attributes, neither for the request scope nor for the session scope. If you rely on such implicit destruction callbacks, consider defining a Spring RequestContextListener in your web.xml.
The purchaseController is actually a simple managed bean (not extending anything an implementing only Serializable), annotated with #Controller.
Update1:
The beans in #Scope("request") and #Scope("session") seem to be affected.
So I wanted to know whether this warn poses any danger to the proper flow. I.e. if something really needs those callbacks. If not, I will just skip the warning with the lo4j config.
Update 2:
I digged a little further, and it seems that this happens only sometimes. IF the listener is used, then RequestContextHolder.currentRequestAttributes() returns the ServletRequestAttributes, rather than FacesRequestAttributes. So it appears that sometimes the listener doesn't work and doesn't set the current attributes in the RequestContextHolder.
Update 3:
I turned debug on for RequestContextListener, and here's the result:
07:21:31,518 DEBUG RequestContextListener:69 - Bound request context to thread: org.apache.catalina.connector.RequestFacade#1190ae9
07:21:31,518 DEBUG RequestContextListener:89 - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#1190ae9
07:21:31,538 WARN FacesRequestAttributes:121 - Could not register destruction callback [org.springframework.beans.factory.support.DisposableBeanAdapter#11aa152] for attribute 'org.apache.myfaces.orchestra.conversation.AccessScopeManager' because FacesRequestAttributes does not support such callbacks
07:21:31,541 WARN FacesRequestAttributes:121 - Could not register destruction callback [org.springframework.beans.factory.support.DisposableBeanAdapter#1552393] for attribute 'localeController' because FacesRequestAttributes does not support such callbacks
....and so on, other request and session beans
It appears that the request gets destroyed before access to the beans is attempted. Which is very odd. Could this be due to a problem in the servlet container implementation of the listener handling?
In the javadoc of FacesRequestAttributes, we can read:
Note: In contrast to ServletRequestAttributes, this variant does not support destruction callbacks for scoped attributes, neither for the request scope nor for the session scope. If you rely on such implicit destruction callbacks, consider defining a Spring RequestContextListener in your web.xml.
And, indeed, the registerDestructionCallback() method of FacesRequestAttributes doesn't do much things:
public void registerDestructionCallback(String name, Runnable callback, int scope) {
if (logger.isWarnEnabled()) {
logger.warn("Could not register destruction callback [" + callback + "] for attribute '" + name +
"' because FacesRequestAttributes does not support such callbacks");
}
}
But my understanding is that the RequestContextListener (that you have declared) will take care of this job. Its requestDestroyed(ServletRequestEvent requestEvent) method is shown below:
public void requestDestroyed(ServletRequestEvent requestEvent) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE);
ServletRequestAttributes threadAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (threadAttributes != null) {
// We're assumably within the original request thread...
if (attributes == null) {
attributes = threadAttributes;
}
RequestContextHolder.resetRequestAttributes();
LocaleContextHolder.resetLocaleContext();
}
if (attributes != null) {
attributes.requestCompleted();
if (logger.isDebugEnabled()) {
logger.debug("Cleared thread-bound request context: " + requestEvent.getServletRequest());
}
}
}
And if you look at the javadoc of ServletRequestAttributes#requestCompleted():
Executes all request destruction callbacks and updates the session attributes that have been accessed during request processing.
So, I think that you can safely skip the WARN with log4j configuration (maybe confirm this with a little debugging session though).
I tried adding
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
as suggested by erhan14 on this forum post.
And that warning disappeared for me. Hope it helps.

Categories