Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to register a JSF managed bean programmatically?

I'd like to register/add a Managed Bean class programmatically (from within a Servlet init()) into application scope. How can I do that with JSF 1.2?

like image 724
Zeemee Avatar asked Jul 19 '11 11:07

Zeemee


4 Answers

It is unlikely that can do do this in a programmatic manner from your application for managed beans of all scopes. BalusC has already pointed out how to do this for application scoped managed beans.

Having taken a look at how managed beans are registered in Mojarra 2.1 (a JSF 2.1 implementation); there isn't a lot of lot of elegant options available for programmatic registration of session and request scoped beans. Simply put, you either have to invoke the implementation specific classes, or you will have to create and destroy i.e. manage the beans yourself instead of relying on the JSF implementation to do this.

Populating the request and session scopes with the beans (the unmanaged way)

Note - This is referred to as the "unmanaged way" because you are constructing the beans, and not the container. Annotations like @PostConstruct and @PreDestroy will not work, unless you process them yourself and invoke the appropriate methods. Even dependency-injection won't work.

EL expressions are always evaluated at runtime, so it gives you enough opportunity to populate the scope with the beans before evaluation (which allows for shooting yourself in the foot, if you have the chance to do so). In Mojarra (and possibly other JSF implementations), the EL resolver will rely on the services of a ScopeHandler (or an equivalent class) to resolve the EL expression values. Mojarra uses the classes ApplicationScopeHandler, RequestScopeHandler and SessionScopeHandler to obtain the values from the different scopes.

You can populate the contents of the Session and Request scopes after after a new session is created, or before a request is processed by the JSF implementation.

Session scope population can be done (ideally using a HttpSessionListener) using:

HttpSession session = request.getSession(false);
session == null ? null : session.setAttribute("<keyname>", new Bean());

The keyname must match the values you are using to reference the bean in EL expressions.

In a similar manner, you can populate the request scope (ideally done in a filter) using:

ServletRequest request = ... // get the reference to the servlet request object
request.setAttribute("<keyname>", new Bean());

If you need to understand how this works, you should take a look at the classes com.sun.faces.context.SessionMap, com.sun.faces.context.RequestMap and com.sun.faces.context.ApplicationMap to see how the context maps are managed internally, and used by the SessionScopeHandler, RequestScopeHandler and ApplicationScopeHandler classes that are static inner classes of the ScopeManager (another static inner) class of the com.sun.faces.mgbean.BeanManager class. The BeanManager class is the one that contains the managed bean registrations, and the next section discusses how to "hack into" the registration process of Mojarra.

Using the Mojarra classes to register the beans

Registration of managed beans in the Mojarra implementation is done by the public void register(ManagedBeanInfo beanInfo) method of the com.sun.faces.mgbean.BeanManager class. It is not trivial to access the BeanManager class using the JSF or Servlet APIs alone. There is however the ApplicationAssociate class of Mojarra that creates the BeanManager instance, and can be accessed using the getCurrentInstance() method. The other answer by Thomas already demonstartes how to register the managed bean programmatically:

ApplicationAssociate.getCurrentInstance().getBeanManager().register(...)

There is a caveat with the above approach. It is unlikely that this approach will work in the init method of Servlet for the simple reason that the getCurrentInstance method relies on a ThreadLocal variable to retrieve the ApplicationAssociate instance. The thread local variable is initialized by the com.sun.faces.application.WebappLifecycleListener class, so you must reproduce the mechanism used by the WebappLifecycleListener class, of invoking the ApplicationAssociate getInstance(ServletContext context) method, to gain access to the ApplicationAssociate instance. The following code therefore, might be (as I have not attempted using it) a better one, if you are willing to use Mojarra specific classes:

ServletContext sc = ... //get the ServletContext reference;
ApplicationAssociate.getInstance(sc).getBeanManager().register(...)

You must still watch out for quirks arising out of this mechanism as it is quite possible that some of the Mojarra classes and instances would not have been loaded or initialized before your Servlet. I would therefore suggest loading attempting to configure your servlet with a load-on-startup value that is higher than the one used by the FacesServlet.

like image 105
Vineet Reynolds Avatar answered Nov 08 '22 16:11

Vineet Reynolds


from within a Servlet init()

So, it concerns a non-JSF request. The FacesContext#getCurrentInstance() would return null here, so it's of no use for you here.

It's good to know that JSF application scoped managed beans are basically stored as an attribute of the ServletContext. In the init() method you've the ServletContext at your hands by the inherited getServletContext() method. So, the following should do:

@Override
public void init() {
    getServletContext().setAttribute("managedBeanName", new BackingBean());
}

That's it. It'll be available in JSF by #{managedBeanName}.

like image 32
BalusC Avatar answered Nov 08 '22 16:11

BalusC


Try FacesContext.currentInstance().getExternalContext().getApplicationMap().put(name, bean);, i.e. put the managed bean instance into the map using the name you want to use in your expressions.

Edit:

To register the bean, try calling: ApplicationAssociate.getCurrentInstance().getBeanManager().register(...) and pass a ManagedBeanInfo you filled.

like image 3
Thomas Avatar answered Nov 08 '22 16:11

Thomas


The following code registers the managed bean correcly using FacesContext, but it requires the servlet request and response. You could use the code and initialize it lazily using servlet and not during the init.

Usage:

UserBean ub = (UserBean) 
    Example.getBean(servletRequest, servletResponse, "user", UserBean.class);

Source:

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

class Example {

    public static Object getBean(HttpServletRequest request, HttpServletResponse response, String beanName, Class expectedType){
        FacesContext ctx = getFacesContext(request, response);
        ValueExpression vex = ctx.getApplication().getExpressionFactory().createValueExpression(ctx.getELContext(), "#{"+beanName+"}", expectedType);
        return vex.getValue(ctx.getELContext());
    }

    private static FacesContext getFacesContext(HttpServletRequest request, HttpServletResponse response) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (facesContext == null) {
            facesContext = ((FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY)).
                getFacesContext(request.getSession().getServletContext(), request, response, 
                ((LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY))
                .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE));

            InnerFacesContext.setFacesContextAsCurrentInstance(facesContext);
            facesContext.setViewRoot(facesContext.getApplication().getViewHandler().createView(facesContext, ""));
        }
        return facesContext;
    }

    private abstract static class InnerFacesContext extends FacesContext {
        protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
            FacesContext.setCurrentInstance(facesContext);
        }
    }
}
like image 1
Pavel Sedek Avatar answered Nov 08 '22 18:11

Pavel Sedek