Wednesday, January 3, 2018

OmniFaces 3.0 released!

OmniFaces 3.0 has been released!

The minimum requirements have been upgraded from Java 1.7, JSF 2.2, EL 2.2, Servlet 3.0, CDI 1.1 and (optional) BV 1.1 to Java 1.8, JSF 2.3, EL 3.0, Servlet 3.1, CDI 2.0 and (optional) BV 2.0. WebSocket 1.1 hasn't been upgraded since, so it's still the same. As of now, OmniFaces 3.0 is technically still backwards compatible with EL 2.2, CDI 1.1 and BV 1.1, as features in newer versions are still unutilized, but that may change in a future 3.x version. OmniFaces 3.0 will explicitly prevent deployment when Java 1.8, JSF 2.3 or CDI 1.1 are absent in the environment.

To learn what's new in JSF 2.3, head to the complete overview of new JSF 2.3 features in Arjan Tijms' blog.

As shown in what's new in OmniFaces 3.0 at the showcase site, below is a summary of breaking changes, new things and deprecated things in OmniFaces 3.0 as compared to OmniFaces 2.6.8, which is also released today.

Breaking changes:

  • <o:form useRequestURI="true"> is now the default behavior of <o:form> as it was basically the main reason to use <o:form>. I.e. it will always submit to exactly the same URL as in browser's address bar whereas the <h:form> only submits to the forwarded URL without query string, causing among others view parameters and path parameters to get lost every time. You can if necessary disable this by <o:form useRequestURI="false"> or switching back to <h:form>.
  • <o:form> will now by default perform a partial submit on any JSF ajax request. In other words, only the parameters actually covered by execute attribute of <f:ajax> will be sent, hereby reducing the request payload to not contain unnecessary parameters. This is similar to PrimeFaces partialSubmit feature. Even though this has proven to work flawlessly in PrimeFaces for ages, you can if necessary disable this by <o:form partialSubmit="false">.
  • <o:validateBean showMessageFor="@violating"> will not anymore show the "remaining" messages (coming from other bean properties which are not covered by the JSF form) as a global message but just suppress them.
  • ValidateMultipleFields (all multi-field validators basically) have previously skipped all disabled/readonly/nonrendered inputs. Now they won't anymore be skipped, and the values argument (3rd argument of validate method) will contain their current model value (and thus not the submitted value).
  • Package org.omnifaces.component.output.cache has been migrated to org.omnifaces.util.cache and several previously private/hidden artifacts have been made public, so that it's now more useful for non-component related caches.
  • All Faces/FacesLocal/Components/Servlets methods which previously threw IOException will now throw Java8's java.io.UncheckedIOException instead, hereby reducing down unnecessary throws IOException boilerplate in methods because those should always be bubbled up into the container.
  • org.omnifaces.facesviews.FacesServletDispatchMethod and ViewHandlerMode which was deprecated since 2.6 have now been removed without replacement (as they have become superfluous since Servlet 3.0).
  • org.omnifaces.renderkit.Html5RenderKit which was deprecated since 2.2 has now been removed without replacement (as this has become superfluous since JSF 2.2 with new passthrough attribtue feature).
  • org.omnifaces.config.BeanManager which was deprecated since 2.5 has now been removed with org.omnifaces.util.Beans as replacement.
  • RichFaces targeted hacks have been removed. OmniFaces 3.0 is therefore not anymore compatible with RichFaces. Note that RichFaces itself was declared "End of Life" June 2016 and is already not compatible with JSF 2.3.

New things:

Deprecated things:

  • <o:commandScript> has been deprecated as it's now moved into JSF 2.3 as <h:commandScript> with exactly the same functionality.
  • fixviewstate.js has been deprecated as it's now finally solved in JSF 2.3 (by yours truly).
  • <o:form includeViewParams="true"> as well as <o:form includeRequestParams="true"> have been deprecated as those have never proven to be more useful than useRequestURI="true".

Noted should be that the <o:socket> is also moved into JSF 2.3 as <f:websocket> with here and there a few small API generifications. But this won't be deprecated anywhere in OmniFaces 3.x as it still has room for new ideas and improvements.

As to new things, the benefits of <o:selectItemGroups>, omnifaces.ImplicitNumberConverter and Faces#getXxxAttribute() overloads are detailed below.

Not anymore explicitly creating new SelectItemGroup()

When grouping select item options in a <optgroup>, you had to manually bake SelectItemGroup instances yourself. Assuming that you have List<Category> as wherein Category in turn has a List<Product>:

private Product selectedProduct;
private List<SelectItem> categories;

@Inject
private ProductService productService;

@PostConstruct
public void init() {
    categories = productService.listCategories().stream().map(category -> {
        SelectItemGroup group = new SelectItemGroup(category.getName());
        group.setSelectItems(category.getProducts().stream()
            .map(product -> new SelectItem(product, product.getName()))
            .toArray(SelectItem[]::new));
        return group;
    }).collect(Collectors.toList());
}
<h:selectOneMenu value="#{bean.selectedProduct}" converter="omnifaces.SelectItemsConverter">
    <f:selectItem itemValue="#{null}" />
    <f:selectItems value="#{bean.categories}" />
