How to keep JSF Conversations from expiring - cdi

I have a requirement to keep the JSF 2.2 CDI conversation from expiring. I tried implementing a heartbeat mechanism where I click a 'hidden' button using Ajax which in turn calls a servlet. But the Conversation still expires. I set the timeout to 10s for testing purposes and my code is as shown below.
// The begin conversation method in my managed bean
public void beginConversation() {
if (conversation.isTransient())
{
conversation.setTimeout(10000);
conversation.begin();
}
}
// JQuery document ready function
$(document).ready(function() {
setInterval(function(){$.get("/HeartbeatServlet");}, 5000);
});
// Heartbeat servlet
#WebServlet("/HeartbeatServlet")
public class HeartbeatServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
{
System.out.println("Heartbeat Received");
request.getSession();
}
}
It prints the "Heartbeat Recieved" text every 5 seconds. But the conversation still expires.

The conversation timeout is how long the conversation will stay alive. It is not a permanent tracker of a conversation. It is not meant to be a keep alive on requests.
There is no extend conversation concept, however you can use begin(id) to recreate a conversation. Any bean state in that conversation will be lost.

We had the same problem. Adding "cid" to the request for the servlet did not help. In our experience, the heartbeat Servlet does not know about the Conversation. It will keep the current session going, but won't help with the Conversation.
Our solution for this was to add a viewscoped managedbean in which we inject the Conversation. On the UI we used PrimeFaces p:poll to periodically call a method on the managebean.
The method is just printing log statements.
#ManagedBean
#ViewScoped
public class ConversationListener implements Serializable {
...
/**
* Inject Conversation instance if it is available.
*/
#Inject
private Conversation conversation;
#Inject
FacesBroker facesBroker;
...
/**
* Method to refresh current conversation
*/
public void renewConversation() {
if (conversation == null) {
System.out.println("no conversation");
} else {
HttpServletRequest request = (HttpServletRequest) facesBroker.getContext().getExternalContext().getRequest();
log.info("*** ConversationListener called for conversation ID: {}", new Object[] { conversation.getId() });
log.info("*** ConversationListener called for session ID: {}", new Object[] { request.getSession().getId() });
}
}
}

I've found that keeping a conversation from expiring can be achieved by calling a method on the conversation scoped bean periodically with Primefaces' p:poll, configured to use a reasonable interval (5 min interval has worked for me), no fancy magic required.

this could be implementation specififc but if you start a conversation with begin() as you do, the framework should append a conversation id as a requestparameter. this is how the conversation is mapped to a request.
with weld as cdi implementation this should be "cid". i dont know how other implementations handle the conversation.
try to append the parameter to your heartbeat request and it should map the conversation and maybe refresh the timeout.
hope that helps

Related

Calling asynchronous EJB and make ManagedBean listen to its answer without blocking navigation

