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.
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>
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");
}
}
When the timeregistration flow is started for the first time I get the following error (too large to embed here):
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.
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.