spring-bootprimefacesjsf-2.2mojarra

Spring Boot 2.7.1 integration with JSF 2.2 (Mojarra) and Primefaces 6.2.9


I have working set up using below stack -

Spring MVC 5
JSF Mojarra 2.2.14
Primefaces 6.2.9

This has been working fine in both weblogic app server as well as tomcat.

We are trying to convert this to a Spring boot project using latest 2.7.1 version. As part of this

@SpringBootApplication(scanBasePackages = {"com.demo.spring.boot.jsfprimefaces","com.xxx.eee"})
@ImportResource({"classpath:spring-root-config.xml","classpath:spring-security-config.xml","classpath:spring-restclient-config.xml"})
public class JsfPrimefacesApplication implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration", Boolean.TRUE.toString()); 
        // Set JSF/Primefaces init params
        servletContext.setInitParameter("javax.faces.DEFAULT_SUFFIX", ".xhtml");
        servletContext.setInitParameter("javax.faces.STATE_SAVING_METHOD", "client");
        servletContext.setInitParameter("javax.faces.PROJECT_STAGE", "Production");
        servletContext.setInitParameter("javax.faces.VALIDATE_EMPTY_FIELDS", "false");
        servletContext.setInitParameter("javax.faces.FACELETS_SKIP_COMMENTS", "true");
        //servletContext.setInitParameter("javax.faces.CONFIG_FILES", "/WEB-INF/faces-config.xml");
        servletContext.setInitParameter("javax.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE", "true");

        servletContext.setInitParameter("primefaces.THEME", "aristo");
        servletContext.setInitParameter("primefaces.PUBLIC_CAPTCHA_KEY", "DDDDD");
        servletContext.setInitParameter("primefaces.PRIVATE_CAPTCHA_KEY", "EEEEE");
        servletContext.setInitParameter("primefaces.UPLOADER", "commons");

        FacesInitializer facesInitializer = new FacesInitializer();
        facesInitializer.onStartup(null, servletContext);

    }

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

}

Few other @Configuration classes -

@Configuration
public class PortalFilterConfiguration {

    @Bean
    public FilterRegistrationBean<RewriteFilter> rewriteFilter() {
        FilterRegistrationBean<RewriteFilter> rwFilter = new FilterRegistrationBean<RewriteFilter>(new RewriteFilter());
        rwFilter.setDispatcherTypes(
                EnumSet.of(DispatcherType.FORWARD, DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR));
        rwFilter.addUrlPatterns("/*");
        return rwFilter;
    }
}
@Configuration
public class PortalListenerConfiguration {

    @Bean
    public ServletListenerRegistrationBean<JsfApplicationObjectConfigureListener> jsfConfigureListener() {
        return new ServletListenerRegistrationBean<JsfApplicationObjectConfigureListener>(
                new JsfApplicationObjectConfigureListener());
    }

    static class JsfApplicationObjectConfigureListener extends ConfigureListener {

        @Override
        public void contextInitialized(ServletContextEvent sce) {
            super.contextInitialized(sce);

            ApplicationFactory factory = (ApplicationFactory) FactoryFinder
                    .getFactory(FactoryFinder.APPLICATION_FACTORY);
            Application app = factory.getApplication();

            app.addELResolver(new SpringBeanFacesELResolver());
        }
    }

}
@Configuration
public class PortalServletConfiguration {

    @Bean
    public ServletRegistrationBean<FacesServlet> jsfServletRegistration(ServletContext servletContext) {
        ServletRegistrationBean<FacesServlet> srb = new ServletRegistrationBean<FacesServlet>();
        srb.setServlet(new FacesServlet());
        srb.setUrlMappings(Arrays.asList("*.xhtml"));
        srb.setLoadOnStartup(1);
        return srb;
    }
}

Below is the pom.xml contents -

<?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>2.7.1</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.demo.spring.boot</groupId>
    <artifactId>jsf-primefaces</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jsf-primefaces</name>
    <description>Demo project for Spring Boot JSF Primefaces</description>
    <packaging>war</packaging>
    <repositories>
        <repository>
            <id>no-commons-logging</id>
            <name>No-commons-logging Maven Repository</name>
            <layout>default</layout>
            <url>http://repository.jboss.org/</url>
        </repository>
        <repository>
            <id>prime-repo</id>
            <name>Prime Repo</name>
            <url>http://repository.primefaces.org</url>
        </repository>
        <repository>
            <id>localrepository</id>
            <url>file://../3rd-Party-Dependencies</url>
        </repository>
    </repositories>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-ldap</artifactId>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- JSF Libraries -->
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.2.14</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.2.14</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <!-- Primefaces & its dependent jars -->
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>6.2.9</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-servlet</artifactId>
            <version>3.4.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-config-prettyfaces</artifactId>
            <version>3.4.1.Final</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>   
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
                <groupId>net.sf.opencsv</groupId>
                <artifactId>opencsv</artifactId>
                <version>2.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.24</version>
        </dependency>           
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <outputDirectory>src/main/webapp/WEB-INF/classes</outputDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

