Hantsy Bai
Posted on April 6, 2021
Github is easy to share codes with others, for example I created jakartaee9-starter-boilerplate as a project template for Jakarta EE developers. For those who are familiar with Github, it is easy to start their new projects by forking or cloning this project directly. But obviously for a general Jakarta EE application, you do not need the configuraitons of all application severs, eg. Glassfish/Payara, WildFly, OpenLiberty, Apache TomEE, etc. For most of Java developers esp. Maven users, a simple and clean Maven archetype is still the preferred option to generate the new project skeleton, and you can add the required configuration back later.
I had created some maven archetype like toys myself in the past years, but I never made them public through the Maven Central repository. In this post, I will replay the steps of creating the Maven Archetype for Jakarta EE 9 and the followed steps of publishing it to the Maven Central repository and making it public to the world.
Generating the archetype project skeleton
Maven archetype is the Maven specific templates used to generate a new project in the Maven build system. Maven itself provides an official archetype (maven-archetype-archetype
) for you to build your own Maven archetype.
The official archetype introduction page lists all official archetypes.
Run the following command to generate the archetype project in an interactive mode.
mvn archetype:generate
When you hit down the Enter key, it will take some seconds to list all available archetypes. Every archetype is attached to a number.
Type the number of maven-archetype-archetype
, then it will ask you to fill groupId
, archetypeId
, package
and version
of your project. After all fields are filled as expected, it will generate the project in seconds.
Alternatively, you can generate the project in a single command aka the batch mode.
mvn archetype:generate -B \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-archetype \
-DarchetypeVersion=1.4 \
-DgroupId=io.github.hantsy \
-DartifactId=maven-archetype-jakartaee9 \
-Dversion=1.0-SNAPSHOT
Note here we use the -B
parameter in the command line to skip the interactive questionnaire steps and run the command in batch mode.
More info about Maven archetype and maven-archetype-plugin
, please check Maven Archetype.
Exploring the project structure
Import the project into your IDEs, such as Apache NetBeans, IntelliJ IDEA, etc.
You will see the following project file structure.
├── pom.xml
└── src
├── main
│ └── resources
│ ├── archetype-resources
│ │ ├── pom.xml
│ │ └── src
│ │ ├── main
│ │ │ └── java
│ │ │ └── App.java
│ │ └── test
│ │ └── java
│ │ └── AppTest.java
│ └── META-INF
│ └── maven
│ └── archetype-metadata.xml
└── test
└── resources
└── projects
└── it-basic
├── archetype.properties
└── goal.txt
The structure is similar to a general Maven project, there are some difference.
- The src/main/resources/archetype-resources folder stores all template resources used to generate projects from this archetype.
- The src/main/resources/META-INF/maven-archetype-metadata.xml is the archetype description file, including the properties, file sets etc.
- The src/test/resources/projects includes resources to test this archetype, including the properties applied to generate new projects and the goal run in the host
integration-test
phase.
Preparing the archetype resources
To simplify the work, I reuse the sample codes from jakartaee9-starter-boilerplate.
- Copy
GreetingMessage
,GreetingService
,GreetingService
andJaxrsActivator
to the src/main/resources/archetype-resources/src/main/java folder. Change the package declaration to the following.
package $package;
...
Similar to the last step, copy the
GreetingResourceTest
andGreetingServiceTest
to the src/main/resources/archetype-resources/src/test/java folder. Remember changing the package declaration.Add empty
beans.xml
file to the src/main/resources/archetype-resources/src/main/resources/META-INF/ folder.
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd"
version="3.0">
</beans>
- Add a simple
arquillian.xml
to the src/main/resources/archetype-resources/src/test/resources folder.
<arquillian
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<defaultProtocol type="Servlet 5.0"/>
<engine>
<property name="deploymentExportPath">target/</property>
</engine>
<container qualifier="glassfish" default="true">
<configuration>
<property name="adminHost">localhost</property>
<property name="adminPort">4848</property>
<property name="adminUser">admin</property>
<!-- if https is enabled via `asadmin enable-secure-admin` on a remote server -->
<!-- <property name="adminHttps">true</property>-->
<!-- if admin password is changed via `asadmin change-admin-password` -->
<!--<property name="adminPassword">adminadmin</property>-->
<!-- default is empty -->
<property name="adminPassword"></property>
</configuration>
</container>
</arquillian>
- Change the
pom.xml
template (src/main/resources/archetype-resources/pom.xml) to the following.
<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>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<packaging>war</packaging>
<name>${artifactId}</name>
<description>A Jakarta EE starter boilerplate for Jakarta EE 9</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- Official Maven Plugins -->
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-war-plugin.version>3.3.1</maven-war-plugin.version>
<maven-dependency-plugin.version>3.1.2</maven-dependency-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-failsafe-plugin.version>3.0.0-M5</maven-failsafe-plugin.version>
<maven-surefire-report-plugin.version>3.0.0-M5</maven-surefire-report-plugin.version>
<!-- Cargo maven plugin -->
<!-- Since 1.9.0, cargo-maven3-plugin is the default for cargo prefix. -->
<cargo-maven3-plugin.version>1.9.0</cargo-maven3-plugin.version>
<!-- Jakarta EE API -->
<jakartaee-api.version>9.0.0</jakartaee-api.version>
<!-- Arquillian BOM -->
<arquillian-bom.version>1.7.0.Alpha9</arquillian-bom.version>
<junit-jupiter.version>5.7.1</junit-jupiter.version>
<!-- Glassfish server -->
<glassfish.version>6.0.0</glassfish.version>
<arquillian-glassfish6.version>1.0.0.Alpha1</arquillian-glassfish6.version>
<jersey.version>3.0.1</jersey.version>
<!-- by default skip tests -->
<skipTests>true</skipTests>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>${jakartaee-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>${arquillian-bom.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit-jupiter.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit5</groupId>
<artifactId>arquillian-junit5-container</artifactId>
<scope>test</scope>
</dependency>
<!-- see: https://github.com/arquillian/arquillian-core/issues/248 -->
<!-- and https://github.com/arquillian/arquillian-core/pull/246/files -->
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet-jakarta</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven3-plugin</artifactId>
<version>${cargo-maven3-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>glassfish</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven3-plugin</artifactId>
<configuration>
<container>
<containerId>glassfish6x</containerId>
<artifactInstaller>
<groupId>org.glassfish.main.distributions</groupId>
<artifactId>glassfish</artifactId>
<version>${glassfish.version}</version>
</artifactInstaller>
</container>
<configuration>
<!-- the configuration used to deploy -->
<home>${project.build.directory}/glassfish6x-home</home>
<properties>
<cargo.remote.password></cargo.remote.password>
</properties>
</configuration>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-glassfish-remote-6</artifactId>
<version>${arquillian-glassfish6.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
Note, the value of groupId
, artifactId
and version
elements in this xml refers to a placeholder which can be intercepted in the generating stage.
- Change the archetype descriptor (src/main/resources/META-INF/maven/archetype-metadata.xml) to the following.
<archetype-descriptor
xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"
name="${artifactId}">
<fileSets>
<fileSet filtered="true" packaged="true">
<directory>src/main/java</directory>
</fileSet>
<fileSet>
<directory>src/main/resources</directory>
</fileSet>
<fileSet filtered="true" packaged="true">
<directory>src/test/java</directory>
</fileSet>
<fileSet>
<directory>src/test/resources</directory>
</fileSet>
</fileSets>
</archetype-descriptor>
In the above codes, when the filtered
attribute is set to true
, it will replace the placeholders(eg. $package
, etc.) you have set in the template files with the parameters when executing mvn archetype:generate
to generate projects. And when the packaged
attribute is set to true
, it will move the generated file to corresponding packages.
To test the archetype resources, there is a archetype.properties and a goal.txt file prepared in the src/test/resources/projects/it-basic. In the integration-test phase, it will generate a test project using archetype resources and replace the placeholders using the properties defined in the archetype.properties and execute goals in the goal.txt
against the generated project.
I created a Github Actions workflow to verify the generated projects.
Publishing to Maven Central Repository
Note, the steps of publishing your packages to the Maven Central repository is a little tedious, please be patient.
Once you have built the maven archetype project successfully, the next step is publishing it to Maven Central repository and make it public, there are some useful guides in Google results. I suggest you pick one or more from the following list and read it firstly.
- Publish JAR To Central Maven Repository
- How to Publish Your Artifacts to Maven Central
- Create your own Maven Archetype in 5 simple steps
- Register an account of the Sonatype issue tracker.
- File a new issue on Community Support to submit your request of publishing your artifact. Check my issue#OSSRH-66424 I created for hantsy/maven-archetype-jakartaee9 as an example.
Follow the response of the issue, create a new empty repo(the name is the issue id) on Github for authentication requirement. eg. OSSRH-66424
Generate GPG keys and send to key servers.
Under Windows 10 system, you have to install GPG tools firstly. Simply run the following command to install gpg4win if you are using Chocolatey.
choco install gpg4win
Refresh the environment.
refreshenv
Then generate GPG keys.
# gpg --full-gen-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(14) Existing key from card
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)
Requested keysize is 3072 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: Hantsy Bai
Email address: hantsy@gmail.com
Comment:
You selected this USER-ID:
"Hantsy Bai <hantsy@gmail.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: AllowSetForegroundWindow(9152) failed: Access is denied.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: C:/Users/hantsy/AppData/Roaming/gnupg/trustdb.gpg: trustdb created
gpg: key E5D9B4272348ADAE marked as ultimately trusted
gpg: directory 'C:/Users/hantsy/AppData/Roaming/gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as 'C:/Users/hantsy/AppData/Roaming/gnupg/openpgp-revocs.d\03B4C55DDA964D942C756D79E5D9B4272348ADAE.rev'
public and secret key created and signed.
pub rsa3072 2021-03-30 [SC]
03B4C55DDA964D942C756D79E5D9B4272348ADAE
uid Hantsy Bai <hantsy@gmail.com>
sub rsa3072 2021-03-30 [E]
Send the keys to the public key servers.
# gpg --send-keys 03B4C55DDA964D942C756D79E5D9B4272348ADAE
gpg: sending key E5D9B4272348ADAE to hkps://hkps.pool.sks-keyservers.net
- Follow the steps of Deploying to OSSRH with Apache Maven to publish your artifact.
I skip the detailed steps here, please read the Synatype's article carefully.
- Then back to the issue#OSSRH-66424, comment and notify the moderator to publish your artifact to the Maven Central repository. In one or two hours, your artifact will be available on Maven Central Repository Search index page.
Publishing to Github Package
The Configuring Apache Maven for use with GitHub Packages page provides detailed steps to publish a package to Github Packages.
Following the steps of Authenticating to Github Packages to set the Github authentication info in your ~/.m2/settings.xml file.
Deploy to Github Packages.
In our case, we have set the distributionManagement
to use the Maven Central repository. Use the following command instead.
mvn clean package deploy -DskipTests -DaltDeploymentRepository=github::https://maven.pkg.github.com/hantsy/maven-archetype-jakartaee9
- By default, Github Packages does not allow anyone to access your repository without an authentication. After consulting from Github community, there is a solution to resolve this issue gracefully, it is possible to make your repository accessible to the public.
Generate a Personal Access Token with
packages:read
scope.-
Run the following command to generate the repository config.
# docker run ghcr.io/jcansdale/gpr encode <your PAT> A NuGet `nuget.config` file: <packageSourceCredentials> <github> <add key="Username" value="PublicToken" /> <add key="ClearTextPassword" value="028d0c8f723084e499eb87a6fe173b3af295d618" /> </github> </packageSourceCredentials> A Maven `pom.xml` file: <repositories> <repository> <id>github-public</id> <url>https://public:028d0c8f723084e499eb87a6fe173b3af295d618@maven.pkg.github.com/<OWNER>/*</url> </repository> </repositories> An npm `.npmrc` file: @OWNER:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken="\u003028d0c8f723084e499eb87a6fe173b3af295d618"
As you see, now you can add the above repository to your Maven proxy server or global Maven settings(~/.m2/settings.xml) in your system.
Check the source codes from my Github.
Posted on April 6, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 14, 2024