Monday, October 28, 2024

How to migrate from EJB to CDI?

Introduction

Since Jakarta EE 10, especially with Jakarta Concurrency 3.0 and Jakarta Transactions 2.0, we can use CDI to substitute a @Stateless EJB. Jakarta Concurrency 3.0 delivers the new @Asynchronous annotation as well as the CronTrigger helper to substitute EJB's @Asynchronous and @Schedule. Jakarta Transactions 2.0 delivers the new Transactional.TxType enum to substitute EJB's TransactionAttributeType. In case you wish to migrate away from EJB to CDI, or simply need to know the "canonical" CDI approach to transactional business service beans, then you may find this guideline helpful.

Migrating @Stateless EJB to CDI

Here's how the average stateless bean in EJB looks like (note: method arguments and return types are omitted for brevity):

package com.example;

import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@Stateless
public class StatelessBeanInEJB {

    @PersistenceContext
    private EntityManager entityManager;

    // The @TransactionAttribute(TransactionAttributeType.REQUIRED) annotation is optional; this is the default already.
    public void transactionalMethod() {
        // ...
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public void nonTransactionalMethod() {
        // ...
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void independentTransactionalMethod() {
        // ...
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public void optionalTransactionalMethod() {
        // ...
    }
}

And here's the equivalent in CDI, with help of the in Jakarta Transactions 2.0 introduced Transactional.TxType enum, and demonstrating that you can perfectly fine continue using the EntityManager the same way:

package com.example;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;

@ApplicationScoped
public class StatelessBeanInCDI {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional // The annotation value TxType.REQUIRED is optional; this is the default already.
    public void transactionalMethod() {
        // ...
    }

    @Transactional(TxType.NOT_SUPPORTED)
    public void nonTransactionalMethod() {
        // ...
    }

    @Transactional(TxType.REQUIRES_NEW)
    public void independentTransactionalMethod() {
        // ...
    }

    @Transactional(TxType.SUPPORTS)
    public void optionalTransactionalMethod() {
        // ...
    }
}

Noted should be that @Stateless has one more feature in EJB: it's pooled. There's no such equivalent in CDI and there is also not really a need for it as the stateless CDI bean has been marked @ApplicationScoped and CDI instances are unsynchronized while EJB instances are synchronized. In case you need a business service bean which potentially holds state, and therefore you're forced to mark it @RequestScoped, and you want to reduce the cost of construction, then you could consider using the @Pooled annotation of the OmniServices utility library for this.

Another reason to use pooling would be "throttling", so that the back-end access is kind of secured behind a FIFO queue at business service bean level (even though it could also be throttled at HTTP server, JDBC connection pool, and DB level). There's also no such equivalent in CDI, but it might be good to know that the upcoming Jakarta Concurrency 3.2 for Jakarta EE 12 may according to issue 136 offer a @MaxConcurrency annotation for that. My advice is, measuring is knowing. Hardware and JVM (garbage collection especially) capabilities have made so much progress since the introduction of EJB (1998!) that one should wonder whether throttling at business service level and/or reducing the constructor calls is still absolutely necessary these days.

Also noted should be that @Transactional is also supported as a class-level annotation. So that opens the possibility to create a new CDI @Stereotype annotation such as @Service which basically combines @ApplicationScoped and @Transactional into a single annotation, much closer to the behavior of @Stateless.

Migrating EJB @Asynchronous to CDI

Here's how the average @Asynchronous methods in EJB look like:

package com.example;

import java.util.concurrent.Future;

import jakarta.ejb.AsyncResult;
import jakarta.ejb.Asynchronous;
import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@Stateless
public class StatelessBeanInEJB {

    @PersistenceContext
    private EntityManager entityManager;

    @Asynchronous
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public Future<Void> asyncTransactionalMethod(YourEntity yourEntity) {
        // ...
        return new AsyncResult<>(null);
    }

    @Asynchronous
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public Future<YourEntity> asyncNonTransactionalMethod() {
        // ...
        return new AsyncResult<>(yourEntity);
    }
}

And here's the equivalent in CDI, with help of the in Jakarta Concurrency 3.0 introduced @Asynchronous annotation:

package com.example;

import java.util.concurrent.CompletableFuture;

import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;

@ApplicationScoped
public class StatelessBeanInCDI {

    @PersistenceContext
    private EntityManager entityManager;

    @Asynchronous
    @Transactional(TxType.REQUIRES_NEW)
    public CompletableFuture<Void> asyncTransactionalMethod(YourEntity yourEntity) {
        // ...
        return Asynchronous.Result.complete(null);
    }

    @Asynchronous
    @Transactional(TxType.NOT_SUPPORTED)
    public CompletableFuture<YourEntity> asyncNonTransactionalMethod() {
        // ...
        return Asynchronous.Result.complete(yourEntity);
    }
}

Be careful with IDE autocomplete when importing the @Asynchronous annotation! In order to get it to work in a CDI managed bean, it needs to come from the jakarta.enterprise.concurrent package instead of the jakarta.ejb package.

Migrating @Startup EJB to CDI

Here's how the average startup bean in EJB looks like:

package com.example;

import jakarta.annotation.PostConstruct;
import jakarta.ejb.Singleton;
import jakarta.ejb.Startup;

@Startup
@Singleton
public class StartupBeanInEJB {

    @PostConstruct
    public void init() {
        // ...
    }
}

And here's the equivalent in CDI, with help of the in CDI 4.0 introduced Startup event:

package com.example;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.event.Startup;

@ApplicationScoped
public class StartupBeanInCDI {

    public void init(@Observes Startup startup) {
        // ...
    }
}

You need to keep in mind that the @ApplicationScoped is not read-write locked, unlike @Singleton, even though many people don't need it and simply unlock the @Singleton via an additional @ConcurrencyManagement(BEAN) annotation. In case read-write locking is actually a technical requirement, generally to avoid DB deadlocks at business service level, then you might want to consider using the @Lock annotation of the OmniServices utility library for this. An equivalent of the @Lock annotation is namely also lacking in CDI, but there's currently an open issue to include it in the upcoming Jakarta Concurrency 3.2 for Jakarta EE 12: issue 135.

In case the startup CDI bean happens to be part of a WAR instead of a JAR, and you happen to already use OmniFaces, then you could also use its @Eager instead of @Observes Startup.

Migrating EJB @Schedule to CDI

Here's how the average background task scheduler in EJB looks like:

package com.example;

import jakarta.ejb.Schedule;
import jakarta.ejb.Singleton;

@Singleton
public class ScheduledTasksBeanInEJB {

    @Schedule(hour="0", minute="0", second="0", persistent=false)
    public void someDailyJob() {
        // ... runs daily at midnight
    }

    @Schedule(hour="*", minute="0", second="0", persistent=false)
    public void someHourlyJob() {
        // ... runs every hour of the day
    }

    @Schedule(hour="*", minute="*/15", second="0", persistent=false)
    public void someQuarterlyJob() {
        // ... runs every 15th minute of the hour
    }

    @Schedule(hour="*", minute="*", second="*/5", persistent=false)
    public void someFiveSecondelyJob() {
        // ... runs every 5th second of the minute
    }
}

And here's the equivalent in CDI, with help of the in Jakarta Concurrency 3.0 introduced CronTrigger helper:

package com.example;

import java.time.ZoneId;

import jakarta.annotation.Resource;
import jakarta.enterprise.concurrent.CronTrigger;
import jakarta.enterprise.concurrent.ManagedScheduledExecutorService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.event.Startup;

@ApplicationScoped
public class ScheduledTasksBeanInCDI {

    @Resource
    private ManagedScheduledExecutorService scheduler;

    public void init(@Observes Startup startup) {
        scheduler.schedule(this::someDailyJob, new CronTrigger(ZoneId.systemDefault()).hours("0").minutes("0").seconds("0"));
        scheduler.schedule(this::someHourlyJob, new CronTrigger(ZoneId.systemDefault()).hours("*").minutes("0").seconds("0"));
        scheduler.schedule(this::someQuarterlyJob, new CronTrigger(ZoneId.systemDefault()).hours("*").minutes("*/15").seconds("0"));
        scheduler.schedule(this::someFiveSecondelyJob, new CronTrigger(ZoneId.systemDefault()).hours("*").minutes("*").seconds("*/5"));
    }

    public void someDailyJob() {
        // ... runs daily at midnight
    }

    public void someHourlyJob() {
        // ... runs every hour of the day
    }

    public void someQuarterlyJob() {
        // ... runs every 15th minute of the hour
    }

    public void someFiveSecondelyJob() {
        // ... runs every 5th second of the minute
    }
}

Yeah, it's remarkably more verbose. Fortunately this will be improved in Jakarta Concurrency 3.1, part of Jakarta EE 11. You can then simply use @Asynchronous(runAt = @Schedule(...)) on the methods like below, theoretically:

package com.example;

import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.enterprise.concurrent.Schedule;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ScheduledTasksBeanInCDI {

    @Asynchronous(runAt = @Schedule())
    public void someDailyJob() {
        // ... runs daily at midnight
    }

    @Asynchronous(runAt = @Schedule(hours = {}))
    public void someHourlyJob() {
        // ... runs every hour of the day
    }

    @Asynchronous(runAt = @Schedule(hours = {}, minutes = { 0, 15, 30, 45 }))
    public void someQuarterlyJob() {
        // ... runs every 15th minute of the hour
    }

    @Asynchronous(runAt = @Schedule(hours = {}, minutes = {}, seconds = { 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 }))
    public void someFiveSecondelyJob() {
        // ... runs every 5th second of the minute
    }
}

It also supports a cron expression string, following the rules of CronTrigger API:

package com.example;

import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.enterprise.concurrent.Schedule;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ScheduledTasksBeanInCDI {

    @Asynchronous(runAt = @Schedule(cron = "0 0 0 * * *"))
    public void someDailyJob() {
        // ... runs daily at midnight
    }

    @Asynchronous(runAt = @Schedule(cron = "0 0 * * * *"))
    public void someHourlyJob() {
        // ... runs every hour of the day
    }

    @Asynchronous(runAt = @Schedule(cron = "0 */15 * * * *"))
    public void someQuarterlyJob() {
        // ... runs every 15th minute of the hour
    }

    @Asynchronous(runAt = @Schedule(cron = "*/5 * * * * *"))
    public void someFiveSecondelyJob() {
        // ... runs every 5th second of the minute
    }
}

To reiterate, you need to keep in mind that the @ApplicationScoped is not read-write locked, unlike @Singleton. In case that is a technical requirement, then you might want to consider using the @Lock annotation of the OmniServices utility library for this.

How about @Stateful EJBs?

It has never had any use in web based applications, it was only useful in CORBA/RMI which is completely obsoleted by web services these days, so forget about it. You'd best migrate it to a stateless @ApplicationScoped bean, if necessary in combination with a @SessionScoped or even @ViewScoped managed bean to keep track of client's stateful data.

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.

Wednesday, June 5, 2024

What's new in Faces 4.1?

Introduction

At 5 June 2024, Faces 4.1 has been released! It's a maintenance release within the umbrella of Jakarta EE 11.

While working towards Faces 5.0 the Mojarra and MyFaces committers found relatively a lot of things that were unintentionally left unpolished in the 4.0 release and hence there was the desire to release an intermediate version 4.1 whereby a few more things were deprecated that already should have been deprecated in 4.0, and a few oversights in changes were caught up that should have been implemented in 4.0, and some inconsitenties in javadocs were fixed. While at it, we added a small handful of relatively simple things on community demand.

Overview

Here's an overview based on Faces 4.1 milestone of what's changed in Faces 4.1 as compared to Faces 4.0.

More deprecations

Catching up missing changes

Fixing spec/documentation

Addition of new stuff

Deprecate unused <cc:extension>

#1549: The <cc:extension> was originally intended as a placeholder to hold design time metadata for visual code editors as per JSR-276, but the entire design time thing never took off and the JSR-276 proposal went dormant. This rendered the <cc:extension> entirely unused. It's now finally marked deprecated for removal.

Deprecate unused @PostConstruct/@PreDestroyCustomScopeEvent

#1707: The @CustomScoped annotation was deprecated in JSF 2.3 and removed in Faces 4.0 in favor of custom CDI scopes. However the associated @PostConstructCustomScopeEvent and @PreDestroyCustomScopeEvent classes were overlooked during the deprecation process in JSF 2.3. With the removal of @ManagedBean and @CustomScoped in Faces 4.0 these event classes became completely unused. They're now finally marked deprecated for removal.

Deprecate discommended Full State Saving

#1829: The in JSF 2.0 introduced Partial State Saving (PSS) has proven its advantages. More performant and less memory consumption. The original Full State Saving (FSS) was discommended/discouraged since JSF 2.0 but it was still supported for backwards compatibility reasons and as fallback in case a bug with PSS was encountered. The original thought was to deprecate FSS in JSF 3.0 but this completely slipped through during the Java EE -> Jakarta EE chaos. It's now finally marked deprecated for removal.

Deprecate duplicated ActionSource2 and improved original ActionSource

#1832: The ActionSource interface was introduced in JSF 1.0. During JSF 1.2 there was the desire to add new methods to the interface but this would break backwards compatibility at compile level. Today we could just have added them as so-called default methods of the interface, but this was only introduced in Java 8 which wasn't yet released at that time. So in JSF 1.2 a new interface was added, ActionSource2, which simply extends the original interface. It could have been "rolled back" in JSF 2.3, the first JSF version which required a minimum of Java SE 8, but this didn't happen. We now finally took the opportunity to deprecate ActionSource2 and add its "new" methods as default methods of ActionSource.

Missing generic types which should have been added in 4.0

#1708 and #1789: During Faces 4.0 some work was done to add generic types to existing argument/return types in Faces API representing raw Collection or Map or Object. However in a few places these were overlooked, the ExternalContextWrapper#getInitParameterMap(), Renderer#getConvertedValue(), FacesMessage.VALUES and FacesMessage.VALUES_MAP. It has been caught up in Faces 4.1.

Deprecated UIComponent.bindings field should have been removed in 4.0

#1725: The protected field UIComponent.bindings was introduced in JSF 1.2 and immediately deprecated in JSF 2.0 in favor of set/getValueExpression(). It should already have been removed in Faces 4.0 along with ValueBinding and friends, but that slipped through. It has finally been removed in Faces 4.1.

Spec/documentation has been fixed

  • #1712: Fixed wrong reference to non-existent variable name "context" in Application#subscribeToEvent javadoc.
  • #1739: Explicitly require that CDI lifecycle events @Initialized, @BeforeDestroyed and @Destroyed must be fired for custom CDI scopes @ViewScoped and @FlowScoped. These were only weakly encouraged by CDI spec, which means that Faces implementors didn't necessarily have to fire them. From now on it's set in stone that the Faces implementors must fire them.
  • #1760: The id attribute of <h:head>/<h:body> was missing in vdldoc. The attributes were added to JSF 2.2 during HTML5 work but it wasn't correctly added to VDL documentation and then unintentionally disappeared from VDL documentation during VDL-related code deduplication efforts in JSF 2.3. It has now been added back.
  • #1811: The if attribute of <f:viewAction> was missing in vdldoc. It is actually a facade of rendered attribute but it wasn't made entirely clear in the VDL documentation because it was still labeled as rendered attribute. It's now correctly labeled as if attribute.
  • #1821: Explicitly specify default value of jakarta.faces.FACELETS_REFRESH_PERIOD in development stage. This was nowhere specified, causing the implementations to have different default values for this. It's now set in stone that this should default to 0 in development stage.
  • #1828: Remove references to already-removed facelets.XYZ context params in javadoc. The old facelets.XYZ context params were already removed in Faces 4.0, but they were still mentioned in some places in the javadocs. These references have now been removed.
  • #1864: The rowStatePreserved attribute of <h:dataTable> was missing in vdldoc. The attribute was added to JSF 2.1 but it was only added to "render kit doc" and wasn't added to VDL documentation. It has now been added to VDL documentation.

Add rowStatePreserved attribute to <ui:repeat>

#1263: The <h:dataTable> already had support for the rowStatePreserved attribute since JSF 2.1 in order to properly deal with <h:form> components within <h:dataTable>. This was only missing in <ui:repeat> and has now been added. It should only be used when the <ui:repeat> has nested <h:form> components which in turn have nested EditableValueHolder components such as <h:inputText>. It is not necessary when the nested forms only have nested ActionSource components such as <h:commandButton>.

Support @Inject of current Flow

#1342: The facesContext.getApplication().getFlowHandler().getCurrentFlow() slipped through during the efforts to make as many as possible Faces artifacts injectable via CDI. It's now injectable as @Inject Flow flow;.

Added ExternalContext#setResponseContentLengthLong()

#1764: The HttpServletResponse#setResponseContentLengthLong() which takes a long argument instead of int argument, so that it could support over 2GB, was added during Servlet 3.1 and the intent was to add it to JSF 3.0 as well (JSF 2.3 still had Servlet 3.0 as minimum requirement), but that slipped through during the Java EE -> Jakarta EE chaos. It has finally been added to Faces 4.1.

New UUIDConverter

#1819: a new default converter has been added for bean properties of java.util.UUID type: the jakarta.faces.convert.UUIDConverter identified by converter ID jakarta.faces.UUID. This is particularly to align with Jakarta Persistence 3.1 which introduced support for UUID as entity keys.

FacesMessage has now a default equals(), hashCode() and toString()

#1823: The FacesMessage didn't have an identity for a long time and couldn't be reliably compared/checked by identity. It didn't have default implementations of equals() and hashCode(). These have finally been added in Faces 4.1, along with the toString().

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.

Tuesday, March 26, 2024

jsf.zeef.com is down

No panic. I've restored the lists below =)

JSF phases/lifecycle cheatsheet

  • fc = FacesContext
  • vh = ViewHandler
  • in = UIInput
  • rq = HttpServletRequest
  • id = in.getClientId(fc);
1 RESTORE_VIEW

String viewId = rq.getServletPath(); fc.setViewRoot(vh.createView(fc, viewId));

2 APPLY_REQUEST_VALUES

in.setSubmittedValue(rq.getParameter(id));

3 PROCESS_VALIDATIONS

Object value = in.getSubmittedValue();
try {
  value = in.getConvertedValue(fc, value);
  for (Validator v : in.getValidators())
    v.validate(fc, in, value);
  }
  in.setSubmittedValue(null);
  in.setValue(value);
} catch (ConverterException
    | ValidatorException e) {
  fc.addMessage(id, e.getFacesMessage());
  fc.validationFailed(); // Skips 4+5.
  in.setValid(false);
}

4 UPDATE_MODEL_VALUES

bean.setProperty(in.getValue());

5 INVOKE_APPLICATION

bean.submit();

6 RENDER_RESPONSE

vh.renderView(fc, fc.getViewRoot());