Friday, July 1, 2016

OmniFaces 2.4 revives c:url, improves @Param and simplifies logging

OmniFaces 2.4 has been released!

This version has relatively few new additions, only one brand new tag <o:url> which revives good 'ol JSTL <c:url> for Facelets, and a few utility methods/functions. There are several enhancements and bugfixes in existing stuff, such as the @Param finally supporting injecting multi-valued parameters such as foo=bar1&foo=bar2 into a String[] or List<String> and injecting into primitive type fields. Those few utility methods and enhancements must further simplify logging of JSF (ajax) actions, exceptions and JavaScript errors.

You can find the complete list of additions, changes and fixes at What's new in OmniFaces 2.4? list in showcase.

Installation

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

Maven users: use <version>2.4</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>2.4</version>
</dependency>

For users who don't want to use CDI, there's the CDI-less 1.14 with all 2.4 enhancements and fixes (but no brand new 2.x additions nor the o:url!).

Good ol' JSTL <c:url> revived

Sometimes there's need to access and/or print the current request URL "plain vanilla" in a JSF page. Common use cases are to prepopulate a ?from= request parameter for the login form so it can figure out where to redirect back after a successful login, and filling the HTML5 link relations referring the current or another page on the same site. Previously, this could to some extent (no control over domains) be done with JSTL ><c:url> which was removed in Facelets.

This has now been revived in OmniFaces 2.4 as <o:url> and even further enhanced. It supports setting the (relative) domain via domain attribute and it supports the same includeViewParams, includeRequestParams and useRequestURI attributes as <o:form>. The <o:url> also supports the var attribute allowing to set the value as an EL variable which can in turn be used in an EL expression elsewhere in the page.

Here's an example:

<o:url var="currentURL" />
...
<h:head>
    ...
    <link rel="canonical" href="#{currentURL}" />
    ...
</h:head>
<h:body>
    ...
    <h:link value="Login" outcome="login">
        <f:param name="from" value="#{currentURL}" />
    </h:link>
    ...
</h:body>

@Param now supports multiple values and primitives

The CDI @Param annotation has in OmniFaces 2.4 been further improved to also support injecting multi-valued request parameters such as foo=value1&foo=value2&foo=value3 into an array or List.

@Inject @Param(name="foo")
private List<String> foos;

@Inject @Param(name="bar")
private String[] bars;

Conversion and validation is also supported on multi-valued parameters like as already supported on single-valued parameters. On multi-valued parameters, JSF native conversion and validation will run on each submitted value. Bean Validation, if any, will however be performed on the entire List or array property and not on each individual item.

Furthermore, the @Param now also supports injecting into a primitive field. Previously this wasn't possible because CDI would attempt to inject an empty value as null into the field, which would only throw an exception (no autoboxing magic there in reflection). The @Param will now automatically take care about injecting the right default value of the primitive field in case the value is absent or empty.

Logging made easier

OmniFaces 2.4 comes with several utilities and enhancements which should further simplify logging of (ajax) actions, exceptions and JavaScript errors.

First, the Components got two new utility methods, one to find out the source component of the (ajax) form submit and another to extract all actions and listeners from it as a list of EL expression strings such as #{bean.action}, #{bean.actionListener}, #{bean.ajaxListener}.

UIComponent actionSource = Components.getCurrentActionSource();
List<String> actionExpressions = Components.getActionExpressionsAndListeners(actionSource);
// ...

This also works for e.g. <h:inputText><f:ajax listener="#{bean.listener}">. You can find a concrete example as PhaseListener in my answer to How can I log method expressions of JSF ajax requests.

Second, the FullAjaxExceptionHandler got a new logException overloaded method with a LogReason enum argument which should help in determining when exactly the exception has been caught and whether it was successfully handled or not. For example, if handling has failed because the response was already committed, or the error page itself contained an error, then you could take additional action by sending an email to site admin.

@Override
protected void logException(FacesContext context, Throwable exception, String location, LogReason reason) {
    switch(reason) {
        case RENDER_EXCEPTION_UNHANDLED:
        case ERROR_PAGE_ERROR:
            yourMethodToSendAnEmailToAdmin(exception, reason);
            break;
        default:
            super.logException(context, exception, location, reason);
    }
}

Third, the CombinedResourceHandler and <o:deferredScript> have been altered to always render crossorigin="anonymous" attribute to the generated <script> element. This will basically enable JavaScript error logging via window.onerror on scripts possibly served via a CDN. Such a logger can look like below, with little help of jQuery and a servlet:

$(window).on("error", function(message, source, line, column, error) {
    try {
        $.post("/script-error", $.param({
            url: window.location.href,
            client: navigator.userAgent,
            message: message,
            source: source,
            line: line,
            column: column,
            error: error ? error.stack : null
        }));
    }
    catch(e) {
        // Ignore.
    }
});
@WebServlet("/script-error")
public class ScriptErrorServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String url = request.getParameter("url");
        String client = request.getParameter("client");
        String message = request.getParameter("message");
        String source = request.getParameter("source");
        String line = request.getParameter("line");
        String column = request.getParameter("column");
        String error = request.getParameter("error");
        // ...
    }

}

Noted should be that the detail of the information being sent depends on the client used. Chrome for example is very kind to include the full stack trace

More OmniFaces in upcoming JSF 2.3

Besides the new JSF 2.3 <f:websocket>, which is largely based on <o:socket> introduced in previous OmniFaces version, more OmniFaces solutions have been integrated into upcoming JSF 2.3 the last months. The FixViewState is now in standard jsf.js as per spec issue 790, the <o:commandScript> is integrated as new JSF 2.3 <h:commandScript> as per spec issue 613, and the <o:importConstants> is integrated as new JSF 2.3 <f:importConstants> as per spec issue 1424 (which should be declared inside <f:metadata>). Those OmniFaces artifacts will be deprecated as per upcoming OmniFaces 3.0 for JSF 2.3.

Maven download stats

Here are the Maven download stats after previous release:

  • April 2016: 8050
  • May 2016: 8590
  • June 2016: 8560

No comments: