JSR 301 - Portlet Bridge Specification for JavaServer Faces

JSF(JavaServer Faces)は、言わずと知れたWebアプリケーション開発のためのフレームワークであり、豊富なUIコンポーネントとモデルとなるJavaBeans(Managed Bean)との連携によって、インタフェース主導によるWebアプリケーションの開発をサポートする。一方でポートレットもUIに主眼を置いたフレームワークだが、こちらは画面の一部を構成するためのコンポーネントである。

JSR 301は、このJSFとポートレットを統合して、JSFアプリケーションを単体のポートレットとして動作させられるようにするブリッジ仕様である。これによって、JSFという高い実績を持つフレームワークによって作成されたアプリケーションが、ポータルアプリケーションの中で自由に利用できるようになる。JSFは広く使われている上にそれをサポートする技術も多く存在しており、これをポートレットとして利用できるメリットは大きい。

このアイデア自身は古くからあり、後述のOpenPortal JSF Portlet Bridgeなどのようにすでに独自の実装を行っているプロジェクトも存在する。しかし、それらの異なる実装 どうしに互換性がない場合、ポートレットの持つ高い相互運用性という特徴を最大限に活かすことができない。JSR 301は、JSFとポートレット間の統一的なブリッジ仕様を策定することによりこれら実装の振る舞いを標準化する目的で提案された。仕様策定にあたっては主に次の3つのエリアにフォーカスが当てられている。

  • 共存: このポートレートブリッジと非ポートレットベースのJSFアプリケーションが、お互いの動作を妨げない
  • 正当性: ポートレットとJSFの間にあるモデルの違いを適切に吸収する
  • 拡張性: JSFには存在しないポートレットの拡張的な機能に対する振る舞いを定義する

JSR 301は現行のポートレット仕様であるJSR 168をターゲットとしているが、次期仕様であるJSR 286もサポートされる予定である。本稿執筆時点では、ちょうどEarly Draft Review 2が行われている最中だ。

OpenPortal JSF Portlet Bridgeを試す

現在、すでにJSFアプリケーションをポートレットとして動作させるための実装がいくつかの団体から公開されており、JSR 301の仕様もそれらのアイデアをスタート地点として議論されるはずである。そこで今回はそのような実装の中から、OpenPotalプロジェクトによる「JSF Portlet Bridge」を試してみたい。

JSF Portlet Bridgeはこのページよりダウンロードすることができる。ポートレットコンテナとしてはOpenPotal Portlet Containerを利用する。Portlet ContainerをGlassFish上で動作させている場合はJSF 1.2に対応したJSF Portlet Bridge 1.2.xを、Sun Java System Portal Server 7やPlutoで動作させている場合は1.1.xを使用する。

第23回ではPlutoを利用しているので、今回はGlassFishを使ってみよう。したがってJSF Portlet Bridge 1.2.xの方をダウンロードする。

まずPortlet Conatiner(ファイル名portlet-container-configurator.jar)をGlassFishにデプロイする。コマンドラインからプロンプト1のように実行する。

プロンプト1

> java -jar portlet-container-configurator.jar

図1のようなウィンドウが表示されるので、GlassFishのインストール先とドメインディレクトリのパスを入力し、[Ok]をクリックする。デプロイに成功したら[Quit]を押せばインストール完了となる。

図1 Portlet Containerのインストール

さて、今回はJSFアプリケーションをポートレットとして動作させるので、まずはベースとなるJSFアプリケーションを作ってから、それをポートレット対応にするという手順で進めていこう。JSFアプリケーションは身長と体重を入力したらBMI値を計算してくれるというもので、入力ページ、結果の出力ページ、Managed Beanはそれぞれリスト1、リスト2、リスト3のようにした。JSFに関する詳細な解説は省略させていただく。

リスト1 身長と体重の入力ページ

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSF-Portletブリッジのサンプル</title>
    </head>

    <body>

        <f:view>
            <h:form>
                <h2><h:outputText value="身長と体重を入力して下さい。"/></h2>
                <h:outputText value="身長:"/>
                <h:inputText id="inputHeight" value="#{CalculateBMI.height}"/>
                <h:outputText value="メートル "/>
                <h:outputText value="体重:"/>
                <h:inputText id="inputWeight" value="#{CalculateBMI.weight}"/>
                <h:outputText value="キログラム "/>
                <h:commandButton id="Submit" value="入力" action="success"/>
            </h:form>

        </f:view>

    </body>
</html>

リスト2 計算結果の出力ページ

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSF-Portletブリッジのサンプル</title>
    </head>

    <body>

        <f:view>
            <h:form>
                <h2><h:outputText value="計算結果:"/></h2>
                <h:outputText value="BMI値は "/>
                <h:outputText id="outputBMI" value="#{CalculateBMI.bmi}"/>
                <h:outputText value=" です。"/>
                <h:commandButton id="Submit" value="戻る" action="success"/>
            </h:form>

        </f:view>

    </body>
</html>

リスト3 CalculateBMI.java - BMI値を計算するManaged Bean

package apisample.jsfportlet;

import java.math.BigDecimal;

public class CalculateBMI {
    private double height;
    private double weight;
    private double bmi;

    public CalculateBMI() {
    }

