I have a JSF 2 application (running on top of JBoss AS 7.1) that has to start a long process when the user clicks a button in a page. The idea is to have a not blocking interaction, so the user can wait and see the results or simply close the page and get back later to see how it is going or the results, if the process is concluded already.
The process itself is coded in a following (simplified) class:
@Stateless
@LocalBean
@ApplicationScoped
public class MyProcessManager {
@Inject
private ProcessHelper processHelper;
@Asynchronous
public void start(final ProcessParameters parameters) {
// the process...
}
}
Such a class is marked as @ApplicationScoped
because all the processes running (for all the users) are kept by it. So, when the button is clicked, the backing bean sets some parameters and calls the asynchronous method start()
.
Everything goes ok until the moment the process tries to use processHelper
, which runs a number of Hibernate queries in order to proceed with the persistence part of the process. When the first method of processHelper
is called I get the follwing exception:
WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
As an additional info, a breakpoint inside of such a method is never hit.
What is happening and how to fix it?
The exception suggests that ProcessHelper
is @RequestScoped
.
When @Asynchronous
is invoked, a brand new and separate thread is spawned which is not controlled by the HTTP servlet container. In the context of that thread, there's thus no means of a HTTP request or HTTP session anywhere. You can only use @ApplicationScoped
, not @RequestScoped
, let alone @SessionScoped
.
As to the ProcessManager
itself, the combination @Stateless @ApplicationScoped
doesn't make sense. You most probably actually want a @javax.ejb.Singleton
. Additional bonus is that it's stateful, so you could just keep the process results there as an instance variable.
You mentioned that ProcessHelper
in turn runs some DB queries. Which means that it should run in transaction. In that case you should make it a fullworthy EJB instead of a CDI managed bean. So make ProcessHelper
a @Stateless
too, or just move all the DB interaction job into the ProcessManager
EJB. That's also possible.
So, all in all, this should do it:
<h:form>
<h:commandButton value="Start" action="#{processBacking.start}" />
</h:form>
<p>
Result (manually refresh page to check): #{processBacking.result}
</p>
@Named
@RequestScoped
public class ProcessBacking {
@Inject
private ProcessManager processManager;
public void start() {
// ...
processManager.start(parameters);
}
public ProcessResult getResult() {
return processManager.getResult();
}
// ...
}
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class ProcessManager {
private ProcessResult result;
@Inject
private ProcessHelper helper;
@Asynchronous
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void start(ProcessParameters parameters) {
ProcessResult result = runSomeLongRunningNonTransactionalProcess(parameters);
this.result = helper.persist(result);
}
public ProcessResult getResult() {
return result;
}
}
@Stateless
public class ProcessHelper {
@PersistenceContext
private EntityManager entityManager;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public ProcessResult persist(ProcessResult result) {
entityManager.persist(result);
return result;
}
}
Do note that a @Singleton
is by default read/write locked. So you can't invoke getResult()
until the start()
is finished. Hence the ConcurrencyManagementType.BEAN
, which means that it's unlocked and thus essentially the caller is itself responsible for concurrency management. This allows you to keep refreshing the page as long as the process still runs.