Apache Karafの依存ライブラリバンドルの自作方法

株式会社 日立製作所 謝洋

#1. はじめに
Apache Karaf(以降、Karaf)では、Mavenレポジトリから依存ライブラリバンドルをインストール時に、直接インストールできないという問題があります。本記事ではmaven-bundle-pluginを利用して、依存ライブラリバンドルを自作する方法をご紹介します。

#2. Karafとは
Karafは、Apache Camel(以降、Camel)を用いて開発したアプリを実行するプラットフォームです。Karafにインストールしたjarやwar(Karafでバンドルと呼ばれる)は、互いにパッケージを共有できます。そのため、Camelアプリはビルド時に依存ライブラリを追加せずとも、Karafにインストールした依存ライブラリからパッケージを利用できます。本書では、Karafにインストールした依存ライブラリを依存ライブラリバンドルと呼びます。

#3. Mavenレポジトリを用いたライブラリバンドルのインストール
Karafでは、簡単にMavenレポジトリから依存ライブラリバンドルをインストールできます。インストールが成功すると、新しいバンドルが生成されて、IDが振られます。下記は、PostgreSQL JDBC Driverのインストールに成功した例です。

```sh
karaf@root()> install -s mvn:org.postgresql/postgresql/42.2.5
Bundle ID: 343
```

一方で、失敗する場合もあります。下記は、暗号化プロトコルを実装したJasypt (Java Simplified Encryption)のインストールに失敗した例です。

```sh
karaf@root()> install -s mvn:org.jasypt/jasypt/1.9.2
Bundle IDs:
Error executing command: Error installing bundles:
        Unable to install bundle mvn:org.jasypt/jasypt/1.9.2: org.osgi.framework.BundleException: OSGi R3 bundle not supported
```

これは、Mavenリポジトリからダウンロードしたjarに同梱されているManifestファイル(META-INF/MANIFEST.MF)に必要なバンドルヘッダがないためです。以降、バンドルヘッダおよびその設定方法を説明し、動作を確認します。また、バンドルヘッダ設定時の懸念点について解説します。

#4. バンドルヘッダの説明
ここではバンドルヘッダを説明します。下記はManifestファイルの例です。この例には7つのバンドルヘッダを記載しています。

```:MANIFEST.MF
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Sample bundle
Bundle-SymbolicName: com.sample
Bundle-Version: 1.0.0
Embed-Dependency: sample
Import-Package: com.sample.import
Export-Package: com.sample.export
```

