jsfcdielspring-webflowjakarta-migration

Managed bean not resolved in EL expression after Jakarta migration


I'm currently trying to upgrade a web application deployed in Apache Tomcat from Java EE 8 to Jakarta 10. The application uses Spring Web Flow, facelets, Mojarra Faces and JBoss Weld as CDI. I'll first try to give as much information as possible.

Setup

Time Registration Web App

pom.xml

<project>
    ...
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>jakarta.faces</artifactId>
        <version>4.0.9</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.weld.servlet</groupId>
        <artifactId>weld-servlet-core</artifactId>
        <version>5.1.4.Final</version>
    </dependency>
    <dependency>
        <groupId>be.liantis.faces</groupId>
        <artifactId>liantis-faces-layout</artifactId>
    </dependency>
    ...
</project>

context.xml

<Context>
    <JarScanner scanManifest="false"/>

    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
    <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

    <Resource name="BeanManager" auth="Container" type="jakarta.enterprise.inject.spi.BeanManager" factory="org.jboss.weld.resources.ManagerObjectFactory"/>

</Context>

WEB-INF/web.xml

<web-app
        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/web-app_6_0.xsd"
        version="6.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:be/admb/hora/timeregistration/web/applicationContext.xml
        </param-value>
    </context-param>
    <context-param>
        <param-name>jakarta.faces.DEFAULT_SUFFIX</param-name>
        <param-value>.xhtml</param-value>
    </context-param>
    <context-param>
        <param-name>jakarta.faces.FACELETS_SUFFIX</param-name>
        <param-value>.xhtml</param-value>
    </context-param>
    <context-param>
        <param-name>defaultHtmlEscape</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>primefaces.THEME</param-name>
        <param-value>liantis</param-value>
    </context-param>
    <context-param>
        <param-name>jakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>jakarta.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE</param-name>
        <param-value>true</param-value>
    </context-param>
    <context-param>
        <param-name>jakarta.faces.FACELETS_SKIP_COMMENTS</param-name>
        <param-value>true</param-value>
    </context-param>
    <servlet>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value/>
        </init-param>
        <load-on-startup>2</load-on-startup>
        <multipart-config/>
    </servlet>
    <servlet-mapping>
        <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    <!-- Just here so the JSF implementation can initialize. *Not* used at runtime. -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <multipart-config/>
    </servlet>
    <!-- Just here so the JSF implementation can initialize -->
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.faces</url-pattern>
    </servlet-mapping>
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <session-config>
        <cookie-config>
            <http-only>true</http-only>
        </cookie-config>
    </session-config>
    <resource-env-ref>
        <resource-env-ref-name>BeanManager</resource-env-ref-name>
        <resource-env-ref-type>jakarta.enterprise.inject.spi.BeanManager</resource-env-ref-type>
    </resource-env-ref>
</web-app>

be/admb/hora/timeregistration/web/applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:webflow="http://www.springframework.org/schema/webflow-config"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd">

    <faces:resources/> 

    <webflow:flow-executor id="flowExecutor">
        <webflow:flow-execution-listeners>
            <webflow:listener ref="facesContextListener" />
            <webflow:listener ref="securityFlowExecutionListener" />
        </webflow:flow-execution-listeners>
        <webflow:flow-execution-attributes>
            <webflow:redirect-in-same-state value="false"/>
        </webflow:flow-execution-attributes>
    </webflow:flow-executor>

    <webflow:flow-registry id="flowRegistry" flow-builder-services="facesFlowBuilderServices" base-path="/WEB-INF/flows">
        <webflow:flow-location-pattern value="/**/*-flow.xml" />
    </webflow:flow-registry>

    <faces:flow-builder-services id="facesFlowBuilderServices"/>

    <bean id="facesContextListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener" />

    <bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" />

    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
        <property name="order" value="1" />
        <property name="flowRegistry" ref="flowRegistry" />
        <property name="defaultHandler">
            <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
        </property>
    </bean>

    <bean id="faceletsViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="order" value="2" />
        <property name="viewClass" value="org.springframework.faces.mvc.JsfView" />
        <property name="prefix" value="/WEB-INF/" />
        <property name="suffix" value=".xhtml" />
    </bean>

    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

    <bean id="flowHandlerAdapter" class="org.springframework.faces.webflow.JsfFlowHandlerAdapter">
        <property name="flowExecutor" ref="flowExecutor" />
    </bean>
</beans>

WEB-INF/faces-config.xml

<faces-config
        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/web-facesconfig_4_0.xsd"
        version="4.0" >

    <application>
        <locale-config>
            <default-locale>nl</default-locale>
            <supported-locale>fr</supported-locale>
        </locale-config>
    </application>
    <lifecycle>
    </lifecycle>
</faces-config>

