0

I need to access an application-scoped managed bean to modify certain properties from within an HttpSessionListener.

I already used something like the following:

@Override
public void sessionDestroyed(HttpSessionEvent se) {
    HttpSession session = se.getSession();
    User user = userService.findBySessionId(session.getId());    

    ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();

     ApplicationScopedBean appBean = (ApplicationScopedBean) externalContext.getApplicationMap().get("appBean");

     appBean.getConnectedUsers().remove(user);
}

externalContext = FacesContext.getCurrentInstance().getExternalContext() causes a null pointer exception here already, and even if it didn't I'm not sure appBean could be accessible the above way.

Any ideas?

Skyhan
  • 871
  • 3
  • 15
  • 26
  • Maybe your page is not using the FacesServlet. Show your web.xml JSF servlet configuration and mapping and the full name of the page. – Luiggi Mendoza May 15 '12 at 14:59
  • seems that it's not possible to get the FacesContext using FacesContext.getCurrentInstance() from a HttpSessionListener; just like it's not possible to do so from a Servlet. Maybe if you can get the ServletContext you can do something like getServletContext().getAttribute("appBean"); – damian May 15 '12 at 15:48

1 Answers1

3

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}.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555