Friday, July 31, 2015

How to Create .war Files with Maven Archetypes and Deploy to Tomcat

 Maven Archetypes are project templating mechanism, implemented as a Maven plugin.Archetypes make it possible to define the barebone of a project’s structure and then use that to create other projects.

This post, which is the fourth in a series of posts on Servlet based web application development in Java, shows how Maven archetypes can be used to generate a Web application Maven project; how to edit it to print “hello world”; and also, how you can use Maven to package it up so it can be deployed to a Servlet container.

We would make use of two Maven archetypes to achieve this:
1. The maven-archetype-webapp  and
2. The Servlet3-maven-archetype

Let's get down to business


Using the maven-archetype-webapp

The steps for using maven-archetype-web archetype is only included here for completeness sake as it is pretty outdated. The plugin is no longer maintained (it was last updated in 2006) and It generates projects that use a much older version of the Servlet API (the 2.x version) and the deployment model of the project it generates depends on the use of a file named web.xml: a model that has since stopped being the preferred way to deploy in newer versions of the Servlet API.

The maven-archetype-webapp archetype generates Maven projects that use an older version of the Servlet API and also makes use of web.xml i.e. a web descriptor.

The web descriptor is an xml file which is used to provide web application related configuration to the Servlet container. For a reference on web.xml see web.xml Reference Guide for Tomcat

There are a lot of projects in the wild that still base their deployment on web.xml (another reason why this method is mentioned, so you know what it entails if encountered in the wild) but ideally you should be building applications against newer versions of the Servlet API.

So let us see how to use the maven-archetype-webapp archetype to create a project with groupId of com.blogspot.geekabyte.servletDemo and artifactId of servletDemo

To do so, we follow the following steps:

mkdir demo
cd demo

then run the following command

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp

This would proceed to series of prompts, asking you to supply the details of the project you want to create: the groupId, artifactId, the version number, and the package structure. For example the screenshot below shows all the prompt supplied before agreeing to generate the project



Entering Y would generate the project with the following output

 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-webapp:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: /home/dadepo/Desktop/demo
[INFO] Parameter: package, Value: com.blogspot.geekabyte.servletDemo
[INFO] Parameter: groupId, Value: com.blogspot.geekabyte.servletDemo
[INFO] Parameter: artifactId, Value: servletDemo
[INFO] Parameter: packageName, Value: com.blogspot.geekabyte.servletDemo
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /home/dadepo/Desktop/demo/servletDemo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9:38:07.178s
[INFO] Finished at: Thu Jul 30 21:07:11 CEST 2015
[INFO] Final Memory: 13M/167M
[INFO] ------------------------------------------------------------------------

with a directory servletDemo created with a layout similar to below:

servletDemo/
|-- src
 |   `-- main
 |           |-- resources
 |           |-- webapp
 |           |   `-- WEB-INF
 |           |       `-- web.xml
 |           `-- index.jsp
  `-- pom.xml

You will notice that the generated structure is missing src/main/java which is actually where we would be having our Servlet code, so we need to create it. Inside it, we also create the package structure.

For example we create src/main/java/com/demo (since our package structure is com.demo) and inside the demo folder we create HelloServlet.java

So we have a structure similar to this:

servletDemo/
|-- src
 |   `-- main
 |       `--java
 |             `--com
 |                 `--demo
 |                    `-- HelloServlet.java
 |           |-- resources
 |           |-- webapp
 |           |   `-- WEB-INF
 |           |       `-- web.xml
 |           `-- index.jsp
  `-- pom.xml

Add the following code in the HelloServlet.java

package com.demo;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
            throws ServletException, IOException
  {
      // Set response content type
      response.setContentType("text/html");

      PrintWriter out = response.getWriter();
      out.println("Hello World");
  }

}

The above code is a simple usage of the Servlet API in which we basically extends the  HttpServlet class and uses the doGet method to write "Hello World" to the client.

We have two things left to do. First is to update our dependency to include the Servlet API, and second is to update the web.xml.

Since we are importing classes from javax.servlet package, as seen in the Java code above, we need to make sure it is a dependency for our project. This is done by adding the following snippets to the pom.xml

<!-- adds dependency to servlet api with version 3.1.0 -->
<dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
</dependency>

The resulting pom would be:

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelversion>4.0.0</modelversion>
  <groupid>com.blogspot.geekabyte.sample-web-demo</groupid>
  <artifactid>servletDemo</artifactid>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>ServletDemo Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupid>junit</groupid>
      <artifactid>junit</artifactid>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
   <dependency>
        <groupid>javax.servlet</groupid>
        <artifactid>javax.servlet-api</artifactid>
        <version>3.1.0</version>
        <scope>provided</scope>
   </dependency>

  </dependencies>
  <build>
    <finalname>servletDemo</finalname>
  </build>
</project>

Now edit the web.xml to look thus:

<?xml version="1.0" encoding="UTF-8"?>
 
<web-app
        version="3.0"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">  <display-name>Archetype Created Web Application</display-name>

<!-- declares the HelloServlet.java as a named servlet-->
<servlet>
   <servlet-name>HelloWorld</servlet-name>
   <servlet-class>com.demo.HelloServlet</servlet-class>
</servlet>

<!-- maps the url /hello to the servlet -->
<servlet-mapping>
   <servlet-name>HelloWorld</servlet-name>
   <url-pattern>/hello</url-pattern>
</servlet-mapping>

</web-app>


Did you notice the top of the web.xml is also updated from

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
to
<?xml version="1.0" encoding="UTF-8"?>
 
<web-app
        version="3.0"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

