5

A Maven Archetype for Jakarta EE 10 Applications

 1 year ago
source link: https://dzone.com/articles/a-maven-archetype-for-jakarta-ee-10-applications
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A Maven Archetype for Jakarta EE 10 Applications

A demonstration of an archetype that generates a Jakarta EE 10 web applications skeleton and its associated artifacts to be deployed on a Payara 6 server.

by

CORE ·

Dec. 06, 22 · Tutorial

Jakarta EE 10 is probably the most important event of this year in the Java world. Since this fall, software editors providing Jakarta EE-compliant platforms are working hard to validate their respective implementations against the TCK (Technology Compatibility Kit) supplied by the Eclipse Foundation. At Payara, as much as in bigger companies like Oracle, Red Hat, or IBM, they aren't left behind, and, as of last September, they announced the availability of the Payara 6 Platform, declined in three versions: Server, Micro, and Cloud. As an implementation of Jakarta EE 10 Web, Core, and Micro Profile, Payara Server 6 is itself proposed in two editions: Community and Enterprise.

But how does this impact Java developers? What does it mean in terms of application development and portability? Is it easier or more difficult to write and deploy code compliant to the new specifications than it was with Release 9 or 8 of the Jakarta EE drafts? Well, it depends. While any new Jakarta EE release aims at simplifying the whole bunch of the API (Application Programming Interface) set and at facilitating the developers' work, the fact that, on a total of 20 specifications, 16 have been updated, and a new one has been added, shows how dynamic the communities and the working groups involved in this process are. Which isn't without some difficulties when trying to transition to the newest releases with a minimal impact.

This is especially true when it comes to combining, for example, JAX-RS 4.0 and its implementation by Jersey 3.1.0 with JSON-B 3.0 and its Yasson provider by Eclipse or when experiencing NoSuchMethodException, due to an inconvenient combination of versions. Or when noticing that a transitive Maven dependency, pulled out by a nonupdated library like RESTassured, still uses the old javax namespace. In order to avoid all these troubles, as marginal as they may be, one of the most practical solutions is to use Maven archetypes. 

A Maven archetype is a set of templates used to generate a Java project skeleton. They use Velocity placeholders which, at the generation time, are replaced by actual values that make sense in the current context. Hence, software editors, different OSS communities and work groups, or even individuals provide such Maven archetypes. The Apache community, for example, provides several hundreds of such Maven archetypes, and one may find one for almost any type of Java project. The advantage of using them is that the developers can generate a basic and clean skeleton of their Java project, on which they can build while avoiding some minor but painful annoyances.

Jakarta EE 10 is so recent that most of its implementations are still in beta testing and consequently, the Maven archetypes dedicated to the Java projects using this release aren't yet available. In this blog post, I'm demonstrating such an archetype that generates a Jakarta EE 10 web applications skeleton and its associated artifacts to be deployed on a Payara 6 server. The code might be found here.

The figure below shows the structure of our Maven archetype:

16553279-tree.png

A Maven archetype is a Maven project like any other, and, as such, it is driven by a pom.xml file, which the most essential part is reproduced below:

<?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>fr.simplex-software.archetypes</groupId>
  <artifactId>jakartaee10-basic-archetype</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>Basic Java EE 10 project archetype</name>
  ...
  <packaging>maven-archetype</packaging>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  </properties>
  <build>
    <extensions>
      <extension>
        <groupId>org.apache.maven.archetype</groupId>
        <artifactId>archetype-packaging</artifactId>
        <version>3.1.1</version>
      </extension>
    </extensions>
  </build>
</project>

The only notable thing that our pom.xml file should contain is the declaration of the archetype-packaging Maven plugin that will be used to generate our Java project skeleton. In order to do that, proceed as follows:

