Showing posts with label OmniFaces. Show all posts
Showing posts with label OmniFaces. Show all posts

Tuesday, July 14, 2026

OmniFaces 5.4 released, now compatible with Faces 5.0

OmniFaces 5.4.1 has been released! This is the first OmniFaces version which is compatible with Jakarta Faces 5.0, while still keeping the Faces 4.1 minimum of the whole 5.x line. In other words, one and the same JAR runs on Jakarta EE 11 (Faces 4.1) as well as on the upcoming Faces 5.0. Next to that there are a few new features, two deprecations and two notable fixes.

New: Jakarta Faces 5.0 compatibility

Until now the whole OmniFaces 5.x line required Jakarta Faces 4.1 as minimum and was not verified against Faces 5.0. As of 5.4 the integration test suite also runs against both Mojarra 5.0.0-SNAPSHOT and MyFaces 5.0.0-SNAPSHOT on Tomcat, next to the existing Faces 4.1 runs. Note that these are still snapshots; Jakarta Faces 5.0 has not been finalized yet, so consider this a first compatibility milestone rather than a guarantee against the final release. The changes needed to span both generations turned out to be small; OmniFaces only had to catch up with the in Faces 5.0 renamed implementation packages and with a changed rendering of the on* attributes. The minimum stays at Faces 4.1, so upgrading to 5.4 is safe on Jakarta EE 11 while you have the opportunity to move to Faces 5.0 whenever you want.

New: OmniFaces.Ajax.validationFailed

Ever needed to know at the client side whether a Faces ajax request failed on validation? Until now you had to inspect the returned partial response or add a hidden component whose changed value acted as a flag. As of 5.4, each OmniFaces ajax response exposes FacesContext#isValidationFailed() to the client side as a boolean OmniFaces.Ajax.validationFailed. So your JavaScript can react to a validation failure without any server or DOM round-trip.

if (OmniFaces.Ajax.validationFailed) {
    // Do your thing.
}

