Saturday, November 18, 2006

Is JSF made for AJAX or does it just "play along"?

My blog has moved, please update your links. You can find this article here

Before I begin, this post is meant as a discussion point, not a flame war. I decided to publish this as a blog instead of an email on one of the JSF mailing lists so that I could get comments and feedback from others.

I have been using JSF for almost two years now, one year as my full time job. It is by far, better than any other server side framework that I have used (C CGI, Perl CGI, PHP, ASP, ASP.NET, JSP and servlets)

Background

With this in mind, it doesn't mean there aren't issue in the JSF space. One of which is it's compatibility with AJAX. Several competitors are available to use AJAX with JSF:

  • AjaxAnywhere
  • Ajax4Jsf
  • G4Jsf
  • Trinidad
  • jsf-extensions/Avatar
  • ICEFaces
  • JBoss-Seam (in the works)
  • etc.

Each of them handle AJAX in their own way unfortunately for the learning user. The one large problem with them, is that they are all on top of JSF. Before you post your comment, here me out. JSF is a "hack" to bring a new technology on top of JSP. It was developed when Sun and Java developers realized that JSP was not succeeding and had fundemental flaws in the design that made ASP.NET, RoR and other languages more appealing. Many went the way of Struts with Java to gain so of that functionality. The problem with struts is that it never provided a good component framework.

The reason that JSF is a "hack" is that it is built from JSP pages. Although nice to those wishing to convert their projects from plain JSP to JSF with a JSP view handler, the combination is useless for large business applications. "The reason for this?" you ask, well it is the component tree. The single worst design of JSF is the saved component state of the UIViewRoot object. The component tree is made to be built once and then saved on the client or in the user's HttpSession object. This is more than just a serialized set of objects. It has methods for saving state and restoring state that custom components may override. Every time a view is restored, then entire component tree must be recursed and each of these methods are invoked. All the EL expressions (a.k.a. value bindings), component properties, attributes, etc. are all serialized into this component state. The overhead is gigantic.

To illustrate this point, let me bring up my companies application. It is just that an application, having dockabe frames, complex dialog box, complex layouts, etc. It actually makes Gmail and Google calendar look simplistic. Now I am not trying to pat myself or my company on the back, but let you know of the complexity of each view. There are hundreds if not thousands of components in this component tree. Each time I POST back to the view, the time it takes to restore this component tree is getting slower and slower as we add more functionality.

Luckily we use Facelets for our view handler not JSP (which I would never recommend as a long term solution). JSP should be though of only as a stepping stone until you convert your legacy application from JSP to JSF. Once you are completely in JSF, use facelets. It is the only way to achieve adequate performance for anything besides a Hello World application.

Enter AJAX

We are using AjaxAnywhere right now. It is very stable in 1.1.0.6 and does the job nicely. If I were to do it all again, I would probably use Ajax4Jsf due to its tight integration with JSF and the fact that it is the closest in design to Avatar (the future of AJAX in JSF from Sun).

The problem

For each AJAX request, the "JSF integrated" technologies must post back to JSF. They then execute the standard JSF lifecycle. This includes the restoring of the component tree. Yes, this is the single worst part of JSF for AJAX. If I simply want to update 2 components on the page, I have to restore the entire tree, counting possibly into the 1000s of components. Sound like a bottleneck? It is!

I can witness this problem when I use JBoss-Seam remoting. Remoting is independent of JSF and therefore does not restore the component tree or the view. I have extended it in my use to integrate with the FacesContext so that I can access & update my backing beans as well. The performance? -- instant. That is right, it doesn't even appear to go back to the server it is so fast. The difference between this and the page POST of AjaxAnywhere back to JSF is huge. Unfortunately, using Java methods on the server to generate HTML feels like the days of pre-JSP servlet development, so while it is good for sending & getting data, it is not a good fit for generating HTML (unless you love JavaScript and you build the entire page via document.createElement() calls -- see my discussion on GWT below).

What can be done? Well many of these technologies "hack and slash" their way into the JSF APIs to try to speed performance. They skip phases such as validation, updating of the model, etc. for components that do not need to change, but the component tree is still always restored. The one solution is that the component tree must go! That is right, in order for JSF to have adequate performance the JSF specifications must be completely changed to mostly remove the component tree. If I want to update 2 components using AJAX, only those two components should be reloaded from the view, no more. This is problematic as the API is not built for this.

Jacob Hookom has had some really good insights on this issue:

Is the grass greener?

I was looking at GWT the other day as it came up when I was looking for some good JavaScript libraries for layouts. In doing so, I started to read more and more, liking it quite a bit. Wondering if this is better for business applications than JSF, I found G4Jsf, an attempt at integration of JSF and GWT. The problem is that I could not find any documentation and the demo is extremely simplistic (no forms, no validation or updating of the JSF model, no JSF navigation, and almost no Views, just GWT code). I will be quite anxious to see where this project goes.

From what I understand in my initial look, GWT is interesting in that it stores the component state in the DOM of the browser using HTML + JavaScript. This means that when calls are made to the server, there is no component tree to update, no view to restore. The client is almost a thick client (and almost getting the performance of one). It's "magic" is to convert Java code into JavaScript, hiding the complexity of writing JavaScript DOM manipulation code by hand.

On the other hand GWT doesn't look like a good tool to create web pages though. There is no HTML for developers, only Java code. WYSIWYG is next to impossible from what I see for complex applications. The ease of using the JSF view and the Facelets templates, just doesn't seem to be there.

What Now?

As I mentioned, the whole point of this blog is simply to start a discussion. I would love to hear what the JSF experts have to say and if they know if future releases beyond 1.2 will start to address these issues (I even wonder if 1.2 will ever take off as ppl. aren't even fully embracing 1.1 yet).

Is there something I missed with these JSF AJAX libraries to get around the component tree performance issue?

Please keep the comments to Java based technologies as they apply to JSF, this is not meant to be language war.

Update: 2007-04-22

Since I created this post, I have had the opportunity to convert my company's software over from AjaxAnywhere to Ajax4Jsf and I wanted to report my findings so far. After some initial issues on converting some very large (and nested) templates over and converting some complex in-house components to A4J, we are now running on A4J with only some minor patching.

Even with a very large component tree, I am seeing ~300-600ms per AJAX request (calculated using FireBug in Firefox). I am using ajaxSingle="true", limitToList="true" and a4j:region tags where possible to enhance performance (it make a significant performance impact). With time permitting, I think I can get performance better by working on our in-house JavaScript code that runs per-AJAX request that is part of the issue.

So even though JSF needs some large improvements to the specification to make AJAX fit better, the performance is definitely dependent on the AJAX tools used. Hopefully we see JSF more stream-lined and less JSP dependent (or hopefully completely separate from JSP) in JSF 2.0 with some improvements to component creation and state saving (letting the view handler take more of the burden on and less on the component develeper).

Tuesday, June 27, 2006

MyFaces Tree2 - Creating a lazy loading tree

My blog has moved, please update your links. You can find this article here
Note: this code is hosted at http://sourceforge.net/projects/jsf-comp under the AjaxAnywhere download. As a result, this blog post's content may be out of date compared to that source.

Although the MyFaces Tree2 WIKI discusses lazy loading children tree nodes, it does not cover the use case of if you don't know if there are children or not. Recently, I had a use case where I was loading children using a WebService that would return me contents of a node per-call. Obviously for performance reasons, I wanted to keep the number of calls to a minumum.

My approach:
Use AJAX via AjaxAnywhere and server-side toggling with Tree2 to lazy load the tree nodes. When asked for the children of a node that isn't loaded, I would load the nodes then and there.

My use case was wrapped around a content management solution, so it was file/directory based nodes.

What was needed:
  • Custom tree node for "directory nodes"
  • Custom tree nodes for "file nodes" that could be selected
Note: this blog will not discuss AJAX and enabling the tree in server side mode, that would be another discussion entirely

Step 1: create a "lazy loading" tree node that we can extend:

public class BaseTreeNode implements TreeNode { private BaseTreeNode parent; private String identifier; private String name; private String type; protected List<BaseTreeNode> children; protected TreeModel model; public BaseTreeNode() {} public BaseTreeNode(TreeModel model, BaseTreeNode parent, String type, String identifier, String name, boolean leaf) { this.model = model; this.type = type; this.parent = parent; this.identifier = identifier; this.name = name; if (leaf) children = Collections.emptyList(); } /** * @return Returns the parent. */ public BaseTreeNode getParent() { return this.parent; } /** * @see org.apache.myfaces.custom.tree2.TreeNode#isLeaf() */ public boolean isLeaf() { return getChildCount() == 0; } /** * @see org.apache.myfaces.custom.tree2.TreeNode#setLeaf(boolean) */ public void setLeaf(boolean leaf) {} /** * @see org.apache.myfaces.custom.tree2.TreeNode#getChildren() */ public List<BaseTreeNode> getChildren() { if (children == null) children = loadChildren(); return children; } /** * @see org.apache.myfaces.custom.tree2.TreeNode#getType() */ public String getType() { return type; } /** * @see org.apache.myfaces.custom.tree2.TreeNode#setType(java.lang.String) */ public void setType(String type) { this.type = type; } /** * @see org.apache.myfaces.custom.tree2.TreeNode#getDescription() */ public String getDescription() { return name; } /** * @see org.apache.myfaces.custom.tree2.TreeNode#setDescription(java.lang.String) */ public void setDescription(String description) {} /** * @see org.apache.myfaces.custom.tree2.TreeNode#setIdentifier(java.lang.String) */ public void setIdentifier(String identifier) {} /** * @see org.apache.myfaces.custom.tree2.TreeNode#getIdentifier() */ public String getIdentifier() { return identifier; } public int getIndex() { return (parent == null) ? 0 : parent.getChildren().indexOf(this); } /** * @return Returns the name. */ public String getName() { return this.name; } public String getFullPath() { StringBuilder sb = new StringBuilder(name); for (BaseTreeNode node = getParent(); node != null; node = node.getParent()) sb.insert(0, '/').insert(0, node.getName()); return sb.toString(); } public String getNodePath() { StringBuilder sb = new StringBuilder(Integer.toString(getIndex())); for (BaseTreeNode node = getParent(); node != null; node = node.getParent()) sb.insert(0, TreeModel.SEPARATOR).insert(0, node.getIndex()); return sb.toString(); } public boolean isExpanded() { if (model == null) return true; return model.getTreeState() .isNodeExpanded(getNodePath()); } /** * @see org.apache.myfaces.custom.tree2.TreeNode#getChildCount() */ public int getChildCount() { if (children == null && !isExpanded()) return 1; if (children == null) children = loadChildren(); if (children == null) return 0; return children.size(); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return name; } protected List<BaseTreeNode> loadChildren() { return null; } }

This base class takes most of the tedious work away from having to create the lazy loading functionality. In the constructor, a "lazy" parameter is provided for nodes that the user knows will not have children (like a file node in this example). This node also includes code for working with the Tree2 default TreeModel and TreeState by implementing a "getNodePath" function that works with node indexes instead of just node IDs.

Step 2: create the directory node (inner class):

public class FolderNode extends BaseTreeNode { FolderNode(TreeModel model, BaseTreeNode parent, String name) { super(model, parent, "folder", name, name, false); } @Override protected List<BaseTreeNode> loadChildren() { return <Your business code class here>.loadChildren(this); } }

The implementation of the folder is simple. I have not shown my code, but I have this node in my code as an inner class that I have this "loadChildren" method that calls my Web service. In that method, I simply create new nodes an add them to the parent node.

The key here is how the tree2 works and how the renderer works (this is source code specific, so if Tree2 was changed drastically enough, this may break). The tree2 renderer looks only at the getChildCount method, not the getChildren method when rendering. Meaning that if getChildCount should return 0, getChildren will never be called unless the node is expanded. In my base node, you will see that I am returning 1 child as the child count if the children have not been loaded and the node is not expanded. This will cause tree2 to render a plus sign icon next to my node (indicating that there are children).

If the node is expanded, and the children are not loaded, my base node then calls the load children method. This will force the accurate count of nodes to be returned for this node. If I were to only put my loading code in the getChildren, the tree would not render correctly, as getChildCount is always called first in the renderer.

What ends up being the result is that when the plus sign is clicked, the node is exanded in the tree model's tree state. Then the tree is rendered (having the getChildCount method called). I load the children, and return the correct count to the renderer which then proceeds in calling getChildren and building the HTML.

This will work if there are actually no children (the plus sign simply dissappears), or if there are many children (not the "1" that was originally reported as the number of children).

FYI, here is the "file" node code:

public class FileNode extends BaseTreeNode { FileNode(TreeModel model, BaseTreeNode parent, String name) { super(model, parent, "file", name, name, true); } }
The XHTML is as follows:
<aa:zoneJSF id="treeZone"> <my:ajaxTree value="#{contentMgmtBean.treeModel}" ajaxZone="treeZone" var="_node" clientSideToggle="false" varNodeToggler="t" showRootNode="false"> <f:facet name="folder"> <t:panelGroup> <t:graphicImage styleClass="treeNodeIcon" url="#{_node.expanded ? '/images/contentMgmt/openfolder.gif' : '/images/contentMgmt/closedfolder.gif'}" /> <t:outputText styleClass="folderNodeText" value="#{_node.name}" /> </t:panelGroup> </f:facet> <f:facet name="file"> <my:ajaxCommandLink ajaxZone="treeZone,myResultZone" actionListener="#{contentMgmtBean.nodeSelected}" styleClass="fileNodeLink#{ contentMgmtBean.currentFileName eq _node.name ? ' selectedNode' : ''}"> <t:updateActionListener property="#{contentMgmtBean.currentFileName}" value="#{_node.name}" /> <t:graphicImage styleClass="treeNodeIcon" url="/images/contentMgmt/file.gif" /> <t:outputText styleClass="fileNodeText" value="#{_node.name}" /> </ost:ajaxCommandLink> </f:facet> </my:ajaxTree> </aa:zoneJSF>

Example of using the node as an inner class:

public class BeanClass { private TreeModel treeModel; public List<BaseTreeNode> loadChildren(FolderNode parent) { List<BaseTreeNode> children = new ArrayList(); List<String> folders = ; // load from web service for (String folder : folders) children.add(new FolderNode(treeModel, parent, folder)); List<String> files = ; // load from web service for (String file : files) children.add(new FileNode(treeModel, parent, file)); return children; } public class FolderNode extends BaseTreeNode { ... protected List<BaseTreeNode> loadChildren() { return loadChildren(this); } } }

Update This code has been moved to a jsf-comp component. Go to http://sf.net/projects/jsf-comp to download. An example war file is included in the release

© Copyright 2006 - Andrew Robinson.
Please feel free to use in your applications under the LGPL license (http://www.gnu.org/licenses/lgpl.html).

Sunday, June 18, 2006

Running actions despite validation errors

My blog has moved, please update your links. You can find this article here

Recently, I was developing a page that had 3 data tables. Each table had an 'add' button and each row in the table had 'delete' buttons. The problem was that I ended up fighting the JSF specification trying to accomplish this simple task.

The functionality that I wanted for my users was to add or remove rows from these tables regardless of if the data on the rest of the page was valid.

Table of contents:
  1. Immediate
  2. Action validation
  3. Tomahawk sandbox subForm
  4. Optional validator framework
  5. New component (solution)

First I tried the immediate attribute on my command links. Although this technically worked, I lost all my data in the data table input components. Due to the way the UIData component works, the submitted values will not be correctly processed unless the validation phase is run.

After that, I looked into using only action validation (no validators or required flags). Once again I didn't get the functionality that I desired. It was not possible, while maintaining a clean Object-Oriented design to be able to correctly assign component IDs to the validation messages for the components in the tables (I would have to hard-code the indexes into the messages as well as the view IDs - not an elegant solution).

The third attempt led me to the subForm Tomahawk component from MyFaces. First problem was that the all the components in the form had to be within a subForm. Then I had to create a dummy sub-form that I would submit:

<f:form> <s:subForm id="mainForm"> <-- Other input componenets here --> <t:commandLink action="#{userBean.addEmail}" actionFor="dummyForm" /> </subForm> <s:subForm id="dummyForm" />

Once again this worked only half-way. My action was run, but like with the immediate attribute, the values were lost in the data tables.

Forth attempt was a look at the Optional validator framework from http://jsf-comp.sf.net. This would have worked but it didn't have the functionality I needed. For one, only validators with IDs were supported (no attributes could be passed, so I would not be able to use validators like validate length which takes a maximum and minimum value. It is also not able to handle required validators on child components of data tables.

Seeing as how my problem again and again was the phase in which my action was executed, I need a different solution in terms of JSF phase. If immediate was set to true, the action would run but the validation phase would be skipped. The validation phase is needed for the submitted values of data tables though. Having the action fire during model update meant that the validation phase had to be skipped or there had to be a way to skip validation errors.

My fifth and final attempt ended up being my solution. It is perhaps not too pretty, but it got the job done. I needed to get the action event to fire during the process validators phase (in between when immediate fires it and non-immediate). I couldn't just extend command link though because UICommand always sets the phase ID of action events during queueEvent. I could have made a special action event that just disabled the setPhaseId funciton (by doing nothing in the method body), but I thought that more of a hack than my final solution.

My solution was to create a component thats sole purpose in life would be to alter the phase ID of an action event. It would need no renderer either. In the queueEvent I would simple set any ActionEvent's phase IDs to process validation. Then, placing this new component as the parent of an action component (or components), I would make sure my action would run during validation.

Using facelets, I needed no tag, so I just wrote the component and gave it an active attribute in case I ever want to disable the behavior using EL:

import javax.faces.component.UIComponentBase; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.event.ActionEvent; import javax.faces.event.FacesEvent; import javax.faces.event.PhaseId; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * * @author Andrew Robinson (andrew) */ public class UIValidationActionPhase extends UIComponentBase { private static final Log log = LogFactory.getLog( UIValidationActionPhase.class); public static final String COMPONENT_FAMILY = "-- set family here --"; public static final String COMPONENT_TYPE = "-- set component type here --"; private Boolean active; /** * @return Returns the active. */ public boolean isActive() { if (active != null) return this.active; ValueBinding vb = getValueBinding("active"); return (vb == null) || (Boolean)vb.getValue(getFacesContext()); } /** * @param active The active to set. */ public void setActive(boolean active) { this.active = active; } /** * @see javax.faces.component.UIComponent#getFamily() */ @Override public String getFamily() { return COMPONENT_FAMILY; } /** * @see javax.faces.component.UIComponentBase#getRendererType() */ @Override public String getRendererType() { return null; } /** * @see javax.faces.component.UIComponentBase#queueEvent(javax.faces.event.FacesEvent) */ @Override public void queueEvent(FacesEvent event) { if (event instanceof ActionEvent && isActive()) { log.debug("Changing Phase ID of action event " + event); event.setPhaseId(PhaseId.PROCESS_VALIDATIONS); } super.queueEvent(event); } /** * @see javax.faces.component.UIComponentBase#saveState(javax.faces.context.FacesContext) */ @Override public Object saveState(FacesContext context) { return new Object[] { super.saveState(context), active, }; } /** * @see javax.faces.component.UIComponentBase#restoreState(javax.faces.context.FacesContext, java.lang.Object) */ @Override public void restoreState(FacesContext context, Object state) { Object[] array = (Object[])state; int index = -1; super.restoreState(context, array[++index]); active = (Boolean)array[++index]; } }
The usage in my XHTML file was simple:
<f:form> <my:validationAction> <t:commandLink action="#{userBean.addEmail}" /> </my:validationAction> </f:form>

Now the user is able to see validation errors and get feedback on bad input data and my action was still able to run with the validation errors. Luckily my actions (add and delete) did not depend on the model being updated. This solution will not work for those that would need data from the user to be applied to the model.

© Copyright 2006 - Andrew Robinson.
Please feel free to use in your applications under the LGPL license
(http://www.gnu.org/licenses/lgpl.html).
Updates/Change log
Date Description
2007.03.07 Fixed some of the code in the component that was a result of pasting the code into the blog incorrectly.
2007.04.22 Reformatting of HTML in blog.

Wednesday, June 14, 2006

Creating composite controls with JSF and facelets

My blog has moved, please update your links. You can find this article here

JSF, although a powerful framework does not have many tools to assist in the development of composite controls. Whether building a control that has a few controls in it or a control comprised of 100s of children, there is no easy solution. The JSF specification was more written to build components that render themselves entirely.

With that said Facelets is a great add on to JSF and has some great templating features. Using a user tag (a.k.a. source tag), a composite control can be easily created. The trouble is that it is not possible out-of-the-box to pass method bindings to children components.

For example:
Snippet from taglib.xml:
<tag> <tag-name>test</tag-name> <source>tags/testTag.xhtml</source> </tag>
Usage in an XHTML file:
<my:test actionListener="#{myBean.doSomething}" />
User Tag file:
<ui:composition> <h:commandButton value="Click Me" actionListener="#{actionListener}" /> </ui:composition>

The problem with the above code is that the user tag handler of facelets always creates ValueExpression objects for each attribute in the source tag. This is fine for properties, but in the above case, a MethodExpression is what "ought" to be created.

I wanted to solve this problem, but in such a way to avoid people having to create new tag handlers or component handlers. I wanted a re-usable complete solution. After dragging myself through the mire of source code, I found what I needed in the facelets API to extend it. My solution is two part:

  1. Create a tag handler with component support
  2. Create a new value expression that returns method expressions

Creating a value expression that is a method expression

The second step above is easiest to discuss first. The idea is to have a value expression that returns a method expression as its value. This will allow "#{myBean.doSomething}" to be interpreted as a method instead of a property called "getDoSomething"

The code below may need some work to be more "correct", but it does work:

import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import javax.el.ELContext; import javax.el.MethodExpression; import javax.el.ValueExpression; public class MethodValueExpression extends ValueExpression implements Externalizable { private ValueExpression orig; private MethodExpression methodExpression; public MethodValueExpression() {} MethodValueExpression(ValueExpression orig, MethodExpression methodExpression) { this.orig = orig; this.methodExpression = methodExpression; } @Override public Class getExpectedType() { return orig.getExpectedType(); } @Override public Class getType(ELContext ctx) { return MethodExpression.class; } @Override public Object getValue(ELContext ctx) { return methodExpression; } @Override public boolean isReadOnly(ELContext ctx) { return orig.isReadOnly(ctx); } @Override public void setValue(ELContext ctx, Object val) {} @Override public boolean equals(Object val) { return orig.equals(val); } @Override public String getExpressionString() { return orig.getExpressionString(); } @Override public int hashCode() { return orig.hashCode(); } @Override public boolean isLiteralText() { return orig.isLiteralText(); } /** * @see java.io.Externalizable#readExternal(java.io.ObjectInput) */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { orig = (ValueExpression)in.readObject(); methodExpression = (MethodExpression)in.readObject(); } /** * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput) */ public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(orig); out.writeObject(methodExpression); } }

Now that we have a value expression that will wrap a method expression, we need to be able to "replace" them into the JSF environment. When facelets is applying components to the tree, it has an EL context that can be used to interpret data. If we know the method signatures of the methods to bind to, we will be able to create MethodExpression objects.

Lets start with the constructor. We want to create an attribute that will be our configuration. Unfortunately, an attribute can only be given once, so I will create a custom format that we will be able to use. Starting code:

public class CompositeControlHandler extends TagHandler { private final TagAttribute methodBindings; private ComponentHandler componentHandler; /** * @param config */ public CompositeControlHandler(TagConfig config) { super(config); methodBindings = getAttribute("methodBindings"); } // TODO... }

We now have a skeleton in which we can declare a "methodBindings" attribute of our custom tag. This will be used to define which variables in the scope of our user tag should be considered methods instead of variables. I have choosen the following format:

attribute-name=java-return-type first-java-parameter-type second-java-parameter-type; second-attribute-name=java-return-type first-java-parameter-type second-java-parameter-type;
Example from above:
actionListener=void javax.faces.event.ActionEvent;

So now that we have a way to specify what attributes are now methods, we need to do the work.

Steps:
  1. Parse the attribute
  2. For each attribute, see if the value is "bound" in the current variable mapper
  3. If bound, create a new method expression from the configuration information
  4. Hide the initial variable with the method expression

The resultant code is as follows:

public class CompositeControlHandler extends TagHandler { private final static Pattern METHOD_PATTERN = Pattern.compile( "(\\w+)\\s*=\\s*(.+?)\\s*;\\s*"); private final TagAttribute methodBindings; /** * @param config */ public CompositeControlHandler(TagConfig config) { super(config); methodBindings = getAttribute("methodBindings"); } /** * @see com.sun.facelets.FaceletHandler#apply(com.sun.facelets.FaceletContext, javax.faces.component.UIComponent) */ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException, ELException { VariableMapper origVarMap = ctx.getVariableMapper(); try { VariableMapperWrapper variableMap = new VariableMapperWrapper(origVarMap); ctx.setVariableMapper(variableMap); if (methodBindings != null) { String value = (String)methodBindings.getValue(ctx); Matcher match = METHOD_PATTERN.matcher(value); while (match.find()) { String var = match.group(1); ValueExpression currentExpression = origVarMap.resolveVariable(var); if (currentExpression != null) { try { FunctionMethodData methodData = new FunctionMethodData( var, match.group(2).split("\\s+")); MethodExpression mexpr = buildMethodExpression(ctx, currentExpression.getExpressionString(), methodData); variableMap.setVariable(var, new MethodValueExpression( currentExpression, mexpr)); } catch (Exception ex) { throw new FacesException(ex); } } } } // TODO: will do this next } finally { ctx.setVariableMapper(origVarMap); } } private MethodExpression buildMethodExpression(FaceletContext ctx, String expression, FunctionMethodData methodData) throws NoSuchMethodException, ClassNotFoundException { return ctx.getExpressionFactory().createMethodExpression(ctx, expression, methodData.getReturnType(), methodData.getArguments()); } private class FunctionMethodData { private String variable; private Class returnType; private Class[] arguments; FunctionMethodData(String variable, String[] types) throws ClassNotFoundException { this.variable = variable; if ("null".equals(types[0]) || "void".equals(types[0])) returnType = null; else returnType = ReflectionUtil.forName(types[0]); arguments = new Class[types.length - 1]; for (int i = 0; i < arguments.length; i++) arguments[i] = ReflectionUtil.forName(types[i + 1]); } public Class[] getArguments() { return this.arguments; } public void setArguments(Class[] arguments) { this.arguments = arguments; } public Class getReturnType() { return this.returnType; } public void setReturnType(Class returnType) { this.returnType = returnType; } public String getVariable() { return this.variable; } public void setVariable(String variable) { this.variable = variable; } } }

Now, that we have has this much fun, why not instead of just having a tag handler, but a component handler as well. The next steps will allow this user tag to be used without any XML configuration. The goal is to allow the user to specify the component type and renderer type for a component that should be created for our user tag (If none is given, the ComponentRef from facelets will be used).

The code isn't much different, so I will show it in its entirety here:

public class CompositeControlHandler extends TagHandler { private final static Pattern METHOD_PATTERN = Pattern.compile( "(\\w+)\\s*=\\s*(.+?)\\s*;\\s*"); private final TagAttribute rendererType; private final TagAttribute componentType; private final TagAttribute methodBindings; private ComponentHandler componentHandler; /** * @param config */ public CompositeControlHandler(TagConfig config) { super(config); rendererType = getAttribute("rendererType"); componentType = getAttribute("componentType"); methodBindings = getAttribute("methodBindings"); componentHandler = new ComponentRefHandler(new ComponentConfig() { /** * @see com.sun.facelets.tag.TagConfig#getNextHandler() */ public FaceletHandler getNextHandler() { return CompositeControlHandler.this.nextHandler; } public Tag getTag() { return CompositeControlHandler.this.tag; } public String getTagId() { return CompositeControlHandler.this.tagId; } /** * @see com.sun.facelets.tag.jsf.ComponentConfig#getComponentType() */ public String getComponentType() { return (componentType == null) ? ComponentRef.COMPONENT_TYPE : componentType.getValue(); } /** * @see com.sun.facelets.tag.jsf.ComponentConfig#getRendererType() */ public String getRendererType() { return (rendererType == null) ? null : rendererType.getValue(); } }); } /** * @see com.sun.facelets.FaceletHandler#apply(com.sun.facelets.FaceletContext, javax.faces.component.UIComponent) */ public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException, ELException { VariableMapper origVarMap = ctx.getVariableMapper(); try { VariableMapperWrapper variableMap = new VariableMapperWrapper(origVarMap); ctx.setVariableMapper(variableMap); if (methodBindings != null) { String value = (String)methodBindings.getValue(ctx); Matcher match = METHOD_PATTERN.matcher(value); while (match.find()) { String var = match.group(1); ValueExpression currentExpression = origVarMap.resolveVariable(var); if (currentExpression != null) { try { FunctionMethodData methodData = new FunctionMethodData( var, match.group(2).split("\\s+")); MethodExpression mexpr = buildMethodExpression(ctx, currentExpression.getExpressionString(), methodData); variableMap.setVariable(var, new MethodValueExpression( currentExpression, mexpr)); } catch (Exception ex) { throw new FacesException(ex); } } } } componentHandler.apply(ctx, parent); } finally { ctx.setVariableMapper(origVarMap); } } private MethodExpression buildMethodExpression(FaceletContext ctx, String expression, FunctionMethodData methodData) throws NoSuchMethodException, ClassNotFoundException { return ctx.getExpressionFactory().createMethodExpression(ctx, expression, methodData.getReturnType(), methodData.getArguments()); } private class FunctionMethodData { private String variable; private Class returnType; private Class[] arguments; FunctionMethodData(String variable, String[] types) throws ClassNotFoundException { this.variable = variable; if ("null".equals(types[0]) || "void".equals(types[0])) returnType = null; else returnType = ReflectionUtil.forName(types[0]); arguments = new Class[types.length - 1]; for (int i = 0; i < arguments.length; i++) arguments[i] = ReflectionUtil.forName(types[i + 1]); } public Class[] getArguments() { return this.arguments; } public void setArguments(Class[] arguments) { this.arguments = arguments; } public Class getReturnType() { return this.returnType; } public void setReturnType(Class returnType) { this.returnType = returnType; } public String getVariable() { return this.variable; } public void setVariable(String variable) { this.variable = variable; } } }

Now we need to register this in a taglib.xml so that we can use it:

<tag> <tag-name>compositeControl</tag-name> <handler-class>mypackage.CompositeControlHandler</handler-class> </tag>
Now that it is registered, lets use it. The XHTML file that uses the tag hasn't changed:
<my:test actionListener="#{myBean.doSomething}" />
The user tag does look different, but not that much:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:my="http://mynamespace"> <body> <ui:composition> <my:compositeControl id="#{id}" methodBindings="action=java.lang.String; actionListener=void javax.faces.event.ActionEvent;"> <ui:debug /> <h:commandButton value="Click me" actionListener="#{actionListener}" action="#{action}" /> </my:compositeControl> </ui:composition> </body> </html>
That should be enough to get you going. © Copyright 2006 - Andrew Robinson. Please feel free to use in your applications under the LGPL license (http://www.gnu.org/licenses/lgpl.html).