DWR が Ajax によるポートレット間メッセージングを簡単にする

Java ポートレットと Ajax は Web 開発に最適です

Comments

ポートレットは、Web ポータルのための Java プラットフォーム・ベースのアプリケーションです。JSR-168 は、ポートレット・アプリケーションを開発するための Java Community Process 標準であり、ポートレットのライフ・サイクル管理、ポートレット・コンテナーの契約、パッケージ化、デプロイメント、その他のポータルに関連する側面について扱います。

Asynchronous JavaScript + XML、すなわち Ajax は、高度な対話式の Web アプリケーションを開発するための手法です。Ajax は、XML、HTML、DHTML、JavaScript、および DOM を組み合わせて使います。

ポートレットと Ajax は、両方ともユーザーに UI を表示するための手段として Web ブラウザーを使うようにフォーカスしているため、お互いに最適な組み合わせです。この 2 つを Java テクノロジーと組み合わせるには、DWR ライブラリーを使うと簡単です。DWR は、Ajax ベースの Web アプリケーションをビルドするための Java ライブラリーで、Apache ライセンスを受けたオープン・ソースです。DWR の基本的な目的は、Ajax の詳細を開発者から見えなくすることです。このため、サーバー・サイドで POJO (Plain Old Java Object) を使用すると、DWR は JavaScript プロキシー機能を動的に生成し、JavaScript によるクライアント・サイドの開発は JavaBeans を直接呼び出すような感じになります。DWR の主要コンポーネントは、ブラウザーからサーバーへの呼び出しを処理する Java サーブレットです。

この記事では、DWR を使って、3 つのポートレットを基にした Ajax アプリケーションのサンプルをビルドします。また、DWR をポートレット・アプリケーションと統合する方法を説明しますが、DWR がどのような動作をしているかの詳細にまでは触れません。DWR ライブラリーについて詳しくは、プロジェクトの Web サイトや developerWorks のページを確認してください (詳細については、参考文献を参照してください)。ここで説明するアプリケーションをビルドするには、バージョン 1.3 以降の Java プラットフォームと JSR-168 に準拠したポータル環境が必要です。このコードを開発およびテストするために、IBM Rational Application Developer V6.0、Apache Jetspeed 2.0 ポータル、および Java 5.0 で構成された環境を使用しました。

始めるにあたって、ポートレットと Ajax 開発を熟知している必要があります。これらのトピックについてさらに学びたい場合は、以下の参考文献セクションを確認してください。DWR を含むサンプル・アプリケーションの完全なコードは、ダウンロード・セクションからダウンロードできます。

サンプルのポートレット間メッセージング・アプリケーションをビルドする

サンプル・アプリケーションには、Orders、Order Details、および Customer Details の 3 つのポートレットがあります。図 1 は、サンプル・アプリケーションを示しています。

図 1. サンプル・アプリケーション
Sample application
Sample application

Orders ポートレットには、注文のリストがあります。ユーザーが注文番号をクリックすると、ポートレットが Order Details ポートレットと Customer Details ポートレットに注文番号を送信します。次に、これらのポートレットが、該当する注文と顧客の詳細を表示します。

開発環境をセットアップする

ポートレットを開発する前に、開発環境と DWR をセットアップする必要があります。私は、Java ポートレット開発のサポートが組み込まれている IBM Rational Application Developer V6.0 を使用しますが、どのような開発環境でもかまいません。それでは、次のステップに従って始めましょう。

  1. 新しい JSR-168 ポートレット・プロジェクトを作成します。プロジェクトに InterPortletMessaging という名前を付けますが、ポートレットはまだ作成しないでください。
  2. dwr.jar (バージョン 1.1。リンクについては、参考文献を参照してください) をダウンロードして、/WebContent//WEB-INF/lib ディレクトリー内のプロジェクトに追加します。
  3. web.xml を開いて、リスト 1 のコードを追加します。これで、DWR サーブレットがアプリケーションに追加されます。このサーブレットは、バックグラウンドで要求を処理して、応答をブラウザーに戻します。
    リスト 1. DWR サーブレット
    <servlet>
    
      <servlet-name>dwr-invoker</servlet-name>
      <display-name>DWR Servlet</display-name>
      <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
      <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
      </init-param>
    </servlet>
    
    <servlet-mapping>
      <servlet-name>dwr-invoker</servlet-name>
      <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>
  4. リスト 2 のコードを使って、WEB-INF ディレクトリーに dwr.xml というファイルを作成します。これは、JavaScript で使用可能なクラスをコンテナーに伝える DWR 構成ファイルです。DWR はこの XML ファイルを読み取って、JavaScript コードを動的に生成し、Java クラスを JavaScript クラスで表します。これで、これらの Java クラスが提供する機能をブラウザーで使えるようになります。実際には、リスト 2 で参照されている Java クラスはまだ作成していませんが、すぐ後で作成します。
    リスト 2. dwr.xml
    <!DOCTYPE dwr PUBLIC
        "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
        "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
    
    <dwr>
      <allow>
        <create creator="new" javascript="MessagingBean">
          <param name="class" value="msg.MessagingBean"/>
        </create>
      </allow>
    </dwr>

これで、ポートレットで DWR を使用できるようになりました。ただし、サンプル・ポートレットを作成する前に、サンプル・アプリケーションのバックエンドの役割を果たす messaging Bean と mockup database Bean を作成する必要があります。

MockupDB と MessagingBean を作成する

リスト 3 に示されている MockupDB は、顧客からの注文のデータベースをシミュレートする singleton クラスです。すべての注文は、このクラスでハードコーディングされています。実際のアプリケーションはおそらくリレーショナル・データベース・システムを使用しますが、ここではこれで十分です。

リスト 3. MockupDB
package db;

import java.util.Hashtable;
import java.util.Map;

public class MockupDB {

  private static MockupDB instance=new MockupDB();

  private String[] orders=new String[4];
  private Map orderDetails=new Hashtable();
  private Map customerDetails=new Hashtable();

  private MockupDB()
  {
    String ordStart="ORD";
    orders[0]=ordStart+"000408015";
    orders[1]=ordStart+"001600023";
    orders[2]=ordStart+"000042000";
    orders[3]=ordStart+"011235813";

    orderDetails.put(orders[0],"1. WebSphere Everyplace Connection Manager<br/>"+
                     "2. WebSphere Portal");
    orderDetails.put(orders[1],"1. DB2 Universal Database<br/>2. DB2 Everyplace");
    orderDetails.put(orders[2],"1. Tivoli Access Manager for e-business <br/>2."+
                     "Tivoli Directory Integrator");
    orderDetails.put(orders[3],"1. IBM System z9<br/>2. IBM System p5 550 Express");

    customerDetails.put(orders[0],"<b>Systems and Technology Group</b><br/>"+
                        "Some Road<br/>Finland");
    customerDetails.put(orders[1],"<b>Global Financing</b><br/>Another Street"+
                        "<br/>Finland");
    customerDetails.put(orders[2],"<b>Software</b><br/>Yet Another Road"+
                        "<br/>Finland");
    customerDetails.put(orders[3],"<b>Global Services</b><br/>Still Another "+
                        "Street<br/>Finland");
  }
    
  public static MockupDB getInstance()
  {
    return instance;
  }
    
  public String[] getOrders()
  {
    return orders;
  }
    
  public String getOrderDetails(String orderNro)
  {
    return (String)orderDetails.get(orderNro);
  }

  public String getCustomerDetails(String orderNro)
  {
    return (String)customerDetails.get(orderNro);
  }
}

リスト 4 に示されている MessagingBean は、2 つのメソッドを持つ単純な POJO です。これらのメソッドは、注文番号を受け取って、それぞれ注文の詳細と顧客の詳細を戻します。MessagingBean は、MockupDB から詳細を取得します。

リスト 4. MessagingBean
package msg;

import javax.servlet.http.HttpSession;

import db.MockupDB;

public class MessagingBean {

  public MessagingBean()
  {        
  }
    
  public String getOrderDetails(String orderNumber,HttpSession httpSession)
  {
    String orderDetails=MockupDB.getInstance().getOrderDetails(orderNumber);
    httpSession.setAttribute("orderDetailsOrderNumber",orderNumber);
    httpSession.setAttribute("orderDetails",orderDetails);
    return orderDetails;
  }

  public String getCustomerDetails(String orderNumber,HttpSession httpSession)
  {
    String customerDetails=MockupDB.getInstance().getCustomerDetails(orderNumber);
    httpSession.setAttribute("customerDetailsOrderNumber",orderNumber);
    httpSession.setAttribute("customerDetails",customerDetails);
    return customerDetails;
  }
}

また、MessagingBean は、HttpSession に注文の詳細と顧客の詳細を追加します。

javaScriptFunctions.jsp

