JSF in SF

Thursday, March 30, 2006

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.)

Wednesday, March 22, 2006

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:



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]}">

Thursday, March 16, 2006

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 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!)

Wednesday, March 15, 2006

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 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.

Thursday, March 02, 2006

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 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.)