which is basically updating the web.xml to use the newer 3.x version.

At this point, let's take a quick run through what we have done so far.

First we had the template created by the maven-archetype-webapp and since we wanted to write against the Servlet API, we added src/java. Next, inside of the src/java, we created our package com.demo and added the java class HelloServlet.java in which we had a trivial piece of code that prints out "Hello World". Then we updated our pom.xml file to include the Servlet api as a dependency. And finally, we then updated the web.xml which maps the servlet class to the /hello url path.

Now our little application is ready to be turned into a .war file which can be deployed. To do this, we change to the root directory, where the pom.xml resides and run the following command

mvn package

Maven will do its thing and if everything goes fine, would print out a success message that should look like this;

Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ servletDemo ---
[INFO] Packaging webapp
[INFO] Assembling webapp [servletDemo] in [/home/dadepo/Desktop/demo/servletDemo/target/servletDemo]
[INFO] Processing war project
[INFO] Copying webapp resources [/home/dadepo/Desktop/demo/servletDemo/src/main/webapp]
[INFO] Webapp assembled in [20 msecs]
[INFO] Building war: /home/dadepo/Desktop/demo/servletDemo/target/servletDemo.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.804s
[INFO] Finished at: Thu Jul 30 21:24:10 CEST 2015
[INFO] Final Memory: 14M/205M
[INFO] ------------------------------------------------------------------------

Take note of the line
Building war: /home/dadepo/webdemo/servletDemo/target/servletDemo.war
in the output. This tells us where Maven has packaged our .war file. In this case the servletDemo.war inside the /home/dadepo/webdemo/servletDemo/target/ directory.

The next thing to do is to have this .war file deployed. And this is as simple as copying the servletDemo.war file into the webapp directory of your Tomcat or using the Tomcat manager app to deploy. After deploying when you navigate to localhost:8080/servletDemo/ you should see the "Hello World" message




Using Servlet3-maven-archetype

Unlike maven-archetype-webapp which is part of Maven, the Servlet3-maven-archetype is a third party archetype.

The maven-archetype-webapp archetype helps in creating a Maven web application project that uses the 3.x version of the Servlet API.

With a web application that uses version 3.x of the Servlet API, we can omit the web.xml. The Servlet3-maven-archetype does exactly that.

The web.xml is used to provide configuration details about your web application to the Servlet container: like what Servlets are contained in your application and how to map them to URLs. But with the 3.x version of the Servlet API, the container can now scan and find Servlets.

Also we can use annotations to provide meta information to the Servlet container (like which URL to map a servlet to).

We can also embed a web server within the application we create, as Java web applications have the ability to bundled a server together with the application, making it possible to run the application without having to deploy to a stand alone server.  This method of packaging an embedded server with our web applications is becoming increasingly popular, more so, with the growing adoption of microservices. It makes for easy development and also easier deployment.

The instructions needed to add  Servlet3-maven-archetype to your computer and generate a project with it, can be found on the archetypes webpage, so I won't be repeating it here.

Note that the steps also includes installing the archetype to your system, a step which we did not have to do with maven-webapp-archetype. This is because maven-webapp-archetype is native to Maven. Servlet3-maven-archetype on the other hand, is a 3rd party archetype.

After you have added the archetype to your system, to use it,  you need to run the following command

mvn archetype:generate -DarchetypeGroupId=pl.maciejwalkowiak -DarchetypeArtifactId=servlet3-webapp-archetype

Fill in the prompts for groupid, artifactid, package etc and the project would be created.

Next thing we need to do is to examine the Java class that is created by the archetype. You can do this by navigating to it via the package you specify in the prompt. ie. it should be something like src/main/java/package/path/HelloServlet.java. You should ensure its content is below:

package com.blogspot.geekabyte.com.demo;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet(value="/hello", name="helloServlet")
public class HelloServlet extends GenericServlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) 
                                 throws ServletException, IOException {
        res.getWriter().println("Hello world!!");
    }
}

The @WebServlet(value="/hello", name="helloServlet") annotation maps the path “/hello” to the Servlet’s service method which will print “Hello world!!” when accessed via the browser.

The next thing to do is to ensure that the pom.xml has the necessary plugin configuration that would enable the application be run and deployed into an embedded tomcat server. For this, edit the pom.xml in the root directory and check that the content is as 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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.blogspot.geekabyte.sample-web-demo</groupId>
    <artifactId>servletDemo</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Servlet 3 Web Application</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java-version>1.6</java-version>
    </properties>

    <dependencies>
        <!-- Servlet 3.0 API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- test dependencies -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}-${project.version}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${java-version}</source>
                    <target>${java-version}</target>
                </configuration>
            </plugin>
<!-- plugin which enables running in an embedded tomcat -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.0</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


You will notice that the Servlet.3 API has already been added for you as a dependency. The plugin configuration we are after is the snippet below:

<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.0</version>
</plugin>

with this we can run an embedded tomcat and have the application be deployed to it. To do this, from the directory where the pom.xml file is, run

mvn tomcat7:run

and from the output in the command line, look for a line similar to

create webapp with contextPath: /servletDemo

this tells you the path to navigate in order to be able to access the application. This path is called the contextPath and by default it would be the directory where your project is in. (It is possible to configure this to something other than the default in the plugin settings in the pom file).

So with the contextPath being /servletDemo, to access the running web application, I would navigate to http://localhost:8080/servletDemo/hello




Next Post: How to package and deploy .war Files to Tomcat in IntelliJ
Previous Post: Quick Overview of Basic Maven Concepts

Additional Resources

No comments: