Tuesday, November 24, 2015

This version brings a bunch of new utility methods in Faces, Messages and Components classes, a new <o:viewAction> component, a new @ContextParam annotation, a whole FileServlet, and a second life for the @ViewScoped annotation.

As usual, in the What's new page of the showcase site you can find an overview of all what's been added/changed/fixed for 2.2. The top three additions are the second life for the @ViewScoped annotation, the new and refactored "BalusC FileServlet", and the new <o:viewAction>.

Installation

Non-Maven users: download OmniFaces 2.2 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

For users who don't want to use CDI, there's the CDI-less 1.12.1 with all 2.2 enhancements and fixes (but no brand new 2.x additions!). UPDATE: FacesViews was broken in 1.12, so a 1.12.1 has been baked with the hotfix. FacesViews continues to work fine in 2.2, so there's no 2.2.1.

Second life for @ViewScoped

In standard JSF 2.0/2.1, the @PreDestroy annotated method on a standard JSF view scoped bean was never invoked when the session expires. This was solved since OmniFaces 1.6 with its new CDI @ViewScoped annotation. However, since JSF 2.2 this problem is solved on JSF's own @ViewScoped beans, hereby basically making the OmniFaces CDI @ViewScoped annotation superflous in JSF 2.2. You could as good just stick to JSF's own @ViewScoped annotation.

Instead being potentially deprecated, the OmniFaces @ViewScoped got a second life: immediately invoke @PreDestroy when the browser unloads the page instead of waiting until the session is expired. In other words, when the user navigates away by a GET link, or closes the browser tab/window, or refreshes the page (by GET), then the OmniFaces @ViewScoped bean will immediately be destroyed. None of the both JSF 2.2 @ViewScoped annotations support this.

The trick is done by a synchronous XHR request during beforeunload event (thus not on the main thread! ;) ) via a helper script omnifaces:unload.js which is automatically included when an OmniFaces @ViewScoped bean is created. The XHR request sends a special request parameter omnifaces.event=unload along with the OmniFaces specific view scope ID and the JSF javax.faces.ViewState ID:

In a nutshell: when the omnifaces.event=unload parameter is present, then only create the view and restore any view scoped beans (and thus don't build the view! which might be time consuming and unnecessarily create new beans) and finally destroy those beans, hereby immediately invoking the @PreDestroy and freeing unused memory space on demand. After that, JSF is instructed to immediately render the response and return the dummy (non-built!) view, preventing potential unnecessary ViewExpiredException.

There's however a small caveat: on slow network and/or poor server hardware, there may be a noticeable lag between the enduser action of unloading the page and the desired result, even though the server side action may take place in only a few milliseconds (in case you're doing expensive tasks inside @PreDestroy method, better delegate it to an @Asynchronous EJB method or so). If the slow network/hardware lag is in your case noticeable and thus undesireable, then better stick to JSF 2.2's own @ViewScoped annotation and accept the postponed destroy.

Then the FileServlet will worry about properly streaming the media range requests. I.e. the servlet is able to return specific parts of the media file on a request with a HTTP Range header. For example, only the bytes at exactly the index 1000 until 2000 on a file of 10MB long. This is mandatory for many media players in order to be able to skip a certain range of the media stream quickly enough and/or to improve buffering speed by creating multiple connections which each requests different parts of the file. Also download accelerators will take benefit of this on large files. They will then simply open multiple HTTP connections which each downloads a specific range of the file and afterwards put the pieces together.

Fix unintuitive "if" attribute of <f:viewAction>

The if attribute of the standard <f:viewAction> not really intuitive. It is checked during APPLY_REQUEST_VALUES phase instead of INVOKE_APPLICATION phase. This would make several straightforward use cases to fail.

In below example, the FooConverter may convert a non-null parameter to null without causing a validation or conversion error, and the intent is to redirect the current page to otherpage.xhtml when the converted result is null.

However, this fails because the if attribute runs before the conversion has taken place, when the component wants to check whether it should queue the action event. This happens during APPLY_REQUEST_VALUES phase. The OmniFaces <o:viewAction> solves this by postponing the evaluation of the if attribute to the INVOKE_APPLICATION phase.

It gives a bit of thinking as the if attribute is basically a renamed rendered attribute, and one would wonder why !isImmediate() is checked instead of e.g. the current phase ID or a hardcoded true. But this way the original and intented behavior of immediate attribute is maintained. Actually, the <o:viewAction immediate="true"> behaves exactly the same as a <f:viewAction immediate="true">.

Maven download stats

Surprising how many 1.8.1 users are still there. To those who can't upgrade to 2.2 or 1.12, I'd like to point out that 1.8.3 contains an important memory leak fix in case you're using UnmappedResourceHandler while having composite components on the pages. So, if you can't upgrade to 2.2 or 1.12, then please at least upgrade to 1.8.3. And those few old RC1/RC2 and M1 users, upgrade to the final version without any RC/M suffix!

About

Donate

For the ones who want to express their excessive thanks for my work, I used to have an Amazon wishlist with a list of books, but right now I don't have any interesting books on the list anymore (to anyone who've sent books before: thank you very much, I got 6 books in 6 months). You can always donate something so that I can use it for other stuff, such as Nespresso coffee.