May 17, 2021

jetty-maven-plugin 10, JSF 2.3, CDI 2.0 and Primefaces 10

Introduction

In this blog we are going to setup a Java EE 8 project, with PrimeFaces 10, JSF 2.3 and CDI 2.0, but we are going to setup it so with can run jetty-maven-plugin for faster development.

Using jetty-maven-plugin is a great tool for faster develop server side generated dynamic web pages. You start jetty plugin with:

$ mvn jetty:run

And have configure jetty-maven-plugin to automatically reload with configuration 'scanIntervalSeconds'.

Which Version of Jetty/jetty-maven-plugin?

For Java EE 8 which includes Servlet 4.0, JSF 2.3, CDI 2.0, EJB 3.2, JPA 2.2, JSON-P 1.1 and JSON-B 1.1.

https://en.wikipedia.org/wiki/Jakarta_EE

And Jetty 10 supports Servlet 4.

https://www.eclipse.org/jetty/download.php

Which Version of Weld (CDI 2.0 Implementation in Java EE 8)

https://weld.cdi-spec.org/documentation/

pom.xml

Now lets put this together. 2 most important reference guide for jetty-maven-plugin and Weld are:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>se.magnuskkarlsson</groupId>
    <artifactId>primefaces-jetty-10</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
        <bouncycastle.version>1.65</bouncycastle.version>
        <hibernate.version>5.3.14.Final</hibernate.version>
        <hibernate-validator.version>6.0.18.Final</hibernate-validator.version>
        <resteasy.version>3.6.1.SP2</resteasy.version>
    </properties>

    <dependencies>
        <!-- Java EE 8 -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- PrimeFaces -->
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>10.0.0</version>
        </dependency>
        <!-- https://www.primefaces.org/showcase-ext/views/home.jsf -->
        <dependency>
            <groupId>org.primefaces.extensions</groupId>
            <artifactId>primefaces-extensions</artifactId>
            <version>10.0.0</version>
        </dependency>
        <!-- https://primefaces.github.io/primefaces/10_0_0/#/core/fonticons -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>font-awesome</artifactId>
            <version>5.12.0</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <release>11</release>
                        <showDeprecation>true</showDeprecation>
                        <showWarnings>true</showWarnings>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <!-- Which Jetty version? https://www.eclipse.org/jetty/download.php -->
            <!-- https://en.wikipedia.org/wiki/Jakarta_EE -->
            <!-- Which WELD version? https://weld.cdi-spec.org/documentation/ -->

            <!-- Java EE 8, Servlet 4.0, JSF 2.3 and CDI 2.0 = Jetty 10 and WELD 3 -->

            <!-- https://www.eclipse.org/jetty/documentation/jetty-10/programming-guide/index.html#jetty-maven-plugin -->
            <!-- https://docs.jboss.org/weld/reference/3.1.7.SP1/en-US/html_single/#weld-servlet -->

            <!-- mvn clean package jetty:run -->
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>10.0.2</version>
                <configuration>
                    <httpConnector>
                        <port>9090</port>
                    </httpConnector>
                    <scan>1</scan>
                    <jettyXml>${project.basedir}/src/test/resources/jetty-env.xml</jettyXml>
                    <webApp>
                        <contextPath>/primefaces-jetty</contextPath>
                        <descriptor>${project.basedir}/src/test/resources/web.xml</descriptor>
                    </webApp>
                    <useTestScope>true</useTestScope>
                    <scanTargetPatterns>
                        <scanTargetPattern>
                            <directory>${project.basedir}/src/main/webapp/</directory>
                            <includes>
                                <include>**/*.xhtml</include>
                                <include>**/*.css</include>
                                <include>**/*.js</include>
                            </includes>
                        </scanTargetPattern>
                    </scanTargetPatterns>
                </configuration>
                <dependencies>
                    <!-- JSF 2.3 Impl https://javaee.github.io/javaserverfaces-spec/ -->
                    <dependency>
                        <groupId>org.glassfish</groupId>
                        <artifactId>javax.faces</artifactId>
                        <version>2.3.8</version>
                    </dependency>
                    <!-- WELD 3.0 (CDI 2.0 Impl/Java EE 8) -->
                    <dependency>
                        <groupId>org.jboss.weld.servlet</groupId>
                        <artifactId>weld-servlet-shaded</artifactId>
                        <version>3.1.7.SP1</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

There are alot of things going on here:

  • We start Jetty on port 9090 and localhost (default)
  • Automatically reload and scan interval 1s - scan.
  • Web context path is /primefaces-jetty.
  • We use custom test web.xml - descriptor.
  • We set test scoped dependecies and test class first in classpath - useTestScope
  • CDI Weld integration - jettyXml.

Then we also add dependency to Jetty web server, since Jetty does not include JSF or CDI support.

  • org.glassfish:javax.faces
  • org.jboss.weld.servlet:weld-servlet-shaded

Lets first look at our custom ${project.basedir}/src/test/resources/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_4_0.xsd"
    version="4.0">

    <servlet>
        <servlet-name>faces-servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>faces-servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>helloworld.xhtml</welcome-file>
    </welcome-file-list>

    <!-- Caused by: java.lang.IllegalStateException: Could not find backup for factory javax.faces.context.FacesContextFactory. -->
    <listener>
        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
    </listener>

</web-app>

The only custom in this file is JSF listener com.sun.faces.config.ConfigureListener.

Next is Jetty environment xml ${project.basedir}/src/test/resources/jetty-env.xml. Which is copy from

  • Weld 3.1.7 Jetty Configuration Guide
  • .

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd"> -->
    <Configure id="webAppCtx" class="org.eclipse.jetty.webapp.WebAppContext">
        <New id="BeanManager" class="org.eclipse.jetty.plus.jndi.Resource">
            <Arg>
                <Ref id="webAppCtx" />
            </Arg>
            <Arg>BeanManager</Arg>
            <Arg>
                <New class="javax.naming.Reference">
                    <Arg>javax.enterprise.inject.spi.BeanManager</Arg>
                    <Arg>org.jboss.weld.resources.ManagerObjectFactory</Arg>
                    <Arg />
                </New>
            </Arg>
        </New>
    </Configure>
    

    PrimeFaces 10 JSF 2.3 Web App

    Now we need a simple web app. Lets start with JSF page - src/main/webapp/helloworld.xhtml

    <!DOCTYPE html>
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
        xmlns:p="http://primefaces.org/ui">
    
    <h:head>
        <title>PrimeFaces Hello World Example</title>
    </h:head>
    
    <h:body>
    
        <h:form>
            <p:panel header="PrimeFaces Hello World Example">
                <h:panelGrid columns="2" cellpadding="4">
                    <h:outputText value="First Name: " />
                    <p:inputText value="#{helloWorld.firstName}" />
    
                    <h:outputText value="Last Name: " />
                    <p:inputText value="#{helloWorld.lastName}" />
    
                    <p:commandButton value="Submit" update="greeting" oncomplete="PF('greetingDialog').show()" />
                </h:panelGrid>
            </p:panel>
    
            <p:dialog header="Greeting" widgetVar="greetingDialog" modal="true" resizable="false">
                <h:panelGrid id="greeting" columns="1" cellpadding="4">
                    <h:outputText value="#{helloWorld.showGreeting()}" />
                </h:panelGrid>
            </p:dialog>
        </h:form>
    
    </h:body>
    </html>
    

    Then PRODUCTION src/main/webapp/WEB-INF/web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app 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-app_4_0.xsd"
        version="4.0">
    
        <servlet>
            <servlet-name>faces-servlet</servlet-name>
            <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>faces-servlet</servlet-name>
            <url-pattern>*.xhtml</url-pattern>
        </servlet-mapping>
    
        <welcome-file-list>
            <welcome-file>helloworld.xhtml</welcome-file>
        </welcome-file-list>
    </web-app>
    

    Then PRODUCTION src/main/webapp/WEB-INF/faces-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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_3.xsd"
        version="2.3">
    
    </faces-config>
    

    And next PRODUCTION beans.xml, but we do not put it in WEB-INF/, because we want to override later, so we put it in src/main/resources/META-INF/beans.xml

    <?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"
        bean-discovery-mode="all" version="2.0">
    
    </beans>
    

    And finally our backing bean for JSF page.

    package se.magnuskkarlsson.example.primefaces.boundary;
    
    import javax.enterprise.context.RequestScoped;
    import javax.inject.Inject;
    import javax.inject.Named;
    
    import se.magnuskkarlsson.example.primefaces.control.HelloControl;
    
    @Named
    @RequestScoped
    public class HelloWorld {
    
        private String firstName = "John";
        private String lastName = "Doe";
    
        @Inject
        protected HelloControl helloControl;
    
        // ----------------------- Logic Methods -----------------------
    
        public String showGreeting() {
            return helloControl.showGreeting(firstName, lastName);
        }
    
        // ----------------------- Helper Methods -----------------------
    
        // ----------------------- Get and Set Methods -----------------------
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
    }
    

    We introduce a Interface for our logic since we later wants to use a Mock for developing our JSF pages.

    package se.magnuskkarlsson.example.primefaces.control;
    
    public interface HelloControl {
    
        public String showGreeting(String firstName, String lastName);
    
    }
    

    Our PRODUCTION logic implementation.

    package se.magnuskkarlsson.example.primefaces.control;
    
    public class HelloControlImpl implements HelloControl {
    
        // ----------------------- Logic Methods -----------------------
    
        @Override
        public String showGreeting(String firstName, String lastName) {
            return "PROD hello " + firstName + " " + lastName + "!";
        }
    
        // ----------------------- Helper Methods -----------------------
    
        // ----------------------- Get and Set Methods -----------------------
    
    }
    

    Using standard CDI technique for using a Mock class in Jetty and Weld was hard. Tried both @Alternative and @Specializes, but neither worked. But what worked was to override custom beans.xml and it that exclude production logic class.

    src/test/java/se/magnuskkarlsson/example/primefaces/control/HelloControlMock.java

    package se.magnuskkarlsson.example.primefaces.control;
    
    public class HelloControlMock implements HelloControl {
    
        @Override
        public String showGreeting(String firstName, String lastName) {
            return "MOCK hello " + firstName + " " + lastName + "!";
        }
    
    }
    

    And finally our custom beans.xml for development - src/test/resources/META-INF/beans.xml

    <?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"
        bean-discovery-mode="all" version="2.0">
    
        <scan>
            <exclude name="se.magnuskkarlsson.example.primefaces.control.HelloControlImpl" />
        </scan>
    </beans>
    

    All files in total:

    ├── pom.xml
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── se
    │   │   │       └── magnuskkarlsson
    │   │   │           └── example
    │   │   │               └── primefaces
    │   │   │                   ├── boundary
    │   │   │                   │   └── HelloWorld.java
    │   │   │                   └── control
    │   │   │                       ├── HelloControlImpl.java
    │   │   │                       └── HelloControl.java
    │   │   ├── resources
    │   │   │   └── META-INF
    │   │   │       └── beans.xml
    │   │   └── webapp
    │   │       ├── helloworld.xhtml
    │   │       └── WEB-INF
    │   │           ├── faces-config.xml
    │   │           └── web.xml
    │   └── test
    │       ├── java
    │       │   └── se
    │       │       └── magnuskkarlsson
    │       │           └── example
    │       │               └── primefaces
    │       │                   └── control
    │       │                       └── HelloControlMock.java
    │       └── resources
    │           ├── jetty-env.xml
    │           ├── META-INF
    │           │   └── beans.xml
    │           └── web.xml
    

    Test

    Lets start web app with jetty

    $ mvn clean package jetty:run
    ...
    [INFO] --- jetty-maven-plugin:10.0.2:run (default-cli) @ primefaces-jetty-10 ---
    [INFO] Configuring Jetty for project: primefaces-jetty-10
    [INFO] Classes = /home/magnuskkarlsson/eclipse-workspace/primefaces-jetty-10/target/classes
    [INFO] Context path = /primefaces-jetty
    [INFO] Tmp directory = /home/magnuskkarlsson/eclipse-workspace/primefaces-jetty-10/target/tmp
    [INFO] web.xml file = /home/magnuskkarlsson/eclipse-workspace/primefaces-jetty-10/src/test/resources/web.xml
    [INFO] Webapp directory = /home/magnuskkarlsson/eclipse-workspace/primefaces-jetty-10/src/main/webapp
    [INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
    [INFO] Web overrides =  none
    [INFO] jetty-10.0.2; built: 2021-03-26T06:15:43.282Z; git: 7bd207b30931f3f61d110b1121118fbb5d10cb48; jvm 11.0.10+9
    May 17, 2021 4:30:22 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup
    INFO: WELD-ENV-001008: Initialize Weld using ServletContainerInitializer
    May 17, 2021 4:30:22 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
    INFO: WELD-000900: 3.1.7 (SP1)
    May 17, 2021 4:30:22 PM org.jboss.weld.bootstrap.WeldStartup startContainer
    INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
    May 17, 2021 4:30:22 PM org.jboss.weld.environment.jetty.JettyLegacyContainer initialize
    INFO: WELD-ENV-001200: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners should work on Jetty 9.1.1 and newer.
    [INFO] Session workerName=node0
    May 17, 2021 4:30:23 PM com.sun.faces.config.ConfigureListener contextInitialized
    INFO: Initializing Mojarra 2.3.8 ( 20181116-0037 55af8b79ca53ec2df566f9c08a430259d30f9ba5) for context '/primefaces-jetty'
    May 17, 2021 4:30:23 PM com.sun.faces.spi.InjectionProviderFactory createInstance
    INFO: JSF1048: PostConstruct/PreDestroy annotations present.  ManagedBeans methods marked with these annotations will have said annotations processed.
    May 17, 2021 4:30:24 PM org.primefaces.webapp.PostConstructApplicationEventListener processEvent
    INFO: Running on PrimeFaces 10.0.0
    May 17, 2021 4:30:24 PM org.primefaces.extensions.application.PostConstructApplicationEventListener processEvent
    INFO: Running on PrimeFaces Extensions 10.0.0
    [INFO] Started o.e.j.m.p.MavenWebAppContext@7de147e9{/primefaces-jetty,[file:///home/magnuskkarlsson/eclipse-workspace/primefaces-jetty-10/src/main/webapp/, jar:file:///home/magnuskkarlsson/.m2/repository/org/primefaces/extensions/primefaces-extensions/10.0.0/primefaces-extensions-10.0.0.jar!/META-INF/resources, jar:file:///home/magnuskkarlsson/.m2/repository/org/primefaces/primefaces/10.0.0/primefaces-10.0.0.jar!/META-INF/resources, jar:file:///home/magnuskkarlsson/.m2/repository/org/webjars/font-awesome/5.12.0/font-awesome-5.12.0.jar!/META-INF/resources],AVAILABLE}{file:///home/magnuskkarlsson/eclipse-workspace/primefaces-jetty-10/src/main/webapp/}
    [INFO] Started ServerConnector@30b29f55{HTTP/1.1, (http/1.1)}{0.0.0.0:9090}
    [INFO] Started Server@2094bf3d{STARTING}[10.0.2,sto=0] @6979ms
    [INFO] Scan interval sec = 1
    

    Now deploy to local JBos EAP 7.3

    $ rm -rf ~/bin/jboss-eap-7.3.0/standalone/deployments/primefaces-jetty*; mvn clean install; cp -f target/primefaces-jetty-10.war ~/bin/jboss-eap-7.3.0/standalone/deployments
    

    No comments: