ページ フローでコントロール コールバックをサポートする方法

WebLogic Workshop バージョン 8.1 では、SP2 以降、コントロール コールバックを受信するカスタム コントロールとページ フローとの対話を行う方法がサポートされていました。カスタム コントロール上のポーリング メソッドを使用することで、受信したコールバック情報をページ フローが要求できるようになっていました。ページ フロー自体はコールバックを受信できませんが、この方法でカスタム コントロールを仲介役にすることにより、ページ フローと、コールバックの送信元である外部サービスとの対話を実現できました。バージョン 10.x では、この機能に対する透過的なサポートが削除されています。この節では、バージョン 8.1 でこのサポートが提供されていた仕組みと、アップグレード後のバージョン 10.x アプリケーションでこの機能を代替し、コールバックとして送信される情報をページ フローで引き続き取得する方法について説明します。

問題 : ポーリング インタフェースとイベント ベースのインタフェースとのギャップ

バージョン 8.1 において、この機能は、ページ フローがサポートするポーリング モデルと、コントロールや Web サービスがサポートするイベント ベースのモデルとの間にあるギャップを橋渡しする 1 つの手段として提供されていました。コールバック対応のコントロールを格納するページ フローおよびカスタム コントロール自体は、いずれも、会話を作成および管理する機能を備えていません。ページ フローにアクセスできるのはブラウザのリクエストを処理する際だけであり、もっと後のタイミングでアクセスする (たとえば、コールバックを受信する) 機能はありません。カスタム コントロールが会話に参加するには、会話をサポートするコンテナの中に格納する必要がありますが、ページ フローはこれに該当するコンテナではありません。バージョン 8.1 SP2 より前には、こうした設計上の制約により、コールバックをクライアント ページ フローに相関することや、コントロールから配信されるコールバックをクライアント ページ フローで処理することはできませんでした。

ところが、ページ フローで会話に参加したりコールバックを受信したりすることは当然できるはずだと誤解する開発者が多く、このギャップが問題視されました。そこで SP2 において、問題に対処するための機能が提供されました。ただし、後述するように、これを実現するために採用された、ドキュメントに記載されていない「舞台裏」でコードを生成する機能は、Workshop バージョン 10.x でサポートされていません。

バージョン 8.1 で提供されていた解決策 : 隠されたコード生成による橋渡し

コールバックを受信するコントロールとページ フローが対話するには、何らかの会話コンテキストによってステートが保持され、クライアントとコールバックを相関する必要がありました。ページ フローおよびカスタム コントロール自体は、いずれも会話をサポートしていません。このように会話による相関をサポートする機能が存在しない問題への対処として、舞台裏でのコード生成という手法が使用されました。具体的には、後述するように、隠された会話形式 Web サービスを WebLogic Workshop で作成するという方法です。この Web サービスは開発時には隠されていますが、デプロイすることで公開されるようになっていました。

以下に、バージョン 8.1 のページ フロー コールバック機能を使用して設計した単純なアプリケーションの例を示します。

アプリケーション コンポーネント

ここで説明する機能に基づいて構築されるのは非常に単純なアプリケーションであり、以下のコンポーネントを含んでいます。

実行時の処理

この単純なアプリケーションの処理フローを以下に説明します。

  1. ページ フローのクライアントが、アプリケーションの機能を開始するためのリクエストを発行する。
  2. ページ フローが、カスタム コントロールのメソッドを呼び出す。ページ フローから見ると、このカスタム コントロールがアプリケーションのもう一方の側にあるコントロールと対話するように見える。
  3. カスタム コントロールが、コールバックを送信するコントロールのメソッドを呼び出す。この呼び出しがインターセプトされ、隠れた Web サービスに送信される。これにより、コントロールのコールバックが相関を行うための会話が開始される。
  4. 隠された Web サービスが、コントロールのメソッド呼び出しをそのコントロール自体に転送する。
  5. コントロールが、仲介役のカスタム コントロールに対してコールバックを送信する。しかし、そのコールバック メソッドが隠された会話形式 Web サービスによってインターセプトされる。
  6. Web サービスが、コールバックをカスタム コントロールに転送する。
  7. カスタム コントロールが、コールバックからデータを受信し、ページ フローによって取得されるように保持する。
  8. ページ フローが、カスタム コントロールに対するポーリングによって、コールバックから受信したデータを取得する。

バージョン 10.x における解決策 : Web サービスを追加することによる会話サポート

アップグレードされたアプリケーションでこの機能を引き続きサポートするには、バージョン 8.1 における自動生成 Web サービスの代わりとなる Web サービスを追加します。その Web サービスは、ステートの保持とコントロール コールバックの相関という役割に関しては、バージョン 8.1 における隠された自動生成 Web サービスと同様です。ただし、アプリケーションの明示的な一部として扱われるため、当然のことながら制御や設計の自由度は非常に大きくなります。

この解決策を実際のアプリケーションのために設計する際は、以下 2 つのうちいずれかの基本アプローチを採用することをお勧めします。

会話処理に関するベスト プラクティス

バージョン 8.1 では、会話処理を開発者に意識させないように、アプリケーションのカスタム コントロールが作成されるタイミングで会話を開始し、ページ フローの HTTP セッションが終了するタイミングで会話を終了するようになっていました。バージョン 10.x のアプリケーションでは、アプリケーションのコードで会話の開始と終了を処理する必要があります。

