Wednesday, November 16, 2011

5 useful methods JSF developers should know

The aim of this post is a summary about some handy methods for JSF developers they can use in their day-to-day work. An utility class is a good place to put all methods together. I would call such class FacesAccessor. The first method is probably the most used one. It returns managed bean by the given name. The bean must be registered either per faces-config.xml or annotation. Injection is good, but sometimes if beans are rare called, it's not necessary to inject beans into each other.
public static Object getManagedBean(final String beanName) {
    FacesContext fc = FacesContext.getCurrentInstance();
    Object bean;
    
    try {
        ELContext elContext = fc.getELContext();
        bean = elContext.getELResolver().getValue(elContext, null, beanName);
    } catch (RuntimeException e) {
        throw new FacesException(e.getMessage(), e);
    }

    if (bean == null) {
        throw new FacesException("Managed bean with name '" + beanName
            + "' was not found. Check your faces-config.xml or @ManagedBean annotation.");
    }

    return bean;
}
Using:
@ManagedBean
public class PersonBean {
    ...
}

PersonBean personBean = (PersonBean)FacesAccessor.getManagedBean("personBean");

// do something with personBean
The second method is useful for JSF component developers and everyone who would like to evaluate the given value expression #{...} and sets the result to the given value.
public static void setValue2ValueExpression(final Object value, final String expression) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ELContext elContext = facesContext.getELContext();

    ValueExpression targetExpression = 
        facesContext.getApplication().getExpressionFactory().createValueExpression(elContext, expression, Object.class);
    targetExpression.setValue(elContext, value);
}
Using:
I personally use this method for the "log off functionality". After an user is logged off, he/she will see a special "logoff page". The "logoff page" uses user settings (e.g. theme, language, etc.) from a sesion scoped bean. But this session scoped bean doesn't exist more because the session was invalidated. What to do? Here is the code snippet from my logout method.
UserSettings userSettings = (UserSettings) FacesAccessor.getManagedBean("userSettings");

// invalidate session
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
HttpSession session = (HttpSession) ec.getSession(false);
session.invalidate();

// create new session
((HttpServletRequest) ec.getRequest()).getSession(true);

// restore last used user settings because login / logout pages reference "userSettings"
FacesAccessor.setValue2ValueExpression(userSettings, "#{userSettings}");

// redirect to the specified logout page
ec.redirect(ec.getRequestContextPath() + "/views/logout.jsf");
The third method maps a variable to the given value expression #{...}. It uses javax.el.VariableMapper to assign the expression to the specified variable, so that any reference to that variable will be replaced by the expression in EL evaluations.
public static void mapVariable2ValueExpression(final String variable, final String expression) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ELContext elContext = facesContext.getELContext();
    
    ValueExpression targetExpression =
        facesContext.getApplication().getExpressionFactory().createValueExpression(elContext, expression, Object.class);
    elContext.getVariableMapper().setVariable(variable, targetExpression);
}
Using:
Assume, "PersonBean" is a managed bean having "name" attribute and "PersonsBean" is a bean holding many instances of "PersonBean" (as array, collection or map). The following code allows to use "personBean" as a reference to a specific bean with "name" Oleg.
 
FacesAccessor.mapVariable2ValueExpression("personBean", "#{personsBean.person['Oleg']}");
 
In a facelets page, say so, personDetail.xhtml, we can write:
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html">
<ui:composition>
    ...
    <h:inputText value="#{personBean.name}"/>
    ...
</ui:composition>
</html>
Note, the reference "personBean" was set in Java. This mapping can be also used in facelets in declarative way via ui:include / ui:param.
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition>
    ...
    <ui:include src="personDetail.xhtml">
        <ui:param name="personBean" value="#{personsBean.person['Oleg']}"/>
    </ui:include>
    ...
</ui:composition>
</html>
The next two methods are used to create MethodExpression / MethodExpressionActionListener programmatically. They are handy if you use component binding via "binding" attribute or create some model classes in Java.
public static MethodExpression createMethodExpression(String valueExpression,
                                                      Class<?> expectedReturnType,
                                                      Class<?>[] expectedParamTypes) {
    MethodExpression methodExpression = null;
    try {
        FacesContext fc = FacesContext.getCurrentInstance();
        ExpressionFactory factory = fc.getApplication().getExpressionFactory();
        methodExpression = factory.
            createMethodExpression(fc.getELContext(), valueExpression, expectedReturnType, expectedParamTypes);
    } catch (Exception e) {
        throw new FacesException("Method expression '" + valueExpression + "' could not be created.");
    }
    
    return methodExpression;
}

public static MethodExpressionActionListener createMethodActionListener(String valueExpression,
                                                                        Class<?> expectedReturnType,
                                                                        Class<?>[] expectedParamTypes) {
    MethodExpressionActionListener actionListener = null;
    try {
        actionListener = new MethodExpressionActionListener(createMethodExpression(
            valueExpression, expectedReturnType, expectedParamTypes));
    } catch (Exception e) {
        throw new FacesException("Method expression for ActionListener '" + valueExpression
                          + "' could not be created.");
    }

    return actionListener;
}
Using:
In one of my projects I have created PrimeFaces MenuModel with menu items programmatically.
MenuItem mi = new MenuItem();
mi.setAjax(true);
mi.setValue(...);
mi.setProcess(...);
mi.setUpdate(...);
mi.setActionExpression(FacesAccessor.createMethodExpression(
    "#{navigationContext.setBreadcrumbSelection}", String.class, new Class[] {}));

UIParameter param = new UIParameter();
param.setId(...);
param.setName(...);
param.setValue(...);
mi.getChildren().add(param);
Do you have nice methods you want to share here? Tips / tricks are welcome.

6 comments:

  1. Hi Oleg, for the first technique, is there a different between your code and this piece of code?

    public static T findBean(String managedBeanName, Class beanClass) {
    FacesContext context = FacesContext.getCurrentInstance();
    return beanClass.cast(context.getApplication().evaluateExpressionGet(context, "#{" + managedBeanName + "}", beanClass));
    }

    ReplyDelete
  2. Hi Oleg, I have problems PrimeFaces MenuModel with menu items programmatically, This code, dont work ,

    mi.setActionExpression(FacesAccessor.createMethodExpression("
    #{navigationContext.setBreadcrumbSelection}", String.class, new Class[] {}));

    You have, some special in your Backing Bean?

    ReplyDelete
  3. Nothing special. This was an example. #{navigationContext.setBreadcrumbSelection}" is an EL binding to my bean (NavigationContext) and my method in this bean (setBreadcrumbSelection).

    ReplyDelete
  4. thk, I solve my problem, I remove //item.setUrl(menuDTO.getUrl());, and all nice

    ReplyDelete
  5. Hello Oleg, I have a problem with the MenuModel
    when the menu is inside an accordion. In this case the code is not working.

    <p:accordionPanel value="#{roles.listaTab}" var="tab">
    <p:tab title="#{tab.name}">
    <h:panelGrid columns="2" cellpadding="10">
    <p:menu model="#{usuarioSesion.miMenu}" id="miMenu3" />
    </h:panelGrid>
    </p:tab>
    </p:accordionPanel>

    Any ideas?
    thanks

    ReplyDelete
  6. Inject into a TagHandler return a null value... The first method save my day... thx... :)

    ReplyDelete