Adam Winer
A blog about JavaServer Faces - or anything else in the Java world that needs saying.Adam Winerhttp://www.blogger.com/profile/13723363818490025216noreply@blogger.comBlogger13125
Updated: 6 hours 23 min ago
AJAX and the Refresh Button
JSF relies heavily on
As a result, AJAX implementations in JSF typically need not only to submit this value to the server when posting an AJAX request, but also to update it as necessary when a request completes. While looking at an ADF Rich Client bug recently, I was rudely reminded that the Refresh/Reload button doesn't always behave as you might imagine, and thought it was worth delving into the details. (I'll be talking about JSF, but the behavior is generic to DHTML and applies outside of JSF, and the code samples are just raw HTML.)
Take the following page:
Now, in your favorite browser, try the following:
This can lead to big problems in a JSF application. Take the following scenario:
First, how about creating the DOM on the fly?
In Firefox, this still doesn't help. The hidden field value is still cached. (And this example doesn't work at all in IE... see this entry for why and how to fix it.)
OK, how about innerHTML?
Now this works... changes made by innerHTML are not remembered by Firefox or Internet Explorer (or Safari). So, if you don't want a hidden field cached, update with innerHTML and browsers won't hassle you.
Alternatively, you could look at tackling this issue during the refresh, by forcibly resetting the value from Javascript:
This works in Firefox, but does not in IE. Whatever code overwrites the value of the hidden field runs after this inline script, but before the page's onload handler. So, if you want to tackle this problem while refreshing, you'll have to do it in onload.
To (finally) come back to JSF, there's a better way to solve this problem, at least for the state token. Use a StateManager that automatically doesn't generate new tokens for AJAX requests, but instead reuses the old token. New tokens are important when you're rendering a new page, but are a waste of space when you're just working on a single page. And, as a nice side-effect, this makes this issue moot. (MyFaces Trinidad 1.0.2 will include this token-reuse optimization, though it's always used innerHTML for updating the state token, so it hasn't been hit by this bug.)
So, to summarize, if you have a programatically-modified, hidden input field that needs to be Reload-proof, two techniques look good:
If you've got any other tricks, I'd be happy to know.
<input type="hidden" name="javax.faces.ViewState">
for its lifecycle. This hidden field carries all UI state for the page. Whether that's client-side state (with the entire page Base64-encoded) or server-side state (with a simple token), it's important that the right field be delivered with any JSF postback for the page to function correctly.As a result, AJAX implementations in JSF typically need not only to submit this value to the server when posting an AJAX request, but also to update it as necessary when a request completes. While looking at an ADF Rich Client bug recently, I was rudely reminded that the Refresh/Reload button doesn't always behave as you might imagine, and thought it was worth delving into the details. (I'll be talking about JSF, but the behavior is generic to DHTML and applies outside of JSF, and the code samples are just raw HTML.)
Take the following page:
<html>
<head>
</head>
<body>
<form name="foo">
<div id="valCtr">
<input name="val" type="hidden" svalue="1">
<script>
document.forms.foo.val.value=1;
</script>
</div>
<a href="#" onclick="forms.foo.val.value =
parseInt(forms.foo.val.value) + 1; return false;">
Increment</a>
</form>
</body>
</html>
Now, in your favorite browser, try the following:
- Click Display (you'll see 1)
- Click Increment a couple of times
- Click Display again (you'll see 3)
- Click or select Refresh/Reload (but not Shift-Refresh)
- And Display once more. You'll still see 3 (unless you're using Safari)
- Now, Shift-Refresh, and Display. Now we're back at 1.
This can lead to big problems in a JSF application. Take the following scenario:
- A page initially renders with state token 1 in a hidden field
- An AJAX request updates the state token to 2
- The user hits Reload, and the new HTML contains state token 3
- But the browser ignores it, and overwrites it with state token 2!
First, how about creating the DOM on the fly?
<script type="text/javascript">
function incrementViaDOM()
{
var value = parseInt(document.forms.foo.val.value) + 1;
var newField = document.createElement("input");
newField.name = "val";
newField.type = "hidden";
newField.value = "" + value;
var oldField = document.forms.foo.val;
var parent = oldField.parentNode;
parent.replaceChild(newField, oldField);
}
</script>
<a href="#" onclick="incrementViaDOM(); return false;">
Increment with replaceChild</a>
In Firefox, this still doesn't help. The hidden field value is still cached. (And this example doesn't work at all in IE... see this entry for why and how to fix it.)
OK, how about innerHTML?
<script type="text/javascript">
</script>
var value = parseInt(document.forms.foo.val.value) + 1;
var valCtr = document.getElementById("valCtr");
valCtr.innerHTML = "<input name=\"val\" " +
"type=\"hidden\" value=\"" + value + "\">";
<a href="#" onclick="increment(); return false;">
Increment with innerHTML</a>
Now this works... changes made by innerHTML are not remembered by Firefox or Internet Explorer (or Safari). So, if you don't want a hidden field cached, update with innerHTML and browsers won't hassle you.
Alternatively, you could look at tackling this issue during the refresh, by forcibly resetting the value from Javascript:
<input name="val" type="hidden">
<script type="text/javascript">
document.forms.foo.val.value=1;
</script>
This works in Firefox, but does not in IE. Whatever code overwrites the value of the hidden field runs after this inline script, but before the page's onload handler. So, if you want to tackle this problem while refreshing, you'll have to do it in onload.
To (finally) come back to JSF, there's a better way to solve this problem, at least for the state token. Use a StateManager that automatically doesn't generate new tokens for AJAX requests, but instead reuses the old token. New tokens are important when you're rendering a new page, but are a waste of space when you're just working on a single page. And, as a nice side-effect, this makes this issue moot. (MyFaces Trinidad 1.0.2 will include this token-reuse optimization, though it's always used innerHTML for updating the state token, so it hasn't been hit by this bug.)
So, to summarize, if you have a programatically-modified, hidden input field that needs to be Reload-proof, two techniques look good:
- Use innerHTML to update the field
- Use onload to set the hidden input field value
If you've got any other tricks, I'd be happy to know.
A year already? So long incubator!
How time flies... A year ago, we checked ADF Faces into the Apache incubator. Now, we're out of the incubator, we're named Trinidad, and we're officially part of the Apache MyFaces project. You can visit our site, and download nightly builds .
A lot has happened in this past year - a few highlights:
A lot has happened in this past year - a few highlights:
- A bunch of new committers were added from inside Oracle and, most importantly, from outside Oracle
- All the ins-and-outs of running an Apache project were ably handled by Matthias Wessendorf
- Skinning functionality has gotten a lot better, mostly courtesy of Jeanne Waldman
- Portlet support from Scott O'Bryan
- Client-side validation now looks much better (no more JS alerts), from Danny Robinson
- Lots and lots of bugs (300+) were put to ground
- New components - a spinbox and an outputDocument
- JSF 1.2 support was implemented (on a branch); the MyFaces implementation of the 1.2 JSF API uses a Trinidad plugin to generate components and tags.
- And, I got engaged! (The future Mrs. even lets me get away with working on Trinidad at home.)
It all took awhile, and a lot of work, but we're all glad to have reached this point.
ADF Faces checked in to open source !
Transmitting file data .........................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
...........................................
Committed revision 404824.
... and it's done. ADF Faces has been checked into an Apache incubator at http://svn.apache.org/repos/asf/incubator/adffaces/trunk/. Now the real work begins!
Facelets 1.1.2 is out!
As Jacob Hookom just announced, Facelets 1.1.2 is now available. This is a major milestone for Facelets: the big issues have been nailed, templating's solidified, and excellent features like <ui:repeat> are in there too. Give it a whirl!
(And, for this blog, that "build before restore" feature that's part of saving JSF state saving has its first public appearance here too.)
(And, for this blog, that "build before restore" feature that's part of saving JSF state saving has its first public appearance here too.)
Usings Sets with UIData
One of the more personally surprising usability complaints I've heard was that the JSF UIData component does not support Sets. I hadn't anticipated that one. The underlying reason is that UIData is built around indexed data. For example, you can ask it to show rows 10,000 through 10,099. Such an operation would be nightmarishly expensive in a Set:
But, hey, we still got it wrong: we should have supported it. Yeah, it'd be slow - O(N) where N is the size of the set, not the amount of data actually displayed at one time - but the ease of use argument is compelling.
That said, there's nothing stopping you from using Sets with UIData right now... if you use the following class:
So, what's this all mean? Well, say you want to write:
... but that doesn't work. Just add one managed-bean entry to your faces-config.xml:
... and now, you can use sets on dataTable via:
Iterator iter = collection.iterator();
for (int i = 0; i < 10000; i++) { iter.next(); }
// Hey, now we can start reading our data!
But, hey, we still got it wrong: we should have supported it. Yeah, it'd be slow - O(N) where N is the size of the set, not the amount of data actually displayed at one time - but the ease of use argument is compelling.
That said, there's nothing stopping you from using Sets with UIData right now... if you use the following class:
import java.util.AbstractMap;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
public class ListFromCollection
{
public ListFromCollection()
{
_map = new MakeList();
_size = _DEFAULT_SIZE;
}
public Map<Collection, List> getList()
{
return _map;
}
public int getSize()
{
return _size;
}
public void setSize(int size)
{
_size = size;
}
private class MakeList extends AbstractMap<Collection, List>
{
public List get(Object o)
{
if (!(o instanceof Collection))
return null;
// Just send RandomAccess lists out; wrap any other Collection
// into a List
if ((o instanceof List) &&
(o instanceof RandomAccess))
return (List) o;
Collection c = (Collection) o;
if (c.isEmpty())
return Collections.EMPTY_LIST;
return new ListImpl(c, getSize());
}
public Set<Map.Entry<Collection, List>> entrySet()
{
// Not worth implementing at the moment; this Map is only
// accessed from
return Collections.emptySet();
}
}
static private class ListImpl extends AbstractList
{
public ListImpl(Collection c, int size)
{
_c = c;
_cSize = c.size();
if (size == 0)
_bufferSize = _cSize;
else
_bufferSize = Math.min(size, _cSize);
_buffer = new ArrayList(_bufferSize);
_offset = -1;
}
public int size()
{
return _cSize;
}
public Object get(int index)
{
if ((index < 0) || (index >= _cSize))
throw new IndexOutOfBoundsException();
int offset = (index / _bufferSize) * _bufferSize;
if (offset != _offset)
{
_loadBuffer(offset);
_offset = offset;
}
return _buffer.get(index - _offset);
}
private void _loadBuffer(int offset)
{
Iterator iter = _c.iterator();
int i = 0;
while (i < offset)
{
assert iter.hasNext();
iter.next();
i++;
}
_buffer.clear();
int count = 0;
while ((count < _bufferSize) && (i < _cSize))
{
assert iter.hasNext();
_buffer.add(iter.next());
i++;
count++;
}
}
private final Collection _c;
private final int _bufferSize;
private final int _cSize;
private int _offset;
private ArrayList _buffer;
}
private Map<Collection, List> _map;
private int _size;
static private int _DEFAULT_SIZE = 50;
}
So, what's this all mean? Well, say you want to write:
<h:dataTable value="#{mySet}">
... but that doesn't work. Just add one managed-bean entry to your faces-config.xml:
<managed-bean>
<managed-bean-name>makeList</managed-bean-name>
<managed-bean-class>
yourPackageHere.ListFromCollection
</managed-bean-class>
<managed-bean-scope>
request
</managed-bean-scope>
</managed-bean>
... and now, you can use sets on dataTable via:
<h:dataTable value="#{makeList.list[mySet]}">
Fixing JSF state saving: Save vs. Restore
(A bit of esoterica, but probably of interest here to those deeply focused on JSF performance...)
An update: I got a chance to take some timings. As expected, saving state is now much faster: 85-90% faster, roughly equivalent to the improvements in the size of the saved state. But I was very surprised to discover that restoring the view isn't any faster, perhaps even marginally slower - until I realized that was the only possible result.
Restore View has to create a full tree, starting from zero. What could possibly be faster than creating that tree from an object tree that specifies every attribute that needs to be set, every class name that needs to be instantiated? Put another way, the old restoreVIew() approach, for all of its memory inefficiencies, is essentially an equivalent workload to building the tree in the first place, and might be even faster. This new approach starts with the overhead of building the tree from scratch, plus the overhead of setting some additional state. (That additional overhead is fairly negligible, but the point remains: fixing JSF state saving doesn't make Restore View fast.)
A slight clarification, BTW: I'm not using client-side state saving a la the RI or MyFaces, which serializes the entire tree to the client. Instead, we use ADF Faces's tokenized client-side state saving - run
So, does that mean that restoreView() is necessarily slow? Well, no. There are approaches to optimizing restoreView(). The most direct is optimizing building the component tree - which will help both with initial component tree creation and with restoreView(). How to do that in another post. (Hint: FacesBean and Facelets are a huge help here, too!)
An update: I got a chance to take some timings. As expected, saving state is now much faster: 85-90% faster, roughly equivalent to the improvements in the size of the saved state. But I was very surprised to discover that restoring the view isn't any faster, perhaps even marginally slower - until I realized that was the only possible result.
Restore View has to create a full tree, starting from zero. What could possibly be faster than creating that tree from an object tree that specifies every attribute that needs to be set, every class name that needs to be instantiated? Put another way, the old restoreVIew() approach, for all of its memory inefficiencies, is essentially an equivalent workload to building the tree in the first place, and might be even faster. This new approach starts with the overhead of building the tree from scratch, plus the overhead of setting some additional state. (That additional overhead is fairly negligible, but the point remains: fixing JSF state saving doesn't make Restore View fast.)
A slight clarification, BTW: I'm not using client-side state saving a la the RI or MyFaces, which serializes the entire tree to the client. Instead, we use ADF Faces's tokenized client-side state saving - run
saveSerializedView
, but then stash the result on the session, and only send a token to the client. If we were using fully serialized trees, then you would get a performance boost even in Restore View, because you'd only have to unserialize a small block, instead of the whole thing.So, does that mean that restoreView() is necessarily slow? Well, no. There are approaches to optimizing restoreView(). The most direct is optimizing building the component tree - which will help both with initial component tree creation and with restoreView(). How to do that in another post. (Hint: FacesBean and Facelets are a huge help here, too!)
Fixing JSF state saving: a progress report
I've implemented the first version of an improved JSF state saving architecture, essentially what I talked about in my last post.
The very latest and greatest Facelets source code has a
And the latest ADF Faces code adds a new
And the results? Well, one test case with a fairly large page dropped from nearly 10K of client-side state to only 600 bytes. That's more than a 90% reduction in saved state! (Presumably, CPU usage is way down, but I haven't measured that yet.)
There's still potential improvements: I could get that 600 bytes down further by overriding the state saving in UIViewRoot. Also, Jacob's suggestion to use a flat Map instead of a hierarchy would also help - currently, if one component deep in the hierarchy needs to store state, we build up a large hierarchy of mostly null Object arrays. This isn't especially expensive, but it is avoidable.
A footnote: Mike Youngstrom cogently noted in a comment to my last post that a delta approach to state saving would let us make UIData both simpler and more generic - instead of handcoding a specific set of properties to save on certain types of child components, just run state saving. Absolutely, Mike! I haven't prototyped this on our UIXTable component (which doesn't extend UIData, FWIW), but that's a great thing to try.
The very latest and greatest Facelets source code has a
facelets.BUILD_BEFORE_RESTORE
initialization parameter. When turned on, it changes ViewHandler.restoreView()
to build the view before asking the StateManager for help.And the latest ADF Faces code adds a new
markInitialState()
method to our components - which calls through to FacesBean.markInitialState()
. When our MyFaces svn repository is set up, you'll be able to see this code too...And the results? Well, one test case with a fairly large page dropped from nearly 10K of client-side state to only 600 bytes. That's more than a 90% reduction in saved state! (Presumably, CPU usage is way down, but I haven't measured that yet.)
There's still potential improvements: I could get that 600 bytes down further by overriding the state saving in UIViewRoot. Also, Jacob's suggestion to use a flat Map instead of a hierarchy would also help - currently, if one component deep in the hierarchy needs to store state, we build up a large hierarchy of mostly null Object arrays. This isn't especially expensive, but it is avoidable.
A footnote: Mike Youngstrom cogently noted in a comment to my last post that a delta approach to state saving would let us make UIData both simpler and more generic - instead of handcoding a specific set of properties to save on certain types of child components, just run state saving. Absolutely, Mike! I haven't prototyped this on our UIXTable component (which doesn't extend UIData, FWIW), but that's a great thing to try.
How we're going to fix JSF state saving
State is not bad. Statelesness is not good. State is your friend. But like a bad friend, State can sometimes hang around the house, eating your Mitchell's ice cream and drinking your Amarula 'til he gets fat and lazy, turns your house into a pig sty, and makes you wonder why you ever were friends with him in the first place.
JSF state is kind of like that. As I've said in a previous post, JSF state saving is way too hefty and saves far too much that doesn't need to be saved in the first place. So, here's my 5 step plan for fixing that.
Step 1
1. Use Facelets.
Step 2
2. Enhance the ADF Faces
Step 3
3. Update the ADF Faces Facelets TagHandlers to call
Step 4
4. Implement Jacob Hookom's idea to save the tree of component state in a Map, not a gigantic Object array hierarchy.
Step 5
5. Rewrite the Facelets restoreView() implementation to recreate the tree from scratch in its original state, then call restoreState() on each component from the Map we built up in step 4.
And done. You don't need to save the tree structure anymore - Facelets is handling that by recreating the tree from its cached TagHandlers. And since most components don't actually change - unless you explicitly set properties on them - most components don't have to save any state.
(P.S.: there's some trickiness here that I'm ignoring: if you add components dynamically to the hierarchy, you do have to save their state and structure, since it's not present in the page. That's one reason why I strongly recommend making all dynamically created components transient if you have any choice in the matter, but that's a subject for another post.)
JSF state is kind of like that. As I've said in a previous post, JSF state saving is way too hefty and saves far too much that doesn't need to be saved in the first place. So, here's my 5 step plan for fixing that.
Step 1
1. Use Facelets.
Step 2
2. Enhance the ADF Faces
FacesBean
API to support a "delta" mode - call a new markInitialState()
method on it, and its saveState()
implementation only saves all the changes made since markInitialState()
. And if there's no changes, just return null. (You can't easily do this without FacesBean; see my earlier post on the subject.) Step 3
3. Update the ADF Faces Facelets TagHandlers to call
markInitialState()
after creating the components.Step 4
4. Implement Jacob Hookom's idea to save the tree of component state in a Map, not a gigantic Object array hierarchy.
Step 5
5. Rewrite the Facelets restoreView() implementation to recreate the tree from scratch in its original state, then call restoreState() on each component from the Map we built up in step 4.
And done. You don't need to save the tree structure anymore - Facelets is handling that by recreating the tree from its cached TagHandlers. And since most components don't actually change - unless you explicitly set properties on them - most components don't have to save any state.
(P.S.: there's some trickiness here that I'm ignoring: if you add components dynamically to the hierarchy, you do have to save their state and structure, since it's not present in the page. That's one reason why I strongly recommend making all dynamically created components transient if you have any choice in the matter, but that's a subject for another post.)
Usability problems in JSF
JSF is not perfect, nor the greatest thing since sliced bread. There, I said it!
What bugs me most (and remember, I'm still a big fan) is that JSF was supposed to be really easy-to-use. In many ways, it is very easy. But the reality isn't always quite so sweet. There's shortfalls that make the learning curve longer than it should be, and they are fixable.
The most annoying problem is that errors simply result in big stack traces. There's nothing wrong with stack traces per se, but by themselves they're totally insufficient for error diagnosis. Real diagnosis requires at a minimum:
In general, all exceptions thrown anywhere in JSF or JSPs should be vetted to ensure that they:
Finally, when you do figure out what you've done wrong, quite often you need to bounce the server to pick up the changes. Any changes to WEB-INF/faces-config.xml require this. That's a huge productivity hit.
So, that's my hit list for JSF usability problems. What are your biggest JSF usability concerns?
[PS: Yes, I edited the wording a bit. It was coming across as an angry rant that JSF isn't useable, which isn't where I'm coming from.]
What bugs me most (and remember, I'm still a big fan) is that JSF was supposed to be really easy-to-use. In many ways, it is very easy. But the reality isn't always quite so sweet. There's shortfalls that make the learning curve longer than it should be, and they are fixable.
The most annoying problem is that errors simply result in big stack traces. There's nothing wrong with stack traces per se, but by themselves they're totally insufficient for error diagnosis. Real diagnosis requires at a minimum:
- Line and column numbers, as well as file name. And even better, the snippet of content from that document that matches up.
- If an EL expression fails, information on what part of the EL expression failed. (When a complicated expression like
#{foo.bar.baz == my.other.expression}
fails, it's very hard to figure out what went wrong.)
In general, all exceptions thrown anywhere in JSF or JSPs should be vetted to ensure that they:
- Contain a meaningful, useful error message
- Contain enough context to point out what bit of user content failed
- Never, ever drop the base error, if one is being wrapped
Finally, when you do figure out what you've done wrong, quite often you need to bounce the server to pick up the changes. Any changes to WEB-INF/faces-config.xml require this. That's a huge productivity hit.
So, that's my hit list for JSF usability problems. What are your biggest JSF usability concerns?
[PS: Yes, I edited the wording a bit. It was coming across as an angry rant that JSF isn't useable, which isn't where I'm coming from.]
ADF Faces and JSF 1.2
JSF 1.2 is right around the corner, and, of course, ADF Faces is going to be right there too, right? Well, not so fast...
ADF Faces took one fairly unusual approach in designing its components. Instead of extending the standard UIComponentBase class like almost everyone else does, we directly extended UIComponent with UIXComponentBase. I chose this path for good reasons - like the FacesBean state saving approach I blogged about a few posts down - but it does carry a penalty. When UIComponent changes, ADF Faces breaks. And UIComponent does change in JSF 1.2. In particular, the move from the JSF EL to the new Unified EL means new getValueExpression() and setValueExpression() methods.
C'est la vie.
So, here's the good news: I'm promising, right here and now, to get a version of ADF Faces out soon that will be based on JSF 1.2. There's one big question to resolve: should it be based on the code in JDev 10.1.3, or on the post-10.1.3 code we're donating to MyFaces? I'm leaning towards the latter, but let me know if you feel differently.
ADF Faces took one fairly unusual approach in designing its components. Instead of extending the standard UIComponentBase class like almost everyone else does, we directly extended UIComponent with UIXComponentBase. I chose this path for good reasons - like the FacesBean state saving approach I blogged about a few posts down - but it does carry a penalty. When UIComponent changes, ADF Faces breaks. And UIComponent does change in JSF 1.2. In particular, the move from the JSF EL to the new Unified EL means new getValueExpression() and setValueExpression() methods.
C'est la vie.
So, here's the good news: I'm promising, right here and now, to get a version of ADF Faces out soon that will be based on JSF 1.2. There's one big question to resolve: should it be based on the code in JDev 10.1.3, or on the post-10.1.3 code we're donating to MyFaces? I'm leaning towards the latter, but let me know if you feel differently.
What belongs in the JSF standard?
As the Expert Group starts working on JSF 2.0, we're faced with a very fundamental question: how do we pick and choose what belongs in the standard? And what, by contrast, does not belong in the standard?
There are several reasons why something should go in the standard:
Some examples of each:
But if you don't meet these standards, why not still add the feature to JSF? Why not add a calendar component? How about a declarative framework that makes it easy to create renderers? Or support for XSLT post-processing of content? These all seem like handy features, but I feel strongly that none belong in the JSF standard yet. A J2EE standard has very difficult constraints placed on it. The long development cycle means we only get to add features occasionally. But we're also required to maintain backwards compatibility, so fixing mistakes is difficult-to-impossible. So, here's a list of some reasons why not to add something to the JSF spec:
These are not at all mutually exclusive lists. For example, the Avatar API is not the last word in handling AJAX requests for JSF. A declarative framework for renderers can be implemented outside the spec, but it would benefit from more tool support. And so forth.
Perhaps the most difficult task of any framework developer is learning how to say "No". As in, "No, we don't want that feature." But that is absolutely our responsibility as architects of JSF.
It's down there somewhere, let me take another look...
-- The Big Lebowski
There are several reasons why something should go in the standard:
- Feature ABC cannot be implemented outside of the standard.
- Feature DEF is not directly needed by the standard, but promotes interoperability.
- Standards receive better tool support, and tool support is essential for feature XYZ.
Some examples of each:
- JSF Avatar requires support from UIComponent, and therefore has to go into the standard.
- The DataModel API allows interoperability between different model providers and component providers. It would be helpful if JSF had a TreeModel for the same reason - instead of everyone inventing their own TreeModel, we can have one abstract interface that lets model experts provide the best implementations possible and component developers design the best tree UIs around.
- Facelets would benefit from tool support (Creator, JDeveloper, etc.)
But if you don't meet these standards, why not still add the feature to JSF? Why not add a calendar component? How about a declarative framework that makes it easy to create renderers? Or support for XSLT post-processing of content? These all seem like handy features, but I feel strongly that none belong in the JSF standard yet. A J2EE standard has very difficult constraints placed on it. The long development cycle means we only get to add features occasionally. But we're also required to maintain backwards compatibility, so fixing mistakes is difficult-to-impossible. So, here's a list of some reasons why not to add something to the JSF spec:
- Feature ABC can be implemented just as easily outside of the spec as inside of it. That is, the current public API is entirely sufficient to implement the feature.
- Feature DEF is not yet well understood - there are multiple possible directions to head. The JCP process works best when it standardizes existing best practices.
- Feature XYZ is of only limited use - not everyone needs it.
These are not at all mutually exclusive lists. For example, the Avatar API is not the last word in handling AJAX requests for JSF. A declarative framework for renderers can be implemented outside the spec, but it would benefit from more tool support. And so forth.
Perhaps the most difficult task of any framework developer is learning how to say "No". As in, "No, we don't want that feature." But that is absolutely our responsibility as architects of JSF.
It's down there somewhere, let me take another look...
-- The Big Lebowski
Should JSF go stateless?
Jacob Hookom's written an interesting blog entry about whether JSF could go stateless. The two of us have gone back and forth on this on e-mail a bunch of times, and I guess you could say that I fall in the "UI state is a good thing" camp. In large part, that comes from all the time I've spent working on component frameworks.
I absolutely agree with Jacob on one of his points - "Statesaving in JSF is [really] bad in implementation". It's a massive and heavyweight "save everything on the page" approach that is, at least conceptually, horrible overkill. For example, it's ridiculous that we have to save the UI component hierarchy, attributes, EL expressions, etc., when we have a JSP or Facelets document containing exactly that information just sitting there on the server. Even though I know why this happened - JSF had to support JSPs, etc., etc. - it's a very frustrating state of affairs.
Nevertheless, I think throwing out state saving altogether in response would be going too far. It'd be like saying that you should never bother sorting because Bubble Sort is a really slow algorithm. Unfortunately, JSF, as designed today, makes it very difficult to even attempt to optimize state saving. Until there's a really sane, solid implementation of state saving, any big policy decisions are premature.
The real problem with optimizing JSF state saving is the way that UIComponent.saveState() (and restoreState()) is typically implemented. Each class is responsible for saving all of its own per-instance state, up and down the hierarchy. So UIComponentBase stores its properties in an array, then UIOutput stores a few more properties (plus the UIComponentBase state) in an array of its own, then UIInput aggregates its own state, and finally HtmlInputText adds its own. For example, here's the start of HtmlInputText.saveState():
Nasty! If a container wanted to optimize state saving, it'd somehow have to mix that in not just into one class, but into every component class up and down the hierarchy.
ADF Faces took a different approach. We store all of our component state in an FacesBean instance. It takes care of storing simple properties, ValueBindings, lists (like listeners and validators), and knows how to save and restore state. Here's the full code for UIXComponentBase.saveState();
And subclasses don't neeed any code at all. Aaahhhh..... Now that's better. More on FacesBean in a later post (there's a lot of other advantages), but what's relevant is the general principle: all state saving is implemented up in the base class, so now there's a hook to implement real cross-cutting optimizations like:
Secondarily, we'd do very well to follow Jacob's advice and re-think how processSaveState() and processRestoreState() function - instead of creating a deep heirarchy, create a flattened Map or array structure.
Implement all of this, and then we're in a place to judge whether fully stateless UIs are necessary. I think they will be, for some limited types of applications, but JSF can be pushed a lot further than it's going now.
I absolutely agree with Jacob on one of his points - "Statesaving in JSF is [really] bad in implementation". It's a massive and heavyweight "save everything on the page" approach that is, at least conceptually, horrible overkill. For example, it's ridiculous that we have to save the UI component hierarchy, attributes, EL expressions, etc., when we have a JSP or Facelets document containing exactly that information just sitting there on the server. Even though I know why this happened - JSF had to support JSPs, etc., etc. - it's a very frustrating state of affairs.
Nevertheless, I think throwing out state saving altogether in response would be going too far. It'd be like saying that you should never bother sorting because Bubble Sort is a really slow algorithm. Unfortunately, JSF, as designed today, makes it very difficult to even attempt to optimize state saving. Until there's a really sane, solid implementation of state saving, any big policy decisions are premature.
The real problem with optimizing JSF state saving is the way that UIComponent.saveState() (and restoreState()) is typically implemented. Each class is responsible for saving all of its own per-instance state, up and down the hierarchy. So UIComponentBase stores its properties in an array, then UIOutput stores a few more properties (plus the UIComponentBase state) in an array of its own, then UIInput aggregates its own state, and finally HtmlInputText adds its own. For example, here's the start of HtmlInputText.saveState():
public Object saveState(FacesContext _context) {
Object _values[] = new Object[31];
_values[0] = super.saveState(_context);
_values[1] = accesskey;
_values[2] = alt;
_values[3] = dir;
_values[4] = this.disabled ? Boolean.TRUE : Boolean.FALSE;
_values[5] = this.disabled_set ? Boolean.TRUE : Boolean.FALSE;
.... and on and on and on for another 26 lines ....
}
Nasty! If a container wanted to optimize state saving, it'd somehow have to mix that in not just into one class, but into every component class up and down the hierarchy.
ADF Faces took a different approach. We store all of our component state in an FacesBean instance. It takes care of storing simple properties, ValueBindings, lists (like listeners and validators), and knows how to save and restore state. Here's the full code for UIXComponentBase.saveState();
public Object saveState(FacesContext context)
{
return getFacesBean().saveState(context);
}
And subclasses don't neeed any code at all. Aaahhhh..... Now that's better. More on FacesBean in a later post (there's a lot of other advantages), but what's relevant is the general principle: all state saving is implemented up in the base class, so now there's a hook to implement real cross-cutting optimizations like:
- Cache reusable FacesBean instances on a Facelet tag handler (with copy-on-write), especially for panels and output components that rarely if ever mutate
- Implement saveState() that only saves deltas from the original state of the bean (and rely on Facelets to recreate the tree in its original state)
Secondarily, we'd do very well to follow Jacob's advice and re-think how processSaveState() and processRestoreState() function - instead of creating a deep heirarchy, create a flattened Map or array structure.
Implement all of this, and then we're in a place to judge whether fully stateless UIs are necessary. I think they will be, for some limited types of applications, but JSF can be pushed a lot further than it's going now.
Blogging at last...
It seems about time that I throw my hat into the ring and give blogging a whirl. There should be plenty to say, what with ADF Faces joining MyFaces (though the name's not going to be Cherokee), and anyone who's met me knows I'm never at a loss for opinions.