会話の開始 : 推奨される開始タイミングの 1 つは、カスタム コントロールが作成される時点です。これには、たとえばカスタム コントロールの onCreate 実装の中で開始処理を実行することが考えられます。onCreate はコントロールが作成された後、使用される前の時点で呼び出されるコールバックです。また、それ以外の場所にあるコードで会話を開始することもできます。ただしその場合、最初に会話を終了するコードにおいては、会話 ID が設定されていることを必ず確認する必要があります。

会話の終了 : ページ フローの onDestroy 実装の中で明示的に会話を終了するようにします。onDestroy はページ フローがユーザ セッションから削除されたとき呼び出されるコールバックです。会話を onDestroy の中で終了することは、ユーザのセッションが終了した後に会話がアクティブ状態のまま残されるのを防ぐ「クリーン アップ」の意味で有用です。たとえば、会話を終了する目的のメソッドを Web サービスに設けておき、そのメソッドをページ フロー コントローラの onDestroy ハンドラから呼び出すという方法が考えられます。

また、会話が万一取り残されてしまった場合に備えて、会話に一定の有効期間を設定しておくこともお勧めします。有効期間の値は目的に応じて、アプリケーションが正常に処理を完了できる程度には十分に長く、また、会話が無用に長く続きすぎない程度には短く設定してください。たとえば、1 日という長さは有効期間の例として現実的です。

単純な Web サービスの例

タイマー コントロールからのコールバックを受信し、ページ フロー コントローラによって取得されるようにデータを保持しておく、非常に単純な設計の Web サービスの例を示します。これは、カスタム コントロールを Web サービスで置き換えるという上記の 2 番目のアプローチを採用した例です。カスタム コントロールを流用するアプリケーションの場合、この Web サービスのメソッドは、ページ フローではなくそのコントロールによって呼び出されます。

package services; 

import com.bea.control.TimerControl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;
import org.apache.beehive.controls.api.bean.Control;
import org.apache.beehive.controls.api.events.EventHandler;
import weblogic.jws.Conversation;
import weblogic.jws.Transactional;
import weblogic.jws.WLHttpTransport;
import weblogic.jws.soap.SOAPBinding;
import weblogic.jws.wlw.UseWLW81BindingTypes;
import weblogic.jws.wlw.WLWRollbackOnCheckedException;
import weblogic.jws.Conversational;

@Transactional(true)
@UseWLW81BindingTypes()
@WLHttpTransport(serviceUri = "services/CallbackGenerator.jws")
@WLWRollbackOnCheckedException()
@WebService(serviceName = "CallbackGenerator",
            targetNamespace = "http://www.openuri.org/")
@javax.jws.soap.SOAPBinding(style = javax.jws.soap.SOAPBinding.Style.DOCUMENT, 
                            use = javax.jws.soap.SOAPBinding.Use.LITERAL, 
                            parameterStyle = javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED)
// 会話の有効期間を最長 1 日に設定する
@Conversational(maxAge = "1 day")
public class CallbackWrapperService implements java.io.Serializable
{ 
    // コールバックから受信したデータをこの List に格納し、ページ フロー コントローラによって
    // 読み出されるまで保持する
    private List<Long> events = new ArrayList<Long>();

    @Control()
    @TimerControl.TimerSettings()
    private com.bea.control.TimerControl myTimer;

    static final long serialVersionUID = 1L;

    // コントローラは、このメソッドを呼び出すことで会話を開始し、
    // タイマーへの最初の呼び出しを実行する。In general, this method would
    // コントロールによる最初のメソッド呼び出しに必要なパラメータを取る
    @Conversation(Conversation.Phase.START)
    @SOAPBinding(style = javax.jws.soap.SOAPBinding.Style.DOCUMENT, 
                 use = javax.jws.soap.SOAPBinding.Use.LITERAL, 
                 parameterStyle = javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED)
    @WebMethod()
    @WebResult(name = "startResult")
    public void start(long timerInterval)
    {
        System.out.println("CallbackGenerator.jws started:timerInterval:" +
                           timerInterval);
        myTimer.setTimeout(5);
        myTimer.setRepeatsEvery(timerInterval);
        myTimer.start();
    }

    // タイマーの onTimeout コールバックに対するコールバック ハンドラ。
    // コールバック ハンドラでは、必要なデータを、ページ フロー コントローラが
    // 取得できる形式で収集する
    @EventHandler(field = "myTimer", 
                  eventSet = TimerControl.Callback.class, 
                  eventName = "onTimeout")
    public void myTimer_onTimeout(long time, Serializable data)
    {
        events.add(new Long(time));
    }

    // コントローラは、このメソッドを呼び出すことでコールバックの
    // データを取得する
    @WebMethod()
    @Conversation(Conversation.Phase.CONTINUE)
	public Long[] getEvents()
    {
        Long[] results = (Long[])events.toArray(new Long[events.size()]);
        events.clear();
        return results;
    }

    // コントローラは、コールバック データの取得後、このメソッドを呼び出すことで
    // 会話を終了する
    @Conversation(Conversation.Phase.FINISH)
    @SOAPBinding(style = javax.jws.soap.SOAPBinding.Style.DOCUMENT, 
                 use = javax.jws.soap.SOAPBinding.Use.LITERAL, 
                 parameterStyle = javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED)
    @WebMethod()
    @WebResult(name = "finishResult")
    public void finish()
    {
        myTimer.stop();
    }
}

 


さらにヘルプが必要ですか。質問は Workshop ニュース グループまでお寄せください。