Portal Bridges は、Portlet 以外で作られた Web フレームワークと、JSR-168 に準拠した Portlet を橋渡しするライブラリです。The Apache Software Foundation によって提供されています。

今回の覚書では、Portal Bridges の中の Struts Bridges について書き留めておきます。

ちなみに、対応している Struts のバージョンは、1.2.4 と 1.2.7 だそうです。

[参考]

Struts Bridge 目次

Struts Bridge を使ってみる 目次

言葉の定義おさらい

ActionRequest
Portlet#processAction を呼び出すトリガーとなるリクエストのこと。アクションリクエスト。
RenderRequest
Portlet#render を呼び出すトリガーとなるリクエストのこと。レンダリングリクエスト
リクエストURL
HttpServletRequest#getRequestURL メソッドで取得できる URL のこと。クライアントがリクエストを行った URL。
PortletURL
Portlet を呼び出すためのURL。Portlet は、URL とは直接的な結びつきが無いため、通常 RenderResponse クラスが生成した URL を使う。
リクエストパラメータ
URL の後ろにくっつくパラメータのこと。 http://localhost/servlet/index.do?abc=hoge の場合、’?’ より後ろがリクエストパラメータと呼ばれる。

Portal Bridgesとは

Portal Briges は、Portlet 以外で作られた Web フレームワークと、JSR-168 に準拠した Portlet を橋渡しするライブラリです。

Struts、JSF、PHP、Perl、Velocity をサポートしています。

Struts をブリッジしてみる

なんか、めちゃめちゃ難しいとかいわれてますが・・・

Apache の Portal Briges のページを見ると、Struts のようなひとつのリクエストを一度で解釈するようなフレームワークは、Portlet 環境に乗せにくいとかいてあります。

たしかに、Portlet はリクエストの処理と描画処理がわかれているので、この二つを同時に行うようなフレームワークとは相性が悪い気はします。

そこで、Portal Bridges はどうするかというと、

2回のリクエストをもって Servlet のリクエストが完結するような独自の実装を提供し、Struts 側には影響が少なくてすむような方法をとります。

コードやJSPを変更しなくても Portlet に対応できる。ただし、条件が・・・

うまいこと作ってあるアプリケーションであれば、コードやJSPファイルを変更せずに Portlet に対応できるそうです。その条件とは

条件

  1. Struts Action configuration にしたがって、適切な MVC アーキテクチャになっていること
  2. JSP ファイルの中で、すべてのリソースへのリンクが Struts Tag を使って記述されていること

逃げ道として、Struts アプリケーションを単独で動かすことも可能だそうです。その場合には、Portlet の機能は使うことができません。

Struts Bridge が提供するもの

PortletURL を Struts 用のURLに変える

Struts は、リクエストURL をもとに Action を判断します。一方で、Portlet は、リクエストパラメータで Action を判断します。

ここに、ひとつの大きな差異が生まれているわけで、Struts Bridge は PortletURL (つまりリクエストパラメータ)を Struts が呼び出されたURL(リクエストURL)に変換する機能を持ちます。

何をやるかというと単純で、Portlet コンテナが渡してくれたURLをもとに Struts 側の Servlet で、HttpServletRequest をつくり直してしまうのです。

ServletAPI にアクセスする標準の方法を提供

Portlet コンテナや Portlet は、独自の実装で ServletContext や HttpServletRequest、HttpServletResponse をラップしています。

そういう、実装に依存しない形で、ServletContext や HttpServletRequest などを使えるようにするために、ServletContextProvider という機能を提供しています。

Portlet の実装に依存しないように ServletAPI を使いたいときに、このクラスを使います。

必要ならば、ServletContextProvider を拡張できます

ServletContextProvider の実装は、必要に応じて拡張できます。

Portlet のAction リクエストを自動的に Struts のアクションとレンダリングに分ける

Portlet の仕様で、ActionRequest の場合は、レンダリングの処理を行ってはいけないという決まりがあります。しかし、Struts からしてみればひとつのリクエストには、アクションとレンダリングで対応するというのが普通です。

なので、Portlet コンテナは ActionrRequest のつもりで Portlet を起動したのに、Struts によってレンダリングまでされてしまっては困るのです。

そこで、Struts Bridge は、Struts の中でレンダリングがされても、RenderRequest が来るまでは、内部で保持したまま処理を続けるという機能があるわけです。

