JSR 286: Portlet Specification 2.0

Javaポートレット仕様の現行バージョンは第23回でも紹介したJSR 168だが、現在はその後継バージョンとなる「JSR 286: Portlet Specification 2.0」の仕様策定が進められている。

2.0では新たに次のような機能が追加される予定となっている。

  • イベント処理 - ポートレット間でイベントの送受信が可能
  • レンダーパラメータ - 他のポートレットとレンダーパラメータを共有できる
  • リソースサービング - 画像などのリソースにポートレット経由でアクセスできる
  • ポートレットフィルター - リクエストおよびレスポンスの途中で情報の変換を行うことが可能

その他、1.0では曖昧であったポートレットの振る舞いのいくつかを明確にすることや、ローカライゼーションの記述をportlet.xmlではなくリソースバンドルに記述できるようにするなどといった変更が加えられる予定。

2.0は1.0との互換性が保たれるため、1.0に準拠したポートレットはそのまま2.0のコンテナでで動作させることができる。また、ターゲットとなるプラットフォームはJava SE 5.0で、J2SE 1.4で動作させる場合にはアノテーションやジェネリクス、Enumを利用したいくつかの機能が使えなくなるとのこと。

現在JSR 286はPublic Review Ballotを賛成多数で通過した段階にある。Public DraftはJCPのサイトから入手することができる。

JSR 286対応のポートレットコンテナを試す

OpenPortalによるPortlet Container Projectでは、JSR 286対応のPortletコンテナがPortlet Container 2.0として開発されている。最新の成果物は、Subversionリポジトリよりプロンプト1のようにしてチェックアウトできる。guestユーザのパスワードは空でよい。

プロンプト1 Subversionリポジトリのチェックアウト

svn checkout https://portlet-container.dev.java.net/svn/portlet-container/branches/portlet_2_branch portlet-container --username guest

チェックアウトしたらportlet-containerのディレクトリに移動し、Apache Mavenを利用してプロンプト2のようにしてビルドする。

プロンプト2 Poerlet Containerのビルド

> mvn package
> mvn verify

ビルドに成功すると/distディレクトリにportlet-container-configurator.jarというファイルが生成される。これを第25回の例と同様にGlassFishにデプロイする(プロンプト3)。

プロンプト3 Portlet Conatinerのデプロイ

> java -jar portlet-container-configurator.jar

準備ができたら、早速2.0の新しい機能を使ったポートレットを作成してみよう。今回はイベントを利用したポートレット間通信を試してみようと思う。プロジェクト作成の際には、/dist/portlet-container/lib以下にあるすべてのJARファイルがクラスパスに含まれるようにしておく。

まず、送受信するイベントの定義を/WEB-INF/portlet.xmlに記述する。イベント定義はリスト1のようにタグで行う。イベント名はXML名前空間のQNameを用いて定義し、で型を指定する。

リスト1 /WEB-INF/portlet.xml - 送受信するイベントの定義

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app version="1.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet" 
    xmlns="http://java.sun.com/xml/ns/portlet">

    <event-definition>
        <qname xmlns:x="http:mycom.co.jp/events">x:message</qname>
        <value-type>java.lang.String</value-type>
    </event-definition>

    <portlet>
        <portlet-name>SendMessagePortlet</portlet-name>
        <display-name>Send Message Portlet</display-name>
        <portlet-class>apisample.portlet.SendMessagePortlet</portlet-class>

        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>

        <supported-publishing-event xmlns:x="http:mycom.co.jp/events">
            x:message
        </supported-publishing-event>

        <portlet-info>
            <title>Send Message</title>
        </portlet-info>
    </portlet>

    <portlet>
        <portlet-name>ReceiveMessagePortlet</portlet-name>
        <display-name>Receive Message Portlet</display-name>
        <portlet-class>apisample.portlet.ReceiveMessagePortlet</portlet-class>

        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>

        <supported-processing-event xmlns:x="http:mycom.co.jp/events">
            x:message
        </supported-processing-event>

        <portlet-info>
            <title>Receive Message</title>
        </portlet-info>
    </portlet>
</portlet-app>

次にイベントの送信側のポートレットを作成する。ページはリスト2のようにJSPファイルで作成し、これをリスト3においてPortletRequestDispatcherを利用してポートレットにディスパッチしている。

このポートレットは、テキストフィールドにメッセージを入力してボタンを押したら、その内容がイベントを介して受信側のポートレットに送られるというもの。ボタンが押されるとprosessAction()メソッドが呼び出され、ActionRequestからパラメータの内容を取得できる。イベントの送信はActionResponseのsetEvent()メソッドによって、イベント名と値をセットすることで行える。

リスト2 /WEB-INF/jsp/input.jsp - イベント送信側のポートレットページ

<?xml version="1.0" encoding="UTF-8" ?>

<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
        xmlns:portlet="http://java.sun.com/portlet">
    <jsp:directive.page language="java"
        contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" />

    <portlet:actionURL var="viewUrl" portletMode="view"/>

    <h3>メッセージ入力</h3>
    <form action="${viewUrl}" method="post">
        <input type="text" name="message" value="${message}"/><br/>
        <input type="submit" name="send" value="Send"/>
    </form>

</jsp:root>

リスト3 SendMessagePortlet.java - イベント送信側のポートレットプログラム

package apisample.portlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.xml.namespace.QName;

public class SendMessagePortlet extends GenericPortlet {
    private PortletContext portletContext;