I don't really have a good knowledge of EJB components, CDI and stuff. I'm having a lot of trouble with asynchronous EJB. I tried some tutorials but I couldn't understand or make them work adapting to the code I'm maintaining here.
I would like my EJB to return an answer to my Managed Bean when it finishes executing. ManagedBean should listen to it and then do other stuff (in my case I need to update view components, which could be done with a redirect), but it can't block navigation and other actions from user.
So far, this is what I got:
1 - this is my managed Bean:
#Named
#ViewScoped
public class MyManagedBean {
#Inject
private ExecutionService executionService;
public void executeAsyncTask(Suite suite) {
try {
Future<Boolean> result = executionService.executeStuff(suite);
if (result.isDone()) {
// send a redirect if he is in the same page of the button that calls this method. It doesn't work, it's result is always false because execution is not finished and method moves on with his life
}
}
2 - And this is my EJB:
#Singleton
#ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class ExecutionService extends BaseBean implements Serializable {
//
#Inject private ExecutionDAO execDao;
//
#Asynchronous
public Future<Boolean> executeStuff(Suite suite) throws BusinessException {
Boolean areStiffExecuted = false;
Process process;
try {
process = Runtime.getRuntime().exec(new String[]{ });
// and other crazy stuff that take some time.
} catch (Exception e) {
// log exception and throw it to managed bean
} finally {
// do some stuff like destroying process
return new AsyncResult<>(true);
}
}
}
Do you guys have any idea of how to get what I want?
Following the links provided by Kukeltje I found stuff about websockets (yeah, I had never heard of them) and went with Omnifaces version since it was already a part of the project. I followed the websocket instructions from Omnifaces showcase.
I also followed BalusC explanation about them and complemented my complete ignorance on this subject in this post about Consumer objects. It looks like I made something that works out of it all!

How to implement async calls from session bean

In the oficial Java EE documentation https://docs.oracle.com/javaee/6/tutorial/doc/gkkqg.html says "Session beans can implement asynchronous methods".
Following this tutorial I'm not able to execute a method asynchronously.
It actually Works, but like any other synchronous method. (It doesn´t start any other thread).
#ManagedBean(name = "inicioSSCCBean")
#SessionScoped
public class InicioSSCCBean implements Serializable {
...
#Asynchronous
public Future<String> sendMessage() {
String status;
try {
// Call to SAP server...
} catch (MessagingException ex) {
// Error handler
}
return new AsyncResult<String>(status);
}
public void otherMethod() {
String result = sendMessage().get(); // The result is what I expect
System.out.println(result);
}
...
}
Do anyone know how to implement an async call from a session bean?
The main goal is to make a call to a SAP service, and get the results asynchronously.
I´m using JSF 2.2, PrimeFaces.
Thanks in advance.
You misunderstood the Java EE tutorial. The term "Session bean" refers to enterprise session beans (EJBs), not to session scoped managed beans (JSF/CDI beans).
The #javax.ejb.Asynchronous annotation, as its package already hints, works only in EJBs. EJBs are recognizable by having a #javax.ejb.Xxx annotation on the class, such as #Stateless or #Stateful.
Below is the correct kickoff example:
#Stateless
public class YourService {
#Asynchronous
public void asyncDoSomething() {
// ...
}
}
#ManagedBean
public class YourBean {
#EJB
private YourService yourService;
public void submit() {
yourService.asyncDoSomething();
}
}
See also:
When is it necessary or convenient to use Spring or EJB3 or all of them together?
Is it safe to start a new thread in a JSF managed bean?
JSF Controller, Service and DAO
How can server push asynchronous changes to a HTML page created by JSF?
Aside from the fact that you're trying to use the wrong kind of bean, you'll have to understand how Future works.
When you call Future.get(), the thread will block until the Future has a result. Therefore sendMessage().get() will act exactly like a synchronous call.
However if you call sendMessage() and then perform other tasks before calling get(), it will be performed asynchronously. As you realize an asynchronous call is only useful when you don't need the result right away, making it less useful than it seems in most cases.

Limitations of using a PhaseListener instead of a Servlet Filter for authorization

I'm currently using a PhaseListener as below to perform user authorization.
private PhaseId phaseId = PhaseId.RESTORE_VIEW;
#Override
public void afterPhase(PhaseEvent event) {
FacesContext fc = event.getFacesContext();
boolean isOnAllowedPage = false;
String[] allowedPages = choseRightPages(); // chose pages for role
for (String s : allowedPages) {
if (fc.getViewRoot().getViewId().lastIndexOf(s) > -1) {
isOnAllowedPage = true;
break;
}
}
if (!isOnAllowedPage) {
NavigationHandler nh = fc.getApplication().getNavigationHandler();
nh.handleNavigation(fc, null, "prohibited");
}
}
It does what I want, however I don't see it being listed in How to handle authentication/authorization with users in a database? and this Coderanch topic titled "authorization with phaselistener problem" also mentions the following:
You shouldn't couple authorization that tight with JSF. Better make use of container managed authentication and/or a simple filter acting on an url-pattern covering the protected pages.
I don't exactly understand the limitations of using a PhaseListener instead of a Filter when performing user authorization. Can someone explain it to me?
A PhaseListener is only fired on a JSF request (i.e. a HTTP request which invoked the FacesServlet). It's not fired when a non-JSF request is executed and thus exposes a potential security leak on non-JSF requests. A servlet Filter can be fired on every single HTTP request, regardless of the target servlet.
In other words: HTTP request authorization should not be tied to having the FacesContext available, but to the ServletRequest available. Always try to authorize as "low level" as possible.

More than one session from the same browser

I need a little direction understanding sessions in JSF (2.2.6). I've tried to find some documentation but am still missing something.
I have a #RequestScoped login bean which saves parameters in the session map for reference by other session scoped backing beans. They get the user info when they go through the PostConstruct method and everything works great.
However the logic fails when more than one window is used or the user doesn't logoff and goes directly back to the login page. JSF treats this as the same session and the #PostConstructs are not invoked.
I am pretty sure I could invalidate the session but that doesn't solve the problem of multiple users from different browser windows.
Any guidance or reference sites would be appreciated.
Thanks in advance!
John
session HAS to be same for every browser window, the only exception is when using anonymous mode: i.e. chrome behave like having two browsers opened at the same time.
another way to have multiple sesssions is to use different server name:
http://localhost:8080/app and http://127.0.0.1:8080/app may not share a single session.
however sessions never overlaps.
your problem, if i understand right, is when a logged user access login page and re-login, preserving his old session, that's why session beans are not PostConstructed again (independently from window used).
a general solution is to forbid access to login page for logged users.
and in general, container will throw an AlreadyAuthenticatedException or similar when user re-login without prior logout.
cut a long story short, just a preliminary example waiting for your code:
#ManagedBean
#SessionScoped
public class UserBean implements Serializable
{
private static final long serialVersionUID = 1L;
private User user;
public boolean isLoggedIn()
{
return user != null;
}
public void login(String username, String password)
{
// maybe you want to check isLoggedIn() and either call logout() or throw an exception
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
request.login(username, password);
user = someDAO.loadFromDatabase(username);
}
public void logout()
{
// maybe you want to check isLoggedIn() and either throw an exception or do nothing
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
request.logout();
user = null;
// suggested for another scenario
// request.getSession().invalidate();
}
// getters and setters
}
and
#ManagedBean
#SessionScoped
public class OperationBean implements Serializable
{
private static final long serialVersionUID = 1L;
#ManagedProperty("#{userBean}")
private UserBean userBean;
public void execute()
{
if(!userBean.isLoggedIn())
{
FacesContext.getCurrentInstance().getExternalContext().redirect("login.jsf");
return;
}
User user = userBean.getUser();
// do something
}
// getters and setters
}
with this combination, instead of using OperationBean's #PostContruct i used #ManagedProperty, so that OperationBean contains an always-up-to-date reference to user, without caring multiple re-logins.

Adding faces message to redirected page using ExternalContext.redirect()

I am using ExternalContext.redirect(String); method to redirect user to another page:
FacesContext.getCurrentInstance().addMessage(new FacesMessage("Bla bla bla..."));
FacesContext.getCurrentInstance().getExternalContext().getFlash().setKeepMessages(true);
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "/scenario.xhtml");
As Matt Handy mentioned in his answer, I used Flash.setKeepMessages(true); but it does not seem to work with ExternalContext.redirect. (Although it works when I redirect by returning a page name from bean's action method.)
Now how can I add FacesMessage so that it is visible in the redirected (scenario.xhtml) page?
This seems to be a timing problem. This listener method is invoked during the preRenderView event. According to the source code of ELFlash (Mojarra's Flash implementation as returned by ExternalContext#getFlash()) it turns out that it won't set the flash cookie when you're currently sitting in the render response phase and the flash cookie hasn't been set yet for the current request:
Here are the relevant lines from ELFlash:
if (currentPhase.getOrdinal() < PhaseId.RENDER_RESPONSE.getOrdinal()) {
flashInfo = flashManager.getPreviousRequestFlashInfo();
} else {
flashInfo = flashManager.getNextRequestFlashInfo(this, true);
maybeWriteCookie(context, flashManager);
}
The maybeWriteCookie would only set the cookie when the flash cookie needs to be passed through for the second time (i.e. when the redirected page in turn redirects to another page).
This is an unfortunate corner case. This ELFlash logic makes sense, but this isn't what you actually want. Basically you need to add the message during INVOKE_APPLICATION phase instead. There is however no such event as postInvokeAction. With the new JSF 2.2 <f:viewAction> tag it should be possible as it really runs during invoke application phase.
<f:viewAction action="#{bean.onload}" />
As long as you're not on JSF 2.2 yet, you'd need to look for alternate ways. The easiest way would be to create a custom ComponentSystemEvent.
#NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
Now you need somewhere a hook to publish this event. The most sensible place is a PhaseListener listening on after phase of INVOKE_APPLICATION.
public class PostInvokeActionListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
If you register it as follows in faces-config.xml
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
then you'll be able to use the new event as follows
<f:event type="postInvokeAction" listener="#{bean.onload}" />
Update this is also available in the JSF utility library OmniFaces, so you don't need to homebrew the one and other. See also the InvokeActionEventListener showcase example.
Use the flash to keep messages over a redirect.
Add these two lines to your code before redirecting:
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().setKeepMessages(true);
Note that the there are some issues with Mojarra's flash scope implementation. Keep this in mind if you use it.
Using Matt Handy's example as a reference, I created the method below that worked very well for me.
public static void Message(String message) {
FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_INFO, mensagem, null);
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().getFlash().setKeepMessages(true);
context.addMessage(null, fm);
}

Resources