javaScriptFunctions.jsp は、DWR (engine.js) と動的に作成された MessagingBean.js ライブラリーから JavaScript ライブラリーをインポートします。MessagingBean.js は、dwr.xml (リスト 2) で紹介した Java Bean と同じ名前を使っていることに気づくでしょう。実際には、DWR が MessagingBean.js を生成します。DWR フレームワークは engine.js ライブラリーを使いますが、一般的には、開発者自身がこのライブラリーを直接使うことはありません。

リスト 5 で確認できるように、sendOrderNr() 関数は、リスト 4 で定義した MessagingBean 関数を呼び出します。DWR は、メソッド呼び出しに HttpSession を自動的に追加します。JavaScript 関数の最後のパラメーターは callback 関数です。この JSP は、次に作成するポートレット JSP に組み込まれています。

リスト 5. javaScriptFunctions.jsp
<%@ page contentType="text/html" 
import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %>
<%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
<portlet:defineObjects/>

<SCRIPT type="text/javascript"
	src='<%= renderResponse.encodeURL(renderRequest.getContextPath() +
	"/dwr/interface/MessagingBean.js") %>'> 
</SCRIPT>

<SCRIPT type="text/javascript"
	src='<%= renderResponse.encodeURL(renderRequest.getContextPath() + 
	"/dwr/engine.js") %>'> 
</SCRIPT>

<SCRIPT type="text/javascript">

function <portlet:namespace />sendOrderNr(orderNr)
{
document.getElementById("orderDetailsOrderNumber").innerHTML=orderNr;
document.getElementById("customerDetailsOrderNumber").innerHTML=orderNr;
MessagingBean.getOrderDetails(orderNr,<portlet:namespace />showOrderDetails);
MessagingBean.getCustomerDetails(orderNr,<portlet:namespace />showCustomerDetails);

return false;
}

function <portlet:namespace />showOrderDetails(orderDetails)
{
document.getElementById("orderDetails").innerHTML=orderDetails;
return false;
}

function <portlet:namespace />showCustomerDetails(customerDetails)
{
document.getElementById("customerDetails").innerHTML=customerDetails;
return false;
}
</SCRIPT>

ポートレットを作成する

