The FacesContext
is only available in the thread serving the HTTP request initiated by the webbrowser which has invoked the FacesServlet
. During a session destroy there's not necessarily means of a HTTP request. Sessions are usually destroyed by a background thread managed by the container. This does not invoke a HTTP request through the FacesServlet
. So you should not expect the FacesContext
to be always there during the session destroy. Only when you call session.invalidate()
inside a JSF managed bean, then the FacesContext
is available.
If your application scoped managed bean is managed by JSF @ManagedBean
, then it's good to know that JSF stores it under the covers as an attribute of the ServletContext
. The ServletContext
in turn is available in the session listener by HttpSession#getServletContext()
.
So, this should do:
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
ApplicationScopedBean appBean = (ApplicationScopedBean) session.getServletContext().getAttribute("appBean");
appBean.getConnectedUsers().remove(user);
}
If you're running a Servlet 3.0 capable container, an alternative is to just let your application scoped bean implement HttpSessionListener
and register itself as such upon construction. This way you have direct reference to the connectedUsers
property.
@ManagedBean
@ApplicationScoped
public class AppBean implements HttpSessionListener {
public AppBean() {
ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
context.addListener(this);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
connectedUsers.remove(user);
}
// ...
}
Again another alternative is to keep the User
in the session scope as a session scoped managed bean. You can then use the @PreDestroy
annotation to mark a method which should be invoked when the session is destroyed.
@ManagedBean
@SessionScoped
public class User {
@ManagedProperty("#{appBean}")
private AppBean appBean;
@PreDestroy
public void destroy() {
appBean.getConnectedUsers().remove(this);
}
// ...
}
This has the additional benefit that the User
is in EL context available as #{user}
.