<< Previous | Home | Next >>

ICEfaces revisited

Since my previous blog-post (Dynamic DataTables in ICEfaces), I have encountered several more situations where calling setId() seems to be required: for example, PanelCollapsible content was not rendered, and the Highlight effect was never fired, unless their generation was accompanied with a setId() call. The strange thing is that in these cases the problem only occurred on some platforms, not on others. Same application source, same ICEfaces version, same GlassFish version, but slightly different Java revisions and completely different operating systems. I gave up trying to find the underlying reason for this, and added the following code instead:

    private int ensureIds(UIComponent x, int i) {
        if (x.getId() == null) {
            i++;
            x.setId("g" + Integer.toString(i));
        }
	for (Iterator j = x.getFacetsAndChildren(); j.hasNext(); ) {
i = ensureIds(j.next(), i);
}
return i;
}

Then immediately after all components have been generated, ensureIds(binding,0) is called, where binding is the root UIComponent of the generated tree.

Some more non-obvious quirks:

  • The ICEfaces Ajax push-server does not work reliably with the GlassFish https listener. It has problems even with ordinary http behind a https-frontend reverse proxy (I am currently using Apache httpd with mod_proxy for this). This is a shame, because no one uses unencrypted transport these days for non-public data of any kind. Apparently ICEsoft provides a non-free Enterprise Push Server that is designed to handle https, and it also handles clustering and failover. Clustering and failover is clearly enterprise stuff, but surely something as basic as https should be among the fundamental feature set. I consider the push-server to be dangerously close to crippleware... Fortunately it is possible to get along without the push-server, as long as you are content to live with some limitations.

  • Some ICEfaces components (e.g. RowSelector) need method expressions with named beans. This is awkward for two reasons: the first is that a named JSF bean, which is a declared container-managed entity, must be referenced in the expression. But a simple listener object (or "lambda closure" in other languages) would have been enough. The second reason is that ICEfaces uses a deprecated API for this method expression, producing a warning at every compilation.

  • The presence of a started but uncommitted UserTransaction seems to inhibit ICEfaces from performing a rendering after a OnDemandRenderer.requestRender(). Actually, I am not 100% sure about this one, but switching the order of my requestRender() and commit() calls got asynchronous rendering working for me.

I just had get this off my chest. All in all, I still like ICEfaces very much, and I wouldn't trade it for any alternative Ajax toolkit.

Dynamic DataTables in ICEfaces

ICEfaces is the best AJAX framework I have come across. Not the most powerful — I have done some rather tricky animated vector-graphics stuff with Dojo together with custom Javascript (both browser and server-side) that would be impossible to emulate with any other AJAX tool that I know of. But the cool thing with ICEfaces is that it provides a rich and highly interactive and scalable AJAX-driven and CSS-styled user interface out of the box, using only a server-side JSF interface. Your hands won't ever have to touch a single line of Javascript code, it's all encapsulated in JSF components. This is extremely good news for productivity, assuming you already have taken the trouble to learn some JSF.

However, ICEfaces is not free from quirks. For example, when creating a DataTable dynamically from Java code, it is essential that UIColumn.setId() is called for each dynamically created UIColumn, otherwise incremental updates to the DataTable model will be rendered incorrectly. This is not at all obvious since everything works without any "id" attributes when the UIColumns are created via static XML code.

Mysterious injection problem...

I just found out (the hard way) about an interesting quirk in how GlassFish handles dependency injections. I updated a library JAR used in my project, and the new filename was different from the previous one due to having the version number in its name. So I had to update my build.xml to reflect the changed name both in the javac classpath and in the copy task that assembles WEB-INF/lib for the webapp. But I made a typo in the second case, so a <file> element in a <fileset> in a copy-task pointed to a non-existent file. Since the correct file was in the javac classpath, compilation of my own source code worked without problems. And "ant" happily proceeded to create the WAR file anyway, despite the missing file in the copy-task. This is not part of the mystery however — "ant" is extremely unintuitive so this behaviour is no different from business as usual.

Deployment failed of course, but in a rather peculiar way. The broken WAR file contained a servlet that was as a subclass of one of the classes in the missing library JAR. It also contained a ServletContextListener that invoked a method on an injected EJB. But this injection never happened, so I got a NullPointerException and initialization of the webapp context failed. It took me quite a long time plus some intense head-scratching to figure out why: the missing servlet superclass somehow stopped injection from happening in the context listener. The mysterious part is why the ServletContextListener.contextInitialized() method was called despite this failure. It wasn't until I removed the EJB invocation that the servlet initialization produced a ClassNotFoundException that revealed the true cause of the problem.

Moral: never assume that a failure in one part of the system won't affect other parts in non-obvious and unpredictable ways.