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