バックエンド機能とプロキシー機能ができたので、ポートレット自体を開発できるようになりました。3 つのポートレットはすべて同じコードベースを使います。異なるのは、それぞれが使っている JSP の名前だけです。

  1. リスト 6 のコードを使用して新しいポートレットを作成して、Orders という名前を付けます。
    リスト 6. Orders.java
    package interportletmessagingusingajax;
    
    import java.io.*;
    
    import javax.portlet.*;
    
    public class Orders extends GenericPortlet {
    
      // JSP folder name
      public static final String JSP_FOLDER = "/interportletmessagingusingajax/jsp/";
    
      // JSP file name to be rendered on the view mode
      public static final String VIEW_JSP = "OrdersView";         
    
    
      public void init(PortletConfig config) throws PortletException{
        super.init(config);
      }
    
      public void doView(RenderRequest request, RenderResponse response) 
        throws PortletException, IOException {
        // Set the MIME type for the render response
        response.setContentType(request.getResponseContentType());
    
        // Invoke the JSP to render
        PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher( 
          getJspFilePath(request, VIEW_JSP));
        rd.include(request,response);
    
        //this is workaround for portletsession sharing between
        //servlets and portlets
        //see http://weblogs.java.net/blog/wholder/archive/2005/02/session_session.html
        //and http://mail-archives.apache.org/mod_mbox/portals-pluto-dev/200502.mbox/%3Ca
        //2519328f3ba1d1eddfc33c924b6805d@umich.edu%3E
        //
        PortletRequestDispatcher rd2 = getPortletContext().getRequestDispatcher("/dwr/");
        rd2.include(request, response);
    
      }
    
      private static String getJspFilePath(RenderRequest request, String jspFile) {
        String markup = request.getProperty("wps.markup");
        if( markup == null )
          markup = getMarkup(request.getResponseContentType());
        return JSP_FOLDER+markup+"/"+jspFile+"."+getJspExtension(markup);
      }
    	
      private static String getMarkup(String contentType) {
        if( "text/vnd.wap.wml".equals(contentType) )
          return "wml";
        return "html";
      }
    
      private static String getJspExtension(String markupName) {
        return "jsp";
      }
    }
  2. OrdersView.jsp を (interportletmessagingusingajax/jsp/html ディレクトリーに) 作成して開き、リスト 7 のコードを追加します。
    リスト 7. OrdersView.jsp
    <%@ page contentType="text/html" 
    import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %>
    <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
    <portlet:defineObjects/>
    <jsp:include page="javascriptFunctions.jsp" />
    
    <DIV style="margin: 6px">
    
    <H4 style="margin-bottom: 3px">Orders</H4>
    <table cellspacing="0" cellpadding="5" border="1">
    <% db.MockupDB database= db.MockupDB.getInstance();
    
    String[] orders=database.getOrders();
    for(int i=0;i<orders.length;i++)
    {
    %>
    <tr>
    
    <td><%="000000000"+String.valueOf(i+1) %></td>
    <td><a href="" onclick="return <portlet:namespace />sendOrderNr('<%=
      orders[i]%>');"><%=orders[i]%></a></td>
    </tr>
    <%
    }
     %>
    
    </table>
    </DIV>
  3. 2 番目のポートレットは OrderDetailsPortlet.java です。このポートレットではリスト 6 のコードを使って、VIEW_JSP 変数の値を OrdersDetailsPortletView.jsp に変更します。この JSP のコードはリスト 8 にあります。
    リスト 8. OrdersDetailsPortletView.jsp
    <%@ page contentType="text/html" 
    import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %>
    <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
    <portlet:defineObjects/>
    
    <DIV style="margin: 6px">
    
    <H4 style="margin-bottom: 3px">Order details</H4>
    
    <table cellspacing="0" cellpadding="5" border="1">
    <tr>
    <th>Order number</th>
    <th>Order details</th>
    </tr>
    
    <tr>
    <%
    String orderDetailsOrderNumber=(String)renderRequest.getPortletSession().getAttribute(
      "orderDetailsOrderNumber",PortletSession.APPLICATION_SCOPE);
    String orderDetails=(String)renderRequest.getPortletSession().getAttribute(
      "orderDetails",PortletSession.APPLICATION_SCOPE);
    
    if(orderDetailsOrderNumber==null)
    {
    orderDetailsOrderNumber="";
    }
    
    if(orderDetails==null)
    {
    orderDetails="";
    }
    %>
    <td><div id="orderDetailsOrderNumber"><%=orderDetailsOrderNumber%>
    </div></td>
    <td><div id="orderDetails"><%=orderDetails%></div></td>
    </tr>
    
    
    </table>
    </DIV>
  4. 3 番目のポートレットは CustomerDetailsPortlet.java です。このポートレットにはリスト 6 のコードを使用して、VIEW_JSP 変数の値を CustomerDetailsPortletView.jsp に変更します。この JSP のコードはリスト 9 にあります。
    リスト 9. CustomerDetailsPortletView.jsp
    <%@ page contentType="text/html" 
    import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %>
    <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
    <portlet:defineObjects/>
    
    <%
    %>
    
    <DIV style="margin: 6px">
    
    <H4 style="margin-bottom: 3px">Customer details</H4>
    <table cellspacing="0" cellpadding="5" border="1">
    <tr>
    <th>Order number</th>
    <th>Customer details</th>
    </tr>
    
    <tr>
    <%
    String customerDetailsOrderNumber=
      (String)renderRequest.getPortletSession().getAttribute(
      "customerDetailsOrderNumber",PortletSession.APPLICATION_SCOPE);
    String customerDetails=(String)renderRequest.getPortletSession().getAttribute(
      "customerDetails", PortletSession.APPLICATION_SCOPE);
    
    if(customerDetailsOrderNumber==null)
    {
    customerDetailsOrderNumber="";
    }
    
    if(customerDetails==null)
    {
    customerDetails="";
    }
    %>
    <td><div id="customerDetailsOrderNumber"><%=customerDetailsOrderNumber%>
    </div></td>
    <td><div id="customerDetails"><%=customerDetails%></div></td>
    </tr>
    
    </table>
    </DIV>

これでサンプル・アプリケーションの準備ができました。次は、ポートレットを WAR ファイルとしてパッケージにして、Apache Jetspeed ポータルでテストします。

サンプル・アプリケーションをテストする

このセクションでは、サンプル・アプリケーションの動作を確認します。最初に、ポートレット WAR を作成して、Jetspeed ポータルにインストールします。次に、ポートレットをポータルに追加して、動作を確認します。すべてを単一のページにビルドしますが、必要であれば複数のページに配置することができます。その場合も背後のメカニズムは機能します。

ポートレット・アプリケーションを Jetspeed にインストールする