</h:selectOneMenu>

This is too much JSF 1.0. Even in JSF 2.3, there's still nothing like <f:selectItemGroups>. OmniFaces 3.0 therefore brings a <o:selectItemGroups> into the game.

private Product selectedProduct;
private List<Category> categories;

@Inject
private ProductService productService;

@PostConstruct
public void init() {
    categories = productService.listCategories();
}
<h:selectOneMenu value="#{bean.selectedProduct}" converter="omnifaces.SelectItemsConverter">
    <f:selectItem itemValue="#{null}" />
    <o:selectItemGroups value="#{bean.categories}" var="category" itemLabel="#{category.name}">
        <f:selectItems value="#{category.products}" var="product" itemLabel="#{product.name}" />
    </o:selectItemGroups>
</h:selectOneMenu>

Not anymore explicitly entering $ or %

Sometimes you need to have the enduser to input currencies. For that, you'd intuitively use <f:convertNumber type="currency">.

<h:outputLabel for="price" value="Price $" />
<h:inputText id="price" value="#{bean.price}">
    <f:convertNumber type="currency" currencySymbol="$" />
</h:inputText>
<h:message for="price" />

However, this would surprisingly fail with a conversion error when the enduser doesn't explicitly specify the currency symbol in the input field.

Price $
'12.34' could not be understood as a currency value. Example: $99.99

Of course you could work around it by replacing type="currency" with pattern="#,##0.00". But this is embarrassingly harder to remember than just type="currency" and even prone to locale-specific differences. In some locales the fractions are optional or even omitted. And what if the enduser thought to enter the currency symbol anyway?

With the new omnifaces.ImplicitNumberConverter you don't need to worry about this all.

<h:outputLabel for="price" value="Price $" />
<h:inputText id="price" value="#{bean.price}">
    <o:converter converterId="omnifaces.ImplicitNumberConverter" type="currency" currencySymbol="$" />
</h:inputText>
<h:message for="price" />

Entering the currency symbol has now become optional and it will be inferred when absent. On outputting, it will be hidden from the output and it will be assumed that the user interface already covers this. The same facility is also available for type="percent".

Not anymore explicitly checking if scoped attribute exists

You might have repeatedly stumbled into the below use case in some non-managed JSF artifact (where you'd otherwise of course just have used CDI):

public SomeObject getSomeObject() {
    SomeObject someObject = Faces.getRequestAttribute(SomeObject.class.getName());

    if (someObject == null) {
        someObject = new SomeObject();
        Faces.setRequestAttribute(SomeObject.class.getName(), someObject);
    }

    return someObject;
}

The getRequest/Flash/View/Session/ApplicationAttribute() methods have now all an overload which takes a Supplier<T>. The usage is now much like Map#computeIfAbsent().

public SomeObject getSomeObject() {
    return Faces.getRequestAttribute(SomeObject.class.getName(), SomeObject::new);
}

Not anymore explicitly rethrowing IOException

Some utility methods in Faces throw IOException when something fails during handling the HTTP response. Generally this is unavoidable, such as end user facing a local network error. Those exceptions are documented to be rethrown. For example,

@PostConstruct
public void init() throws IOException {
    if (someCondition()) {
        Faces.redirect("other.xhtml");
    }
}

However, redeclaring this everytime is tiresome and moreover, in this specific example it goes against the @PostConstruct contract as it may actually not throw a checked exception. Weld will even warn about this during webapp startup.

With OmniFaces 3.0, all methods which previously threw IOException will now throw it as Java 8's new UncheckedIOException instead. So you can safely strip out any unnecessary throws IOException coming from Faces/FacesLocal/Servlets utility methods over all place.

@PostConstruct
public void init() {
    if (someCondition()) {
        Faces.redirect("other.xhtml");
    }
}

Installation

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

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

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

But my server doesn't support JSF 2.3!

Don't panic. I wrote a clean set of installation instructions in Mojarra README. In a nutshell, for Tomcat 8.x you just need to upgrade the JARs and add JSONP API as compared to Mojarra 2.2 (with CDI + BV). For Java EE 7 (e.g. WildFly 8-11.x, Payara 4.x, TomEE 7.x, etc) you need to manually swap out server-provided JARs as instructed in the README.

How about OmniFaces 2.x and 1.1x?

2.x is basically already since 2.6 in maintenance mode. I.e. only bugfix versions will be released. It's currently already at 2.6.8, also released today. As long as you're still on JSF 2.2 with CDI, you can continue using latest 2.6.x, but it won't contain new things introduced in 3.x.

1.1x is basically already since 2.5 in maintenance mode. I.e. only bugfix versions will be released. It's currently still at 1.14.1 (May 2017), featuring the same features as OmniFaces 2.4, but without any JSF 2.2 and CDI things and therefore compatible with CDI-less JSF 2.0/2.1.

Maven download stats

Here are the 2017's Maven download stats

  • January 2017: 10889
  • February 2017: 12060
  • March 2017: 14669
  • April 2017: 11999
  • May 2017: 12521
  • June 2017: 11535
  • July 2017: 12197
  • August 2017: 13925
  • September 2017: 13502
  • October 2017: 13080
  • November 2017: 14372
  • December 2017: 11735