    public void setHeight(double height) {
        this.height = height;
    }
    public double getHeight() {
        return this.height;
    }
    public void setWeight(double weight) {
        this.weight = weight;
    }
    public double getWeight() {
        return this.weight;
    }
    public double getBmi() {
        double tmpBmi = this.weight / Math.pow(this.height, 2);
        BigDecimal bd = new BigDecimal(String.valueOf(tmpBmi));
        this.bmi = bd.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
        return this.bmi;
    }
}

faces-config.xmlにはManaged Beanの設定と、入力ページと出力ページへの遷移を定義してリスト4のようにする。

リスト4 WEB-INF/faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
    <navigation-rule>
        <from-view-id>/input.jsp</from-view-id>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/output.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <from-view-id>/output.jsp</from-view-id>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/input.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>

    <managed-bean>
        <managed-bean-name>CalculateBMI</managed-bean-name>
        <managed-bean-class>apisample.jsfportlet.CalculateBMI</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>

</faces-config>

web.xmlはリスト5のようになる。

リスト5 WEB-INF/web.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
    <navigation-rule>
        <from-view-id>/input.jsp</from-view-id>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/output.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <from-view-id>/output.jsp</from-view-id>
        <navigation-case>
            <from-outcome>success</from-outcome>
            <to-view-id>/input.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>

    <managed-bean>
        <managed-bean-name>CalculateBMI</managed-bean-name>
        <managed-bean-class>apisample.jsfportlet.CalculateBMI</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>

</faces-config>

以上を「JSFPortletSample」というアプリケーション名で作成したとすると、WARファイルの構成は図2のようになる。

図2 JSFPortletSample.warの構成

これをGlassFishにデプロイし、Webブラウザから「http://localhost:8080/JSFPortletSample/faces/input.jsp」にアクセスすると図3のように表示される。身長と体重を入力して[入力]をクリックすると、BMI値が計算されて図4のように結果が表示される。

図3 身長と体重の入力

図4 結果の出力

さて、これをJSF Portlet Bridgeを利用してポートレットとして動作させたい。まず、ダウンロードした「jsf-portlet.jar」をアプリケーションディレクトリに配置する。その上で、WEB-INF/ディレクトリにportlet.xmlを作成し、ここにポートレットの設定を記述する。ここではリスト2.11のようにした。

基本的な部分は第23回で紹介した通りだが、には必ず「com.sun.faces.portlet.FacesPortlet」と指定する。にはこのポートレットでサポートするモード(VIEW/EDIT/HELP)を指定できる。そして各モードの初期ページをに設定する。

リスト6 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/portlet-app_1_0.xsd 
                        http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" 
    xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" >

    <portlet>
        <portlet-name>jsfportlet</portlet-name>
        <display-name>JSF Portlet</display-name>
        <portlet-class>com.sun.faces.portlet.FacesPortlet</portlet-class>

        <init-param>
          <description>Portlet init page</description>
          <name>com.sun.faces.portlet.INIT_VIEW</name>
          <value>/input.jsp</value>
        </init-param>

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

        <portlet-info>
            <title>JSF Portlet</title>
        </portlet-info>
    </portlet>

</portlet-app>

最後に、JSPページから

のタグを削除し、かわりにポートレットのためのタグを追加する(リスト7、リスト8)。

リスト7 修正後のinput.jsp

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@ taglib uri="http://java.sun.com/jsf/portlet/components" prefix="p" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<f:view>
    <p:portletPage>
        <h:form>
               <h2><h:outputText value="身長と体重を入力して下さい。"/></h2>
               <h:outputText value="身長:"/>
               <h:inputText id="inputHeight" value="#{CalculateBMI.height}"/>
               <h:outputText value="メートル "/>
               <h:outputText value="体重:"/>
               <h:inputText id="inputWeight" value="#{CalculateBMI.weight}"/>
               <h:outputText value="キログラム "/>
               <h:commandButton id="Submit" value="入力" action="success"/>
           </h:form>
   </p:portletPage>           
</f:view>

リスト8 修正後のoutput.jsp

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@ taglib uri="http://java.sun.com/jsf/portlet/components" prefix="p" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<f:view>
    <p:portletPage>
           <h:form>
               <h2><h:outputText value="計算結果:"/></h2>
               <h:outputText value="BMI値は "/>
               <h:outputText id="outputBMI" value="#{CalculateBMI.bmi}"/>
               <h:outputText value=" です。"/>
               <h:commandButton id="Submit" value="戻る" action="success"/>
           </h:form>
   </p:portletPage>           
</f:view>

以上で修正は完了だ。JSFPortletSample.warの構成は図5のようになる。

図5 修正後のJSFPortletSample.warの構成

これをPortlet Containerにデプロイする。前回行ったように、Webブラウザから「http://localhost:8080/portletdriver/admin」にアクセスし、図6の画面からポートレットをデプロイする。

図6 ポートレットのデプロイメント

デプロイに成功したら、[ポートレット]タグのページを表示させると図7のように先ほど作成したJSFアプリケーションがポートレット内で動作しているはずだ。図8のようにBMI値の計算も有効になっているのがわかる。

図7 JSFアプリケーションがポートレットとして動作

図8 Managed Beanの動作も有効

なお、OpenPortal JSF Portlet BridgeではすでにJSR 301に準拠した実装を開発する作業が始められている。