jenkins.war のような実行可能 war ファイル作りたい

[Jenkins][] ([Hudson][])が配布している war ファイルは2つの使い方がある。

* サーブレットコンテナに読み込ませて war ファイルとして利用
* `$ java -jar jenkins.war` ように単体で実行

この2つを実現するための簡単なサンプルを作ったので、実現するための要所を簡単に書く。

* 実行可能 war サンプル: [kui/executable-war-sample · GitHub](https://github.com/kui/executable-war-sample)

[Jenkins]: http://jenkins-ci.org/
[Hudson]: http://hudson-ci.org/

サンプルのビルド方法
---------------------

まず実際に動いているのを確認してもらうために、ビルド・実行してほしい。

このサンプルは、比較のために Jetty, Winstone, Tomcat, Glassfish を使って、実行可能 war を構築する。

ビルドには [Maven 3][] が必要。

```sh
git clone git@github.com:kui/executable-war-sample.git
cd executable-war-sample
mvn package
ls **/sample.war
java -jar winstone/target/sample.war
```

[Maven 3]: http://maven.apache.org/


実行可能 war を作るためのポイント
------------------------------------

やるべきことは大体3つ: 

* 依存関係にサーブレットコンテナを追加
* Main-Class の作成・指定・コピー
* サーブレットコンテナの関連クラスのコピー

Jetty を使った実行可能 war サンプルを例に説明していきます。基本的にはどのコンテナも同じです。

### 依存関係にサーブレットコンテナを追加

実行時に必要ですが、後述する通り自力でコピーしてしまうので、scope は `provided` で大丈夫:

```pom.xml
<project >
  ...
  <dependencies>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-webapp</artifactId>
      <version>9.0.2.v20130417</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
```


### Main-Class の作成

サーブレットコンテナを立ち上げて、自身を war ファイルとして読み込ませるようにする。例えば Jetty だとこんな感じ:

```Main.java
package jp.k_ui.sample;

import java.net.URL;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {
    public static void main(String[] args) throws Exception {
        int port = Integer.parseInt(System.getProperty("port", "8888"));
        URL warLocation = Main.class.getProtectionDomain().getCodeSource()
                .getLocation();

        WebAppContext webapp = new WebAppContext();
        webapp.setWar(warLocation.toExternalForm());
        webapp.setContextPath("/");

        Server server = new Server(port);
        server.setHandler(webapp);
        server.start();
        server.join();
    }
}
```

このまま `$ mvn package` 実行して `sample.war`を作ったとしても、`$ java -jar sample.war` の時に、どのクラスを実行すればよいかわからない。なので、次の設定をする必要がある。


### Main-Class の指定

どのクラスの `#main(String[])` を呼べば良いのか、という指定をする設定:

```xml
<project >
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <archive>
            <manifest>
              <mainClass>jp.k_ui.sample.Main</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
```

この状態で `$ mvn package` もできるし、`$ java -jar sample.jar` で実行も可能。

ただし、`$ unzip -l sample.jar` で見るとわかるとおり、`jp.k_ui.sample.Main` が無いため実行は失敗する。これをコピーする設定を書く。


### Main-Class のコピー

上で梱包されていないことが分かった `Main` クラスをコピーする設定:

```xml
<project >
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.7</version>
        <executions>
          <execution>
            <id>main-class-placement</id>
            <phase>prepare-package</phase>
            <configuration>
              <tasks>
                <move todir="${project.build.directory}/${project.build.finalName}/">
                  <fileset dir="${project.build.directory}/classes/">
                    <include name="jp/k_ui/sample/Main.class" />
                  </fileset>
                </move>
              </tasks>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
```

[Apache Ant][] を使って、war 化する前のディレクトリに `Main.class` をコピーしている。

この状態で `java -jar sample.war` を実行すると、`Main` 見つからないという文句は消える代わりに、Jetty 関連のクラスが見つからないと言ってくる。

これは先程同様、`unzip -l sample.jar` で中身を見てわかるとおり、サーブレットに関わるクラスが war に梱包されていないのが問題になっている。

[Apache Ant]: http://ant.apache.org/


### サーブレットコンテナの関連クラスのコピー

サーブレットコンテナに関わるクラス全てを、これから war 化する前のディレクトリにコピーする設定:

```xml
<project >
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.7</version>
        <executions>
          <execution>
            <id>jetty-classpath</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>unpack-dependencies</goal>
            </goals>
            <configuration>
              <includeGroupIds>org.eclipse.jetty,javax.servlet</includeGroupIds>
              <excludes>META-INF/ECLIPSEF.*</excludes>
              <outputDirectory>
                ${project.build.directory}/${project.build.finalName}
              </outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
      ...
```

これでおわり
--------------

`$ mvn clean package` で、jenkins.war のような、実行可能 war が作成できるようになりました。

なんだか意外と結構な文章量になってしまった。。。

各コンテナの比較は、[github の README](https://github.com/kui/executable-war-sample) でやってます。


*この記事は、[jenkins.war みたいな実行可能な war ファイルの作成 - 電卓片手に](http://k-ui.jp/blog/2013/05/08/building-executable-war/) で書いた内容とほとんど同じだが、差分ができたのでこっちにも書いてみた*