Creating runnable wars with Maven (and Jetty)

13 Feb

Hudson is a really great tool if you work with source code in a team with more than two persons. One thing in hudson always fascinated me: the fact, that Hudson is runnable just by typing

java -jar hudson.war

I do not know if it really has practical relevance, but I searched a way to create such a jar only with Maven and Jetty.

The problem is easy to understand: when starting Java with -jar, it expects a „normal“ file structure. So if the Main-Class is „de.jowisoftware.Main“, Java looks for de/jowisoftware/Main.class. The classloader does not understand the concept, that, in war files all class files are in WEB-INF/classes. If jetty is included in the war file, it is included as a jar file, so the classloader has to support nested archives. The default one doesn’t. Thankfully, there are solutions.

There already are a few blog posts which gave interesting hints. I decided to take an easy way: using overlays in the maven-war-plugin.

The idea of overlays is, that you can „mix in“ other zip or jar files, when the war file is created. This trick can be used to include additional resources like static images. It can also be used to include jetty and the startup logic, because overlays are extracted into the root of the war file. So first we need to create an overlay which starts Jetty. You find the following source all over the web:

package de.jowisoftware;
 
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.webapp.WebAppContext;
import java.security.ProtectionDomain;
import java.net.URL;
 
public class Main {
    public static void main(String[] args) {
    Server server = new Server();
    SocketConnector connector = new SocketConnector();
 
    // Set some timeout options to make debugging easier.
    connector.setMaxIdleTime(1000 * 60 * 60);
    connector.setSoLingerTime(-1);
    connector.setPort(8080);
    server.setConnectors(new Connector[]{connector});
 
    WebAppContext context = new WebAppContext();
    context.setServer(server);
    context.setContextPath("/");
 
    ProtectionDomain protectionDomain = Main.class.getProtectionDomain();
    URL location = protectionDomain.getCodeSource().getLocation();
    context.setWar(location.toExternalForm());
 
    server.addHandler(context);
    try {
        server.start();
        System.in.read();
        server.stop();
        server.join();
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(100);
    }
  }
}

The trick happens between the lines 25 and 27. Jetty is started with the own war file. The corresponding pom file looks like this:

<?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>de.jowisoftware</groupId>
    <artifactId>embedded-jetty</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
   
    <properties>
        <jetty.version>6.1.25</jetty.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty-util</artifactId>
            <version>${jetty.version}</version>
        </dependency>
    </dependencies>
</project>

Looks simple, does it? Next, use „mvn install“ to install the artifact into your local maven repository. Okay, now to the real project. Let’s assume we already have a simple webapp. It can be a simple Servlet, a wicket-application, a scala/lift application, anything. Also, let’s assume we already have a pom.xml for our project. We first add dependencies to it:

<project …>
    …
    <properties>
        <jetty.version>6.1.25</jetty.version>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        …
    </properties>
    …
    <dependencies>
        …
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty-util</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <dependency>
            <groupId>de.jowisoftware</groupId>
            <artifactId>embedded-jetty</artifactId>
            <version>0.0.1</version>
        </dependency>
        …
    </dependencies>
</project>

This leads to the effect that three jar files are added into the war file. That is exactly what we do not want. So we tell the maven-war-plugin to use the packages as overlay and to ignore the jar-files entirely when creating the list of dependencies. So again, in the pom.xml, we add/change the following lines:

<project>  
    …
    <build>
        …
        <plugins>
            …
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>de.jowisoftware.Main</mainClass>
                        </manifest>
                    </archive>
                    <packagingExcludes>WEB-INF/lib/jetty-*.jar,WEB-INF/lib/*EmbeddedJetty*.jar</packagingExcludes>
                    <overlays>
                        <overlay>
                            <groupId>de.jowisoftware</groupId>
                            <artifactId>embedded-jetty</artifactId>
                            <type>jar</type>
                        </overlay>
                        <overlay>
                            <groupId>org.mortbay.jetty</groupId>
                            <artifactId>jetty</artifactId>
                            <type>jar</type>
                        </overlay>
                        <overlay>
                            <groupId>org.mortbay.jetty</groupId>
                            <artifactId>jetty-util</artifactId>
                            <type>jar</type>
                        </overlay>
                        <overlay>
                            <groupId>javax.servlet</groupId>
                            <artifactId>servlet-api</artifactId>
                            <type>jar</type>
                        </overlay>
                    </overlays>
                </configuration>
            </plugin>
            …
        </plugins>
        …
        </build>
</project>

Note the <packagingExcludes/> on line 17, which prevents maven from adding the jars into the war. The three overlay blocks let maven extract the jars and add the content into the root of the war file. So the war file looks like:

de/jowisofware
org/mortbay
META-INF/…
WEB-INF/…
…

The mainClass-Entry specifies the default entry point. This is enough to do the following:

mvn package && java -jar target/my-war.war

Have fun

3 Replies to “Creating runnable wars with Maven (and Jetty)

  1. Maybe it would be a better way to use ant to move the class files of the own project into the root of the war file, because otherwise it would be a cyclic dependency and hudson or other build server would’nt build the project.

    So:

    1. Set the packaging to war
    2. Don’t use dependencies to your own project (in this case de.jowisoft)
    3. Use ant to copy the class files to the root of the build:

    maven-antrun-plugin


    move-main-class compile







    run


    Thanks for the great guide here! 🙂

  2. Oh YES. It works!!!
    Thank you a lot. This was very helpful especially your sample of maven configuration! Thank you once more.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert