Apache Pluto の覚書きです。Apache Pluto は、Java Portlet 仕様 のリファレンス実装です。この覚書きを書いている段階では、Java Portlet Specification (JSR-168) が Java Portlet 仕様として、まとめられています。

この覚書きで使っている環境は次の通りです。

  • Eclipse 3.1.2
  • Pluto 1.1 (alpha release 以前)
  • Tomcat 5.5.9
  • J2SE 5.0

参考にしたリソースは次の通りです。

Pluto の中を追っていったときの覚書です。ソースコードを手元に読んでいただければ、より理解できるのではないかと思います。

間違い等に気づいた方は、ご連絡いただけると助かります。

Apache Pluto1.1 覚書 目次

Pluto をもうちょっと詳しく見てみる 目次

環境構築

今回使うのは、Apache Pluto1.1 です。2006/4/20 現在では、 Pluto のバージョンは 1.0.1 と 1.1 の二つが用意されています。バイナリで配布されているのは 1.0.1 だけなので、今回はソースコードを手に入れるところからはじめます。

Pluto 1.0.1 と Pluto 1.1 は、アーキテクチャが大きく変わったようです。1.1 のほうがよりシンプルに美しくなりました。それに伴って、設定の仕方なども色々と変わっています。 Pluto 1.0.1 を使う場合は、この覚書は参考にならないかもしれませんので注意してください。

今回使うのは、Pluto1.1 です。

ライセンス

The Apache Software License, Version 2.0

ソースコードをダウンロードする

Apache Pluto 1.1 のソースコードは、Subversion で管理されています。下記の URL からチェックアウトできます。

 http://svn.apache.org/repos/asf/portals/pluto/trunk/

下のコマンドを実行することでも、ソースコードがダウンロードできます。

> svn checkout http://svn.apache.org/repos/asf/portals/pluto/trunk/ pluto-site

インストールする

ソースコードをダウンロードしたら、Apache Pluto のサイトにある Getting Started を見ながら、インストールを行います。ソースコードをダウンロードしたパスに移動して

> mvn install

を実行すると、ずらずらと、ライブラリができるので、Getting Started に書いてある Step にしたがって配置していきます。

配置が終了したら、Tomcat を起動します。http://localhost:8080/pluto/portal にアクセスすると、Pluto のポータル画面が表示されます。

Pluto のアーキテクチャ

ポータルもWebアプリケーションだから、サーブレット が使われている

まず、最初に Pluto がどういう風に動作するのかを簡単にまとめて見ました。(画像をクリックすると大きな画像が表示されます。)

Pluto は、Web ブラウザからリクエストを受けると次のように動作します。

1. PortalDriverServlet がポータルに対するリクエストを受け取る
        ↓
2. サーブレットコンテキスト、サーブレットリクエスト、サーブレットレスポンスをそれぞれポートレット API でラップする
        ↓
3. ポートレットコンテナを呼び出す
        ↓
4. ポートレットコンテナは、リクエストパラメータを解析して、どのポートレットが呼び出されたのかを判断する
        ↓
5. 呼び出されたポートレットにリクエストをフォワードする
        ↓
6. ポートレットコンテナは、レンダリングリクエストを表示するページ内の全ポートレットに対して送信する
        ↓
7. ポートレットのレスポンスをマージして、画面をブラウザに返す

ポータルアプリケーションもひとつの Web アプリケーションなので、当然サーブレットを使っています。

Tomcat を起動した際に webapp ディレクトリにできる pluto フォルダの下には、典型的な Web アプリケーションの構成ができています。

pluto   のディレクトリ構成

{TOMCAT_HOME}/webapps/pluto/WEB-INF/web.xml を開くと、こんな記述があると思います。

web.xml 抜粋

<servlet>  
  <servlet-name>plutoPortalDriver</servlet-name>  
  <display-name>Pluto Portal Driver</display-name>  
  <description>Pluto Portal Driver Controller</description>  
  <servlet-class>org.apache.pluto.driver.PortalDriverServlet</servlet-class>  
