Build an automated deployment pipeline for your Java EE applications using leading open-source technologies, including NetBeans, Git, Maven, JUnit, Jenkins, and GlassFish. All source code for this post is available on GitHub.
Introduction
In my earlier post, Build a Continuous Deployment System with Maven, Hudson, WebLogic Server, and JUnit, I demonstrated a basic deployment pipeline using leading open-source technologies. In this post, we will demonstrate a similar pipeline, substituting Jenkins CI Server for Hudson, and Oracle’s GlassFish Application Server for WebLogic Server. We will use the same NetBeans Java EE ‘Hello World’ RESTful Web Service sample project.
The three main goals of our deployment pipeline will be continuous integration, automated testing, and continuous deployment. Our objective is to automatically compile, test, assemble, and deploy our Java EE application to multiple environments, as the project progresses through the software development life cycle (SDLC).
Building a reliable deployment pipeline is complex and time-consuming. To make it as easy as possible in this post, I chose NetBeans IDE for development, Git Distributed Version Control System (DVCS) for managing our source code, Jenkins Continuous Integration (CI) Server for build automation, JUnit for automated unit testing, GlassFish for application hosting, and Apache Maven to manage our project’s dependencies. Maven will also manage the build and deployment process to GlassFish, along with Jenkins. The beauty of NetBeans is its out-of-the-box, built-in integration with Git, Maven, JUnit, and GlassFish. Likewise, Jenkins has plugin-based integration with Git, Maven, JUnit, and GlassFish. Also, Maven has plugin-based integration with GlassFish.
Maven is a powerful tool for managing modern software development projects. This post will only draw upon a small part of Maven’s functionality and plug-in architecture extensibility. Specifically, we will use the Maven GlassFish Plugin. According to the Java.net website, which host’s the plug-in project, ‘the Maven GlassFish Plugin is a Maven2 plugin allowing management of GlassFish domains and component deployments from within the Maven build life cycle.’
Requirements
To follow along with this post, I will assume you have recent versions of the following software installed and configured on your Windows OS-based computer (the process is nearly identical for Linux):
- NetBeans IDE. Current version: 7.4
- JUnit. Current version: 4.11 (included with NetBeans 7.4)
- GlassFish Server. Current version: 4.0 (included with NetBeans 7.4)
- Jenkins CI Server. Current version: 1.538
- Apache Maven. Current version: 3.1.1
- cURL. Current version: 7.33.0
- Git with Git Gui and gitk. Current version: 1.8.4.3
- Necessary system environmental variables:
M2_HOME, M2, JAVA_HOME, GLASSFISH_HOME, and PATH
GlassFish Domains
To simulate a simple deployment pipeline, we will create three GlassFish domains, simulating three common software environments, Development, Testing, and Production. A typical software project is promoted through these environments as it moves from development, to testing, and finally release to production. Each environment has distinct stakeholders with specific roles to play in the software development life cycle, including developers, testers, deployment teams, and end-users. Larger-scale, enterprise software development often includes other environments, such as Performance and Staging.
Create the domains from the command line using ‘asadmin’ commands such as the ones below. Note I have a ‘GLASSFISH_HOME’ system environment variable set up. The ports are your choice, but make sure they don’t conflict with existing installations of other applications, such as Jenkins, Tomcat, IIS, WebLogic, and so forth.
asadmin create-domain --domaindir "%GLASSFISH_HOME%\domains" --adminport 7070 --instanceport 7071 production | |
asadmin create-domain --domaindir "%GLASSFISH_HOME%\domains" --adminport 6060 --instanceport 6061 testing | |
asadmin create-domain --domaindir "%GLASSFISH_HOME%\domains" --adminport 5050 --instanceport 5051 development |
As part of the creation process, you’re prompted for an admin account and a new password. I kept the ‘admin’ username, but added a new password for each domain created. This password is the same as one used in the separate password files (explained below).
C:\Users\gstaffor>asadmin create-domain --domaindir "%GLASSFISH_HOME%\domains" --adminport 7070 --instanceport 7071 production | |
Enter admin user name [Enter to accept default "admin" / no password]>admin | |
Enter the admin password [Enter to accept default of no password]> | |
Enter the admin password again> | |
Using port 7070 for Admin. | |
Using port 7071 for HTTP Instance. | |
Using default port 7676 for JMS. | |
Using default port 3700 for IIOP. | |
Using default port 8181 for HTTP_SSL. | |
Using default port 3820 for IIOP_SSL. | |
Using default port 3920 for IIOP_MUTUALAUTH. | |
Using default port 8686 for JMX_ADMIN. | |
Using default port 6666 for OSGI_SHELL. | |
Using default port 9009 for JAVA_DEBUGGER. | |
Distinguished Name of the self-signed X.509 Server Certificate is: | |
[CN={my_computer_name},OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US] | |
Distinguished Name of the self-signed X.509 Server Certificate is: | |
[CN={my_computer_name}-instance,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C | |
=US] | |
Domain production created. | |
Domain production admin port is 7070. | |
Domain production admin user is "admin". | |
Command create-domain executed successfully. |
Add the GlassFish domains to NetBeans’ Services -> Server tab, and start them.
Setting Up the Project
To set up our NetBeans project, you can clone the repository on GitHub or build your own project from scratch and copy the files into the project. I will not spend a lot of time explaining the code since we have used it in earlier posts. This post is about the deployment pipeline system, not the project’s code.
If you choose to create a new project, first, create a new Maven ‘Project from Archetype’. Select the Archetype for a ‘web application using Java EE 7’ (webapp-javaee7).
I recommend you create the project inside of your local Git repository folder.
Maven will execute a series of commands to create the default NetBeans project with dependencies.
Git
As a part of a development team using Git, you place your project on a remote Git Server. You and your team members each clone the repository on the Git Server to your local development environments. You and your team commit your code changes locally, then pull, merge, and push your changes back to the Git Server. Jenkins will pull the project’s source code from the remote Git Server.
In part 2, we will properly set-up our project on the Git Server, exporting our existing repository into a new, bare repository on the Git Server. However, for brevity in part 1 of this post, we will just create a local Git repository. To start, create a new Git repository for the project. In NetBeans, select Team -> Git -> Initialize Repository… Choose the new Maven project folder.
The initial view of the Maven project should look like the below screen grabs. Note the icons and the green files show that the project is part of the Git repository.
Perform an initial commit of the project to Git to make sure everything is working.
Next, copy the supplied HelloWorldResource. java and NameStorageBean.java classes into the project. The package classpath will be refactored by NetBeans. Copy all the remaining files and folders, including the (3) files in the WEB-INF folder, properties folder with (3) properties files, and passwords folder with (3) password files.
JUnit
Next, right-click on the NameStorageBean.java class and select Tools -> Create Tests. Replace the contents of the new NameStorageBeanTest.java file’s NameStorageBeanTest class with the contents of the supplied NameStorageBeanTest.java file. These are two very simple unit tests that will show how JUnit provides automated testing capabilities.
Project Object Model (POM)
Copy the contents of the supplied pom file into the new pom file. There is a lot of configuration in the supplied pom. It will be easier to copy the supplied pom file’s contents into your project then trying to configure it from scratch.
Basically, beyond the normal boilerplate pom configuration, we have defined (3) properties, (3) dependencies, and (5) build plugins. The three dependencies are junit, jersey-servlet, and javaee-web-api. The five plugins are maven-compiler-plugin, maven-war-plugin, maven-dependency-plugin, properties-maven-plugin, and the maven-glassfish-plugin. Each plugin contains individual plug-in specific configuration. The name of the plugin should be sufficient to explain their primary purpose.
<?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>com.blogpost</groupId> | |
<artifactId>HelloGlassFishMaven</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
<packaging>war</packaging> | |
<name>HelloGlassFishMaven</name> | |
<properties> | |
<!-- Input Parameter - GlassFish properties file --> | |
<glassfish.properties.file.argument></glassfish.properties.file.argument> | |
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> | |
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
</properties> | |
<dependencies> | |
<dependency> | |
<groupId>junit</groupId> | |
<artifactId>junit</artifactId> | |
<version>4.11</version> | |
</dependency> | |
<dependency> | |
<groupId>com.sun.jersey</groupId> | |
<artifactId>jersey-servlet</artifactId> | |
<version>1.13</version> | |
</dependency> | |
<dependency> | |
<groupId>javax</groupId> | |
<artifactId>javaee-web-api</artifactId> | |
<version>7.0</version> | |
<scope>provided</scope> | |
</dependency> | |
</dependencies> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>3.1</version> | |
<configuration> | |
<source>1.7</source> | |
<target>1.7</target> | |
<compilerArguments> | |
<endorseddirs>${endorsed.dir}</endorseddirs> | |
</compilerArguments> | |
</configuration> | |
</plugin> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-war-plugin</artifactId> | |
<version>2.3</version> | |
<configuration> | |
<failOnMissingWebXml>false</failOnMissingWebXml> | |
<filteringDeploymentDescriptors>true</filteringDeploymentDescriptors> | |
<webresources> | |
<resource> | |
<directory>${basedir}/src/main/webapp/WEB-INF</directory> | |
<filtering>true</filtering> | |
<targetpath>WEB-INF</targetpath> | |
<includes> | |
<include>**/glassfish-web.xml</include> | |
</includes> | |
</resource> | |
</webresources> | |
</configuration> | |
</plugin> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-dependency-plugin</artifactId> | |
<version>2.6</version> | |
<executions> | |
<execution> | |
<phase>validate</phase> | |
<goals> | |
<goal>copy</goal> | |
</goals> | |
<configuration> | |
<outputDirectory>${endorsed.dir}</outputDirectory> | |
<silent>true</silent> | |
<artifactItems> | |
<artifactItem> | |
<groupId>javax</groupId> | |
<artifactId>javaee-endorsed-api</artifactId> | |
<version>7.0</version> | |
<type>jar</type> | |
</artifactItem> | |
</artifactItems> | |
</configuration> | |
</execution> | |
</executions> | |
</plugin> | |
<plugin> | |
<groupId>org.codehaus.mojo</groupId> | |
<artifactId>properties-maven-plugin</artifactId> | |
<version>1.0-alpha-2</version> | |
<configuration> | |
<files> | |
<file>${basedir}/properties/${glassfish.properties.file.argument}.properties</file> | |
</files> | |
</configuration> | |
</plugin> | |
<plugin> | |
<groupId>org.glassfish.maven.plugin</groupId> | |
<artifactId>maven-glassfish-plugin</artifactId> | |
<version>2.1</version> | |
<configuration> | |
<glassfishDirectory>${GLASSFISH_HOME}</glassfishDirectory> | |
<user>${glassfish.user}</user> | |
<passwordFile>${basedir}/passwords/${glassfish.pwdfile}</passwordFile> | |
<echo>true</echo> | |
<debug>true</debug> | |
<terse>true</terse> | |
<domain> | |
<name>${glassfish.domain}</name> | |
<host>${glassfish.host}</host> | |
<adminPort>${glassfish.adminport}</adminPort> | |
</domain> | |
<components> | |
<component> | |
<name>${project.artifactId}</name> | |
<artifact>${project.build.directory}/${project.build.finalName}.war</artifact> | |
</component> | |
</components> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
When complete, right-click on the project and do a ‘Build with Dependencies…’. Make sure everything builds. The final view of the project, with all its Maven-managed dependencies should look like the two screen grabs shown below. Make sure to commit all your new code to Git.
Maven and Properties Files
In part 2, will be deploying our project to multiple GlassFish domains. Each domain’s configuration is different. We will use Java properties files to store each of the GlassFish domain’s configuration properties. The ability to use Java properties files with Maven is possible using the Mojo Project’s Properties Maven Plugin. I introduced this plugin in an earlier post, Build a Continuous Deployment System with Maven, Hudson, WebLogic Server, and JUnit.
Each environment (Development, Testing, Production), represented by a GlassFish domain, has a separate properties file in the project (see the Files Tab view above). The properties files contain configuration values the Maven GlassFish Plugin will need to deploy the project’s WAR file to each GlassFish domain. Since the build and deployment configurations are required by the project, including them into our Git repository and automating their use based on the environment, are two best practices.
# contents of all three files shown here | |
# development domain properties file | |
glassfish.domain=development | |
glassfish.host=glassfish4-app-server | |
glassfish.adminport=5050 | |
glassfish.user=admin | |
glassfish.pwdfile=pwdfile_development | |
# testing domain properties file | |
glassfish.domain=testing | |
glassfish.host=glassfish4-app-server | |
glassfish.adminport=6060 | |
glassfish.user=admin | |
glassfish.pwdfile=pwdfile_testing | |
# production domain properties file | |
glassfish.domain=production | |
glassfish.host=glassfish4-app-server | |
glassfish.adminport=7070 | |
glassfish.user=admin | |
glassfish.pwdfile=pwdfile_production |
In our project’s particular workflow, Maven accepts a single argument (‘glassfish.properties.file.argument’), which represents the environment we want to deploy to, such as ‘development’. The property value tells Maven which properties file to read, such as ‘development.properties’. Maven replaces the keys in the pom file with the values from the ‘development.properties’ file.
The properties file also tells Maven the full path to the separate password file, containing the admin user password, such as ‘pwdfile_development’. In an actual production environment, we would store encrypted password files on a secured file path. For simplicity in our example, we have included them unencrypted, within the project’s main directory.
There are other Maven capabilities that also would achieve our deployment goals. For example, you might consider the Maven Release Plugin, as well as look at using Maven Build Profiles.
Testing the Pipeline
Although we have not built the second half of our deployment pipeline yet, we can still test the system at this early stage. All the necessary foundational elements are in place. To test the our system, right-click on the Maven Project icon in the Projects tab and select Custom -> Goals… Enter the following Maven Goals: ‘properties:read-project-properties clean install glassfish:redeploy -e’. In the Properties text box, enter the following: ‘glassfish.properties.file.argument=testing’ (see screen grab below). This will execute a number of Maven Goals and associated commands, visible in the Output tab.
With this one simple command, we are asking Maven to 1) read in our Java properties file and password file, 2) clean the project, 3) pull down all our project’s dependencies, 4) compile the project’s code, 5) execute the unit tests with JUnit, 6) assemble the WAR file, and 7) deploy it to the ‘testing’ GlassFish domain using asadmin. The terse nature of the command really demonstrates the power of Maven to manage our project and the deployment pipeline!
If successful you should see a message in the Output tab, indicating as much. Reviewing the contents of the Output tab will give you complete insight into the Maven process under the NetBeans hood. We used the ‘-e’ (echo) argument with Maven and the ‘Show Debug Output’ to further provide information to us about the process. The output contains all calls to Maven and subsequently to asadmin (GlassFish). You can learn a lot about using Maven and asadmin (GlassFish) by studying the Debug Output.
Conclusion
In the first part of this post, we learned how to create a simple Java EE web application project in NetBeans, using Maven. We learned how to integrate JUnit for automated testing, and how use Git to manage our source code.
In the second half of this post, we will learn how to configure Jenkins CI Server to retrieve our project from the remote Git repository, build, test, and assemble it into a WAR file. If these steps are successful, Jenkins will deploy our project to a GlassFish domain or multiple domains, based on the project’s stage in the software development life cycle. We will demonstrate how to automate Jenkins to achieve true continuous integration and continuous deployment.
#1 by PaulMB on November 8, 2013 - 10:51 pm
Ow, great walktrough, gonna implement it right away on our javaEE project, Thanks!
#2 by Gary A. Stafford on November 8, 2013 - 11:20 pm
Thank you for your comments. Part 2 is coming within the a week.
#3 by Enrico Goosen on November 12, 2013 - 9:50 am
Thanks for the article…exactly what I need right now. When’s part 2 coming??
#4 by Gary A. Stafford on November 12, 2013 - 11:42 am
This week! It is longer than I expected due to some problem-solving around NetBeans and Git limitations with Git Hooks. Thank you for reading.
#5 by Enrico Goosen on November 12, 2013 - 12:15 pm
Excellent, I look forward to it.
#6 by Richard on June 29, 2014 - 9:35 pm
web.xml contains:
index.jsp
but I can’t find that in the repository.
#7 by vishal on July 16, 2014 - 8:54 am
Hi ,
We have our project in the Git hub and we use Jenkins to Build and promote the builds.
We also have different Environments like DEV server, QA server, UAT server, Production server.
need to know a process /strategy/ flow of using GIT & jenkin for build and deployment of the project with complete Release management.
Example: How to manage GIt for different Environments ? how to promote builds from lower env to upper env?
how to provide hot fixs and defect fixes for each env and merge them.
I have read this :http://nvie.com/posts/a-successful-git-branching-model/
but not sure how Jenkins can help in managing the release and do all the manual task. And make developer life easy 🙂
#8 by mauroprogram on March 12, 2015 - 11:48 am
Reblogged this on mauroprogram's Blog.
#9 by sangdn on May 28, 2015 - 8:47 am
Reblogged this on sangdn and commented:
Really helpful 🙂