Access raw expression of ValueExpression attribute

2019-05-11 11:34发布

问题:

Can I access the expression string of a ValueExpression passed as attribute value to my taglib component?

My goal is to programmatically derive missing attribute values from it. In this case I'm trying to avoid having to repeat an attribute as a literal.

now:

<a:columnText title="name" value="#{entity.name}" sortBy="entity.name" />

desired:

<a:columnText title="name" value="#{entity.name}" />

-taglib.xml

<tag>
    <tag-name>columnText</tag-name>
    <source>column-text.xhtml</source>
    <attribute>
        <name>value</name>
        <required>true</required>
    </attribute>
    <attribute>
        <name>title</name>
        <required>false</required>
    </attribute>
    <attribute>
        <name>sortBy</name>
        <required>false</required>
    </attribute>
</tag>

column-text.xhtml

<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:p="http://primefaces.org/ui" xmlns:h="http://java.sun.com/jsf/html"
                xmlns:a="http://www.kwa.nl/jsf/app-legacy" xmlns:c="http://java.sun.com/jstl/core">

    <c:if test="#{empty sortBy}">
        <a:expressionAsLiteral var="#{sortBy}" value="#{value}" />
    </c:if>

    <p:column headerText="#{title}" sortable="true" sortBy="#{sortBy}">
        <h:outputText value="#{value}"/>
        <a:sortByUnwrapper/>
    </p:column>
</ui:composition>

<a:expressionAsLiteral /> is intended to unwrap the ValueExpression '#{value}' to '#{entity.name}' and set '#{sortBy}' to the literal 'entity.name'. In this example to feed the primefaces sortBy column.

public class ExpressionAsLiteral extends TagHandler {

    private final TagAttribute var;
    private final TagAttribute value;

    public ExpressionAsLiteral(TagConfig config) {
        super(config);
        var = getRequiredAttribute("var");
        value = getRequiredAttribute("value");
    }    

    @Override
    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
       // abstracted for example.
       setAsLiteral(ctx, var, unwrapFaceletAttributeValue(ctx,value));
    }
}

My debugger tells me the information needed is hidden in value's ValueExpressionImpl private VariableMapper varMapper. My problem is unwrapping the returned ValueExpressionImpl without resorting to code smells.

My google-fu is failing me. I get the feeling my approach is all wrong, any tips?

EDIT #1: Attempted the following. Results in #{value} instead of desired attribute value #{iRow.title}.

valueExpressionString = value.getValueExpression(ctx, Object.class).getExpressionString();

回答1:

As to the concrete question, you can access the ValueExpression representing the tag attribute value as defined in template client via FaceletContext#getVariableMapper() and then VariableMapper#resolveVariable() passing the tag attribute name. Then, you can get the literal expression string via ValueExpression#getExpressionString().

<my:expressionAsLiteral tagAttributeName="value" />
String tagAttributeName = getRequiredAttribute("tagAttributeName").getValue();
ValueExpression ve = context.getVariableMapper().resolveVariable(tagAttributeName);
String expression = ve.getExpressionString(); // #{entity.name}
String literal = expression.substring(2, expression.length() - 1); // entity.name

However, after that, it's not possible to put it in the EL scope via some "var", because PF 4.x would ultimately interpret the value of sortBy="#{sortBy}" literally as #{sortBy}, not as entity.name. You'd better nest it inside <p:column> and have the tag handler explicitly set it.

<p:column>
    <my:expressionAsLiteral tagAttributeName="value" componentAttributeName="sortBy" />
    #{value}
</p:column>
String componentAttributeName = getRequiredAttribute("componentAttributeName").getValue();
parent.getAttributes().put(componentAttributeName, literal);

As to the functional problem you're actually trying to solve, the following works just fine for me on PF 5.2. Based on the source code they fixed it in 5.0 and improved further in 5.1.

/WEB-INF/tags/column.xhtml:

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:p="http://primefaces.org/ui"
>
    <p:column sortBy="#{empty sortBy ? value : sortBy}">#{value}</p:column>
</ui:composition>

Template client:

<p:dataTable value="#{bean.items}" var="item">
    <my:column value="#{item.id}" />
    <my:column value="#{item.name}" sortBy="#{item.value}" />
    <my:column value="#{item.value}" />
</p:dataTable>