nesting JSF expression strings
Asked Answered
E

2

5

I want to achieve this using dynamic parameters for value expressions:

<h:dataTable value="#{someBean.someValue}" var="field">
    <h:column>#{anotherBean[field]}</h:column>
</h:dataTable>

where field is 'user.name' or 'location.address.zip' or...

Is it possible?

Note that this is a simple example, I'm interested in ValueExpression not in dataTable component.

UPDATE now the question is: how to replace standard BeanELResolver?

looking in ELUtils:

    ...
    composite.addRootELResolver(IMPLICIT_RESOLVER);
    composite.add(FLASH_RESOLVER);
    composite.addPropertyELResolver(COMPOSITE_COMPONENT_ATTRIBUTES_EL_RESOLVER);
    addELResolvers(composite, associate.getELResolversFromFacesConfig());
    addVariableResolvers(composite, FacesCompositeELResolver.ELResolverChainType.Faces,
            associate);
    addPropertyResolvers(composite, associate);
    composite.add(associate.getApplicationELResolvers());
    composite.addRootELResolver(MANAGED_BEAN_RESOLVER);
    composite.addPropertyELResolver(RESOURCE_RESOLVER);
    composite.addPropertyELResolver(BUNDLE_RESOLVER);
    ...

but i don't fully understand resolver chain yet... so i'll go studying :)

UPDATE 2

this code works ;)

public class ExtendedBeanELResolver extends BeanELResolver
{
    @Override
    public Object getValue(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException
    {
        try
        {
            return super.getValue(context, base, property);
        }
        catch(PropertyNotFoundException e)
        {
            try
            {
                Object value = base;

                for(String part : property.toString().split("\\."))
                {
                    value = super.getValue(context, value, part);
                }

                return value;
            }
            catch(PropertyNotFoundException e1)
            {
                context.setPropertyResolved(false);
            }
        }

        return null;
    }
}
Ellsworthellwood answered 22/5, 2012 at 0:23 Comment(1)
no, i'm saying right: public List<String> getSomeValue() { ... }Ellsworthellwood
C
9

This is by default not supported. You need a custom ELResolver here. Easiest is to extend the existing BeanELResolver.

Here's a kickoff example:

public class ExtendedBeanELResolver extends BeanELResolver {

    @Override
    public Object getValue(ELContext context, Object base, Object property)
        throws NullPointerException, PropertyNotFoundException, ELException
    {
        if (property == null || base == null || base instanceof ResourceBundle || base instanceof Map || base instanceof Collection) {
            return null;
        }

        String propertyString = property.toString();

        if (propertyString.contains(".")) {
            Object value = base;

            for (String propertyPart : propertyString.split("\\.")) {
                value = super.getValue(context, value, propertyPart);
            }

            return value;
        }
        else {
            return super.getValue(context, base, property);
        }
    }

}

To get it to run, register it as follows in faces-config.xml:

<application>
    <el-resolver>com.example.ExtendedBeanELResolver</el-resolver>
</application>
Carafe answered 22/5, 2012 at 4:23 Comment(8)
this is exactly what i was looking for to solve this and some other little thing. thanks @BalusC, you save me so much time that you deserve part of my salary ;)Ellsworthellwood
however declaring it in faces-config.xml overrides default CompositeELResolver. how to replace standard BeanELResolver in CompositeELResolver.elResolvers?Ellsworthellwood
oops, i was wrong. it does not override, but get inserted before. see question update.Ellsworthellwood
What problem exactly did it cause for you? The change should be transparent as it consistently delegates to the super.Carafe
causes PropertyNotFoundException trying to read bundles properties. however doc states Because this resolver handles base objects of any type, it should be placed near the end of a composite resolver. Otherwise, it will claim to have resolved a property before any resolvers that come after it get a chance to test if they can do so as well. so i solved this catching PropertyNotFoundException and setting context.setPropertyResolved(false);Ellsworthellwood
Right. Add a line if (base instanceof ResourceBundle) return null; to top. You might want to do the same for Map and Collection. I edited the example.Carafe
@Carafe I saw that we can add multiple <el-resolver> in one single faces-config.xml (two for exmaple), in that case how would JSF now which one to use (assuming that each resolver is for a specific kind of expressions)? I tried to make it as an independent question but doesn't seem to me a big matter, so I said maybe you could give me some hint that I can follow. Thanks in advanceGnarled
@Tarik: they follow decorator pattern (like InputStream and friends). The one registered later wraps/delegates to the previously registered one via super.xxx() call.Carafe
J
4

The work made on this topic is very interesting, but not complete.

In case you pass value in a Composite Component, which is set in this component, it doesn't work.

Example :

<composite:interface>
    <composite:attribute name="value" type="java.lang.Boolean" required="true"  />
</composite:interface>
<composite:implementation>
        <h:inputText id="inputValue" value="#{cc.attrs.value}" 
        </h:inputText>
</composite:implementation>

With the ExtendedBeanElResolver, it throws a PropertyNotFoundException when I set value.

So, I spend few hours to find a solution, and here is the working solution, to be able to use the ExtendedBeanElResolver with the ability to set values inside composite components :

public class ExtendedBeanELResolver extends BeanELResolver {

    private static final String PRIMEFACES_RESOURCE_PREFIX = "primefaces:";
    private static final String RESOURCES_HANDLER = "class org.omnifaces.resourcehandler.GraphicResourceHandler";
    private static final String PRIMEFACES_EXT_RESOURCES_HANDLER = "class org.primefaces.extensions.application.PrimeFacesExtensionsResourceHandler";

    @Override
    public Object getValue(ELContext context, Object base, Object property) {

            if (property == null || base == null || base instanceof ResourceBundle || base instanceof Map || base instanceof Collection
                    || property.toString().startsWith(PRIMEFACES_RESOURCE_PREFIX) || base.getClass().toString().equals(RESOURCES_HANDLER)
                    || base.getClass().toString().equals(PRIMEFACES_EXT_RESOURCES_HANDLER)) {
                return null;
            }
            String propertyString = property.toString();
            if (propertyString.contains(".")) {
                Object value = base;

                for (String propertyPart : propertyString.split("\\.")) {
                    value = super.getValue(context, value, propertyPart);
                }

                return value;
            } else {
                Object v = super.getValue(context, base, property);
               return v;
            }
    }

    @Override
    public void setValue(ELContext context, Object base, Object property, Object val) {
        if (base != null && !(base instanceof ResourceBundle) && !(base instanceof Map) && !(base instanceof Collection))
            super.setValue(context, base, property, val);
    }
}

(I added the "setValue" part).

And now it works. Don't hesitate to give me your feedback, as this is my first post in this website !

Jeana answered 2/3, 2016 at 16:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.