Shell
$ git clone https://github.com/nicolasduminil/jakartaee10-basic-archetype.git
$ cd jakartaee10-basic-archetype
$ mvn clean install
[INFO] Scanning for projects...
[INFO] 
[INFO] -----< fr.simplex-software.archetypes:jakartaee10-basic-archetype >-----
[INFO] Building Basic Java EE 10 project archetype 1.0-SNAPSHOT
[INFO] --------------------------[ maven-archetype ]---------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ jakartaee10-basic-archetype ---
[INFO] 
[INFO] --- maven-resources-plugin:3.3.0:resources (default-resources) @ jakartaee10-basic-archetype ---
[INFO] Copying 11 resources
[INFO] 
[INFO] --- maven-resources-plugin:3.3.0:testResources (default-testResources) @ jakartaee10-basic-archetype ---
[INFO] skip non existing resourceDirectory /home/nicolas/jakartaee10-basic-archetype/src/test/resources
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.1:jar (default-jar) @ jakartaee10-basic-archetype ---
[INFO] Building archetype jar: /home/nicolas/jakartaee10-basic-archetype/target/jakartaee10-basic-archetype-1.0-SNAPSHOT.jar
[INFO] Building jar: /home/nicolas/jakartaee10-basic-archetype/target/jakartaee10-basic-archetype-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.1:integration-test (default-integration-test) @ jakartaee10-basic-archetype ---
[WARNING] No Archetype IT projects: root 'projects' directory not found.
[INFO] 
[INFO] --- maven-install-plugin:3.1.0:install (default-install) @ jakartaee10-basic-archetype ---
[INFO] Installing /home/nicolas/jakartaee10-basic-archetype/pom.xml to /home/nicolas/.m2/repository/fr/simplex-software/archetypes/jakartaee10-basic-archetype/1.0-SNAPSHOT/jakartaee10-basic-archetype-1.0-SNAPSHOT.pom
[INFO] Installing /home/nicolas/jakartaee10-basic-archetype/target/jakartaee10-basic-archetype-1.0-SNAPSHOT.jar to /home/nicolas/.m2/repository/fr/simplex-software/archetypes/jakartaee10-basic-archetype/1.0-SNAPSHOT/jakartaee10-basic-archetype-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.1:update-local-catalog (default-update-local-catalog) @ jakartaee10-basic-archetype ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.151 s
[INFO] Finished at: 2022-12-02T13:32:35+01:00
[INFO] ------------------------------------------------------------------------

Here we're cloning first the GIT repository containing our archetype, and then, we're installing it in our local Maven repository, such that we can use it in order to generate projects. As explained earlier, our archetype is a set of template files located in the directory src/main/resources/atchetype-resources.  These files use the Velocity notation to express placeholders that will be processed and replaced during the generation process. For example, looking at the file MyResource.java which exposes a simple REST API:

package $package;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;
import jakarta.inject.*;

import org.eclipse.microprofile.config.inject.*;

@Path("myresource")
public class MyResource
{
  @Inject
  @ConfigProperty(name = "message")
  private String message;

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String getIt()
  {
    return message;
  }
}

Here, the placeholder $package will be replaced by the actual Java package name of the generated class. The whole bunch of resources that will be included in the generated project are described by the file archetype-metadata.xml located in src/main/resources/META-INF/maven.

<archetype-descriptor
  xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0 http://maven.apache.org/xsd/archetype-descriptor-1.0.0.xsd"
  xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  name="jakarta-ee-10-webapp">
  <fileSets>
    <fileSet filtered="true" packaged="false" encoding="UTF-8">
      <directory>src/main/java</directory>
      <includes>
        <include>**/*.java</include>
      </includes>
    </fileSet>
    <fileSet filtered="true" packaged="false" encoding="UTF-8">
      <directory>src/test/java</directory>
      <includes>
        <include>**/*.java</include>
      </includes>
    </fileSet>
    <fileSet filtered="true" packaged="false" encoding="UTF-8">
      <directory>src/main/resources</directory>
      <includes>
        <include>**/*.xml</include>
        <include>**/*.properties</include>
      </includes>
    </fileSet>
    <fileSet filtered="false" packaged="false" encoding="UTF-8">
      <directory>src/main/webapp</directory>
      <includes>
        <include>index.jsp</include>
      </includes>
    </fileSet>
    <fileSet filtered="false" packaged="false" encoding="UTF-8">
      <directory></directory>
      <includes>
        <include>.gitignore</include>
      </includes>
    </fileSet>
    <fileSet filtered="true" packaged="false" encoding="UTF-8">
      <directory></directory>
      <includes>
        <include>README.md</include>
        <include>Dockerfile</include>
        <include>build.sh</include>
      </includes>
    </fileSet>
  </fileSets>
</archetype-descriptor>

The syntax above is self-descriptive and probably already known by all the Maven users. 

Once we installed our Maven archetype in our local Maven repository, we can proceed with the generation process:

