Knowing the size of the JSF component tree is often times helpful for JSF developers. Unfortunately, JSF does not provide an easy way to inspect the tree itself. Though
JSF 2.0 introduced the concept of VisitCallbacks to conveniently traverse the component tree or parts of it. The basic idea is to call visitTree(VisitContext context, VisitCallback callback) on any component to traverse the subtree, starting from this component. The VisitContext holds state information for the traversal. Instances are typically created by JSF , though developers can subclass it to provide further capabilities.
The interface VisitCallback defines the contract for user defined functionality that should be performed on every tree node during traversal. The traversal itself is carried out by JSF, so developers don’t need to deal with tree traversal algorithms.
The following example shows how a page-specific PhaseListener can be used to retrieve the number of components in the tree.
/** * VisitCallback that is used to gather information about the component tree. * Keeps track of the total number of components and maintains a list * of basic component information. */ public class CountingVisitCallback implements VisitCallback { private int count = 0; private List componentInfo = new ArrayList(); /** * This method will be invoked on every node of the component tree. */ @Override public VisitResult visit(VisitContext context, UIComponent target) { count++; getComponentInfo().add(target.getClientId() + " ["+target.getClass().getSimpleName()+"]"); // descend into current subtree, if applicable return VisitResult.ACCEPT; } public int getCount() { return count; } public List getComponentInfo() { return componentInfo; } }
For every visited node, JSF calls visit(VisitContext context, UIComponent target), passing in both the VisitContext and the current component. The custom VisitCallback increases its counter and adds some basic information about the current component to a List. Both can be retrieved subsequent to the tree traversal using the appropriate getter methods.
/** * PhaseListener that logs number of components in the * JSF component tree as well as some basic component information. */ public class ComponentCounter implements PhaseListener { private static final long serialVersionUID = 8573925562596090327L; private final static Logger LOG = Logger.getLogger(ComponentCounter.class.getName()); @Override public void beforePhase(PhaseEvent event) { FacesContext facesContext = FacesContext.getCurrentInstance(); VisitContext visitContext = VisitContext.createVisitContext(facesContext); CountingVisitCallback callback = new CountingVisitCallback(); facesContext.getViewRoot().visitTree(visitContext, callback); LOG.log(Level.INFO, ("Number of Components: " + callback.getCount())); for (String info : callback.getComponentInfo()) { LOG.log(Level.INFO, "Component found: " + info); } } @Override public PhaseId getPhaseId() { return PhaseId.RENDER_RESPONSE; } @Override public void afterPhase(PhaseEvent event) { // do nothing }
The PhaseListener will log the gathered information prior to rendering of the current view. An instance of VisitContext is retrieved using a factory method. An instance of the custom VisitCallback is created and passed to visitTree(VisitContext context, VisitCallback callback). Finally, the information gathered during the tree traversal is fetched an logged.
The PhaseListener can be used on demand using the tag
Finally a quick word on clean software design: following the “Separation of Concerns” principle both the number of components and the list of component information should be attributes of a custom VisitContext. Development of a custom VisitContext was omitted for the sake of simplicity.
Another approach would be to decorate the Application and override the createComponent() methods to do the counting.
In the meanwhile I created a tool using this approach to create a nice summary of the components in the tree: http://tasel.github.io/jsfinspector/
Thomas I could not get jsfinspector to work with primefaces. Any ideas?
Great article, many thanks for it.