ポートレット WAR ファイルを <Jetspeed install dir>/webapps/jetspeed/WEB-INF/deploy ディレクトリーにコピーして、WAR ファイルを Jetspeed にインストールします。すると、Jetspeed がポートレットを自動的にインストールします。これで、ポートレットを使うための準備ができました。

新規ページを Jetspeed ポータルに追加するには、次のステップを使用します。

  1. Jetspeed ポータルにアクセスして、管理者としてログインします。
  2. 図 2 に示すように、右側の隅にあるEdit アイコンをクリックして、Inter-Portlet Messaging という名前の新規ページを追加します。
    図 2. 新規ページを追加する
    Add new page
    Add new page
  3. Inter-Portlet Messaging ページを選択して、Edit アイコンをクリックします。次に、このページにポートレットを追加するには、Add a portlet アイコンをクリックします。OrdersOrder Details、およびCustomer Details の各ポートレットを選択して、Select portlets をクリックしてポータル・ページにポートレットを追加します。これが完了すると、作成したページは図 3 のようになります。
    図 3. ページ上のポートレット
    Portlets on the page
    Portlets on the page

ポートレット

図 4 に示す Orders ポートレットには、注文がリストされています。

図 4. Orders ポートレット
Orders portlet
Orders portlet

注文番号をクリックすると、他のポートレットに特定の注文の詳細が表示されます。Customer Details ポートレットには、図 5 に示すように顧客情報が表示されます。この情報は MockupDB から取得されます。

図 5. Customer Details ポートレット
Customer Details portlet
Customer Details portlet

また、Order Details ポートレットには、図 6 で確認できるように、MockupDB から取得された情報が表示されます。

図 6. Order Details ポートレット
Order Details portlet
Order Details portlet

必要であれば、前に戻って、ポートレットをさらに別のページに追加することもできます。ポートレットの内容はユーザー・セッションに保管されるため、ポートレットを単一のページに配置する必要はないことがわかると思います。

まとめ

この記事では、Ajax を使ってポートレット間通信を行う方法を紹介しました。Ajax は、対話式の Web ページを開発するための非常に強力な手法です。また、Ajax 対応のポートレットでは、ポータルでよくありがちな、要求から応答までの遅延がなくなることで、ユーザー・エクスペリエンスが大幅に向上します。

この記事のサンプル・コードは、独自のアプリケーションを開発するための開始点として使うことができます。このコードは、DWR がどのようにして、Java プログラミング・モデルを Web ブラウザーでも使えるようにしているかも示しています。DWR を使うと、ブラウザーで JavaBeans を使うことができるかのように感じられます。DWR は、Ajax の詳細のほとんどすべてを見えなくすることで作業を単純化し、ユーザーが Ajax 開発の基本ではなく目先の作業に集中できるようにします。


ダウンロード可能なリソース


関連トピック

  • Mastering Ajax」(Brett McLaughlin 著、developerWorks、2006 年) を読んで、Ajax プログラミングと概念を把握してください。
  • DWR: Easy Ajax for Java を読んで、パッケージの作成者による一連の参考資料を使って DWR を開始してください。
  • Ajax for Java developers: Ajax with Direct Web Remoting」(Philip McCarthy 著、developerWorks、2005 年 11 月) には、DWR を使って JavaBeans メソッドを直接 JavaScript コードに公開し、Ajax の大変な作業を自動化する方法が示されています。
  • Build and test JSR 168-compliant portlets with Apache Pluto (Mark Talbot と Kulvir Singh Bhogal 共著、developerWorks、2006 年 4 月) には、簡単なポートレットをビルド、コンパイル、パッケージ化して、Pluto にデプロイして JSR 168 に準拠しているかどうかテストする方法が示されています。
  • Stefan Hepper 著の「Comparing the JSR 168 Java Portlet Specification with the IBM Portlet API」(developerWorks、2003 年 12 月) を読んで、Java ポートレット開発についてさらに学んでください。
  • Direct Web remoting をダウンロードしてください。これは、この記事で開発したアプリケーションを使用可能にするオープン・ソースの Java ライブラリーです。
  • Apache Jetspeed をダウンロードして、このポータルの詳細を調べてください。
  • Rational Application Developer for WebSphere Software V6.0 で、この開発環境の無料のトライアル・バージョンをダウンロードしてください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, WebSphere, Web development
ArticleID=212081
ArticleTitle=DWR が Ajax によるポートレット間メッセージングを簡単にする
publish-date=07142006