This works out of the box; there is nothing to configure. See also Ajax in the showcase. (#955)

New: org.omnifaces.CDN_RESOURCE_HANDLER_EXCLUDED_RESOURCES

The CDNResourceHandler rewrites resource URLs to a CDN host. When you use a wildcard mapping for a whole library, it may happen that a specific resource of that library is not actually hosted on the CDN (a typical example is PrimeFaces dynamiccontent.properties). As of 5.4 you can exclude such resources from rewriting via the new context parameter org.omnifaces.CDN_RESOURCE_HANDLER_EXCLUDED_RESOURCES. It takes a comma separated list of libraryName:resourceName identifiers which are then served as-is by the default Faces resource handler. The match is exact; wildcards are not supported here.

<context-param>
    <param-name>org.omnifaces.CDN_RESOURCE_HANDLER_EXCLUDED_RESOURCES</param-name>
    <param-value>primefaces:dynamiccontent.properties</param-value>
</context-param>

See also CDNResourceHandler in the showcase. (#954)

New: query params in FullAjaxExceptionHandler error pages

The FullAjaxExceptionHandler forwards to the error page declared in web.xml. Until now the declared <location> could only be a plain path. As of 5.4 you can add query params to it and they are honored on the forward.

<error-page>
    <exception-type>java.lang.NullPointerException</exception-type>
    <location>/WEB-INF/errorpages/general.xhtml?type=NPE</location>
</error-page>

See also FullAjaxExceptionHandler in the showcase. (#962)

Improved: OnDemandResponseBufferFilter

The OnDemandResponseBufferFilter, which backs <o:cache>, previously buffered only responses written via the Writer. It now also buffers responses written via the OutputStream. This was never a problem in practice; it was a long-standing TODO, addressed so the filter is reusable for binary or streamed responses too. (#959)

Deprecated: <o:selectItemGroups>

The <o:selectItemGroups> component has been deprecated. Faces 4.0 introduced a standard <f:selectItemGroups> which is functionally equivalent, so the OmniFaces one is not needed anymore. As of 5.4 a warning is logged once at runtime, and the component is marked for removal in a future version. Replace it with the standard <f:selectItemGroups>. (#958)

Deprecated: @Param without @Inject

Using @Param without @Inject on the same field has been deprecated. As of 5.4 a warning is logged at deployment time for each affected field. Add @Inject next to @Param to get rid of the warning. (#960)

Fixes

<o:inputFile> would duplicate the client side validation script in its onchange when the component was re-rendered. This has been fixed; the script is now added only once. (#963)

@ViewScoped could lose an active bean under concurrent requests. When the LRU eviction of the view scope bean storage kicked in, it could destroy a bean storage that was still actively used by a concurrent request. This has been fixed; a storage in active use is never anymore destroyed. (#966)

Installation

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

Maven users: use the following coordinates.

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

The <o:inputFile> and @ViewScoped fixes (#963 and #966) have also been backported to 4.x and 3.x, so OmniFaces 4.7.11 and OmniFaces 3.14.22 have been released as well. The Jakarta Faces 5.0 compatibility, the new features and the deprecations are exclusive to 5.4.1.

For the complete list of additions, changes and fixes, see What's new in OmniFaces 5.4.1? in the showcase.

Thursday, April 23, 2026

OmniFaces 5.3 released!

OmniFaces 5.3 has been released!

This is a relatively small feature release on top of 5.2. One new component has been added, and the rest of the changes are under the hood for better long-term maintenance: automated code formatting, a simpler and faster JavaScript build, and reorganized TypeScript sources. You can find the complete list of additions, changes and fixes at What's new in OmniFaces 5.3? in the showcase.

New: <o:lazyPanel>

Ever had a page with an expensive region below the fold which the user may never scroll to, but which gets built on every page load anyway? The traditional workaround is to wire up an IntersectionObserver in custom JavaScript and fire an ajax request yourself, which is a lot of boilerplate for something so common.

The new <o:lazyPanel> defers rendering of its children until the panel has scrolled into view:

<o:lazyPanel>
    <h:dataTable value="#{bean.expensiveList}" var="row">
        ...
    </h:dataTable>
</o:lazyPanel>

On initial render, the component writes a wrapper element with an optional placeholder and schedules a viewport intersection listener on it via OmniFaces.js, which uses IntersectionObserver when available and falls back to scroll/resize/orientationChange listeners otherwise. As soon as the wrapper intersects the viewport, a single faces.ajax.request targeting its own client id is fired. The component then flips its loaded flag, optionally invokes a listener bean method with a LazyPanelEvent, and renders its children in place of the placeholder.

The loaded attribute is a server-side escape hatch: when true, the children are rendered immediately without any client side observer. This is useful for print views, SEO crawlers, or tests.

<o:lazyPanel loaded="#{bean.printPreview}">
    ...
</o:lazyPanel>

Nested <f:param> or <o:param> children are sent along with the lazy panel ajax request, so that a single listener can serve multiple panels by distinguishing on an entity id, filter key, or page number:

<o:lazyPanel listener="#{bean.preload}">
    <f:param name="productId" value="#{product.id}" />
    ...
</o:lazyPanel>

The listener can read them via Faces#getRequestParameter(). Parameter values are evaluated at initial render (snapshot semantics), consistent with UIParameter usage elsewhere in Faces.

The closest equivalent in PrimeFaces is <p:outputPanel deferred="true" deferredMode="visible">, which loads its contents once the panel is scrolled into view. Under the hood however it uses jQuery scroll handlers on the window combined with $.offset() and window height math, which fires on every scroll event and scales poorly when you have multiple deferred panels on the same page. <o:lazyPanel> uses the native IntersectionObserver which is browser-native, more efficient, and only observes the panel itself; it falls back to scroll/resize/orientationChange listeners only when IntersectionObserver is unavailable. <o:lazyPanel> also has no jQuery or PrimeFaces runtime dependency, it's just a standard faces.ajax.request, so it works in vanilla Faces applications without PrimeFaces. On top of that, <o:lazyPanel> supports <f:param>/<o:param> for passing context to the listener, which <p:outputPanel> does not natively offer.

The homegrown alternative is to wire up an IntersectionObserver yourself which then calls a <h:commandScript> or <p:remoteCommand> from the intersection callback, and to manually swap placeholder markup on response. This works, but it's imperative JavaScript scattered across the view, and you'll have to repeat it for every lazy region. <o:lazyPanel> is the declarative equivalent: one tag, no JavaScript, and the placeholder and listener are just regular Faces markup.

Under the hood

Relatively a lot of things have been cleaned up in the build and source tree. These have no impact on runtime behavior, but they do make the project easier to maintain and contribute to:

  • Automated code formatting via Spotless and Stylistic; all Java, XML, XHTML and TypeScript sources are now formatted consistently on every build. This avoids inconsistently formatted source code coming in with pull requests.
  • The TypeScript sources have been reorganized into their own src/main/ts subfolder. This keeps the context of src/main/webapp clean.
  • The JavaScript build has been improved: browserify and closure-compiler-maven-plugin have been replaced by esbuild for performance and simplicity.
  • Vdlgen now also runs during Eclipse incremental builds, so workspace resolution into sandbox projects continues to work.

Fixes

MultiViews welcome file resolution failed on Windows-based servers due to wrong parent path handling. This has been fixed (#949).

<o:validateBean> did not collect nested properties of @Valid-annotated beans, so validation could silently skip nested constraints. This has been fixed (#951).

@ViewScoped unload threw a NullPointerException during pending view state removal in the specific combination of Spring WebFlow with MyFaces. This has been fixed (#952).

Installation

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

Maven users: add below entry to pom.xml, replacing the older version if any.

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

The 5.3.4 fixes have also been backported to 4.x and 3.x, so OmniFaces 4.7.10 and OmniFaces 3.14.21 have been released as well.

Wednesday, March 25, 2026

OmniFaces 5.2 released!

OmniFaces 5.2 has been released! Relatively a lot of things have been added in barely 2 weeks (5.1 was kind of forcibly released 2 weeks ago because it had an important bugfix). Three new components, a new push transport, and a handful of improvements and fixes.

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

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

New: <o:sse>

Have you ever used <o:socket> for server-to-client push and then discovered that corporate proxies or firewalls block WebSocket connections?

The new <o:sse> component provides an alternative push transport based on Server-Sent Events. It uses plain HTTP, works through any proxy or CDN, has built-in reconnect, and benefits from HTTP/2 multiplexing. No additional dependencies needed. Just async servlet support which is already available since Servlet 3.0 in 2009.

The client side looks familiar:

<o:sse channel="liveUpdates" onmessage="handleUpdate" />

The server side uses the same PushContext interface as <o:socket>, but with a new type attribute on @Push:

@Inject @Push(type = SSE)
private PushContext liveUpdates;

public void sendUpdate() {
    liveUpdates.send("Hello from SSE!");
}

Both <o:sse> and <o:socket> provide one-way (server to client) push. The key difference is the transport. SSE runs over plain HTTP so it works through HTTP infrastructure that may block WebSocket. WebSocket on the other hand is not affected by the browser's per-origin connection limit when the server does not support HTTP/2.

When the server does not support HTTP/2, SSE is the worse choice because browsers hard-limit concurrent HTTP/1.1 connections per origin (current Chrome version has a limit of 6), and multiple SSE channels across tabs may hit this limit and queue further HTTP requests. So the <f:ajax> will simply stop working when the connection limit is hit by solely SSE connections. So in case you wish to use <o:sse>, you need to make absolutely sure that your server (and proxy!) supports HTTP/2, else your application will simply stop working when a page containing a SSE connection is being opened in multiple browser tabs.

When using <o:sse>, then you also need to make sure that every single servlet filter which is mapped on match-all URL pattern of /* is explicitly configured as @WebFilter(asyncSupported=true) on the class or as <async-supported>true</async-supported> in web.xml. In case that's not possible for your filter (e.g because it relies on some request/thread-specific state after invoking chain.doFilter() such as DB connection, locked/shared resource, ThreadLocal, etc), then you'll need to map it on a more specific URL-pattern excluding /omnifaces.sse/* and create yet another filter on /* which forwards to that filter when the request path does not match /omnifaces.sse/*.

In any case, this is a big candidate to end up as new <f:sse> component in a future Jakarta Faces version (like as that <o:socket> ultimately ended up as <f:websocket> in JSF 2.3).

New: <o:notification>

What if you could send browser notifications from the server side as easy as sending a push message? The new <o:notification> component basically extends <o:sse> with the Web Notifications API integration (which was only recently finished, at 16 March 2026). It opens an SSE connection and shows incoming push messages as browser notifications, even when the user is in another tab.

It requires the PWAResourceHandler for the service worker, and a user gesture to request permission:

<h:head>
    ...
    <link rel="manifest" href="#{resource['omnifaces:manifest.webmanifest']}" /> <!-- Activates PWAResourceHandler -->
</h:head>
<h:body>
    <button type="button" onclick="OmniFaces.Notification.requestPermission()">Enable Notifications</button>
    ...
    <o:notification channel="notifications" />
</h:body>

From the server side, inject a NOTIFICATION-typed push context and send a Notification.Message instance:

@Inject @Push(type = NOTIFICATION)
private PushContext notifications;

public void sendNotification() {
    notifications.send(Notification.createNotificationMessage("System maintenance", "The system will undergo maintenance at 22:00 UTC."));
}

You can optionally add a URL so that clicking the notification navigates to it. User-targeted notifications are supported via the user attribute.

public void sendOrderShippedNotification(@Observes OrderShippedEvent event) {
    var userId = event.getUserId();
    var orderId = event.getOrderId();
    notifications.send(Notification.createNotificationMessage("Order shipped", "Your order #" + orderId + " has been shipped.", "/orders/" + orderId), userId);
}

Stacking, silent mode, and requireInteraction can be configured via boolean attributes on <o:notification>.

New: <o:scriptErrorHandler>

Ever wondered how many JavaScript errors your users are silently swallowing and therefore you're unaware which bugs they're actually facing? The new <o:scriptErrorHandler> catches uncaught JavaScript errors and unhandled promise rejections on the client and sends them to the server via navigator.sendBeacon(), where they are fired as CDI events. No additional endpoint boilerplate needed. The servlet is auto-registered when at least one CDI observer on ScriptError is present.

Just put it in the head before any other <h:outputScript> components:

<h:head>
    <o:scriptErrorHandler />
    ...
</h:head>

And observe the events in any (typically application scoped) CDI bean:

@ApplicationScoped
public class ScriptErrorObserver {

    private static final Logger logger = Logger.getLogger(ScriptErrorObserver.class.getName());

    public void onScriptError(@Observes ScriptError error) {
        logger.warning(error.toString());
    }
}

The ScriptError event provides the page URL, error message, error name, stack trace, source URL, line/column number, remote address, user agent, and user principal. Client-side deduplication prevents flooding the server with repeated errors. You can customize default deduplication via maxRecentErrors and errorExpiry attributes which default to 100 errors and 1 minute respectively.

omnifaces.taglib.xml migrated to Vdlgen

The hand-maintained omnifaces.taglib.xml file, which had grown to over 8,000 lines of basically copypasted javadoc blocks, has been completely replaced by Vdlgen. This is a new OmniFaces project: a Java annotation processor that generates the .taglib.xml file from annotations on the source code during compilation. The taglib can never anymore drift from the actual component, converter, validator or function implementation.

Components annotated with @FacesComponent will automatically have their class javadoc copied as tag description and all attribute setters will automatically have their method javadoc copied as attribute descriptions. You can optionally add Vdlgen-provided @FacesAttribute(required = true) to the setter method in order to mark it as a required attribute.

Tag handlers, which don't have annotation support out the box by Faces API, need explicit metadata annotations like this:

/**
 * Tag description.
 */
@FacesTagHandler(namespace = "example.taglib")
public class ExampleTagHandler extends TagHandler {

    /** Tag attribute description. */
    @FacesAttribute(required = true)
    private final TagAttribute type;

    /** Tag attribute description. */
    @FacesAttribute(name = "var", description = "Tag attribute description which overrides javadoc")
    private final String varValue;
}

There are also @FacesComponentConfig, @FacesConverterTag, @FacesValidatorTag, @FacesFunctions, and @FacesFunction annotations. They cover all cases that the .taglib.xml supports. Jakarta Faces own @FacesComponent and @FacesConverter and @FacesValidator annotations already by default recognized; Vdlgen just extends them with the missing metadata.

Ultimately the .taglib.xml file will be read by Vdldoc to generate the VDL documentation (like this), so you only have to write those descriptions in one place. If you maintain your own component library, one should wonder whether this makes your life easier too :)

Changes

The <o:socket> web socket endpoint URL pattern has changed from /omnifaces.push/* to /omnifaces.socket/* because "push" is now not anymore exclusively for web sockets. If you have any web.xml security constraints or proxypass configurations on the old URL pattern, you need to update them!

The @Push annotation got a new type attribute which defaults to SOCKET. The existing @Inject @Push PushContext injection points continue to work unchanged.

The <o:socket> endpoint is now auto-registered when at least one @Inject @Push PushContext appears in the source code. You no longer need to configure the org.omnifaces.SOCKET_ENDPOINT_ENABLED context parameter for this.

SocketPushContextProducer has been deprecated and replaced by PushContextProducer.

Fixes

CombinedResourceHandler: was incompatible with mixed "use strict" scripts. When all scripts are "use strict" then it works fine but when at least one script is not "use strict", then it would break the entire script. So all "use strict" lines will now be stripped during combining. (#921)

@ViewScoped: unload threw IllegalArgumentException in Hacks#removeViewState() when session was already expired at same moment. It's now suppressed and logged as FINEST. (#937)

All 5.2.3 fixes are also available in 4.7.5 for Faces 3.0/4.0 and 3.14.16 for JSF 2.3.

AI assisted development

Large parts of this release were developed with the help of Claude Code. It was used as a pair programming partner for prototyping new components, refactoring shared code, writing javadocs, writing unit and integration tests, and backporting fixes across branches. All generated code was reviewed, tested, and adjusted by hand before committing. AI didn't design the features, it accelerated the implementation of decisions already made, reducing the estimated development time by more than half.

Monday, March 9, 2026

OmniFaces 5.1 released

OmniFaces 5.1 is released! This release brings two new additions and a handful of fixes.

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

As per What's new in OmniFaces? in the showcase:

New: <o:compositeConverter>

Have you ever needed to chain multiple converters on a single input? Until now you'd have to create a custom converter class that delegates to several others. With <o:compositeConverter> you can do it declaratively:

<h:inputText value="#{bean.value}">
    <o:compositeConverter converterIds="trimConverter, sanitizeConverter, entityConverter" />
</h:inputText>

The converterIds attribute takes a comma-separated list of converter IDs (registered via @FacesConverter or in faces-config.xml). The execution order follows a natural symmetry:

  • getAsObject (decode): executes converters left-to-right: 1st → 2nd → 3rd
  • getAsString (encode): executes converters right-to-left: 3rd → 2nd → 1st

The output of each converter is passed as the input to the next. This makes it trivial to compose small, single-responsibility converters without any glue code.

New: #{o:flagEmoji(countryCode)}

A small but a handy one. The new #{o:flagEmoji(countryCode)} EL function converts an ISO 3166-1 alpha-2 country code to its corresponding Unicode flag emoji:

#{o:flagEmoji('NL')}  →  πŸ‡³πŸ‡±
#{o:flagEmoji('US')}  →  πŸ‡ΊπŸ‡Έ
#{o:flagEmoji('BR')}  →  πŸ‡§πŸ‡·

The function is case-insensitive. It returns null for empty input (so EL will basically render it as empty string) and throws IllegalArgumentException for anything that is not a valid 2-letter alphabetic code.

Very useful whenever you want to display a country flag next to a locale selector, a phone prefix dropdown, or any other country-aware component. Never anymore fiddling with a boatload of flag icon files in your project or relying on a 3rd party flag icon library. You can now control it like a font.

Fixes

<o:validateMultiple>: The invalidateAll attribute was missing from the VDL documentation. Fixed. (#927)

CompressableResponseFilter: The IOException was being unnecessarily wrapped in UnsupportedOperationException before being rethrown, making it difficult to filter "connection reset" errors from server logs. It is now rethrown as-is. (#928)

<o:socket> on WildFly: The SocketEndpoint#onClose() failed to clean up socket sessions in memory and hence leaked memory (only on WildFly). This was a regression of #913 (introduced in 5.0 / 4.7.1 / 3.14.12 / 2.7.30). (#931)

All these fixes are also available in 4.7.2 / 3.14.13 / 2.7.31.

Sunday, December 28, 2025

OmniFaces 5.0 has been released!

OmniFaces 5.0 has been released!

The 5.0 is the first major version to finally use the XML namespace in URN format: xmlns:o="omnifaces".

<html lang="en"
    xmlns:ui="jakarta.faces.facelets"
    xmlns:h="jakarta.faces.html"
    xmlns:f="jakarta.faces.core"
    xmlns:a="jakarta.faces.passthrough"
    xmlns:c="jakarta.tags.core"
    xmlns:p="primefaces"
    xmlns:o="omnifaces"
>
    ...
    <o:form id="...">
        ...
        #{o:formatDate(now, 'yyyy-MM-dd HH:mm:ss z')}
        ...
    </o:form>
    ...
</html>

The older XML namespaces are still available for backwards compatiblity, but if you can, you have during OmniFaces 5.x the opportunity to migrate the older XML namespaces xmlns:o="http://omnifaces.org/tags" and xmlns:of="http://omnifaces.org/functions" to the single new XML namespace xmlns:o="omnifaces". Yes, the both old namespaces of OmniFaces tags and EL functions have been merged into a single namespace for sake of simplicity.

The remaining changes in 5.x as seen at What's new in OmniFaces? in the showcase are as follows:

  • Alignment with Jakarta EE 11, so minimum dependencies have changed from Java 11, Faces 3.0, EL 4.0, Servlet 5.0, CDI 3.0, WS 2.0 and BV 3.0 to Java 17, Faces 4.1, EL 6.0, Servlet 6.1, CDI 4.1, WS 2.2 and BV 3.1
  • All things which were during 4.x @Deprecated have been physically removed.
  • JsfLabelMessageInterpolator has been renamed to FacesLabelMessageInterpolator.
  • CombinedResourceHandler won't anymore generate crossorigin/integrity attribute, this has been split into the new and automatically registered CorsAwareResourceRenderer so that it can also deal with non-combined resources.
  • Faces/FacesLocal#isOutputHtml5Doctype() to determine whether the rendered output of the current view is using HTML5 doctype
  • Faces/FacesLocal/Servlets#resetResponse() to reset the HTTP response while retaining the original content type and character encoding
  • Faces/FacesLocal#createResource(UIComponent) to create a Resource based on name and/or library attributs of the given UIComponent
  • <o:formatXyzForLocale> to format using a specific java.util.Locale
  • @RateLimit CDI annotation to rate limit method invocations in CDI managed beans
  • Faces/FacesLocal#getResponseHeaders() which returns all headers with given name from the HTTP response
  • <sec:authorize>, <sec:isAuthenticated> and <sec:isAnonymous> tags for Jakarta Security

Installation

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

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

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

Friday, August 29, 2025

Smallest Working Quarkus 3 Application with Faces 4, OmniFaces 4, and CDI 4

For reference, here is the minimal Maven project structure for a Quarkus 3 application using Faces 4, OmniFaces 4, and CDI 4.

An alternative to Quarkus is Spring Boot. You can find it here: Smallest Working Quarkus 3 Application with Faces 4, OmniFaces 4, and CDI 4.

pom.xml

Below is the minimal Maven configuration, including the quarkus-maven-plugin setup required to produce a JAR:

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>quarkus3-omnifaces4</artifactId>
    <version>1.0.0</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-bom</artifactId>
                <version>3.26.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.apache.myfaces.core.extensions.quarkus</groupId>
            <artifactId>myfaces-quarkus</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>io.quarkiverse.omnifaces</groupId>
            <artifactId>quarkus-omnifaces</artifactId>
            <version>4.6.5</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>3.26.0</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Currently, only MyFaces provides a Quarkus-compatible Faces 4.x implementation. Mojarra still has issue 5442 open on this. OmniFaces 4.x can be used via the Quarkus OmniFaces extension. CDI 4.x is already natively supported by Quarkus.

src/main/java/com/example/backing/ExampleBacking.java

The CDI managed bean representing a Jakarta Faces backing bean utilizing OmniFaces powerful @ViewScoped annotation with its memory-saving unload feature:

package com.example.backing;

import java.io.Serializable;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.omnifaces.cdi.ViewScoped;

import com.example.service.ExampleService;

@Named
@ViewScoped
public class ExampleBacking implements Serializable {

    private static final long serialVersionUID = 1L;

    private String input;
    private String output;

    @Inject
    private ExampleService service;

    public void submit() {
        output = service.process(input);
    }

    public String getInput() {
        return input;
    }

    public void setInput(String input) {
        this.input = input;
    }

    public String getOutput() {
        return output;
    }
}

src/main/java/com/example/service/ExampleService.java

The CDI service:

package com.example.service;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ExampleService {

    public String process(String input) {
        return "Hello! You have typed: " + input;
    }
}

src/main/resources/META-INF/resources/index.xhtml

The Facelet file with a basic Jakarta Faces form:

<!DOCTYPE html>
<html lang="en"
    xmlns:f="jakarta.faces.core"
    xmlns:h="jakarta.faces.html"
>
    <h:head>
        <title>Hello!</title>
    </h:head>
    <h:body>
        <h1>Hello!</h1>
        <h:form>
            <h:outputLabel for="input" value="Type something: " />
            <h:inputText id="input" value="#{exampleBacking.input}" />
            <h:commandButton value="Submit" action="#{exampleBacking.submit}">
                <f:ajax execute="@form" render=":output" />
            </h:commandButton>
        </h:form>
        <h:outputText id="output" value="#{exampleBacking.output}" />
    </h:body>
</html>

Note that web resources, such as Facelets files, must to be placed in the src/main/resources/META-INF/resources folder as if it were a web fragment JAR project rather than src/main/webapp as in a WAR project!

Unlike the Spring Boot 3 approach, you don't need a configuration class like ExampleApplication or a beans.xml file.

Build and run it!

First cd into the folder where the pom.xml is located.

Now create the Thin JAR:

mvn clean package

Then execute the Thin JAR:

java -jar target/quarkus-app/quarkus-run.jar

Finally launch your default web browser on http://localhost:8080/index.xhtml:

browse http://localhost:8080/index.xhtml

How about a Fat JAR?

In Quarkiverse, this is called an Uber JAR. You need to provide an additional build argument:

mvn clean package -Dquarkus.package.jar.type=uber-jar

Then execute the Uber JAR:

java -jar target/quarkus3-omnifaces4-1.0.0-SNAPSHOT-runner.jar

You can add a property to the pom.xml to make this the default behavior when running the mvn clean package command.

<properties>
    <quarkus.package.jar.type>uber-jar</quarkus.package.jar.type>
</properties>

Friday, December 27, 2024

Smallest Working Spring Boot 3 Application with Faces 4, OmniFaces 4, and CDI 4

For reference, here is the minimal Maven project structure for a Spring Boot 3 application using Faces 4, OmniFaces 4, and CDI 4.

While Spring can function without CDI, the OmniFaces @ViewScoped annotation, with its powerful unload feature, doesn't work out of the box without CDI. Along with the CDI managed bean, we're also creating a simple @ApplicationScoped CDI service for demonstration purposes.

An alternative to Spring Boot is Quarkus. You can find it here: Smallest Working Quarkus 3 Application with Faces 4, OmniFaces 4 and CDI 4.

pom.xml

Below is the minimal Maven configuration, including the spring-boot-maven-plugin setup required to produce a Fat JAR:

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>springboot3-omnifaces4</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>org.joinfaces</groupId>
            <artifactId>omnifaces-spring-boot-starter</artifactId>
            <version>5.4.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

We're using JoinFaces to auto-configure a Spring Boot 3.x application with Faces 4.x (Mojarra), OmniFaces 4.x, and CDI 4.x (Weld) with minimal effort. Noted should be that we need a minimum version of JoinFaces 5.4.3 in case you wish to be able to launch it as a Fat JAR, because in previous versions the beans.xml wasn't correctly autodiscovered, see also JoinFaces issue 2673.

src/main/java/com/example/ExampleApplication.java

The mandatory Spring Boot application configurer and launcher:

package com.example;

import jakarta.inject.Named;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;

@Configuration
@SpringBootApplication
@ComponentScan(excludeFilters = @Filter(Named.class))
public class ExampleApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

Note that the @ComponentScan is configured to exclude all classes annotated with CDI's @Named. This basically prevents Spring from auto-registering all @Named-annotated classes as Spring managed beans, hereby completely overriding CDI. Without it, any @Named-annotated class would behave as a Spring singleton bean, which is like a CDI @ApplicationScoped.

src/main/java/com/example/backing/ExampleBacking.java

The CDI managed bean representing a Jakarta Faces backing bean utilizing OmniFaces powerful @ViewScoped annotation with its memory-saving unload feature:

package com.example.backing;

import java.io.Serializable;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.omnifaces.cdi.ViewScoped;

import com.example.service.ExampleService;

@Named
@ViewScoped
public class ExampleBacking implements Serializable {

    private static final long serialVersionUID = 1L;

    private String input;
    private String output;

    @Inject
    private ExampleService service;

    public void submit() {
        output = service.process(input);
    }

    public String getInput() {
        return input;
    }

    public void setInput(String input) {
        this.input = input;
    }

    public String getOutput() {
        return output;
    }
}

src/main/java/com/example/service/ExampleService.java

The CDI service:

package com.example.service;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ExampleService {

    public String process(String input) {
        return "Hello! You have typed: " + input;
    }
}

Note, in case you wish this class to be a Spring-managed bean such as @Service and inject it via @AutoWired in your CDI managed bean, then you need a custom CDI extension. Detail can be found in this related article: Using OmniFaces CDI @ViewScoped with unload/destroy in a Spring Boot project.

src/main/resources/META-INF/resources/index.xhtml

The Facelet file with a basic Jakarta Faces form:

<!DOCTYPE html>
<html lang="en"
    xmlns:f="jakarta.faces.core"
    xmlns:h="jakarta.faces.html"
>
    <h:head>
        <title>Hello!</title>
    </h:head>
    <h:body>
        <h1>Hello!</h1>
        <h:form>
            <h:outputLabel for="input" value="Type something: " />
            <h:inputText id="input" value="#{exampleBacking.input}" />
            <h:commandButton value="Submit" action="#{exampleBacking.submit}">
                <f:ajax execute="@form" render=":output" />
            </h:commandButton>
        </h:form>
        <h:outputText id="output" value="#{exampleBacking.output}" />
    </h:body>
</html>

Note that web resources, such as Facelets files, must to be placed in the src/main/resources/META-INF/resources folder as if it were a web fragment JAR project rather than src/main/webapp as in a WAR project!

src/main/resources/BOOT-INF/classes/META-INF/beans.xml

The mandatory beans.xml file to activate all the things CDI such as @Named beans:

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
    version="4.0" bean-discovery-mode="annotated"
>
</beans>

Do note that the beans.xml needs to go into the src/main/resources/BOOT-INF/classes/META-INF folder in order to get the Fat JAR execution using java -jar command to work. In case you wish to be able to execute/debug the application by directy executing the main() method using an IDE or the mvn spring-boot:run command, then you need to place a copy of the beans.xml in the src/main/resources/META-INF folder as well.

Also note that the bean-discovery-mode is explicitly set to annotated so that CDI leaves any Spring managed beans alone, even though this is the default behavior since CDI version 4.0.

In case you wish to use a src/main/webapp folder like in a standard Maven WAR project structure, even if only in order to have a clear oversight of all the web resources and WAR-related deployment descriptors, or to avoid the need to create copies of beans.xml files, then you can always reconfigure the <build> section of your pom.xml accordingly so that all these files ultimately end up in the right locations of the produced Fat JAR file as expected by Spring Boot. A concrete example can be found in this related Stack Overflow answer: src/main/webapp is IGNORED when packaging is JAR instead of WAR.

Build and run it!

First cd into the folder where the pom.xml is located.

Now create the Fat JAR:

mvn clean package

Then execute the Fat JAR:

java -jar target/springboot3-omnifaces4-1.0.0.jar

Finally launch your default web browser on http://localhost:8080/index.xhtml:

browse http://localhost:8080/index.xhtml

BONUS: adding PrimeFaces

Simply add this to the <dependencies> section of the pom.xml:

        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>15.0.0</version>
            <classifier>jakarta</classifier>
        </dependency>

And update the index.xhtml as follows:

<!DOCTYPE html>
<html lang="en"
    xmlns:f="jakarta.faces.core"
    xmlns:h="jakarta.faces.html"
    xmlns:p="primefaces"
>
    <h:head>
        <title>Hello!</title>
    </h:head>
    <h:body>
        <h1>Hello!</h1>
        <h:form>
            <p:outputLabel for="input" value="Type something: " />
            <p:inputText id="input" value="#{exampleBacking.input}" />
            <p:commandButton value="Submit" action="#{exampleBacking.submit}" update=":output" />
        </h:form>
        <h:outputText id="output" value="#{exampleBacking.output}" />
    </h:body>
</html>

That's all! Now re-execute the previously instructed build-and-run commands.

Sunday, November 24, 2024

OmniFaces 5.0-M1 / 4.6 / 3.14.7 / 2.7.27 have been released!

OmniFaces 5.0-M1 has been released!

The 5.0-M1 is the first version to finally use the XML namespace in URN format: xmlns:o="omnifaces".

<html lang="en"
    xmlns:ui="jakarta.faces.facelets"
    xmlns:h="jakarta.faces.html"
    xmlns:f="jakarta.faces.core"
    xmlns:a="jakarta.faces.passthrough"
    xmlns:c="jakarta.tags.core"
    xmlns:p="primefaces"
    xmlns:o="omnifaces"
>
    ...
    <o:form id="...">
        ...
        #{o:formatDate(now, 'yyyy-MM-dd HH:mm:ss z')}
        ...
    </o:form>
    ...
</html>

The older XML namespaces are still available for backwards compatiblity, but if you can, you have during OmniFaces 5.x the opportunity to migrate the older XML namespaces xmlns:o="http://omnifaces.org/tags" and xmlns:of="http://omnifaces.org/functions" to the single new XML namespace xmlns:o="omnifaces". Yes, the both old namespaces of OmniFaces tags and EL functions have been merged into a single namespace for sake of simplicity.

The remaining changes in 5.x are:

  • Alignment with Jakarta EE 11, so minimum dependencies have changed from Java 11, Faces 3.0, EL 4.0, Servlet 5.0, CDI 3.0, WS 2.0 and BV 3.0 to Java 17, Faces 4.1, EL 6.0, Servlet 6.1, CDI 4.1, WS 2.2 and BV 3.1; as of now, OmniFaces 5.0 is technically still backwards compatible with Faces 4.0, EL 4.0, Servlet 5.0, CDI 3.0, WS 2.0 and BV 3.0 as none of the newer features are actually being used, but that may thus change in the future. Do note that it is not anymore backwards compatible with Faces 3.0.
  • All things which were during 4.x @Deprecated have been physically removed.
  • JsfLabelMessageInterpolator has been renamed to FacesLabelMessageInterpolator.
  • CombinedResourceHandler won't anymore generate crossorigin/integrity attribute, this has been split into the new and automatically registered CORSAwareResourceRenderer so that it can also deal with non-combined resources.

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

Installation

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

Maven users: use <version>5.0-M1</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>5.0-M1</version>
</dependency>

How about OmniFaces 4.x?

OmniFaces 4.6 has also been released at the same time. There are a bunch of relatively minor changes, among others:

  • org.omnifaces.util.Ajax has been split into Ajax and AjaxLocal following the same ideology as the already-existing FacesLocal, MessagesLocal and BeansLocal.
  • org.omnifaces.util.Components has been split into Components and ComponentsLocal following the same ideology as the already-existing FacesLocal, MessagesLocal and BeansLocal.

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

The 4.6 will probably be the latest version of the 4.x range in OmniFaces and in order to move forward with 5.x, the 4.x will only accept bugfixes and be released as 4.6.x.

How about OmniFaces 3.x, 2.x and 1.1x?

OmniFaces 3.x got the same bugfixes as 4.6 and has been released as 3.14.7. This version is for JSF 2.3 users with CDI. In case you've already migrated to Faces 3.0 or 4.0, please use OmniFaces 4.x instead. OmniFaces 2.x got the same bugfixes as well and has been released as 2.7.27. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, please use OmniFaces 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), basically 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.

Saturday, August 31, 2024

OmniFaces 4.5 / 3.14.6 / 2.7.26 released!

OmniFaces 4.5 has been released!

Relatively a lot of things have been added/updated/fixed! :)

The most important being:

  • <o:criticalStylesheet> component which which extends the standard <h:outputStylesheet> with default rendering of <link rel="preload" as="style"> attributes which automatically change to <link rel="stylesheet"> during window load event and is automatically moved to very top of the head and is also treated separately from default CSS resources when using CombinedResourceHandler
  • All OmniFaces converters and validators are now also available as tags, so e.g. omnifaces.SelectItemsConverter is now also available as <o:selectItemsConverter>
  • FullAjaxExceptionHandler will now automatically register the FacesExcepitonFilter when absent in web.xml
  • GzipResponseFilter has been renamed to CompressedResponseFilter and got additional support for Brotli and Deflate compression algorithms whereby the best and available algorithm will be auto-detected

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

Installation

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

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

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

How about OmniFaces 3.x, 2.x and 1.1x?

OmniFaces 3.x got the same bugfixes as 4.5 and has been released as 3.14.6. This version is for JSF 2.3 users with CDI. In case you've already migrated to Faces 3.0 or 4.0, please use OmniFaces 4.x instead. OmniFaces 2.x got the same bugfixes as well and has been released as 2.7.26. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, please use OmniFaces 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), basically 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.

Sunday, April 14, 2024

OmniFaces 4.4 / 3.14.5 / 2.7.25 released!

OmniFaces 4.4 has been released!

It contains a major change in the LRU map which is used under the covers of @ViewScoped and <o:cache> in order to reduce the amount of AtomicReference instances leaving behind in the heap memory. For the remainder two new helper methods to create components have been introduced along a handful bugfixes.

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

Installation

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

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

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

How about OmniFaces 3.x, 2.x and 1.1x?

OmniFaces 3.x got the same bugfixes as 4.4 and has been released as 3.14.5. This version is for JSF 2.3 users with CDI. In case you've already migrated to Faces 3.0 or 4.0, please use OmniFaces 4.x instead. OmniFaces 2.x got the same bugfixes as well and has been released as 2.7.25. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, please use OmniFaces 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), basically 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.

Thursday, November 23, 2023

Using OmniFaces CDI @ViewScoped with unload/destroy in a Spring Boot project

Introduction

While working on a Spring Boot based Faces project having memory issues, one of the solutions is to migrate the existing @Scope("view") beans and/or @SessionScope beans disguised as view scoped beans to use OmniFaces @ViewScoped instead, so that they get destroyed immediately when the webpage unloads and hereby also immediately frees up memory occuppied by the managed bean state as well as the Faces view state instead of basically having them to accumulate during the rest of the HTTP session.

Install JoinFaces in any case

JoinFaces is a great library for easily integrating Jakarta Faces in a Spring Boot project. However it unfortunately only supports using the standard Faces view scope @jakarta.faces.view.ViewScoped on a Spring managed bean. On the other hand, though, JoinFaces is already kind of the standard approach in order to effortlessly be able to use Jakarta Faces, Jakarta CDI and OmniFaces (and PrimeFaces, of course) in a Spring Boot project. It really takes away a lot of potential boilerplate in order to properly configure them in a Spring Boot based project. It's a matter of adding the following dependency (in Maven flavor) when you're already on Spring Boot 3.x or newer:

<dependency>
    <groupId>org.joinfaces</groupId>
    <artifactId>omnifaces-spring-boot-starter</artifactId>
    <version>5.x.x</version>
</dependency>

Or when you're still on Spring Boot 2.x:

<dependency>
    <groupId>org.joinfaces</groupId>
    <artifactId>omnifaces3-spring-boot-starter</artifactId>
    <version>4.x.x</version>
</dependency>

Getting OmniFaces @ViewScoped to work

One way would be to create a custom Spring Scope for this and port all the existing logic behind OmniFaces @ViewScoped over there, including the auto-inclusion of the unload script. However this seems to be unnecessary (not DRY) and brittle (sensitive to breaking when something incompatibly changes in the original OmniFaces @ViewScoped implementation).

Another way would be to just use a CDI managed bean instead of a Spring managed bean. Thanks to JoinFaces, CDI is readily available. However, the project already has a lot of existing Spring managed beans/services/repositories which couldn't be injected in a CDI managed bean via CDI's own @Inject. So they had to be injectable in a CDI managed bean in some way, without the need to convert these Spring managed beans to CDI managed beans and possibly losing any Spring-induced advantages/features on them and then requiring us to unnecessarily rewrite/migrate yet another bunch of code.

The idea (with help of Arjan Tijms) is to autodetect fields annotated with Spring-specific @Autowired in a CDI managed bean and simply let CDI fetch it from Spring context while injecting. This can be done with help of a CDI Extension. The major advantage is that we can then fully leverage to the original implementation of the CDI scope, including all of its originally intended behavior and features. In other words, we'll with a special CDI Extension be able to use @Named beans with @Autowired properties. This way we can transparently use OmniFaces @ViewScoped or any other CDI-specific scope without the need to copy/rewrite it.

Activate CDI

To activate CDI in a Spring Boot project after installing JoinFaces, which by default uses the Weld implementation, simply add a beans.xml file to the src/main/resources/META-INF folder of the Spring Boot project:

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
    version="4.0" bean-discovery-mode="annotated">
</beans>

Or when you're still on Spring Boot 2.x:

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
    version="2.0" bean-discovery-mode="annotated">
</beans>

Note that this doesn't affect any existing Spring managed bean configurations. By default, they still have higher precedence. Moreover, the key configuration is explicitly setting CDI bean discovery mode to annotated so that CDI only tries to scan for beans with CDI's own bean defining annotations.

Also note that when running the Spring Boot application as a (fat) JAR file, the beans.xml needs to be located in BOOT-INF/classes/META-INF folder of the generated JAR file, not in the root META-INF folder containing web resources.

Instruct Spring to ignore CDI managed beans

We need to explicitly instruct Spring to ignore CDI managed beans annotated by @Named. Otherwise the Spring bean management facility may still have higher precedence to find and manage them, particularly if they are in the same package which is covered by @ComponentScan. This can be achieved by adding excludeFilters=@Filter(Named.class) to the @ComponentScan annotation on your Spring @Configuration class.

@Configuration
@ComponentScan(basePackages = YourSpringConfiguration.BEANS_PACKAGE, excludeFilters = @Filter(Named.class))
public class YourSpringConfiguration implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    public static final String BEANS_PACKAGE = "com.example.beans";

    // ...
}

Make ApplicationContext available in static context

Next step is to make sure that Spring's ApplicationContext is available in static context, so that the CDI extension can access it while trying to fetch Spring managed beans. There's namely no dedicated API for this in Spring Boot (yet?) such as Faces has with FacesContext.getCurrentInstance() and as CDI has with CDI.current().getBeanManager(). In a Spring Boot application, the canonical approach is to have a Spring managed bean implementing ApplicationContextAware. The average Spring Boot project should already have one somewhere, but in case yours doesn't seem to have one, then you can add one as follows:

@Component
public class Spring implements ApplicationContextAware {
 
    private static ApplicationContext context;
 
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
    }
 
    public static ApplicationContext getContext() {
        return context;
    }
}

Create CDI extension

Now we can create the CDI extension which autodetects fields annotated with Spring's @Autowired in CDI managed beans annotated with @Named and makes sure that these fields are injected with beans obtained from Spring context.

public class SpringAutowiredExtension implements Extension {

    private Map<Class<?>, String> autowiredFields = new ConcurrentHashMap<>();

    public <T> void processAnnotatedType(@Observes @WithAnnotations(Named.class) ProcessAnnotatedType<T> processAnnotatedType, BeanManager beanManager) {
        Class<T> beanClass = processAnnotatedType.getAnnotatedType().getJavaClass();

        if (!beanClass.getPackage().getName().startsWith(YourSpringConfiguration.BEANS_PACKAGE)) {
            return; // This should filter out any CDI managed beans provided by e.g. Faces and OmniFaces themselves.
        }

        processAnnotatedType.configureAnnotatedType()
            .filterFields(field -> field.isAnnotationPresent(Autowired.class))
            .forEach(this::registerAutowiredField);
    }

    private void registerAutowiredField(AnnotatedFieldConfigurator<?> fieldConfigurator) {
        fieldConfigurator.add(InjectLiteral.INSTANCE);
        Field field = fieldConfigurator.getAnnotated().getJavaMember();
        autowiredFields.put(field.getType(), field.getName());
    }

    public void afterBeanDiscovery(@Observes AfterBeanDiscovery event) {
        autowiredFields.entrySet().forEach(autowiredField -> injectBeanViaSpringContext(event, autowiredField.getKey(), autowiredField.getValue()));
    }

    private static void injectBeanViaSpringContext(AfterBeanDiscovery event, Class<?> type, String name) {
        event.addBean().addType(type).createWith(ignoreCdiContext -> getBeanFromSpringContext(type, name));
    }

    private static Object getBeanFromSpringContext(Class<?> type, String name) {
        try {
            try {
                return Spring.getContext().getBean(type);
            }
            catch (NoUniqueBeanDefinitionException ignore) {
                return Spring.getContext().getBean(name);
            }
        }
        catch (Exception e) {
            throw new IllegalStateException("Cannot get bean from Spring context", e);
        }
    }
}

Register CDI extension

In order to get it to run, create a file with the exact name jakarta.enterprise.inject.spi.Extension in the src/main/resources/META-INF/services folder of the Spring Boot project and add a line to it representing the FQN of the CDI extension:

com.example.cdi.SpringAutowiredExtension

When you're still on Spring Boot 2.x, then the file should be named javax.enterprise.inject.spi.Extension

To confirm, when running the Spring Boot application as a (fat) JAR file, this file needs to be located in BOOT-INF/classes/META-INF/services folder of the generated JAR file.

Profit

That's basically all! You should now be able to use CDI managed beans with OmniFaces @ViewScoped and still therein have Spring managed beans at your availability via @Autowired.

@Named
@ViewScoped
public class YourViewScopedBean implements Serializable {
 
    private static final long serialVersionUID = 1L;

    @Autowired
    private SomeSpringComponent someSpringComponent;

    @PostConstruct
    public void init() {
        // SomeSpringComponent should be readily available at this point.
        // This is the key task of the CDI extension.
    }
 
    @PreDestroy
    public void destroy() {
        // This should be correctly invoked on page unload.
        // This is the key advantage over standard Faces @ViewScoped annotation.
    }
}

Saturday, September 23, 2023

OmniFaces 4.3 / 3.14.4 / 2.7.24 released!

OmniFaces 4.3 has been released!

A couple of new things were introduced:

  • <o:importConstants> and <o:importFunctions> got a new loader attribute where you can specify an object whose class loader will be used to load the class specified in the type attribute. In the end this should allow you to use a more specific class when there are duplicate instances in the runtime classpath, e.g. via multiple (plugin) libraries.
  • Inspired by this new loader attribute, a new <o:loadBundle> taghandler was introduced which also allows you to use loader attribute to load the bundle from a more specific library in case there are duplicate instances in the runtime classpath.
  • Faces(Local)#isAuthenticated() so that you can with less code check whether the current request is authenticated.

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

Installation

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

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

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

How about OmniFaces 3.x, 2.x and 1.1x?

OmniFaces 3.x got the same bugfixes as 4.3 and has been released as 3.14.4. This version is for JSF 2.3 users with CDI. In case you've already migrated to Faces 3.0 or 4.0, please use OmniFaces 4.x instead. OmniFaces 2.x got the same bugfixes as well and has been released as 2.7.24. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, please use OmniFaces 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), basically 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.

Saturday, June 24, 2023

OmniFaces 4.2 / 3.14.3 / 2.7.23 released!

OmniFaces 4.2 has been released!

The PWAResourceHandler can now also be triggered via manifest.webmanifest instead of manifest.json. The manifest.webmanifest will eventually become the default resource name as per latest W3C draft on this.

The Components utility class got a bunch of new methods:

  • getRenderedValue(ValueHolder valueHolder) so that you can easily grab the to-be-rendered value of any ValueHolder component, particularly taking into account the internal state of any UIInput component, without the need to do all the checks if there's a submitted value or a local value or a converter etc
  • invalidateInputs(String... clientIds) so that you can explicitly invalidate specific UIInput components by (relative) client ID, e.g. when a specific DB constraint violation exception was thrown during a bean action which was unavoidable by a validator
  • invalidateInput(String clientId, String message, String... messageParams) which does basically the same but then allows you to add a faces message

The Messages utility class got a little brother who doesn't anywhere invoke FacesContext.getCurrentInstance(), the MessagesLocal. It has the same philosophy as FacesLocal.

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

Installation

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

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

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

How about OmniFaces 3.x, 2.x and 1.1x?

OmniFaces 3.x got the same bugfixes as 4.2 and has been released as 3.14.3. This version is for JSF 2.3 users with CDI. In case you've already migrated to Faces 3.0 or 4.0, please use OmniFaces 4.x instead. OmniFaces 2.x got the same bugfixes as well and has been released as 2.7.23. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, please use OmniFaces 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), basically 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.

Sunday, October 2, 2022

OmniFaces 4.0 has been released!

OmniFaces 4.0 has been released!

Finally, as a response to the Jakarta EE 10 release on 22 September 2022, OmniFaces 4.0 is now final after a long serie of milestones!

It's basically the Jakartified version of 3.14 with a few breaking changes. OmniFaces 4.0 is compatible with Faces 4.0 from Jakarta EE 10 as well as Faces 3.0 from Jakarta EE 9.1. OmniFaces 4.0 requires a minimum of Java 11.

Breaking changes

  • Minimum dependencies are changed from Java 1.8, JSF 2.3, EL 3.0, Servlet 3.1, CDI 2.0, WS 1.1 and BV 2.0 to Java 11, Faces 3.0, EL 4.0, Servlet 5.0, CDI 3.0, WS 2.0 and BV 3.0; as of now, OmniFaces 4.0 is NOT backwards compatible with previous versions of these dependencies because of the compiler-incompatible rename of the javax.* package to jakarta.* package.
  • All things which were during 3.x @Deprecated have been physically removed, such as <o:form includeViewParams="true"> (has become the default behavior), WebXml.INSTANCE and FacesConfigXML.INSTANCE (replaced by WebXml.instance() and FacesConfigXml.instance() respectively), and omnifaces:fixviewstate.js (replaced by JSF 2.2 native fix)
  • #{now} and #{startup} will now return an instance of java.time.temporal.Temporal instead of java.util.Date; it still supports the time property as in #{now.time} and #{startup.time} in a backwards compatible manner; additionally, it's got two new convenience properties: instant and zonedDateTime as in #{now.instant}, #{now.zonedDateTime}, #{startup.instant} and #{startup.zonedDateTime}.
  • Callback interfaces which dated from Java 1.7 and have replacements available in Java 1.8 are now @Deprecated. Callback.Void is replaced by Runnable, Callback.Returning is replaced by Supplier, Callback.WithArgument is replaced by Consumer and Callback.ReturningWithArgument is replaced by Function. Utility methods in Components and Events are have been adjusted to use the new types. True, this could have been done during the OmniFaces 3.x era, but this was simply completely overlooked.

New things

There's a new utility method important for component library authors who'd like to be compatible with both Faces 3.0 and 4.0: Components#addFacesScriptResource(). This will allow the component developer to support both Faces 3.0 jakarta.faces:jsf.js and Faces 4.0 jakarta.faces:faces.js as resource dependency. This utility method exists because it is technically not possible to use a variable as an attribute of the annotation such as @ResourceDependency. In other words, something like below won't work because it's not allowed by the Java compiler:

@FacesComponent
@ResourceDependency(library="jakarta.faces", name=(isFaces4() ? "faces.js" : "jsf.js"), target="head")
public class YourCustomComponent extends UISomething {
    // ...
}

It really has to be compile time constant. So the best way to go is to perform this in a constructor with help of Events#subscribeToRequestBeforePhase() and Components#addFacesScriptResource().

@FacesComponent
public class YourCustomComponent extends UISomething {
   
    public YourCustomComponent() {
        Events.subscribeToRequestBeforePhase(RENDER_RESPONSE, Components::addFacesScriptResource);
    }

    // ...
}

Another new thing is that the Faces#addResponseCookie() got a new overload accepting a new Map<String, String> argument representing custom attributes to be added to the cookie. This way you can add e.g. SameSite:None to the cookie.

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

As a refresher, you can find everything about new things in Faces 4.0 in this blog post: What's new in Faces 4.0?

Changed things

The omnifaces.js is now not anymore sourced by "plain vanilla JavaScript", but by TypeScript. The biggest advantage of TypeScript is that it allows us to transpile the exact same source code to an older ES version such as ES5 or even ES3 for ancient webbrowsers. It's the idea to do the same for Jakarta Faces own faces.js. The omnifaces.js is currently targeted at ES5, so it's compatible with the browsers listed here.

Integration tests

Currently, all integration tests pass successfully on the following servers:

  • WildFly 27 (Mojarra 4)
  • WildFly 26 (Mojarra 3)
  • GlassFish 7 (Mojarra 4)
  • GlassFish 6 (Mojarra 3)
  • TomEE 9 (MyFaces 3)
  • Tomcat 10 + Mojarra 4
  • Tomcat 9 + Mojarra 3
  • Tomcat 9 + MyFaces 3

The following servers were also available but are deactivated because not all integration tests pass:

  • Liberty 21.0.0.12-beta -- because of deployment timeout on EagerIT and ParamIT for a yet unclear reason; probably related to CDI extension
  • Tomcat 10 + MyFaces 4.0.0-RC1 -- because every subsequent Ajax form submit isn't seen as type=XHR but as type=NONE by Graphene
  • Piranha -- because it explicitly requires JDK17 and a newer Arquillian/Selenium version which isn't yet supported by all other servers

This will be investigated and fixed later on.

Installation

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

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

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

In case you're still on JSF 2.3, then use 3.14. With this version, the 3.x branch will enter maintenance mode like as 2.x. For now.

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.14 and has been released as 2.7.20. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead. Or in case you've already migrated to Faces 3.0 or 4.0, use 4.x instead.

The 1.1x is basically already since 2.5 in securityfix mode. I.e. only security-related 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.

Monday, November 15, 2021

OmniFaces 3.12 released!

OmniFaces 3.12 has been released!

Nothing shocking. Just one new tag attribute and a bunch of improvements/fixes as compared to 3.11.

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

Installation

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

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

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

Faces 4.0 compatibility

In the meanwhile, Mojarra 4.0.0-M1 has been released as the first Faces 4.0 implementation. Where JSF 3.0 is basically exactly the same as JSF 2.3 with merely the package renamed from javax.* to jakarta.*, Faces 4.0 will bring real changes along. Basically everything which was @Deprecated in all previous JSF versions has now finally been physically removed, including JSP support, hereby reducing the total JAR file size with nearly a half megabyte. Further it also comes with a handful goodies, which you can read about in What's new in Faces 4.0?

OmniFaces 4.0-M12 is the first OmniFaces version which is not only compatible with JSF 3.0, but also fully compatible with Faces 4.0. It's basically the Jakartified version of OmniFaces 3.12 with here and there small adjustments in order to be runtime compatible with both JSF 2.3 and Faces 4.0. Especially the ability to detect whether jsf.js or faces.js should be auto-included as a resource dependency of a custom component had to be added in order to achieve this. This is also available as a new utility method: Components#addFacesScriptResource().

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.12 and has been released as 2.7.14. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical 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.

Sunday, April 4, 2021

OmniFaces 3.11 released!

OmniFaces 3.11 has been released!

Nothing shocking. Just one new utility method and a small handful improvements/fixes. It's been more than 2 months since previous release, so it's about time for a new release even if it's relatively minor.

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

Installation

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

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

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

If you're already on Jakarta EE 9 (e.g. WildFly 22, OpenLiberty 21, etc), then use 4.0-M8 instead. It's the Jakartified version of 3.11.

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.11 and has been released as 2.7.11. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical 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.

Sunday, January 24, 2021

OmniFaces 3.10 released!

OmniFaces 3.10 has been released!

It introduces among others the new ViewResourceHandler which enables using JSF components and EL expressions in non-Facelets files, such as /sitemap.xml and /robots.txt, a new <o:sitemapUrl> component, and a new lazy="true" attribute for the <o:graphicImage.

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

Installation

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

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

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

If you're already on Jakarta EE 9 (e.g. WildFly 22, OpenLiberty 21, etc), then use 4.0-M7 instead. It's the Jakartified version of 3.10.1.

Using JSF components and EL expressions in non-Facelets files

The ViewResourceHandler allows you to register a specific set of non-Facelets files as JSF views. This will allow you to use JSF components and EL expressions in among others /sitemap.xml and /robots.txt.

In order to install it, add it as resource handler in faces-config.xml:

<application>
    <resource-handler>org.omnifaces.resourcehandler.ViewResourceHandler</resource-handler>
</application>

The specific set of non-Facelets files can in turn be defined as the value of a web.xml context parameter with the name org.omnifaces.VIEW_RESOURCE_HANDLER_URIS which accepts a comma separated string:

<context-param>
    <param-name>org.omnifaces.VIEW_RESOURCE_HANDLER_URIS</param-name>
    <param-value>/robots.txt, /sitemap.xml</param-value>
</context-param>

A live demo can be seen on the ViewResourceHandler showcase page.

New component specifically for sitemap.xml

The <o:sitemapUrl> component is designed specifically for the /sitemap.xml. The prerequisite is that the above mentioned ViewResourceHandler is installed and configured for /sitemap.xml. It basically generates a sitemap-specific <url> element, whereby a given JSF view ID will be automatically converted to a bookmarkable URL in <loc>. Optionally the lastModified, changeFrequency and priority attributes can also be specified which then generate the associated <lastmod>, <changefreq> and <priority> elements.

A live demo can be seen on the ViewResourceHandler showcase page.

Lazy images

The existing <o:graphicImage> component has been enhanced to support the new lazy="true" attribute.

<o:graphicImage ... lazy="true" />

You can use it with any type of image and image source, as long as the dataURI is not set, else the lazy attribute is simply ignored.

When set, then the image will only be actually loaded when the window is finished loading, and the image is visible in the viewport. The trick is done by initially rendering an empty SVG image as data URI in src attribute and rendering the actual URL in the data-src attribute, along with a data-lazy="true" flag. Something like below (taken from the <o:graphicImage> showcase page):

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E" 
     data-src="/javax.faces.resource/Images_getSvgLogo.svg.xhtml?ln=omnifaces.graphic&v=1611510995216"
     data-lazy="true" />

The actual lazy loading work is done by the automatically included graphicimage.js script.

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.10.1 and has been released as 2.7.10. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical 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.