May 31, 2021

Auto Generate OpenAPI/Swagger Specification from Annotated Java Code, with maven, Java 11, Eclipse Microprofile, Java EE 8

OpenAPI Specification (formerly Swagger Specification)

Auto generate documentation for your REST API, is a great way to document and it's auto generated and finally you follows a specification, that people have thought throw.

OpenAPI (part of Eclise Microprofile) Annotation

Maven dependency.

        <dependency>
            <groupId>org.eclipse.microprofile</groupId>
            <artifactId>microprofile</artifactId>
            <version>3.0</version>
            <type>pom</type>
            <scope>provided</scope>
        </dependency>

And annotated Java code.

package se.magnuskkarlsson.example_openapi_swagger.boundary;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;

import se.magnuskkarlsson.example_openapi_swagger.entity.Person;

@Path("/persons")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    @GET
    @Operation(summary = "Get all persons.", description = "Get all persons in DB.")
    @APIResponses(value = { //
            @APIResponse( //
                    responseCode = "500", //
                    description = "Internal Error", //
                    content = @Content(mediaType = MediaType.APPLICATION_JSON)),
            @APIResponse( //
                    responseCode = "200", //
                    description = "All persons in DB.", //
                    content = @Content( //
                            mediaType = MediaType.APPLICATION_JSON, //
                            schema = @Schema(implementation = Person.class))) })
    public List<Person> search( //
            @Parameter( //
                    description = "The name to search for.", //
                    required = false, //
                    example = "*he*", //
                    schema = @Schema(type = SchemaType.STRING)) //
            @QueryParam("name") String name) {
        var persons = new ArrayList<Person>();

        var person1 = new Person().setName("FOO").setAge(24);
        persons.add(person1);

        var person2 = new Person().setName("BAR").setAge(51);
        persons.add(person2);

        return persons;
    }

}

And maven plugin, which will auto generate OpenAPI specification and output in YAML file in your webapp root folder.

            <!-- https://github.com/kongchen/swagger-maven-plugin -->
            <plugin>
                <groupId>com.github.kongchen</groupId>
                <artifactId>swagger-maven-plugin</artifactId>
                <version>3.1.8</version>
                <configuration>
                    <apiSources>
                        <apiSource>
                            <springmvc>false</springmvc>
                            <locations>se.magnuskkarlsson.example_openapi_swagger.boundary</locations>
                            <!-- <schemes>http</schemes> <host>localhost:8081</host> -->
                            <basePath>/${project.build.finalName}</basePath>
                            <info>
                                <title>Users API</title>
                                <version>v1</version>
                                <description>Users rest endpoints</description>
                            </info>
                            <outputFormats>yaml</outputFormats>
                            <swaggerDirectory>${basedir}/src/main/webapp</swaggerDirectory>
                        </apiSource>
                    </apiSources>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>javax.xml.bind</groupId>
                        <artifactId>jaxb-api</artifactId>
                        <version>2.3.1</version>
                    </dependency>
                </dependencies>
            </plugin>

Reference:

Generated OpenAPI YAML Specification

---
swagger: "2.0"
info:
  description: "Users rest endpoints"
  version: "v1"
  title: "Users API"
basePath: "/example-openapi-swagger-1.0.0-SNAPSHOT"
paths:
  /persons:
    get:
      operationId: "search"
      consumes:
      - "application/json"
      produces:
      - "application/json"
      parameters:
      - name: "name"
        in: "query"
        required: false
        type: "string"
      responses:
        200:
          description: "successful operation"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/Person"
definitions:
  Person:
    type: "object"
    properties:
      name:
        type: "string"
      age:
        type: "integer"
        format: "int32"

Complete pom.xml

<?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>example-openapi-swagger</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>
        <primefaces.version>10.0.0</primefaces.version>
    </properties>

    <dependencies>
        <!-- Java EE 8 -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- JBoss EAP 7.3 -->
        <dependency>
            <groupId>org.eclipse.microprofile</groupId>
            <artifactId>microprofile</artifactId>
            <version>3.0</version>
            <type>pom</type>
            <scope>provided</scope>
        </dependency>

        <!-- Test Support -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>0.17.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
            <version>${hibernate.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate-validator.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>jakarta.el</artifactId>
            <version>3.0.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <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>
            <!-- https://github.com/kongchen/swagger-maven-plugin -->
            <plugin>
                <groupId>com.github.kongchen</groupId>
                <artifactId>swagger-maven-plugin</artifactId>
                <version>3.1.8</version>
                <configuration>
                    <apiSources>
                        <apiSource>
                            <springmvc>false</springmvc>
                            <locations>se.magnuskkarlsson.example_openapi_swagger.boundary</locations>
                            <!-- <schemes>http</schemes> <host>localhost:8081</host> -->
                            <basePath>/${project.build.finalName}</basePath>
                            <info>
                                <title>Users API</title>
                                <version>v1</version>
                                <description>Users rest endpoints</description>
                            </info>
                            <outputFormats>yaml</outputFormats>
                            <swaggerDirectory>${basedir}/src/main/webapp</swaggerDirectory>
                        </apiSource>
                    </apiSources>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>javax.xml.bind</groupId>
                        <artifactId>jaxb-api</artifactId>
                        <version>2.3.1</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

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
    

    May 14, 2021

    jetty-maven-plugin, Java 11, JSF 2.3, Primefaces 10 and Java EE 8 with CDI 2.0

    Introduction

    Using maven jetty plugin is a fast way to develop your web application.

    You run your webapp with

    $ mvn clean package jetty:run
    

    And whenever you change something the jetty server is automatically reloaded.

    pom.xml

    <?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</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>
    
            <!-- JBoss EAP 7.3 -->
            <dependency>
                <groupId>org.eclipse.microprofile</groupId>
                <artifactId>microprofile</artifactId>
                <version>3.0</version>
                <type>pom</type>
                <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>
                <!-- https://codenotfound.com/jsf-primefaces-hello-world-example-jetty-maven.html -->
                <!-- mvn clean package jetty:run -->
                <plugin>
                    <groupId>org.eclipse.jetty</groupId>
                    <artifactId>jetty-maven-plugin</artifactId>
                    <version>9.4.40.v20210413</version>
                    <configuration>
                        <!-- https://www.eclipse.org/jetty/documentation/jetty-9/index.html#jetty-maven-plugin -->
                        <httpConnector>
                            <port>9090</port>
                        </httpConnector>
                        <scanIntervalSeconds>1</scanIntervalSeconds>
                        <webApp>
                            <contextPath>/primefaces-jetty</contextPath>
                        </webApp>
                    </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>
                        <!-- CDI 2.0 Impl http://fritzthecat-blog.blogspot.com/2019/08/jsf-23-maven-project-in-eclipse.html -->
                        <dependency>
                            <groupId>org.jboss.weld.servlet</groupId>
                            <artifactId>weld-servlet-shaded</artifactId>
                            <version>3.1.2.Final</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
    </project>
    

    Web App

    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>
    

    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>
    

    src/main/webapp/WEB-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>
    

    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>
    

    src/main/java/se/magnuskkarlsson/example/primefaces/HelloWorld.java

    package se.magnuskkarlsson.example.primefaces;
    
    import javax.enterprise.context.RequestScoped;
    import javax.inject.Named;
    
    @Named
    @RequestScoped
    public class HelloWorld {
    
        private String firstName = "John";
        private String lastName = "Doe";
    
        // ----------------------- Logic Methods -----------------------
    
        public String showGreeting() {
            return "Hello " + 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;
        }
    
    }