
The Problem
A common problem encountered by writers of dynamic JSF 1.x components (like Metawidget) was "at what point in the lifecycle can I safely manipulate the component tree"?
For a page's initital GET request, there weren't many lifecycle methods you could hook into - you basically had encodeBegin. For subsequent POST-backs there were a slew of hooks, including decode, processValidators and processUpdates, but none were at exactly the right place in the lifecycle to do safe component tree manipulation. What would be 'exactly the right place'? Namely:
- the component tree should be fully realized (ie. all parents and children should be set)
- the component tree should not have been serialized yet

The Saviour
Enter JSF 2. From the spec "System Events are introduced in version 2 of the specification and represent specific points in time for a JSF application". They are like the old PhaseEvents, but much more fine-grained. For the purposes of this blog, the SystemEvent we are interested in is PostAddToViewEvent.
To use it, first subscribe to the event in your UIComponent's constructor...
public class UITestComponent
extends UIComponentBase
implements SystemEventListener {
public UITestComponent() {
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.getViewRoot().subscribeToViewEvent(PostAddToViewEvent.class, this);
}
extends UIComponentBase
implements SystemEventListener {
public UITestComponent() {
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.getViewRoot().subscribeToViewEvent(PostAddToViewEvent.class, this);
}
...then, when it gets fired, you can safely manipulate the component tree ...
public void processEvent( SystemEvent event ) throws AbortProcessingException {
FacesContext ctx = FacesContext.getCurrentInstance();
UIComponent dynamicallyGenerated = ctx.getApplication().createComponent( "javax.faces.HtmlInputText" );
getChildren().add( dynamicallyGenerated );
}
FacesContext ctx = FacesContext.getCurrentInstance();
UIComponent dynamicallyGenerated = ctx.getApplication().createComponent( "javax.faces.HtmlInputText" );
getChildren().add( dynamicallyGenerated );
}

The Gotchas
While working through this solution, there were a number of gotchas I encountered. Huge thanks to Ryan Lubke for holding my hand through them:
First, you should use subscribeToViewEvent, not subscribeToEvent. The latter will mean the event subscription itself get serialized into the component tree, so you'll keep doubling up subscribers every time the page refreshes.
Second, you must hook into UIViewRoot's PostAddToViewEvent, not UITestComponent's. This is because, at the time UITestComponent's PostAddToViewEvent is fired, its children will not be initialized yet.
Third, you cannot use the @ListenerFor annotation to hook into SystemEvents from a UIComponent. This is because UIComponent implements ComponentSystemEventListener, and @ListenerFor is designed to ignore the SystemEventListener interface for any class that implements ComponentSystemEventListener.

The Ugly
There is, unfortunately, still a problem for certain types of dynamic component.
If your component, in its processEvent method, dynamically creates not just simple HtmlInputText components but nested components that are themselves dynamic, we have a problem. This is because the 'official' way to safely manipulate the component tree is through SystemEvents, but there is no official way to safely manipulate the SystemEvent list.
In the current release of the JSF Reference Implementation (2.0.1), attempting to dynamically create a component that in turn tries to subscribe to a SystemEvent will get you a ConcurrentModificationException. In future releases, there will not be an exception but the newly added SystemEvent will simply not fire, so your newly added dynamic component will never get an opportunity to populate itself.
This new issue is being tracked by the JSF RI team. Until it is resolved, JSF 2 can safely create dynamic components (a big step forward), but it cannot safely create dynamic components that themselves create dynamic components.
Suggestions welcome!