Shell
$ cd example
$ ../jakartaee10-basic-archetype/generate.sh
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:3.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO] 
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [fr.simplex-software.archetypes:jakartaee10-basic-archetype:1.0-SNAPSHOT] found in catalog local
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: jakartaee10-basic-archetype:1.0-SNAPSHOT
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.exemple
[INFO] Parameter: artifactId, Value: test
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.exemple
[INFO] Parameter: packageInPathFormat, Value: com/exemple
[INFO] Parameter: package, Value: com.exemple
[INFO] Parameter: groupId, Value: com.exemple
[INFO] Parameter: artifactId, Value: test
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Project created from Archetype in dir: /home/nicolas/toto/test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.898 s
[INFO] Finished at: 2022-12-02T13:53:32+01:00
[INFO] ------------------------------------------------------------------------
$ cd test
$ ./build.sh 
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.exemple:test >--------------------------
[INFO] Building test 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ test ---
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ test ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ test ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/nicolas/toto/test/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ test ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/nicolas/toto/test/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ test ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/nicolas/toto/test/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ test ---
[INFO] 
[INFO] --- maven-war-plugin:3.3.1:war (default-war) @ test ---
[INFO] Packaging webapp
[INFO] Assembling webapp [test] in [/home/nicolas/toto/test/target/test]
[INFO] Processing war project
[INFO] Copying webapp resources [/home/nicolas/toto/test/src/main/webapp]
[INFO] Building war: /home/nicolas/toto/test/target/test.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.620 s
[INFO] Finished at: 2022-12-02T13:54:33+01:00
[INFO] ------------------------------------------------------------------------
Sending build context to Docker daemon  13.66MB
Step 1/2 : FROM payara/server-full:6.2022.1
 ---> ada23f507bd2
Step 2/2 : COPY ./target/test.war $DEPLOY_DIR
 ---> 96650dc307b0
Successfully built 96650dc307b0
Successfully tagged com.exemple/test:latest
Error: No such container: test
39934e82c8b164c4e6cd91036df7e2b0731254cdb869d7f2321ad1f2aaf37350

The generate.sh script that we're running above only contains the maven archetype:generate goal, as shown:

Shell
#!/bin/sh
mvn -B archetype:generate \
  -DarchetypeGroupId=fr.simplex-software.archetypes \
  -DarchetypeArtifactId=jakartaee10-basic-archetype \
  -DarchetypeVersion=1.0-SNAPSHOT \
  -DgroupId=com.exemple \
  -DartifactId=test

Here we're using our Maven archetype in order to generate a new artifact which GAV (GroupID, ArtifactID, Version) are: com.example:test:1.0-SNAPSHOT. Once generated, the new project may be imported in your preferred IDE. As you may see, it consists of a simple REST API exposing and endpoint returning some text. For this purpose, we leverage Jakarta JAX-RS 4.0 and its Jersey 3.1 implementation with the Eclipse Microprofile Configuration 5.0. Please take some time to look at the generation project, including the pom.xml file and the dependencies used with their associated versions. All these dependencies are mandatory in order to get a valid artifact.

We just generated our new project; let's build it now. In the listing above, we did that by running the script build.sh.

Shell
#!/bin/sh
mvn clean package && docker build -t ${groupId}/${artifactId} .
docker rm -f ${artifactId} || true && docker run -d -p 8080:8080 -p 4848:4848 --name ${artifactId} ${groupId}/${artifactId}

This script is first packaging the newly generated Java project in a WAR, and after that, it builds a new Docker image based on the Dockerfile below:

Dockerfile
FROM payara/server-full:6.2022.1
COPY ./target/${artifactId}.war $DEPLOY_DIR

As you may see, this Dockerfile is just extending the standard Payara server Docker image provided by the company to copy the WAR that was previously packaged into the auto-deployment server directory. Copying the WAR in the mentioned directory, which by the way is /opt/payara/deployments, automatically deploys the packaged application. Once this new Docker image built, we run it under the same name as our Maven artifactId, by mapping the ports 8080 and 4848. Please notice the way that the Velocity placeholders are again used.

Once the Maven build process is successfully finished, a Docker container of the name of the test should be running. Of course, you need to have a running Docker daemon. You can test that everything is okay using the following curl request:

Shell
curl http://localhost:8080/test/api/myresource

or by executing the script myresource.sh. An integration test is generated as well. It leverages test containers to execute an instance of Payara server 6 in a Docker container in which the application has been deployed. Then it uses the JAX-RS client, as implemented by Jersey Client 3.1, to perform HTTP requests to the exposed endpoint. You can experience it by running the following maven command:

Shell
$ mvn verify

Please notice that this command can only be run after having previously executed the build.sh script or having manually run:

Shell
$ mvn -DskipTests clean package

This is because the integration test uses testcontainers to deploy the WAR and, consequently, the WAR has to exist then. Hence, the package goal which creates the WAR should have been executed already. And we need to skip tests in order to avoid trying to execute them before packaging.

Enjoy!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK