The usual way to use EL expression in JSF could seem a little too restrictive for some of us who are used to scripting languages.
For instance, if you'd like to display a bean property, you will write a getter on it:
public class MyBean {String myProperty;public String getMyProperty() {return myProperty;}}
Then you'll be able to write the following value expression in a template:
<h:outputText value="#{myBean.myProperty}" />
Now imagine that your bean has to perform a more complex task to retrieve the property, like calling a service, and pass parameters to it. Even if there is always the possibility to pass the parameter using a "f:parameter" tag, the bean API will look kind of awkward. The more natural way to do so is to write a method with this parameter, and find a way to call it from the template.
For instance, we could have:
public String getMyProperty(String param) {// execute any function to get the resultreturn function(param);}
<h:outputText value="#{myBean.getMyProperty('foo')}" />
Sadly, there is no way to do that using "pure" JSF implementations.
That's where facelets can be very handy. In a very nice blog post, Andrew Robinson explains how to pass method bindings to children components using the facelet user tag system. I will explain how Nuxeo uses the same tricks to invoke method expressions with parameters as regular value expressions.
First let's define the famous MethodValueExpression class, that will behave as a regular value expression but will invoke a method expression when trying to resolve the value:
public class MethodValueExpression extends ValueExpression implementsExternalizable {public MethodValueExpression(MethodExpression methodExpression,Class[] paramTypesClasses) {this.methodExpression = methodExpression;this.paramTypesClasses = paramTypesClasses;}...@Overridepublic Object getValue(ELContext context) {// invoke method instead of resolving valueObject res;try {return methodExpression.invoke(context, paramTypesClasses);}catch(Throwable t) {return null;}}}
Nuxeo benefits from an extension to the EL provided by Seam: it makes it possible to use parameters on any method expression without having to configure parameter types. That's why parameter types classes are never actually set in the Nuxeo code.
When this is done, we can use facelets meta rules to use this class instead of the generic one. This is done via a component handler:
public class GenericHtmlComponentHandler extends HtmlComponentHandler {...protected MetaRuleset createMetaRuleset(Class type) {MetaRuleset m = super.createMetaRuleset(type);if (ValueHolder.class.isAssignableFrom(type)) {m.addRule(GenericValueHolderRule.Instance);}return m;}}
This configuration tells to use the GenericValueHolderRule class when setting a component attributes. This rule does not do much but use our MethodValueExpression when appropriate, e.g. when brackets are detected.
We can configure tags to use this handler in a facelet taglib:
<tag><tag-name>outputText</tag-name><component><component-type>javax.faces.HtmlOutputText</component-type><renderer-type>javax.faces.Text</renderer-type><handler-class>org.nuxeo.ecm.platform.ui.web.tag.handler.GenericHtmlComponentHandler</handler-class></component></tag>
Note that there is no need to use another term than "value" as shown in this code (using "genericValue") as the last rule added to the MetaRuleSet will apply first and override the default behaviour.
The nxh taglib, using the namespace "http://nuxeo.org/nxweb/html" redefines all basic jsf html tags to use this handler.
We could add any number of attributes to be dealt in the same way than "value": for instance, being able to write <nxh:outputText rendered="#{myBean.getProperty('foo')}" /> can be handy too.
Now it can a little painful to define a new taglib with this handler when reusing custom tag libraries. The Nuxeo tag library defines a new tag "nxu:methodResult", that will make the result of the given expression available in the variable map:
<nxu:methodResult name="prop" value="#{myBean.getMyProperty('foo'}"><h:outputText value="foo" rendered="#{prop == 'bar'}" /></nxu:methodResult>
The variable named "prop" is available inside the methodResult tag, as a row variable in a "h:dataTable" tag.
This behaviour is achieved using a specific tag handler that will use the MethodValueExpression presented above:
public class MethodResultTagHandler extends MetaTagHandler {private final TagAttribute name;private final TagAttribute value;public MethodResultTagHandler(TagConfig config) {super(config);name = getRequiredAttribute("name");value = getRequiredAttribute("value");}public void apply(FaceletContext ctx, UIComponent parent)throws IOException {String nameStr = name.getValue(ctx);// parameter types evaluation not needed using SeamMethodExpression meth = value.getMethodExpression(ctx, Object.class,new Class[0]);ValueExpression ve = new MethodValueExpression(meth, paramTypesClasses);ctx.getVariableMapper().setVariable(nameStr, ve);this.nextHandler.apply(ctx, parent);}}
This tag handler is linked to the MethodResult tag in a taglib file:
<tag><tag-name>methodResult</tag-name><handler-class>org.nuxeo.ecm.platform.ui.web.tag.handler.MethodResultTagHandler</handler-class></tag>
Nice, huh?
Nuxeo Tag Library documentation: http://maven.nuxeo.org/nuxeo-platform-parent/nuxeo-platform-ui-web/tlddoc/.
Complete code mentioned above is available here:
Important announcement: Join the Nuxeo team and contribute to the Nuxeo project! We have open positions in France and the UK for open source Java EE developers and sales engineers, both junior and senior.
联系客服