他にも色々とやってくれるみたいです。

  • ActionRequest 時のコンテキストを StrutsRenderContext に保存する
  • StrutsRenderContext には、ActionForm や ActionMessages/ActionErrors も保存される
  • RenderRequest がやってきたら、StrutsRenderContext に保存されているコンテキストを復元する
  • StrutsRenderContext は一度しか復元されない
  • StrutsRenderContext は ActionRequest 直後の RenderRequest に対して一度だけ機能する
  • ただし、例外があり、入力エラーがあったときだけ、ちょっと違う動きになる
  • ActionRequest の後に ActionErrors が見つかったときには、入力元のページに遷移するようになる
  • Redirect するときは、Web Browser で行うようにするべし
  • Redirect をStruts の中で行った場合、Portlet が予期しない動きをする可能性がある

ActionRequest と RenderRequest の間で、アトリビュートを受け渡す

Struts の中では、Action 中で作ったメッセージを JSP に表示したいといったときに、HttpServletRequest#setAttribute を使います。

Struts 中でセットしたアトリビュートは、ActionRequest に格納されます。しかし、レンダリングには RenderRequest が使われるため、このままではアトリビュートの情報が消えてしまいます。

そこで、ActionRequest から RenderRequest に渡したいアトリビュートを XML に記述することで、StrutsServlet が自動で受け渡しを行ってくれる機能があります。(ActionResponse#setRenderParameter をつかいます)

ActionMessages や ActionErrors などは、受け渡すようにする必要があります。

Tags サポート

JSP の中で使っている Tag を拡張、もしくは Portlet 仕様にあわせたものが提供されます。

JSP の中で使われている URL が ActionURL なのか、 RenderURL なのかは、Portlet からすると、すごく重要なことです。このあたりの設定を JSP を触らずに規定することができる機能が用意されています。

RequestProcessor が用意されている

PortletRequestPorcessor というものが用意されています。struts-config.xml に、この RequestProcessor を使うように指定します。

なお、PortletTilesRequestProcessor というのも用意されています。Tiles を使うときには、こちらを使います。

Portal モードと、スタンドアローンモードを同時に使える

Struts アプリケーションを、Portal とスタンドアローンとで、同時に動かすことができます。

[参考]

今回は、uPortal という Portlet 製品に Struts アプリケーションを乗せることを考えます。

Struts アプリケーションを Portlet に対応させる

変更が必要なファイル

  • JSP ファイル(一部)
  • /WEB-INF/web.xml
  • /WEB-INF/struts-config.xml

新しく作る必要があるファイル(追加する必要があるファイル)

  • /WEB-INF/portlet.xml
  • /WEB-INF/struts-portlet-config.xml
  • /WEB-INF/lib/portals-bridges-common-xxx-SNAPSHOT.jar
  • /WEB-INF/lib/portals-bridges-struts-xxx-SNAPSHOT.jar
  • /WEB-INF/lib/uPortalContextProvider.jar

web.xml を編集する

ActionServlet の代わりに次のクラスを使うように変更します。

org.apache.portals.bridges.struts.PortletServlet

Portlet を使うように設定を追加します。

(Portlet 設定例)

<servlet> 
  <servlet-name>JPetstorePortlet</servlet-name> 
  <display-name>JPetstorePortlet Wrapper</display-name> 
  <description>Automated generated Portlet Wrapper</description> 
  <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class> 
  <init-param> 
      <param-name>portlet-class</param-name> 
      <param-value>org.apache.portals.bridges.struts.StrutsPortlet</param-value> 
  </init-param> 
  <init-param> 
      <param-name>portlet-guid</param-name> 
      <param-value>jpetstore.JPetstorePortlet</param-value> 
  </init-param> 
</servlet> 
 
<servlet-mapping> 
  <servlet-name>JPetstorePortlet</servlet-name> 
  <url-pattern>/JPetstorePortlet/*</url-pattern> 
</servlet-mapping>

portlet-guid パラメータは、[context-name].[portlet-name] で記述します。Pluto の Portlet Definition ID のことです。

struts-config.xml を編集する

struts-config.xml の <controller> 設定に、次のように記述します。

<controller pagePattern="$M$P" inputForward="false"  
processorClass="org.apache.portals.bridges.struts.PortletRequestProcessor" />

JSP で使っている Struts HTML Taglib を変更する

JSP 中で使っている struts-html Taglib の URI を次のように変更します。

<@ taglib uri="http://portals.apache.org/bridges/struts/tags-portlet-html" prefix="html" %>

もしくは

<@ taglib uri="http://portals.apache.org/bridges/struts/tags-portlet-html-el" prefix="html-el" %>

この変更を行うことで、html:linkhtml:rewrite タグが拡張され、次の3つの属性を指定することができるようになります。(true / false で指定可能。排他選択)

  • actionURL
  • renderURL
  • resourceURL

何も指定しない場合は、renderURL="true" が指定されたものとして扱われます。

actionURL、 renderURL、 resourceURL の指定が無い場合にどの種類の URL として扱われるかというのは、次に説明する struts-portlet-config.xml で変更可能です。

struts-portlet-config.xml と Taglib で指定した URL 種類が違う場合は、Taglib の方が優先されます。

struts-portlet-config.xml ファイルを作成する

このファイルは、taglib によって出力されるURL がどの種類の URL (actionURL、renderURL、resourceURL) かを支持するものです。

<?xml version="1.0" encoding="UTF-8"?> 
<config> 
  <render-context> 
    <attribute name="errors"/> 
    <attribute name="message" keep="true"/> 
  </render-context> 
  <portlet-url-type> 
    <action path="/shop/add"/> 
    <action path="/shop/switch"/> 
    <action path="/shop/remove"/> 
    <action path="/shop/signoff"/> 
    <action path="/shop/viewCategory"/> 
    <action path="/shop/viewItem"/> 
    <action path="/shop/viewProduct"/> 
    <action path="/shop/viewCart"/> 
    <action path="/shop/newOrder"/> 
    <render path="/shop/newOrderForm"/> 
    <action path="/shop/listOrders"/> 
    <resource path="/images/"/> 
  </portlet-url-type> 
</config> 
render-context タグ

Portlet では、ActionRequest で受け取ったパラメータは、RenderRequest では受け取ることができません。RenderRequest でも受け取るには、ActionRequest の処理中に setRenderParameter メソッドを呼び出す必要があります。

render-context に記述した属性に関しては、ブリッジによって自動的に ActionRequest から RenderRequest に引き渡してくれるようになります。keep 属性を true にすると、別リクエスト間でも保持するようになります。(つまり、セッションに格納されます)

portlet-url-type タグ

JSP 中で記述されている URL が、ActionURL なのか、RenderURL なのか、ResponseURL なのかを指定します。

portlet-url-type の属性の default を action とすることで、URL のデフォルトを render から action へ変更することができます。

<config> 
  <portlet-url-type default="action"/> 
<config>

portlet.xml を作成する

portlet.xml ファイルを作成します。例を次に記述しておきます。

<?xml version="1.0" encoding="UTF-8"?> 
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" 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"> 
   <portlet> 
    <portlet-name>JPetstorePortlet</portlet-name> 
    <portlet-class>org.apache.portals.bridges.struts.StrutsPortlet</portlet-class> 
    <init-param> 
      <name>ServletContextProvider</name> 
      <value>ca.mun.portal.bridges.PortalServletContextProvider</value> 
    </init-param> 
    <init-param> 
      <name>ViewPage</name> 
      <value>/index.shtml</value> 
    </init-param> 
    <init-param> 
      <name>HelpPage</name> 
      <value>/help.shtml</value> 
    </init-param> 
    <expiration-cache>-1</expiration-cache> 
    <supports> 
      <mime-type>text/html</mime-type> 
      <portlet-mode>VIEW</portlet-mode> 
      <portlet-mode>HELP</portlet-mode> 
    </supports> 
    <portlet-info> 
      <title>JPetstore</title> 
      <keywords>Struts</keywords> 
    </portlet-info> 
  </portlet> 
</portlet-app>

ca.mun.portal.bridges.PortalServletContextProvider というクラスは、ServletContextProvider の実装クラスです。北米のほうで実装されたものがあるということで、今回はこれを使います。uPortal / pluto で使用可能です。

ca.mun.portal.bridges.PortalServletContextProvider.java

package ca.mun.portal.bridges; 
 
import javax.portlet.GenericPortlet; 
import javax.portlet.PortletRequest; 
import javax.portlet.PortletResponse; 
import javax.servlet.ServletContext; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
 
import org.apache.pluto.core.impl.ActionRequestImpl; 
import org.apache.pluto.core.impl.PortletContextImpl; 
import org.apache.pluto.core.impl.RenderRequestImpl; 
import org.apache.portals.bridges.common.ServletContextProvider; 
 
/** 
 * PortalServletContextProvider supplies access to 
 * the Servlet context of uPortal Portlet. 
 *  
 * @author Satish Sekharan 
 */ 
public class PortalServletContextProvider implements ServletContextProvider { 
 
  public ServletContext getServletContext(GenericPortlet portlet) { 
    return
  ((PortletContextImpl) portlet.getPortletContext()).getServletContext();
  }  
 
  public HttpServletRequest getHttpServletRequest(GenericPortlet portlet, PortletRequest request) {         
    return 
(HttpServletRequest) ((HttpServletRequestWrapper) request).getRequest();
  } 
 
  public HttpServletResponse getHttpServletResponse(GenericPortlet portlet, 
        PortletResponse response) { 
    return
(HttpServletResponse) ((HttpServletResponseWrapper) response).getResponse();
  }
}