faces-config.xml is placed under src/main/webapp/WEB-INF. Contents below -

<faces-config
    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/web-facesconfig_2_2.xsd"
    version="2.2">
<application>
<el-resolver>
            org.springframework.web.jsf.el.SpringBeanFacesELResolver
        </el-resolver>
<locale-config>.....
<resource-bundle>.....
 </application>
</faces-config>

ISSUE

Application is starting up fine. But it seems none of the JSF managed beans are getting initialized as I do not see @PostContruct methods being called at all. All EL references like #{} where managed bean name is used are failing with errors like

javax.faces.view.facelets.TagAttributeException Attribute did not evaluate to a String or Locale: null

When I added @Component on top of @ManagedBean to one of the JSF managed beans, that causes @PostConstruct to be called probably meaning that in our old set up, @ManagedBean classes were loaded by JSF but in this new set up they are getting ignored.

Could anyone please help advise what needs to be done to make this work?

Regards
Jacob

UPDATE 1
Based on comments, have modified the configuration class as below -

@SpringBootApplication(scanBasePackages = { "com.demo.spring.boot.jsfprimefaces", "com.ddd.efulfillment" })
@ImportResource({ "classpath:spring-root-config.xml", "classpath:spring-security-config.xml",
        "classpath:spring-restclient-config.xml" })
public class JsfPrimefacesApplication extends SpringBootServletInitializer {

    @Bean
    public ServletContextInitializer servletContextInitializer() {
        return servletContext -> {
            //servletContext.addListener(com.sun.faces.config.ConfigureListener.class);
            servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration", Boolean.TRUE.toString());
            // Set JSF/Primefaces init params
            servletContext.setInitParameter("javax.faces.DEFAULT_SUFFIX", ".xhtml");
            servletContext.setInitParameter("javax.faces.STATE_SAVING_METHOD", "client");
            servletContext.setInitParameter("javax.faces.PROJECT_STAGE", "Production");
            servletContext.setInitParameter("javax.faces.VALIDATE_EMPTY_FIELDS", "false");
            servletContext.setInitParameter("javax.faces.FACELETS_SKIP_COMMENTS", "true");
            // servletContext.setInitParameter("javax.faces.CONFIG_FILES",
            // "/WEB-INF/faces-config.xml");
            servletContext.setInitParameter("javax.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE",
                    "true");

            servletContext.setInitParameter("primefaces.THEME", "aristo");
            servletContext.setInitParameter("primefaces.PUBLIC_CAPTCHA_KEY",
                    "ddd");
            servletContext.setInitParameter("primefaces.PRIVATE_CAPTCHA_KEY",
                    "ssss");
            servletContext.setInitParameter("primefaces.UPLOADER", "commons");
            FacesInitializer facesInitializer = new FacesInitializer();
            facesInitializer.onStartup(null, servletContext);

        };
    }

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

but still same issue is there.

While debugging, seems managed beans are identified in the class com.sun.faces.config.processor.ManagedBeanConfigProcessor via method "processAnnotations(ManagedBean.class);". It has code similar to below

 FacesContext ctx = FacesContext.getCurrentInstance();
        ApplicationAssociate associate =
              ApplicationAssociate.getInstance(ctx.getExternalContext());
        AnnotationManager manager = associate.getAnnotationManager();
        manager.applyConfigAnnotations(ctx,
                                      annotationType,
                                      ConfigManager.getAnnotatedClasses(ctx).get(annotationType));

I think the MAP of @ManagedBean annotated classes is coming as NULL. Not sure why.


Solution

  • JSF 2.2 annotations were only being recognized after the classes having those annotations were explicitly provided to FacesInitializer like below -

    facesInitializer.onStartup(loadJSFAnnotatedClasses(package1...packageN),servletContext);
    
    import com.google.common.collect.ImmutableMap;
    import com.google.common.reflect.ClassPath;
    
    private Set<Class<?>> loadJSFAnnotatedClasses(String... packageNames) {
            Set<Class<?>> annotatedClasses = new HashSet<Class<?>>();
            try {
                for (String packageName : packageNames) {
                    annotatedClasses.addAll(ClassPath.from(ClassLoader.getSystemClassLoader()).getAllClasses().stream()
                            .filter(clazz -> clazz.getPackageName().equalsIgnoreCase(packageName))
                            .map(clazz -> clazz.load()).collect(Collectors.toSet()));
                }
            } catch (Exception e) {
                return annotatedClasses;
            }
    
            return annotatedClasses;
        }