下記はそれぞれのヘッダの説明です。
※ 各バンドルヘッダの詳細説明については、[OSGiのマニュアル](https://docs.osgi.org/specification/osgi.core/7.0.0/framework.module.html#d0e3379)をご参照ください。

| # | ヘッダ名 | 説明 | 必須 |
| ---- | ---- | ---- | ---- |
| 1 | Manifest-Version | マニフェストファイルのバージョン。Karafを利用する場合に、値を1.0に設定。 | 〇 |
| 2 | Bundle-ManifestVersion | Manifestファイル構文のバージョン。Karafを利用する場合に、値を2に設定。 | 〇 |
| 3 | Bundle-Name | Karafでのバンドルの表示名。 | 〇 |
| 4 | Bundle-SymbolicName | バンドルを一意に識別する名前。 | 〇 |
| 5 | Bundle-Version | バンドルのバージョン。この設定値は、バンドルがエクスポートするパッケージに自動的に付与される。 | 〇 |
| 6 | Embed-Dependency | バンドルのnested jarを設定。 | － |
| 7 | Import-Package | バンドルが外部からインポートするJavaパッケージとバージョンを設定。 | － |
| 8 | Export-Package | バンドルが外部にエクスポートするJavaパッケージとバージョンを設定。 | － |

#5. バンドルヘッダの設定方法

ここではバンドルヘッダの設定方法を説明します。本記事では、maven-bundle-pluginを利用してバンドルヘッダを設定します。以降、設定の手順を説明します。

1. 空のCamelプロジェクトを新規で作成して、src/mainの下のファイルをすべて削除します。
1. pom.xmlの依存関係を確認します。本記事では例としてjasypt 1.9.2の依存ライブラリバンドルを作成するので、下記の通りjasypt 1.9.2の依存関係を設定します。

    ```:pom.xml
    <dependency>
      <groupId>org.jasypt</groupId>
      <artifactId>jasypt</artifactId>
      <version>1.9.2</version>
    </dependency>
```
1. maven-bundle-pluginの設定を確認します。instructions要素の下に、Bundle-Name、Bundle-SymbolicName、Embed-Dependecy、Import-PackageおよびExport-Packageを設定します。 Manifest-VersionおよびBundle-ManifestVersionには自動的にデフォルト値が設定されるので、ここは設定不要です。

    ```:pom.xml
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>${version.maven-bundle-plugin}</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Bundle-Name>Jasypt 1.9.2</Bundle-Name>
            <Bundle-SymbolicName>com.sample.jasypt</Bundle-SymbolicName>
            <Bundle-Version>1.9.2</Bundle-Version>
            <Embed-Dependency>jasypt;scope=compile</Embed-Dependency>
            <Import-Package>com.ibm.icu.text;resolution:=optional,
               javax.crypto,
               javax.crypto,
               javax.servlet;resolution:=optional,
               javax.servlet.http;resolution:=optional
            </Import-Package>
            <Export-Package>org.jasypt.commons,
                org.jasypt.contrib.org.apache.commons.codec_1_3,
                org.jasypt.contrib.org.apache.commons.codec_1_3.binary;uses:=org.jasypt.contrib.org.apache.commons.codec_1_3,
                org.jasypt.digest;uses:="org.jasypt.digest.config,org.jasypt.salt",
                org.jasypt.digest.config;uses:="org.jasypt.salt",
                org.jasypt.encryption,
                org.jasypt.encryption.pbe;uses:="org.jasypt.encryption,org.jasypt.encryption.pbe.config,org.jasypt.exceptions,org.jasypt.salt",
                org.jasypt.encryption.pbe.config;uses:="org.jasypt.salt",
                org.jasypt.exceptions,
                org.jasypt.intf.cli,
                org.jasypt.intf.service,
                org.jasypt.normalization,
                org.jasypt.properties;uses:="org.jasypt.encryption,org.jasypt.util.text",
                org.jasypt.registry,
                org.jasypt.salt,
                org.jasypt.util.binary,
                org.jasypt.util.digest,
                org.jasypt.util.numeric,
                org.jasypt.util.password;uses:="org.jasypt.digest.config",
                org.jasypt.util.password.rfc2307;uses:="org.jasypt.util.password",
                org.jasypt.util.text,
                org.jasypt.web.pbeconfig;uses:="javax.servlet,javax.servlet.http,org.jasypt.encryption.pbe.config"
            </Export-Package>
          </instructions>
        </configuration>
      </plugin>
```

Import-PackageとExport-Packageに設定するパッケージは、カンマで区切ります。また、各パッケージにオプション設定を追加できます。オプション設定はセミコロンで区切ります。下記は各オプション設定の説明です。
※ 本記事で記載しているもの以外にもオプション設定はありますが、本記事では割愛します。興味がある方は、[OSGiのマニュアル](https://docs.osgi.org/specification/osgi.core/7.0.0/framework.module.html#framework.module.importpackage)をご参照ください。

| # | 名前 | 種類 | 設定可能値 | 説明 |
| ---- | ---- | ---- | ---- | ---- |
| 1 | resolution | Import-Package | mandatory, optional | 依存関係が解決できない場合の操作を設定。デフォルト値のmandatoryでは、該当パッケージを解決できない場合に、バンドルを起動しない。optionalでは、該当パッケージを解決できなくても起動する。 |
| 2 | version | Import-Package | 記法は以下の表に記載 | インポートするパッケージのバージョン範囲を設定。
| 3 | use | Export-Package | パッケージ名 | 該当パッケージの依存パッケージが同じバンドルに存在する場合、この項目で依存パッケージを設定。この設定をしないと、バンドルは外からパッケージを探す。 |
| 4 | version | Export-Package | ピリオドで区切る3桁のバージョン | 該当パッケージのバージョンを設定。設定しない場合、パッケージのバージョンがBundle-Versionの設定値となる。 | 

- Import-Packageのversionの記法例

| # | 記法例 |	説明 |
| ---- | ---- | ---- | ---- |
| 1 | [1.2.3, 4.5.6) | 1.2.3 <= x < 4.5.6 |
| 2 | [1.2.3, 4.5.6] | 1.2.3 <= x <= 4.5.6 |
| 3 | (1.2.3, 4.5.6) | 1.2.3 < x < 4.5.6 |
| 4 | (1.2.3, 4.5.6] | 1.2.3 < x <= 4.5.6 |
| 5 | 1.2.3 | 1.2.3 <= x |

#6. 動作確認

ここでは動作を確認します。動作確認の手順は下記です。

1. pom.xmlを編集した後、Camelプロジェクトをビルドします。
1. ビルドして生成したjarをKarafのホットデプロイフォルダに置きます。
1. Karafコンソールに入って、下記のコマンドでバンドルが起動されたことを確認します。

    ```sh
karaf@root()> list
START LEVEL 100 , List Threshold: 50
 ID │ State  │ Lvl │ Version                         │ Name
────┼────────┼─────┼─────────────────────────────────┼────────────────────────────────────────────
...
347 │ Active │  80 │ 1.9.2                           │ Jasypt 1.9.2
```

1. 下記のコマンドで、作成したjasypt 1.9.2バンドルがパッケージをエクスポートしていることを確認します。


    ```sh
karaf@root()> package:exports -b 347
Package Name                                           │ Version │ ID  │ Bundle Name
───────────────────────────────────────────────────────┼─────────┼─────┼──────────────────
org.jasypt.commons                                     │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.contrib.org.apache.commons.codec_1_3.binary │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.contrib.org.apache.commons.codec_1_3        │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.digest.config                               │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.digest                                      │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.encryption.pbe.config                       │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.encryption.pbe                              │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.encryption                                  │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.exceptions                                  │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.intf.cli                                    │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.intf.service                                │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.normalization                               │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.properties                                  │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.registry                                    │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.salt                                        │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.util.binary                                 │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.util.digest                                 │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.util.numeric                                │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.util.password.rfc2307                       │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.util.password                               │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.util.text                                   │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.web.pbeconfig                               │ 1.9.2   │ 347 │ com.sample.jasypt
```

#7. 懸念点

Karafでは、ユースケースに柔軟に対応するために同じパッケージの複数バージョンを配置できます。例えば、jasypt 1.9.2およびjasypt 1.9.3の依存ライブラリバンドルをkarafにインストールした場合、下記の通り1.9.2および1.9.3の両方のorg.jasypt.commonsパッケージがエクスポートされます。

```sh
karaf@root()> package:exports -p org.jasypt.commons
Package Name       │ Version │ ID  │ Bundle Name
───────────────────┼─────────┼─────┼─────────────────────────────────────
org.jasypt.commons │ 1.9.2   │ 347 │ com.sample.jasypt
org.jasypt.commons │ 1.9.3   │ 348 │ com.sample2.jasypt
```

このとき、Camelアプリでorg.jasypt.commonsをインポートするために、下記の通りImport-Packageのversionを設定したとします。

```:pom.xml
            <Import-Package>org.jasypt.commons;resolution:=optional;version="[1.9.0,2.0.0)"</Import-Package>
```

この場合、org.jasypt.commmons 1.9.2および1.9.3の両方がバージョンの範囲に該当するので、どちらもインポートされる可能性があります。また、バンドルの再起動などによって、他のバージョンに切り替わる可能性もあります。例えば、org.jasypt.commons 1.9.2をベースにCamelアプリを開発した場合、org.jasypt.commons 1.9.3にバージョンが切り替わってしまうと、Camelアプリの動作に影響が出る恐れがあります。このような問題を避けるため、Camel アプリのImport-Packageを厳密に設定することをお勧めします。
例えば、org.jasypt.commons 1.9.2のみをインポートしたい場合、下記のように設定をします。

```:pom.xml
            <Import-Package>org.jasypt.commons;resolution:=optional;version="[1.9.2)"</Import-Package>
```

※ `version=1.9.2`と設定すれば同じ結果を得られるのではとお考えの方もいるかと思いますが、`Import-Packageのversionの記法例`に示した通り、`version=1.9.2`と設定すると1.9.2以上のバージョンのパッケージをインポートすることを意味するため、同じ結果を得ることはできません。

#8. おわりに

本記事ではmaven-bundle-pluginを活用することで、Karafの依存ライブラリバンドルを自作する方法を説明しました。Mavenレポジトリからダウンロードした依存ライブラリのjarファイルは直接Karafにインストールできない場合が多いので、本記事に記載した方法が役に立てばうれしいです。