</servlet>

このクラスが、ポータルアプリケーションの窓口、ポータルアプリケーションのサーブレットです。ポータルにアクセスしたいときは、このサーブレットにアクセスすることになります。web.xml の下のほうにマッピングが書かれていますので、見てみます。

web.xml 抜粋

<servlet-mapping>  
  <servlet-name>plutoPortalDriver</servlet-name>  
  <url-pattern>/portal/*</url-pattern>  
</servlet-mapping>

つまり、http://localhost:8080/pluto/portal/ にアクセスすることで、このサーブレットが呼ばれ、ポートレットコンテナに格納されているポートレットが呼び出されるわけです。

なお、ポートレットの初期化は、サーブレットリスナで行われており、org.apache.pluto.driver.PortalStartupListener が使われていました。これについては後の節で詳しく見ることにします。

Pluto で使われる主なクラス群

Pluto のアーキテクチャを調べた中で出てきた主なクラスは次の4つです。

org.apache.pluto.driver.PortletDriverServelt

このクラスは、Pluto の本体ともいえるサーブレットの実装クラスです。Web ブラウザからのアクセスは、このサーブレットが受け取ります。このクラスは、Pluto 全体の初期化やポートレットコンテナの作成と初期化を行います。

org.apache.pluto.PortletContainer

ポートレットコンテナの実装クラスです。PortletDriverServlet から呼び出されます。内部にはポートレットを格納しています。リクエストを解釈し、適切なポートレットに割り振ることを行います。

org.apache.pluto.core.PortletServlet

ポートレットアプリケーション側に配備されるクラスです。ポートレットコンテナからの呼び出しを受け、ポートレットに処理を委譲します。

org.apache.pluto.driver.tags.PortletRenderTag

ポートレットのレンダリングを呼び出すためのタグクラスです。JSP に記述され、ポートレットのレンダリングリクエストを発行します。<pluto:render /> であらわされます。

クロスコンテキストを有効にする必要がある
ポータルは、複数のWeb アプリケーションをまたがるため、クロスコンテキスト(crossContext) が可能でなければなりません。{TOMCAT_HOME}/conf/Catalina/localhost にコピーした pluto.xml には、crossContext="true" が記述されています。

ポートレットコンテナにポートレットを登録するにはどうすればいいのか?

{TOMCAT_HOME}/webapps/pluto/WEB-INF/web.xml を見ると、いくつかのサーブレットの設定に org.apache.pluto.core.PortletServlet というクラスが使われています。サーブレットの init-param 設定で、portlet-name というパラメータが設定されています。

<servlet>  
  <servlet-name>AdminPortlet</servlet-name>  
  <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>  
  <init-param>  
    <param-name>portlet-name</param-name>  
    <param-value>AdminPortlet</param-value>  
  </init-param>  
  <load-on-startup>1</load-on-startup>  
</servlet>

どうやら、Pluto1.1 では、ポートレットコンテナ がこの PotletServlet を呼び出して、さらに PortletServlet が ポータルコンテンツ を呼び出す仕組みになっているようです。呼び出される ポータルコンテンツは、portlet-name で指定しています。

portlet-name で指定されているのは、あくまでポートレット名です。では、ポートレットの本体はどこで定義するのかというと、JSR-168 で決められている通り、portlet.xml に定義します。あたりまえですね。

web.xml をもう少し見ていくと、今度は、servlet-mapping が見つかります。

<servlet-mapping>  
  <servlet-name>AdminPortlet</servlet-name>  
  <url-pattern>/PlutoInvoker/AdminPortlet</url-pattern>  
</servlet-mapping>

/PlutoInvoker/ という記述がなんとも不思議です。Pluto 1.1 は、PortletServlet が呼び出される URL にプレフィックスとして、この /PlutoInvoker/ という名前を使います。

Portlet 用のサーブレットは、マッピング URL に /PlutoInvoker/ を記述するようにします。その後ろの名前は、ポートレット名をつけます。

ちなみに、web.xml と portal.xml の設定は、ポートレットアプリケーション(自分が作るアプリケーション)側に記述します。

ポートレットを呼び出すぞ。(でも、URL がわからない・・・)

さて、これでポートレットの設定は完了したわけですが、これだけではポートレットは呼び出せません。JSR-168 には、

Portlets are not directly bound to a URL

JSR-168

とあります。これはつまり、ブラウザのアドレスバーに URL を直接打ち込んでもポートレットを呼び出すことはできませんということを言っています。例えば、ブラウザのアドレスに http://localhost:8080/pluto/PortletInvoker/AdminPortal 入力してもエラーになります。

では、どうすればよいかというと、ポートレットは、ポートレットコンテナから呼び出されることが決められているので、ポートレットコンテナに呼び出しをお願いします。Pluto1.1 の場合、このお願いは {TOMCAT_HOME}/webapps/pluto/WEB-INF/pluto-portal-driver-config.xml に記述することになります。

(pluto-portal-driver-config.xml 抜粋)

<pluto-portal-driver   
  ...  
  <portlet-app>  
    <context-path>/pluto</context-path>  
    <portlets>  
      <portlet name="AboutPortlet" />  
      <portlet name="AdminPortlet" />  
    </portlets>  
  </portlet-app>  
  ...  
  <render-config default="Pluto Admin">  
    <page name="Pluto Admin" uri="/WEB-INF/themes/pluto-default-theme.jsp">  
      <portlet context="/pluto" name="AdminPortlet"/>  
    </page>  
  </render-config>  
</pluto-portal-driver>

portlet-app タグ

このタグでポートレットの定義を記述します。

context-path タグ

ここに、ポートレットのコンテキストパスを記述します。ポートレットは別の Web アプリケーションとして作成できるので、コンテキストも Pluto とは別になります。コンテキスト名に使える文字は 「a-z, A-Z, 0-9, _, /」 です。’.’ や ‘-’ は使えないので注意です。

portlets タグ

このタグを使って、このコンテキストに含まれるポートレットの名前を定義します。ここに記述するのは、portlet.xml に定義したポートレットです。

render-config タグ

このタグは、PlutoPortletDriver の設定を記述するところです。ここには、レンダリングに関する設定をおこないます。設定しているのは、ポータルページを呼び出すときのパス名(name 属性)と、ポータルページに使用するページ(uri 属性)、どのポートレットを使うか(portlet タグ)です。portlet タグに記述するのは、portlet-app で記述したコンテキストと、ポートレット名です。

name に指定した値 (ページ名) をブラウザのアドレスバーに打ち込むことで、ポータルページを開くことができます。

http://localhost:8080/pluto/portal/Pluto%20Admin

これで、Pluto を起動したときに、pluto-portal-driver-config.xml が読み込まれ、ポートレット URLを生成したページが表示できるようになります。Pluto は、この設定ファイルを解析して、URL と ポートレットアプリケーションのマッピングを行います。

http://localhost:8080/pluto/portal にアクセスして、ログインした後のページの左上にある Navigation から ページを移動してみましょう。画面が表示されるはずです。

ポートレットID

すべてのポートレットには、一意の ID が振られます。Pluto の場合、次のようなルールによって ID が決められます。

[portlet-context-name].[portlet-name]

[portlet-context-name] とは、ポートレットアプリケーションのコンテキスト名のことです。[portlet-name] は、文字通りポートレット名です。これらを ‘.’ でつないだものが Portlet Definition ID と呼ばれるポートレットの一意な ID です。

Pluto の設定には、SpringFramework が使われている

{TOMCAT_HOME}/webapps/pluto/WEB-INF/ を見ると、pluto-portal-driver-service-config.xml というファイルがあるのがわかります。このファイル、中を見ると実は SpringFramework の Bean 設定ファイルだったりします。

Pluto の設定が行われている部分を抜粋します。

(pluto-portal-driver-service-config.xml 抜粋)

<bean id="DriverConfiguration"  
  class="org.apache.pluto.driver.config.impl.DriverConfigurationImpl">  
  <!-- ===== Portal Services ===== -->  
  <constructor-arg><ref local="PropertyConfigService"/></constructor-arg>  
  <constructor-arg><ref local="PortletRegistryConfig"/></constructor-arg>  
  <constructor-arg><ref local="RenderConfigService"/></constructor-arg>  
 
  <!-- === Container Services === -->  
  <constructor-arg><ref local="PortalCallbackService"/></constructor-arg>  
 
  <!--  Optional Container Services -->  
  <!--  
  <property name="portletPreferencesService" > 
    <ref local="PortletPreferencesService" /> 
  </property>  
  <property name ="userAttributeService" > 
    <ref local="UserAttributeService" /> 
  </property>  
  -->  
</bean>

4つのサービスクラスをインジェクションしています。Portal Services とコメントされているサービスクラスは、pluto-portal-driver-service-config.xml を読み込んで、それぞれの担当範囲の設定を返す役割を持っています。

Container Services とコメントされているサービスクラスは、ポートレットコンテナから必要に応じて呼び出されるクラスです。例えば、ポータルのタイトルを設定するときに呼び出されたり、ポートレット URL を生成するクラスの取得に使われたりします。

Pluto の実装を拡張したものに、uPortal があります。これらのサービスクラスを拡張して作られています。

まとめ

  • ポータルはただのWeb アプリケーション
  • ポートレットもただのWeb アプリケーション
  • ポータルアプリケーションとポートレットアプリケーションは別々に開発できる
  • ポータルアプリケーションの設定は{PORTLET_APP}/WEB-INF/portlet.xml を作成することで行う
  • ポートレットコンテナに対するポートレットの追加・変更や削除は {PLUTO_HOME}/WEB-INF/pluto-portal-driver-config.xml に記述する
  • 画面デザインの変更は、{PLUTO_HOME}/WEB-INF/themes/pluto-default-theme.jsp、portlet-skin.jsp を変更することで行う
  • ポートレットコンテナの初期化に関する設定は、{PLUTO_HOME}/WEB-INF/pluto-portal-driver-service-config.xml に記述する

さらに、ポートレットアプリケーションの開発者がやらなければならないのは、次の点です。

Portlet 開発者がやる作業
  1. javax.portlet.GenericPortlet を継承したポートレットを作成する
  2. /WEB-INF/portlet.xml を作成し、ポートレットの定義を行う
  3. web.xml に org.apache.pluto.core.PortletServlet を設定し、portal-name を記述する
  4. Pluto の pluto-portal-driver-config.xml に、ポートレットの設定を記述する

PortalStartupListener の動作

もうちょっと突っ込んで Pluto を見てみます。といっても、PortalDriverServletPortletContainer の動きを追っていくだけですが。

PortalStartupListener の中で、Pluto で使われるいろいろな初期設定が行われます。このクラスは、Pluto がアプリケーションサーバにロードされると起動します。デフォルトの実装では、次の3つが行われます。

デフォルトの設定メソッド

  • initDriverConfiguration(servletContext);
  • initAdminConfiguration(servletContext);
  • initContainer(servletContext);

順番に見ていきます。まずは、initDriverConfiguration です。

このメソッドは、DriverConfiguration の生成と初期化を行います。

最初に DriverConfigrationFactory クラスが呼び出されます。 Factory の中で、SpringFramework が呼び出され、 pluto-portal-driver-service-config.xml の内容を元に DriverConfigration が作成されます。

あとは、DriverConfiguration に設定されたサービスクラスの init メソッドが呼び出され、pluto-portal-driver-config.xml のパースを行うという仕組みです。

生成された DriverConfiguration は、ServletContext"driverConfig" という名前で格納されます。

つぎに、AdminConfiguration の生成と初期化が行われます。

ほとんどの処理が、DriverConfiguration の同じです。違うのは、ServletContext に格納するときのキーが違うくらいです。

"driverAdminConfig" という値で格納されます。

AdminConfiguration は、ポートレットの追加や削除がシステム的に行えるものです。ポートレットを管理するツールを作るときに使えそうです。

最後に ポートレットコンテナの生成と初期化です。

ここも大したことをしていません。DriverConfiguration をもとに、コンテナの実装クラスを生成しているだけです。

生成したコンテナは、ServletContext"portletContainer" というキーで格納されます。

これで Pluto の初期化が完了しました。あとは、PortletDriverServletPortletContainer の動作がわかれば、一通り Pluto の動作を追えるかなと思います。

PortletDriverServlet の動作

このクラスは、何をやっているかというと、サーブレット API を ポートレット API にラップしてポートレットコンテナを呼び出すということを行っています。

ポータルは、リクエストパラメータの値によって呼び出すポートレットを判断します。リクエストパラメータとポートレットのマッピングルールは、ポータル製品ごとに決められています。

Pluto 1.1 は、PortalURLParser クラスの parse メソッドで変換ルールを規定しています。ここで、リクエスト URL をポータルで使える形に変換しています。

リクエスト種類によって、呼び出しが変わる

ポータルに対するリクエストには種類があります。一つはアクションリクエストで、ポートレットの processAction メソッドが呼び出されます。もう一つはレンダリングリクエストで、ポートレットの render メソッドが呼び出されます。

アクションリクエストの場合、ポートレットコンテナの doAction が呼び出されます。これは、ポートレットの呼び出しを行うメソッドになっています。

レンダリングリクエストの場合、画面の描画が行われます。このとき、{PLUTO_HOME}/WEB-INF/themes/pluto-default-theme.jsp の表示されます。

ポートレットコンテナの動作

ポートレットコンテナの呼び出しを整理しておくと

JSR-168 では、アクションURL がリクエストされたときには、対応するポートレットの processAction メソッドの呼び出しと、ポータルページに含まれるすべてのポートレットの render メソッドが呼び出されるとされています。

If the client request is triggered by an action URL, the portal/portlet-container must first trigger the action request by invoking the processAction method of the targeted portlet. <中略> Then, the portal/portlet-container must trigger the render request by invoking the render method for all the portlets in the portal page with the possible exception of portlets for which their content is being cached.

JSR-168

PortletDriverServlet から呼び出されるのは、ポートレットコンテナの doAction メソッドです。この doAction メソッドの中でリクエストを解析し、どのポートレットを呼び出すかを決めます。ポートレットの呼び出しには、サーブレットレスポンスの sendRedirect を使っています。

Pluto は、Portlet のレンダリングトリガーを <pluto:render/> タグで行っています。

ポートレットの呼び出し方法

ポートレットコンテナが、どうやってポートレットを呼び出すかですが、上に書いたようにサーブレットレスポンスの sendRedirect を使っています。このリダイレクト URL のパラメータに、ポートレットコンテナが受け取ったリクエストパラメータ等の情報をエンコードして付与しています。

そして、呼び出されたポートレットプリケーション側で、待ち構えていた org.apache.pluto.core.PortletServlet クラスがパラメータをデコードして、サーブレットリクエストを完成させます。

これで、ポートレットの呼び出しは完了です。

クロスコンテキストを有効にする!
ポートレットの呼び出しを行うには、クロスコンテキストを有効にしておかなければなりません。

ポータルは、異なるコンテキスト間のポートレットを呼び出します。そのため、コンテキストをまたいだ呼び出しが可能なように設定を行う必要があります。

Tomcat の場合、これは、クロスコンテキスト(crossContext)というパラメータで行います。{TOMCAT_HOME}/conf/server.xml に記述するコンテキストの設定に crossContext を設定します。

エラーで困ったら

error.config.context.null

org.apache.pluto.PortletContainerRuntimeException: error.config.context.null

こんなエラーで困ったら、crossContext を true に設定したかどうかを疑ってください。また、コンテキスト名が間違っていないか確認してください。

検討事項

TBD