OmniFaces 2.2 has been released!
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.
Maven users: use <version>2.2</version>
.
<dependency>
<groupId>org.omnifaces</groupId>
<artifactId>omnifaces</artifactId>
<version>2.2</version>
</dependency>
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:
var xhr = new XMLHttpRequest();
xhr.open("POST", window.location.href.split(/[?#;]/)[0], false);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("omnifaces.event=unload&id=" + id + "&" + VIEW_STATE_PARAM + "=" + viewState);
The existing OmniFaces RestorableViewHandler
has been extended with below lines:
if (isUnloadRequest(context)) {
UIViewRoot createdView = createView(context, viewId);
createdView.restoreViewScopeState(context, getRenderKit(context).getResponseStateManager().getState(context, viewId));
BeanManager.INSTANCE.getReference(ViewScopeManager.class).preDestroyView();
responseComplete();
return createdView;
}
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.
New FileServlet with advanced HTTP range and caching support
The well known "BalusC FileServlet" from the blog article FileServlet supporting resume and caching has been cleaned, refactored and reworked into an abstract template. Now, instead of copypasting and editing the code from the blog, you can simply extend from
org.omnifaces.servlet.FileServlet
and implement the getFile()
method in its simplest form as below:
@WebServlet("/media/*")
public class MediaFileServlet extends FileServlet {
@Override
protected File getFile(HttpServletRequest request) throws IllegalArgumentException {
return new File("/var/webapp/media", request.getPathInfo());
}
}
When referenced in e.g. a HTML5 <video>
tag like below:
<video src="#{request.contextPath}/media/video.mp4" controls="controls" />
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
.
<f:viewParam name="foo" value="#{bean.foo}" converter="fooConverter" />
<f:viewAction action="otherpage" if="#{bean.foo eq 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.
<f:viewParam name="foo" value="#{bean.foo}" converter="fooConverter" />
<o:viewAction action="otherpage" if="#{bean.foo eq null}" />
The implementation of the extended component is really simple. It's nothing more than below:
@Override
public void broadcast(FacesEvent event) throws AbortProcessingException {
if (super.isRendered()) {
super.broadcast(event);
}
}
@Override
public boolean isRendered() {
return !isImmediate() || super.isRendered();
}
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
Here are the Maven download stats after previous release:
- June 2015: 6949
- July 2015: 6574
- August 2015: 5906
- September 2015: 6561
- October 2015: 5904
Below is the version pie of October 2015:
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!