HttpServiceとWAB(Web Application Bundle)

GlassfishでWebアプリケーションからOSGiバンドルを利用する
前回のサンプルでは、Webアプリケーション自体はwarとしてディプロイし、依存するサービスをOSGiバンドルとしてディプロイして@Resourceでインジェクションしていました。今回は、アプリケーション自体をOSGiバンドルとしてディプロイする方法として以下を試してみます。

  1. HttpService
  2. Web Application Bundle(WAB)

HttpService

HttpServiceは、OSGi環境でサーブレットを利用するための仕組みです。特定のコンテキストパスにサーブレットや静的リソースを紐付けて登録します。ただし、Servlet2.2までしか対応していないためFilterやJSPが使用できません。Felixの個別実装のようですが、Filterも使えるExtHttpServiceというのもあります。
GlassfishでHttpServiceを使用する場合には、まず、こちらからHttpServiceを実装したバンドルをダウンロードしてディプロイしておきます。
次にBundleActivatorにて、HttpService#registerServletサーブレットを登録します。以下のサンプルでは、/helloにHelloServletを登録し、/imagesに/com/azuki3/osgi/hello/httpservice/imagesパッケージにあるリソースを登録しています。

public class HelloActivator implements BundleActivator {

   public void start(BundleContext context) throws Exception
   {
      ServiceReference sRef = context.getServiceReference(HttpService.class.getName());
      if (sRef != null)
      {
         HttpService service = (HttpService) context.getService(sRef);
         HelloServlet servlet = new HelloServlet();
         service.registerServlet("/hello", servlet, null, null);
         service.registerResources("/images", "/com/azuki3/osgi/hello/httpservice/images", null);
      }
   }

   // ....
}

バンドルを作成してディプロイすると、http://localhost:8080/osgi/helloサーブレットにアクセスできます(最初のosgiというパスはHttpServiceを使用する場合の固定値のようです)。実際には、HttpServiceなど利用するサービスがActiveになった時点でサーブレットを登録し、解除する処理も必要になります。

Web Application Bundle(WAB)

WABは、OSGi 4.2で追加されたエンタープライズ向けの仕様で、warファイルのマニフェストOSGiバンドルの情報とコンテキストパス等を追加することで、Webアプリケーションとして直接、OSGiコンテナにディプロイする方法です。
WABに含まれるサーブレットから、前回のサンプルで作成したHelloServiceを利用してみます。まず、NetBeansMaven Webアプリケーションプロジェクトを作成します。作成したプロジェクトのpom.xmlOSGi関連の記述を追加します。

<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.azuki3</groupId>
    <artifactId>WABHelloClient</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>WABHelloClient Java EE 6 Webapp</name>
    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>4.2.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>MavenHelloServiceAPI</artifactId>
            <version>${project.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>osgi-cdi-api</artifactId>
            <version>3.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.0.1</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>bundle-manifest</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>manifest</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <manifestLocation>${project.build.directory}/META-INF</manifestLocation>
                    <supportedProjectTypes>
                        <supportedProjectType>bundle</supportedProjectType>
                        <supportedProjectType>war</supportedProjectType>
                    </supportedProjectTypes>
                    <instructions>
                        <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
                        <Web-ContextPath>hello</Web-ContextPath>
                    </instructions>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1-alpha-2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <attachClasses>true</attachClasses>
                    <archive>
                        <manifestFile>${project.build.directory}/META-INF/MANIFEST.MF</manifestFile>
                        <manifestEntries>
                          <Bundle-ClassPath>WEB-INF/classes</Bundle-ClassPath>
                        </manifestEntries>
                    </archive>
		</configuration>
            </plugin>
        </plugins>
        <finalName>WABHelloClient</finalName>
    </build>
</project>

pom.xmlのポイントは以下のとおりです。

  • packagingはwarにする
  • Declarative Serviceをインジェクションするためのアノテーションを含むosgi-cdi-apiをdependencyに追加する
  • maven-bundle-pluginの設定のWeb-ContextPathでコンテキストパスを指定する

Web-ContextPathの値はMANIFEST.MFに反映されディプロイ時に使用されます。
サーブレットは以下のようになります。HelloServiceに@Injectと@OSGiServiceというアノテーションを付加しています。これで別のOSGiバンドルのDSがインジェクションされます。

@WebServlet(name="HelloClient", urlPatterns={"/HelloClient"})
public class HelloClientServlet extends HttpServlet {

    @Inject
    @OSGiService(dynamic=true)
    HelloService helloService;

    /**
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            out.println("<h1>Servlet HelloClient at " + request.getContextPath() + "</h1>");
            out.println(helloService.sayHello("Duke"));

            out.println("</body>");
            out.println("</html>");
        } finally {
            out.close();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }
}

バンドルを作成してディプロイすると、http://localhost:8080/hello/HelloClientサーブレットにアクセスできます。

まとめ

HttpServiceは、Javaのコードからサーブレットやリソースを登録していくのがおもしろいですね。提供される機能はかなりシンプルなものですが、ApacheのWeb ConsoleもHttpServiceの上に構築されているようなので、用途によっては使えそうです。例えばクライアントがFull AjaxのWebアプリケーションで、サーバサイドはJAX-RS(Jersey)のみでよいようなケースなど。
一方、WABはwarとほぼ同じイメージで使えるので、Spring MVCなどの現在よく使われるWebフレームワークを使用するのであれば、こっちを使うことになると思います。その上で、アプリケーションに含まれるサービスやコンポーネントを上手く別バンドルに切り出して、バンドル毎に開発していくのが、OSGiを利用した開発のイメージということになるのでしょうか。今後はもう少し実践的なアプリケーションを作ってみたいと思います。

今回のコードは以下にあります。
kenichiro22 / glassfish-osgi-sample / overview – Bitbucket