I need to use some session-scope data in an asynchronous job. The job is run with Spring #Async annotation.
To achieve this goal, request attributes are first remembered in a request thread:
currentAttributes = RequestContextHolder.getRequestAttributes();
Then, in a thread running the async job, the request attributes are restored:
RequestContextHolder.setRequestAttributes(currentAttributes);
Almost everything works - session beans are available. However, I cannot access the HttpSession object - to check if the session is still valid.
In a session-scoped bean:
#Autowired
transient private HttpSession session;
//...
private boolean isSessionInvalid() {
try {
session.getCreationTime();
return false;
} catch (IllegalStateException e) {
return true;
}
}
The call session.getCreationTime() throws an IllegalStateException, even when the session is still valid:
java.lang.IllegalStateException: The request object has been recycled and is no longer associated with this facade
at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:904)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:366)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:361)
at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:307)
What can I do to make the session available from an async job? Or, alternatively, is there any other way to check that the session is still valid?
Related
I had to cancel the running queries. In my DAO I set the Entity.session in the ServletContext when I start the query and remove it when the query is finished. Then check in the Controller if the session is present in the ServletContext, if it is present then I cancel the query by calling the session.cancelQuery() from the session object in the ServletContext.
This was working fine in my dev environment, but in my pre-prod testing the entire code does not run inside the tomcat container. The web part runs in tomcat whereas the data layer runs a java application. hence I could not find the ServletContext in the DAO class and it gave me a ClassNotFound Exception
So I decoupled the web layer in the DAO. Now I set the hibernate.session in the controller itself when it calls the DAO calculate(). But this created a problem, now the session exists even if there are no calculations going on and in actual there are some post or precalculations. And my mechanism to cancel the query doesn't work.
So what I need is a way to access the Controller from the DAO to set the session. I could have used a static method in the Controller and then set the session from it but I think this is again not a good practice.
DAO initial Code:
public calculate(){
Session session = mEntityManager.unwrap(Session.class);
//pre possing
if(session != null)
{
mContext.setAttribute(""+view.getId(), session);
}
List<Object[]> results = query.getResultList();
mContext.removeAttribute(""+view.getId());
//post processing
}
Decoupled DAO code:
The method getSession() is called from the controller before the calculate method is called in the controller. And then when the user requests a cancel from the UI the cancel method is called in the controller.
public Session getSession()
{
Session session = mEntityManager.unwrap(Session.class);
return session;
}
public calculate(){
//pre possing
List<Object[]> results = query.getResultList();
//post processing
}
Controller:
#RequestMapping
public WebServiceResponse cancel(HttpServletRequest request)
{
if(mContext.getAttribute(id) != null)
((Session)mContext.getAttribute(id)).cancelQuery();
}
It seems you have convoluted control flow because you are not properly separating detection of a problem from handling the problem: you detect a problem in the data layer but need to handle it in the presentation layer.
Consider using exceptions. Have the data layer throw an exception when it detects a problem. The presentation layer can handle problems in the data layer by catching exceptions.
We're developing a SaaS solution for several consumers. This solution is based on Spring, Wicket and Hibernate. Our
database contains data from several customers. We've decided to model the database as follows:
public
Shared data between all customers, for example user accounts as we do not know which customer a user belongs to
customer_1
customer_2
...
To work with this setup we use a multi-tenancy setup with the following TenantIdentifierResolver:
public class TenantProviderImpl implements CurrentTenantIdentifierResolver {
private static final ThreadLocal<String> tenant = new ThreadLocal<>();
public static void setTenant(String tenant){
TenantProviderImpl.tenant.set(tenant);
}
#Override
public String resolveCurrentTenantIdentifier() {
return tenant.get();
}
#Override
public boolean validateExistingCurrentSessions() {
return false;
}
/**
* Initialize a tenant by storing the tenant identifier in both the HTTP session and the ThreadLocal
*
* #param String tenant Tenant identifier to be stored
*/
public static void initTenant(String tenant) {
HttpServletRequest req = ((ServletWebRequest) RequestCycle.get().getRequest()).getContainerRequest();
req.getSession().setAttribute("tenant", tenant);
TenantProviderImpl.setTenant(tenant);
}
}
The initTenant method is called by a servlet filter for every request. This filter is processed before a connection
is opened to the database.
We've also implemented a AbstractDataSourceBasedMultiTenantConnectionProviderImpl which is set as our
hibernate.multi_tenant_connection_provider. It issues a SET search_path query before every request. This works like charm for requests passing through the servlet filter described above.
And now for our real problem: We've got some entrypoints into our application which do not pass the servlet filter,
for instance some SOAP-endpoints. There are also timed jobs that are executed which do not pass the servlet filter.
This proves to be a problem.
The Job/Endpoint receives a value somehow which can be used to identify which customer should be associated with the
Job/Endpoint-request. This unique value is often mapped in our public database schema. Thus, we need to query the
database before we know which customer is associated. Spring therefore initializes a complete Hibernate session. This
session has our default tenant ID and is not mapped to a specific customer. However, after we've resolved the unique
value to a customer we want the session to change the tenant identifier. This seems to not be supported though, there
is no such thing as a HibernateSession.setTenantIdentifier(String) whereas there is a
SharedSessionContract.getTenantIdentifier().
We thought we had a solution in the following method:
org.hibernate.SessionFactory sessionFactory = getSessionFactory();
org.hibernate.Session session = null;
try
{
session = getSession();
if (session != null)
{
if(session.isDirty())
{
session.flush();
}
if(!session.getTransaction().wasCommitted())
{
session.getTransaction().commit();
}
session.disconnect();
session.close();
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
}
catch (HibernateException e)
{
// NO-OP, apparently there was no session yet
}
TenantProviderImpl.setTenant(tenant);
session = sessionFactory.openSession();
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
return session;
This method however does not seem to work in the context of a Job/Endpoint and leads to HibernateException such as
"Session is closed!" or "Transaction not succesfully started".
We're a bit lost as we've been trying to find a solution for quite a while now. Is there something we've misunderstood?
Something we've misinterpreted? How can we fix the problem above?
Recap: HibernateSession-s not created by a user request but rather by a timed job or such do not pass our servlet
filter and thus have no associated tenant identifier before the Hibernate session is started. They have unique values
which we can translate to a tenant identifier by querying the database though. How can we tell an existing Hibernate
session to alter it's tenant identifier and thus issue a new SET search_path statement?
We've never found a true solution for this problem, but chimmi linked to a Jira-ticket were others have requested such a feature: https://hibernate.atlassian.net/browse/HHH-9766
As per this ticket, the behavior we want is currently unsupported. We've found a workaround though, as the number of times we actually want to use this feature is limited it is feasible for us to run these operations in separate threads using the default java concurrency implementation.
By running the operation in a separate thread, a new session is created (as the session is threadbound). It is very important for us to set the tenant to a variable shared across threads. For this we have a static variable in the CurrentTenantIdentifierResolver.
For running an operation in a separate thread, we implement a Callable. These callables are implemented as Spring-beans with scope prototype so a new instance is created for each time it is requested (autowired). We've implemented our own abstract implementation of a Callable which finalizes the call()-method defined by the Callable interface, and the implementation starts a new HibernateSession. The code looks somewhat like this:
public abstract class OurCallable<TYPE> implements Callable<TYPE> {
private final String tenantId;
#Autowired
private SessionFactory sessionFactory;
// More fields here
public OurCallable(String tenantId) {
this.tenantId = tenantId;
}
#Override
public final TYPE call() throws Exception {
TenantProvider.setTenant(tenantId);
startSession();
try {
return callInternal();
} finally {
stopSession();
}
}
protected abstract TYPE callInternal();
private void startSession(){
// Implementation skipped for clarity
}
private void stopSession(){
// Implementation skipped for clarity
}
}
Another workaround I've found thanks to #bas-dalenoord comment regarding OpenSessionInViewFilter/OpenEntityManagerInViewInterceptor which led me to this direction, is to disable this interceptor.
This can be achieved easily by setting spring.jpa.open-in-view=false either in the application.properties or environment-variable.
OpenEntityManagerInViewInterceptor binds a JPA EntityManager to the thread for the entire processing of the request and in my case it's redundant.
Another workaround is to break the request that needs to make DB calls on behalf of 2 different tenants into 2 separate requests.
First the client ask for his associated tenant in the system, and then creates a new request with the given tenant as a parameter. IMO, until (and if) the feature will be supported, it's a relatively clean alternative.
I have an EJB interceptor and I follow the BCE pattern suggested by Adam Bien, that is, all EJB calls on the boundary starts and finish a transaction which means there is no nested EJB calls (there might be nested CDI injected Bean calls though, but those should be inside the same transaction started at the ejb Boundary).
So in those ejb Boundaries I have an interceptor and I want to intercept or know if after the method call of the EJB the transacction commited already? (that is, if a EntityManager was involved that the COMMIT sql call was sent to the DB and returned succeesfuly)
Will I get that info from inside an Interceptor ?
If not, how can I get notified of a transaction that sucessfully commited or failed ?
NOTE: Of course, if I am the client of the EJB and I am calling the method, after the method call I know what happened with the transaction, but I am interested in intercepting that BEFORE the client receives the response from the EJB.
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object proceed = null;
try {
proceed = ctx.proceed();
// is the transacction finished/commited already?
// is it still open ?
return proceed;
} catch (Exception e) {
throw e;
}
}
[UPDATE]: I accepted one good answer, but the thing is that THERE IS NO WAY in Java EE to receive an event of a transaction that HAS BEEN COMMITED. So regardless of the good answer, sadly there is no way to be notified in Java EE of a completed transaction, inside the server, of course, if you are the client caller, then you sure know the transaction commited or rolled back...
unless otherwise stated on the exception thrown, if an ejb method invocation throws an exception, it shall be rolled-back. Additionally, provided all calls to the DB were in the same transaction, they shall be deemed committed at the end of the transaction cycle.
In retrospect, all interceptors are invoked within the same transaction on which the ejb method it intercepts, was invoked (That's the reason the interceptor may decide in an event of an exception, to either roll-back or still commit the transaction).
Hence, you can know for sure, that the transaction completed successfully if within your interceptor call, after the proceed has been invoked and returned, there is no exception thrown, with a potential of transaction rollback.
So in your scenario:
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object proceed = null;
try {
proceed = ctx.proceed();
// is the transacction finished/commited already?
// The transaction is successful, but afaik, it is not yet committed, until this method returns successfully
// is it still open ? More or less. You can still grab the Ejbtransaction and commit it manually or rollback if some other conditions have not been met yet
return proceed;
} catch (Exception e) {
//If this happens, and you propagate it, then for sure the transaction will be rolledback, and never get committed. Since all db calls were being done within this transaction, then no DB commit will be done.
throw e;
}
}
Edit:
for you to actually commit the transaction in an interceptor, you will need to be running application-managed transaction, otherwise, it is prohibited by the EJB specs to call commit on a container managed transaction, you can of course call setOnrollback method of the EJBContext.
Edit
If you truly want to do some DB changes, i would recommend:
user ApplicationManaged transaction, from which you manually start
and commit the transaction within the interceptor
Use the concept of the observer, and listen for #Observes(AFTER_SUCCESS) event which will be invoked when the
transaction is successfully committed and complete, and hence you
can be guaranteed to do a db call, and the new updates will be
available.
If you can ignore the BCE pattern, and spin off a new transaction to do the update, so that after it returns successfully, you will be guaranteed of commit, and then continue normally
```
#Stateless
public class TransactionService {
#TransactionAttribute(REQUIRES_NEW)
public Object executeTransaction(final Callable<Object> task) {
return task.call();
}
}
#Interceptor
public class MyInterceptor {
#EJB
private TransactionService service;
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object proceed = null;
try {
proceed = service.executeTransactional(()->ctx.proceed());
//If you reach here, you will be guaranteed of commit and then you can do the elastic search update
return proceed;
} catch (Exception e) {
//If this happens, and you propagate it, then for sure the transaction will be rolledback, and never get committed. Since all db calls were being done within this transaction, then no DB commit will be done.
throw e;
}
}
}
Ok - the question is 4 years old now, but I think it makes still sense to give an answer.
You can for sure register a callback to get informed about the Transaction outcome. You simply use the registerSynchronization() API of javax.transaction.Transaction.
Transaction tx = ((TransactionManager) (new InitialContext()).lookup("java:/TransactionManager")).getTransaction();
tx.registerSynchronization(new Synchronization() {
public void beforeCompletion() {
// do stuff before completion
}
public void afterCompletion(int status) {
if (status == Status.STATUS_COMMITTED) {
// do something after successful commit }
}
});
I have a List<entity> ctx of my entity class in my java web application(tomcat8, servlet3), I keep one instance for each session(user) with my context(list), and a reference copy in user's sessions, like following.
javax.servlet.http.HttpSession sess=request.getSession(true);
//declaring and initializing the entity object
entity e=new entity();
e.timestamp=System.currentTimeMillis();
//keep the e with the session
sess.setAttribute("e",e);
//and a reference copy with another context
ctx.add(e);
Question:
I just want to know(an event, listener, ...) about user http session, in order to remove the entity object from the ctx when session gets expired(removed).Now how may I realized that a session is being expired from the server?
Just use a HttpSessionListener. It exists specially for that usage. Simply override the sessionDestroyed(HttpSessionEvent se) :
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
// do your processing
}
#Override
public void sessionCreated(HttpSessionEvent se) {
// empty implementation if you do not need it ...
}
Do not forget to declare it. Extract from javadoc : In order to receive these notification events, the implementation class must be either declared in the deployment descriptor of the web application, annotated with WebListener, or registered via one of the addListener methods defined on ServletContext.
I'm developing a web-app using Struts 2 with a Spring 3 backend. I'm using Spring aop:proxy beans to handle my session beans rather than the Struts 2 SessionAware interface. Everything was working fine until I have an Action that is running under the Struts ExecAndWait interceptor. Because this interceptor in effect runs my action under a seperate thread, when I come to try and access my proxied session bean, I get a BeanCreationException/IllegalStateException. Is there another "spring way" that I can get hold of my session beans in this scenario?
Regards
From Execute and Wait Interceptor documentation
Important: Because the action will be running in a seperate thread, you can't use ActionContext because it is a ThreadLocal. This means if you need to access, for example, session data, you need to implement SessionAware rather than calling ActionContext.getSession().
The problem with session scoped-beans is that they depend on thread-local attributes set by RequestContextListener or RequestContextFilter. But the latter allows you to set very interesting threadContextInheritable flag...
If your ExecAndWait interceptor creates new thread per every request it serves, inheritable thread local should propagate session scoped beans to child threads. However if Struts uses thread pool (more likely, thou I haven't used Struts2 for ages) to serve this requests, this will have very unexpected and dangerous results. You might experiment with this flag, maybe it will do the trick.
You can Implement your own ExecAndWait interceptor using Spring. You can also delegate the management/creation of this action to Spring. For the later the details are in the S2 spring plugin documentation.
You can use ,RequestContextHolder(Holder class to expose the web request in the form of a thread-bound RequestAttributes object.) to make session scoped proxy beans available to child threads.
Define a custom ExecuteAndWait Interceptor and in doIntercept method use the following static method from RequestContextHolder
public static void setRequestAttributes(RequestAttributes attributes,boolean inheritable)
Bind the given RequestAttributes to the current thread.
Parameters:
attributes - the RequestAttributes to expose, or null to reset the thread-bound context
inheritable - whether to expose the RequestAttributes as inheritable for child threads (using an InheritableThreadLocal)
Sample Code
public class CustomExecuteAndWaitInterceptor extends ExecuteAndWaitInterceptor {
#Override
protected String doIntercept(ActionInvocation actionInvocation) throws Exception {
RequestAttributes requestAtteiAttributes = RequestContextHolder.getRequestAttributes(); //Return the RequestAttributes currently bound to the thread.
RequestContextHolder.setRequestAttributes(requestAtteiAttributes, true);
//do something else if you want ..
return super.doIntercept(actionInvocation);
}
}