    public void init(PortletConfig config) throws PortletException {
        super.init(config);
        portletContext = config.getPortletContext();
    }

    public void processAction(ActionRequest request, ActionResponse response) {
        String myMessage = request.getParameter("message");
        // イベントを送信
        QName qname = new QName("http:mycom.co.jp/events", "message");
        response.setEvent(qname, myMessage);
    }

    public void doView(RenderRequest request, RenderResponse response)
        throws PortletException, IOException {
        response.setTitle("Send Message Portlet");
        response.setContentType(request.getResponseContentType());

        // ページの出力をJSPファイルにディスパッチ
        try {
            PortletRequestDispatcher dispatcher = 
                    portletContext.getRequestDispatcher("/WEB-INF/jsp/input.jsp");
            dispatcher.include(request, response);
        } catch (IOException e) {
            throw new PortletException("SendMessagePortlet.doView exception", e);
        }
    }
}

/WEB-INF/portlet.xmlには、SendMessagePortletのための定義をとしてリスト4の内容を追加する。ここでは、イベントの送信を有効にするためにタグによって送信したいイベントを指定しておく必要がある。

リスト4 /WEB-INF/portlet.xml - SendMessagePortletのための定義を追加

    <portlet>
        <portlet-name>SendMessagePortlet</portlet-name>
        <display-name>Send Message Portlet</display-name>
        <portlet-class>apisample.portlet.SendMessagePortlet</portlet-class>

        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>

        <supported-publishing-event xmlns:x="http:mycom.co.jp/events">
            x:message
        </supported-publishing-event>

        <portlet-info>
            <title>Send Message</title>
        </portlet-info>
    </portlet>

続いてイベント受信側のポートレットを作成する。/WEB-INF/portlet.xmlに記述するポートレット定義はリスト5のようになる。ここで、タグによって受信するイベントの名前を設定しておくことで、このイベントが送信された際にそれをポートレットで受け取って処理することが可能になる。

リスト5 /WEB-INF/portlet.xml - ReceiveMessagePortletのための定義を追加

    <portlet>
        <portlet-name>ReceiveMessagePortlet</portlet-name>
        <display-name>Receive Message Portlet</display-name>
        <portlet-class>apisample.portlet.ReceiveMessagePortlet</portlet-class>

        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>

        <supported-processing-event xmlns:x="http:mycom.co.jp/events">
            x:message
        </supported-processing-event>

        <portlet-info>
            <title>Receive Message</title>
        </portlet-info>
    </portlet>

ポートレットのプログラムは(gsld:ReceiveMessagePortlet.java)のようにする。イベントを受け取るとprosessEvent()メソッドが実行される。そこでEventRequestからイベントを取り出し、メッセージの中身をEventResponseに対してパラメータとして渡している。

表示部分はリスト6にディスパッチする。

リスト6 ReceiveMessagePortlet.java - イベント受信側のポートレットプログラム

package apisample.portlet;

import java.io.IOException;
import javax.portlet.Event;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

public class ReceiveMessagePortlet extends GenericPortlet{
    private PortletContext portletContext;

    public void init(PortletConfig config) throws PortletException {
        super.init(config);
        portletContext = config.getPortletContext();
    }

    public void processEvent(EventRequest request, EventResponse response) {
        // イベントを受信
        Event event = request.getEvent();
        if(event.getName().equals("message")) {
            String myMessage = (String)event.getValue();
            response.setRenderParameter("message", myMessage);
        }
    }

    public void doView(RenderRequest request, RenderResponse response)
        throws PortletException, IOException {
        response.setTitle("Receive Message Portlet");
        response.setContentType(request.getResponseContentType());

        String message = request.getParameter("message");
        if (message == null) {
            message = "";
        }
        request.setAttribute("message", message);

        // ページの出力をJSPファイルにディスパッチ
        try {
            PortletRequestDispatcher dispatcher = 
                    portletContext.getRequestDispatcher("/WEB-INF/jsp/output.jsp");
            dispatcher.include(request, response);
        } catch (IOException e) {
            throw new PortletException("ReceiveMessagePortlet.doView exception", e);
        }
    }
}

リスト7 /WEB-INF/jsp/output.jsp - イベント受信側のポートレットページ

<?xml version="1.0" encoding="UTF-8" ?>

<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
    xmlns:portlet="http://java.sun.com/portlet">
    <jsp:directive.page language="java"
        contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" />

    <portlet:actionURL var="viewUrl" portletMode="view"/>

    <h3>メッセージ出力</h3>
    <h2>${message}</h2>

</jsp:root>

なお、/WEB-INF/web.xmlに対しては今回は特に設定することがないので、リスト8のようにくらいを記述しておけばよい。

リスト8 /WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app 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_2_5.xsd"
     version="2.5">

    <display-name>PortletEventSample</display-name>
</web-app>

両ポートレットを同じWARファイルにまとめるとすると、ファイルの構成は図1のようになる(必要なライブラリを/WEB-INF/libディレクトリに配置した場合)。

図1 WARファイルの構成

これをGlassFishのポートレットコンテナにデプロイし、WEBブラウザから

http://localhost:8080/portletdriver/

にアクセスすると図2のように2つのポートレットが表示される。「Send Message Portlet」側にメッセージを入力してボタンをクリックすれば、それが「Receive Message Portlet」側に反映されるはずだ(図3)。

図2

図3