Programmatically get expression value of facelets

2019-02-15 13:54发布

问题:

Following java code allows to access any object or variable from faces context:

ELContext elCtx = facesContext.getELContext();
ExpressionFactory exprFac = facesContext.getApplication().getExpressionFactory();
MyProperty myProperty = (MyProperty) exprFac.createValueExpression(elCtx, "#{somebean.someattr.someproperty}", MyProperty.class).getValue(elCtx);

I use the code from within my custom converter to read additional converting parameters from context.

The code works correctly if #{somebean} is defined as normal backing bean within JSF context.

Facelets allow to create 'shortcut' to JSF expressions. Example:

<ui:param name="shortcut" value="#{somebean.someattr.someproperty}" />
<div>#{somebean.someattr.someproperty} equals #{shortcut}</div>

In this case both #{somebean.someattr.someproperty} and #{shortcut} have the same value.

However these 'shortcut' names are not accessible using java code above. For example:

MyProperty myProperty1 = (MyProperty) exprFac.createValueExpression(elCtx, "#{somebean.someattr.someproperty}", MyProperty.class).getValue(elCtx);
// myProperty1 has expected value

MyProperty myProperty2 = (MyProperty) exprFac.createValueExpression(elCtx, "#{shortcut}", MyProperty.class).getValue(elCtx);
// myProperty2 is null

Is there a way to access a facelets context and to read 'shortcut' parameter values, defined on the current JSF page?

回答1:

I had the same problem and have chosen the following approach:

/**
     * Führt eine Expression im aktuellen Faces EL Context 
     * UND im Facelets El Context aus.
     * 
     * @param facesContext
     * @param expression
     * @return object
     */
    private static Object executeExpressionInUIContext (final FacesContext facesContext, final String expression) {
        final ELContext elContext = facesContext.getELContext();
        final Application application = facesContext.getApplication();

        Object result = executeExpressionInElContext(application, elContext, expression);
        if (null == result) {
            FaceletContext faceletElContext = (FaceletContext) FacesContext.getCurrentInstance().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
            result = executeExpressionInElContext(application, faceletElContext, expression);
        }
        return result;
    }

    private static Object executeExpressionInElContext (Application application, ELContext elContext, String expression) {
        ExpressionFactory expressionFactory = application.getExpressionFactory();
        ValueExpression exp = expressionFactory.createValueExpression(elContext, expression, Object.class);
        return exp.getValue(elContext);
    }

"ui:param" is part of the Facelet view handling technology. Facelets extends JSF. Both technologies use their own Context when storing variables. Beside the Faces El Context there is a Facelet El Context (FaceletContext).

The stated method evaluates expressions in both contexts. Be aware that this will not work if two values are stored under the same name in each context.



回答2:

It seems that facelet shortcuts do not exist in the context, where I try to access them.

I have made following workaround: On JSF page where my input element is placed, I have added a <f:param> element as child of the input with my converter.

<h:inputText id="myid" value="#{action.myinput}">
     <f:converter converterId="myConverter" />
     <f:param name="converterParameters" shortcut="#{somebean.someattr.someproperty}"/>
</h:inputText>

Then in converter I'm able to find UIParam element as one of the input children and read my shortcuts from it.

public Object getAsObject(FacesContext context, UIComponent component, String value) {

    MyProperty myProperty = null;
    try {
        for (final UIComponent child : component.getChildren()) {
            if ("converterParameters".equals(child.getAttributes().get("name"))) {
                final ELContext elCtx = context.getELContext();
                myProperty = (MyProperty) child.getValueExpression("shortcut").getValue(elCtx);
                break;
            }
        }
        if (myProperty == null) {
            throw new NullPointerException("My property is undefined.");
        }
    } catch (Exception e) {
        LOG.error("Cannot convert " + value + ".  Use <f:param name=\"converterParameters\" "
                + "shortcut=\"#{here.comes.shortcut}\"/> for your input element. ", e);
        throw new ConverterException("Cannot initialize converter.", e);
    }
//...
}


回答3:

The mapping of ui:param is not stored in context, it's in the VariableMapper of each individual ValueExpression. So if you need to create ValueExpression programmatically, relying on another ValueExpression's varMapper, you can do something like this:

VariableMapper varMapper = new DefaultVariableMapper();
varMapper.setVariable(mappingName, component.getValueExpression(mappedAttributeName));
return new ValueExpressionImpl(expression, null, null, varMapper, expectedType);