WEB-INF/templates/standard.xhtml

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:f="jakarta.faces.core"
                xmlns:ui="jakarta.faces.facelets"
                xmlns:e="http://java.sun.com/jsf/composite/liantis-faces-components/components"
                xmlns:layout="http://java.sun.com/jsf/composite/liantis-layout-components/components"
                xmlns:helpLink="http://java.sun.com/jsf/composite/hora-commons/components/helplink"
                template="#{layoutStyleBeanWF.siteModePage}">

    <ui:define name="headerHead">
        <layout:headerHead pageTitle="#{msgs.TimeRegistration}"
            siteStyle="#{layoutStyleBeanWF.siteStyle}" 
            siteModeCss="#{layoutStyleBeanWF.siteModeCss}" />
    </ui:define>

    <ui:define name="headIncludes">
        <layout:outputStylesheet name="style.css" library="css" />
        <layout:outputScript name="js/primefaces-locales.js" library="liantis-layout-components" />
        <layout:outputScript name="timeRegistration.js" library="js"/>
        <f:loadBundle basename="settings-${layoutBean.environment}" var="settings" />
        <script src="jakarta.faces.resource/js/postmessage.js.html?v=${layoutBean.versionCode}"></script>
        <script src="jakarta.faces.resource/js/hora-tools-navigation-api-common.js.html?v=${layoutBean.versionCode}"></script>
        <script src="jakarta.faces.resource/js/hora-tools-navigation-api-client.js.html?v=${layoutBean.versionCode}"></script>
        <e:googleTagManager />
    </ui:define>
</ui:composition>

WEB-INF/flows/timeregistration/import-file-main-view.xhtml

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="jakarta.faces.core"
                xmlns:ui="jakarta.faces.facelets"
                xmlns:p="primefaces"
                xmlns:a="http://www.admb.be/jsf/tags"
                template="/WEB-INF/templates/standard.xhtml">
    ...
</ui:composition>

WEB-INF/flows/main/main-flow.xml

<flow xmlns="http://www.springframework.org/schema/webflow"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/webflow https://www.springframework.org/schema/webflow/spring-webflow.xsd">

    <var name="layoutStyleHelper" class="be.liantis.faces.layout.utils.LayoutStyleHelper" />
    <var name="jsfUtilBean" class="be.admb.hora.commons.jsf.beans.util.JsfUtilBean" />

    <on-start>
        <evaluate
            expression="layoutStyleHelper.getLayoutStyleBean(conversationScope.layoutStyleBeanWF, externalContext.sessionMap.layoutStyleBean)"
            result="conversationScope.layoutStyleBeanWF" />
        <evaluate expression="layoutStyleBeanWF.setSiteStyle()" />
        <evaluate expression="requestParameters.layoutMode" result="layoutStyleBeanWF.siteMode" />
    </on-start>

    <on-end>
        <evaluate expression="conversationScope.layoutStyleBeanWF" result="externalContext.sessionMap.layoutStyleBean" />
    </on-end>

    <exception-handler bean="exceptionHandler" />
</flow>

WEB-INF/flows/timeregistration/timeregistration-flow.xml

<flow xmlns="http://www.springframework.org/schema/webflow"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/webflow https://www.springframework.org/schema/webflow/spring-webflow.xsd"
        parent="main">

    <var name="collaboratorConversionBean" class="be.admb.hora.timeregistration.web.beans.CollaboratorConversionBean" />
    <var name="importFileBean" class="be.admb.hora.timeregistration.web.beans.ImportFileBean" />
    <var name="notProcessInTrBean" class="be.admb.hora.timeregistration.web.beans.NotProcessInTrBean" />
    <var name="timeRegistrationBean" class="be.admb.hora.timeregistration.web.beans.TimeRegistrationBean" />

    <input name="employerId" value="flowScope.employerId" required="true"/>

    <view-state id="import-file-main-view">
        <on-entry>
            <evaluate expression="timeRegistrationAction.loadCollaboratorGroupList()" />
            <evaluate expression="importFileAction.searchImportFiles()" />
        </on-entry>

        <transition on="add" to="import-file-upload-view"/>

        <transition on="edit" to="import-file-detail-view">
            <evaluate expression="importFileAction.reloadSelectedImportFile()"/>
        </transition>

        <transition on="delete" to="import-file-main-view">
            <evaluate expression="importFileAction.deleteSelectedImportFile()"/>
        </transition>

        <transition on="configuration" to="configuration-main-view"/>

        <transition on="applyFilter" to="import-file-main-view"/>

    </view-state>
</flow>

liantis-faces-layout (company library)

META-INF/faces-config.xml

<faces-config
        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/web-facesconfig_4_0.xsd"
        version="4.0"
        metadata-complete="false">
    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
</faces-config>

LayoutBean.java

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named("layoutBean")
@ApplicationScoped
public class LayoutBean implements Serializable {

    @Inject
    private VersionReporter versionReporter;

    public LayoutBean() {
    }

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

    public String getEnvironment() {
        return System.getProperty("server.env");
    }
}

The problem

When the timeregistration flow is started for the first time I get the following error (too large to embed here):

stacktrace

It seems my web app can't resolve the ${layoutBean.environment} EL expression in <f:loadBundle basename="settings-${layoutBean.environment}"> defined in WEB-INF/templates/standard.xhtml.

The Java EE (previous) version of LayoutBean.java:

import javax.annotation.PostConstruct;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

@ManagedBean
@ApplicationScoped
public class LayoutBean implements Serializable {

    @ManagedProperty(value = "#{versionReporter}")
    private VersionReporter versionReporter;

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

    public String getEnvironment() {
        return System.getProperty("server.env");
    }
}

I can't figure out why my application does not resolve ${layoutBean.environment}.

I've already tried the #{layoutBean.environment} syntax, without success.

I realise this is not an easy question since there are so many parts in place (faces, EL, Spring Web Flow, ...). I've tried to give as much detail as possible, without overloading with redundant information.

Any help is much appreciated.


Solution

  • I've found the solution to my problem. Weld CDI did not scan my CDI annotated classes in dependent liantis-faces-layout JAR. Adding a beans.xml file in the META-INF directory solved the issue.