Kiss My App

Thursday, October 26, 2006

JSF 2.0 Ideas

We've reached the age of annotations, you are seeing tons of APIs revised and nouns/assets being refactored into compact metadata. The first API to really take this leap with EJB3, followed by an extension from JBoss, called Seam. I saw this coming a couple years ago in my article for OnJava ;-)

Now many other APIs are following suit in various degrees, replacing interfaces and abstract class declarations with type annotations and avoiding the traditional getter/setters with annotated fields, declaring injection or outjection (props to Gavin King-- see what ideas you come up with when you don't own a TV?).

Anyways, I was talking with the great Matthias and others (including HLS), on some ideas for JSF 2.0 and I thought I'd throw them up on the 'ole blog. I should also note that I think JSF and JSP should consolidate into one presentation spec instead of trying to continue to integrate the two. While the existing APIs would continue to exist, much of application development could be deferred to one unified API for the presentation tier.


  • @Attribute(required=true)
    One of the most obvious enhancements to the JSF API is to dump the use of an AttributeMap and ValueBindingMap to resolve dynamic state. Instead, we would automate this resolution with the annotation: @Attribute. This would allow both type-safety and remove the need to deal with the EL-API directly at all.

  • @UIComponent(name="dataTable")
    This is an obvious one. I strongly believe that much of the issues with the JSF API deals with maintaining each and every execution concern within the JSF 1.x UIComponent code. This has caused problems in multiple fronts because the more you put into the end developer's hands, the less your can predict and consequently: optimize. I'm still on the ropes about going back and possibly using an abstract class or an interface, but I haven't found a reason to yet.

  • @SaveState(Scope.View)
    State saving sucks and is the bane of the JSF API, but could be corrected in future revisions. With JSF 1.x, a component developer must manage saving the state of all its children and any properties they may have declared on the component. In addition, there's no room for transient parent/stateful child because of this hierarchical evaluation of state saving. From the JSF implementation perspective, there's really no room to optimize since the UIComponent tree just returns an 'Object'. Transient parents will prune their children, forcing you to save large chunks of data between requests. If we move to an annotation, we remove the state saving logic out of the component developer's hands and we can start saving data in a flat/normalized manner, allowing for stateful and stateless components to intermix with each other.

  • @Facet(name="header")
    Again, lets get rid of all of the Maps in the JSF UIComponent API. Following other injection logic found in existing frameworks, you could type-safely inject Facets declared in the UI or assigned directly to the object. If the name is not specified, it will default to the field name.

  • @Children(UIColumn.class)
    I don't think it's healthy to have component's be required to work directly with their children unless specifically required. In the cases where it is required, you can specify this annotation on a member variable of type Collection. In the case of a UIDataTable, you can say, only populate my member Collection with children of type UIColumn. The annotation defaults to Object (everything), but could be an array of Classes to take any amount of filtered children.

  • Templating
    This is probably the most up in the air, but given where JSF has gone with this so far, it would have to be extremely pluggable. The only idea I can throw out here is possibly using package names for the namespace such that the package, in combination with the @UIComponent annotation, you can dynamically load components without needing to cross reference them in XML.

  • Everything is a Component
    One of the things I found difficult in working on Avatar was that I wanted to have the 'div' and 'span' elements to be refreshable without ever declaring them as a special component. When we get into things like AJAX and callback, ideally we should be able to also callback on a normal HTML div or span for re-rendering.

  • public class PhaseEvent
    First, we should drop phase execution that mimics JSP with separate before, after, and children methods. This could be reduced to an evaluation style like Facelets which follows the CoR pattern. This greatly simplifies wrapping child content and doing logical predicates or iteration. Some other examples out there of this style of execution is the EJB Interceptor API. So you can do things like PhaseEvent.propagate(); or PhaseEvent.propagate(component); to evaluate or re-evaluate children. This style of execution will also encapsulate clientId generation and incrementing other state and pre/post injection and initializing of children for each propagation.

  • public void onEvent(PhaseEvent)
    Martin Marinscheck proposed the idea of generic phases in JSF whereas we currently only have the 5 specific ones that components know about or can 'walk' on. If we come up with a generic event handling convention, we can accomplish this by defaulting to 'onEvent', but if you were propagating an Encode event and wanted to handle it, you would declare a 'onEncode' method. If you were creating a generic repeater component or predicate component for all phases, you would just declare the 'onEvent' since the logic would be the same for all others.

  • EncodeEvent and DecodeEvent
    I think JSF should move to two default phases and queue the other events on a per component basis. While walking the component tree isn't that expensive, it does add up. Walking the component model 3 or 4 times within each request seems unnecessary to me. You encode/render the response and on postback, you have all the information you need to queue up changes to the model. Things like validation, updating, and invocation can be handled like Swing and all queued during the decode phase. During decode, not only are you processing request information, but you also have the 5th Employee object from your Company Object, so why not queue that assignment with an anonymous class?



Here's an example of UIDataTable (probably the ugliest implementation) if written with the above suggestions:

@UIComponent(name="column")
public class UIColumn {

@Facet
private Object header;

@Facet
private Object footer;

@Children
private Collection<?> children;

public Collection<?> getChildren() {
return children;
}

public void setChildren(Collection<?> children) {
this.children = children;
}

public Object getFooter() {
return footer;
}

public void setFooter(Object footer) {
this.footer = footer;
}

public Object getHeader() {
return header;
}

public void setHeader(Object header) {
this.header = header;
}

}

@UIComponent(name="dataTable")
public class UIDataTable {

@Attribute(required=true)
private Object var;

@Attribute(required=true)
private Iterable<?> value;

@Children(UIColumn.class)
private Collection<UIColumn> columns;

public void onEncode(PhaseEvent event) {
ResponseWriter writer = event.getWriter();
Object facet;

writer.start("table");

// header
writer.start("thead");
for (UIColumn c : this.columns) {
writer.start("tr");
facet = c.getHeader();
event.propagate(facet);
writer.end();
}
writer.end();

// footer
writer.start("tfooter");
for (UIColumn c : this.columns) {
writer.start("tr");
facet = c.getFooter();
event.propagate(facet);
writer.end();
}
writer.end();

// body
writer.start("tbody");
for (Object i : this.value) {
this.var = i;
writer.start("tr");
for (UIColumn c : this.columns) {
writer.start("td");
event.propagate(c);
writer.end();
}
writer.end();
}
writer.end();

writer.end();
}

}


@UIComponent(name="repeat")
public class UIRepeat {

@Attribute(required=true)
private Iterable<?> value;

@Attribute(required=true)
private Object var;

@Attribute
private int index;

public void onEvent(PhaseEvent event) {
if (value != null) {
this.index = 0;
for (Object obj : value) {
this.var = obj;
this.index++;
event.propagate();
}
}
}

}

5 Comments:

Post a Comment

<< Home