この章では、ADF Facesコンポーネントに対してリアルタイムのデータ更新を行うために、アクティブ・データ・サービス(ADS)に非同期バックエンドを登録する方法を説明します。
この章の内容は次のとおりです。
Fusionテクノロジ・スタックには、アクティブ・データ・サービス(ADS)が含まれ、これはサーバー側のプッシュ・フレームワークで、これによりADF Facesコンポーネントのリアルタイムのデータ更新を提供できます。ADF Facesコンポーネントをデータ・ソースにバインドすると、ADSでは、ブラウザ・クライアントに明示的な更新リクエストを要求することなく、ブラウザ・クライアントにデータ更新をプッシュします。たとえば、ADFデータ・コントロールの属性にバインドされた表があり、その属性値はサーバー上で定期的に変化するため、表には更新された値が表示されるようにします。ユーザーはJava Beanを作成してActiveModel
インタフェースを実装し、それをイベント・リスナーとして登録すると、コンポーネントにバックエンドからのデータ・イベントを通知できます。そのコンポーネントは図37-1に示されるように、新しい値が強調表示された変更済データを再レンダリングします。
ADF FacesコンポーネントにバインドされているADFデータ・コントロールに関連付けられたビジネス・ロジックの結果として、バックエンドで変化するデータをレンダリングするには、自動部分ページ・レンダリング(PPR)のかわりにADSを使用します。自動PPRでは(通常、ユーザーが)サーバーに要求を送信する必要がありますが、ADSは、変更されたデータがサーバーに到着したときに、このデータをデータ・ストアからプッシュできるようにします。また、PPRとは対照的に、ADSにより、コンポーネントはコンポーネント全体ではなく、変更されたデータのみをレンダリングできるようになります。したがって、アプリケーションが、定期的に変化するデータに対応する必要がある場合にはADSが理想的です。
この機能を使用するには、ADSを使用するようにアプリケーションを構築する必要があります。アプリケーション・サービスがADSをサポートしていない場合は、サービスのプロキシを作成して、データがソースで更新されたときに、コンポーネントがこのデータを表示できるようにする必要もあります。
どのADF FacesページでもADSを使用できます。ただし、アクティブ・データを処理するように構成できるのは、次のADF FacesコンポーネントおよびADFデータ視覚化(DVT)コンポーネントのみです。
activeImage
activeOutputText
pivotTable
tree
treeTable
geoMap
(mapPointTheme
のみ)
sunburst
treemap
DVTコンポーネントに対するADSサポートの詳細は、22.2.3項「アクティブ・データ・サポート」を参照してください。
また、アクティブなデータを表示するためにoutputText
コンポーネントまたはsparkChart
が構成されている場合のみ、コレクションベースのコンポーネント(table
、tree
およびpivotTable
など)はADSをサポートすることに注意してください。他のコンポーネントはコレクションベースのコンポーネント内部ではサポートされません。
アクティブ・データ・サービス・フレームワークと構成に関する重要な情報の詳細は、『Oracle Application Development FrameworkによるFusion Webアプリケーションの開発』を参照してください。
ADSを使用するために、必要に応じてアプリケーションを構成して、データ転送の方法やその他のパフォーマンス・オプションを決定することができます。
始める前に:
次のタスクを実行します。
データ・ソースから非同期的にアクティブ・データ・イベントを起動するロジックを実装します。たとえば、このロジックとしては、データベースを更新するビジネス・プロセス、またはJMSから通知を受けるJMSクライアントなどが考えられます。
アクティブ・データ・フレームワークは、ユーザー・プロファイルやセキュリティなどADF実行時コンテキストを必要とする複雑なビジネス・ロジックや変換をサポートしません。たとえば、このフレームワークはADFコンテキストのロケール依存値を変換し、ロケール固有の値を返すことはできません。そのかわりに、ユーザーは、データ変更イベントを公開する前に、データ・ソースにこれを処理させる必要があります。
ユーザーがADF Facesページをアプリケーション用に構成されたADSで実行できるようになるには、その前にWebブラウザのポップアップ・ブロッカを無効にする必要があります。ポップアップ・ブロッカが有効になっているWebブラウザでは、アクティブ・データはサポートされません。
アクティブ・データ・サービスを使用する手順:
必要に応じて、データ転送モードを決定し、待機しきい値や再接続情報など、その他の構成も設定するようにADSを構成します。ADSの構成はadf-config.xml
ファイルで行われます。
ADSの構成の詳細は、『Oracle Application Development FrameworkによるFusion Webアプリケーションの開発』を参照してください。
オプションで、サーブレットのパラメータを構成し、ユーザーの非アクティブによりタイムアウトとなるまでのアクティブ・セッションの継続時間を指定します。クライアント側のサーブレットのタイムアウト・パラメータの構成は、web.xml
ファイルで行われる。
サーブレット・タイムアウト・パラメータの構成の詳細は、『Oracle Application Development FrameworkによるFusion Webアプリケーションの開発』を参照してください。
ActiveModel
インタフェースを実装し、バックエンドからのアクティブ・データ・イベントのリスナーとして登録するバッキングBeanを作成します。
イベント・オブジェクトをADSフレームワークに渡すようにBaseActiveDataModel
APIを拡張するクラスを作成します。
バックエンドからのデータ変更イベントのデータ変更リスナーを登録します。
Webページで、プッシュされたデータを取得および表示するためのADFコンポーネントを実装するマネージドBeanを指定する式を追加して、プッシュされたデータを取得および表示するADF Facesコンポーネントを構成します。
プロパティとしてアクティブ・モデル実装を含むバッキングBeanを作成します。このクラスでは、JSFモデルをラップするのにADSデコレータ・クラスを使用します。このクラスでは、データをADSフレームワークにプッシュするバックエンドからのコールバックも実装されます。
次のADSデコレータ・クラスのいずれかをサブクラスとして持つJavaクラスを作成する必要があります。
ActiveCollectionModelDecorator
クラス
ActiveGeoMapDataModelDecorator
クラス
これらのクラスは、アクティブ・データ機能をActiveDataModel
のデフォルト実装に委譲するラッパー・クラスです。ActiveDataModel
クラスは、データ変更イベントをリッスンし、イベント・マネージャと対話します。
特に、ActiveModel
インタフェースを実装するときには、次のことを完了します。
JSFモデル・インタフェースをラップします。たとえば、ActiveCollectionModelDecorator
クラスはCollectionModel
クラスをラップします。
データ・ソースから、データ変更イベントに基づいてアクティブ・データ・イベントを生成します。
ActiveModel
インタフェースを実装するには、データの送信先に対してモデルを取得し、それ自体をアクティブ・データ・ソースのリスナーとして登録するJavaクラスに、メソッドを実装する必要があります(例37-1を参照)。
コンポーネントに適したデコレータ・クラスを拡張するJavaクラスを作成します。
例37-1に、ActiveCollectionModelDecorator
を拡張するStockManager
クラスを示します。このクラスでは、データがADF Faces table
コンポーネント用に表示されます。
ActiveDataModel
クラスを返すデコレータ・クラスのメソッドを実装し、スカラー・モデルを返すメソッドを実装します。
例37-1に、既存の非同期バックエンドに登録するgetCollectionModel()
メソッドの実装を示しています。メッソッドにより、バックエンドから在庫コレクションのリストが返されます。
アクティブ・モデル上でデータを挿入または更新するために使用できるアプリケーション固有のイベントを作成するメソッドを実装します。
例37-1に示すバックエンドからのonStockUpdate()
コールバック・メソッドでは、アクティブ・モデル(ActiveStockModel
のインスタンス)を使用して、データをADF FacesコンポーネントにプッシュするActiveDataUpdateEvent
オブジェクトを作成します。
例37-1 デコレータ・クラスの拡張
package sample.oracle.ads; import java.util.List; import sample.backend.IBackendListener; import sample.bean.StockBean; import sample.oracle.model.ActiveStockModel; import oracle.adf.view.rich.event.ActiveDataEntry; import oracle.adf.view.rich.event.ActiveDataUpdateEvent; import oracle.adf.view.rich.model.ActiveCollectionModelDecorator; import oracle.adf.view.rich.model.ActiveDataModel; import oracle.adfinternal.view.faces.activedata.ActiveDataEventUtil; import org.apache.myfaces.trinidad.model.CollectionModel; import org.apache.myfaces.trinidad.model.SortableModel; // 1. This example wraps the existing collection model in the page and implements // the ActiveDataModel interface to enable ADS for the page. public StockManager extends ActiveCollectionModelDecorator implements IBackendListener { // 2. Implement methods from ADF ActiveCollectionModelDecorator class to // return the model. @Override public ActiveDataModel getActiveDataModel() { return stockModel; } @Override protected CollectionModel getCollectionModel() { if(collectionModel == null) { // connect to a backend system to get a Collection List<StockBean> stocks = FacesUtil.loadBackEnd().getStocks(); // make the collection become a (Trinidad) CollectionModel collectionModel = new SortableModel(stocks); } return collectionModel; } // 3. Implement a callback method to create active data events and deliver to // the ADS framework. /** * Callback from the backend to push new data to the decorator. * The decorator itself notifies the ADS system that there was a data change. * * @param key the rowKey of the updated Stock * @param updatedStock the updated stock object */ @Override public void onStockUpdate(Integer rowKey, StockBean stock) { ActiveStockModel asm = getActiveStockModel(); // start the preparation for the ADS update asm.prepareDataChange(); // Create an ADS event, using an _internal_ util. // This class is not part of the API ActiveDataUpdateEvent event = ActiveDataEventUtil.buildActiveDataUpdateEvent( ActiveDataEntry.ChangeType.UPDATE, // type asm.getCurrentChangeCount(), // changeCount new Object[] {rowKey}, // rowKey null, //insertKey, null as we don't insert stuff new String[] {"value"}, // attribute/property name that changes new Object[] { stock.getValue()} // the payload for the above attribute ); // Deliver the new Event object to the ADS framework asm.notifyDataChange(event); } /** * Typesafe caller for getActiveDataModel() * @return */ protected ActiveStockModel getActiveStockModel() { return (ActiveStockModel) getActiveDataModel(); } // properties private CollectionModel collectionModel; // see getCollectionModel()... private ActiveStockModel stockModel = new ActiveStockModel(); }
クラスをマネージドBeanとしてfaces-config.xml
ファイルに登録します。例37-2では、StockManager
Beanの登録を示しています。マネージドBeanを定義することにより、ADF Facesコンポーネントの値プロパティの式でそのマネージドBeanを指定できます。
アクティブ・データを使用するということは、コンポーネントに2つのデータ・ソース(アクティブ・データ・フィードと標準データ・フェッチ)があることを意味します。このため、アプリケーションでは読取り一貫性が確保されるようにする必要があります。
たとえば、ページには表が1つ含まれ、この表ではアクティブ・データが有効化されているとします。この表は、データを更新するための配信メソッドとして、標準の表データ・フェッチとアクティブ・データ・プッシュの2種類を持っています。ここで、バック・エンド・データがfoo
からbar
に変更され、さらにfred
に変更されたとしましょう。変更のたびに、アクティブ・データ・イベントが起動されます。これらのイベントがブラウザに到着する前に表をリフレッシュした場合、表にはfred
と表示されます。これは、標準のデータ・フェッチでは、必ず最新のデータが取得されるからです。しかし、アクティブ・データ・イベントに長い時間がかかることもあるため、場合によっては、リフレッシュの後で、このデータ変更イベントの結果、foo
がブラウザに到着し、表が更新されて、しばらくの間、fred
のかわりにfoo
と表示されます。したがって、読取り一貫性を維持する方法を実装する必要があります。
読取り一貫性を実現するために、ActiveDataModel
は、効果的にデータにタイム・スタンプを押す変更カウントの概念を採用しています。データ・フェッチとアクティブ・データ・プッシュのどちらも、このchangeCount
オブジェクトを単調にカウントを増やすことで維持する必要があるため、返されたデータのchangeCount
が現在のカウントより低い場合は、アクティブ・イベントにより廃棄される可能性があります。例37-3は、ActiveDataModel
クラスの実装を使用して、読取り一貫性を維持する方法を示しています。
マネージドBeanで作成されたイベントを渡すために、BaseActiveDataModel
クラスを拡張するクラスを作成する必要があります。ActiveDataModel
クラスは、データ変更イベントをリッスンし、イベント・マネージャと対話します。具体的には、実装するメソッドで次のことを行います。
必要に応じて、アクティブ・データおよびActiveDataModel
オブジェクトを開始および停止し、データ・ソースに対するリスナーの登録および登録取消しを行います。
イベント・マネージャからリスナーを管理し、アクティブ・データ・イベントをイベント・マネージャにプッシュします。
例37-3に、オブジェクトをfireActiveDataUpdate()
メソッドに入れることで、モデルのnotifyDataChange()
メソッドでEvent
オブジェクトをADSフレームワークに渡す方法を示します。
例37-3 ADSへのEventオブジェクト渡し
import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import oracle.adf.view.rich.activedata.BaseActiveDataModel; import oracle.adf.view.rich.event.ActiveDataUpdateEvent; public class ActiveStockModel extends BaseActiveDataModel { // -------------- API from BaseActiveDataModel ---------- @Override protected void startActiveData(Collection<Object> rowKeys, int startChangeCount) { /* We don't do anything here as there is no need for it in this example. * You could use a listenerCount to see if the maximum allowed listerners * are already attached. You could register listeners here. */ } @Override protected void stopActiveData(Collection<Object> rowKeys) { // same as above... no need to disconnect here } @Override public int getCurrentChangeCount() { return changeCounter.get(); } // -------------- Custom API ----------- /** * Increment the change counter. */ public void prepareDataChange() { changeCounter.incrementAndGet(); } /** * Deliver an ActiveDataUpdateEvent object to the ADS framework. * * @param event the ActiveDataUpdateEvent object */ public void notifyDataChange(ActiveDataUpdateEvent event) { // Delegate to internal fireActiveDataUpdate() method. fireActiveDataUpdate(event); } // properties private final AtomicInteger changeCounter = new AtomicInteger(); }
バックエンドからのデータ変更イベントに対するデータ変更リスナーを登録する必要があります。例37-4では、リスナーBeanStockBackEndSystem
がfaces-config.xml
ファイルに登録されます。この例では、リスナーをバックエンドに挿入するために式言語が使用されています。
例37-4 データ更新イベント・リスナーの登録
... <managed-bean> <managed-bean-name>backend</managed-bean-name> <managed-bean-class> oracle.afdemo.backend.StockBackEndSystem </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>listener</property-name> <value>#{stockManager}</value> </managed-property> </managed-bean>
コレクションベースのデータを表示するADFコンポーネントは、ADSと提携するように構成することができます。ビュー・レイヤーでは余分なセットアップは必要ありません。リスナーを登録すると、ADSを使用してデータをビュー・レイヤーに流すことができます。たとえば、JSPXページでtable
コンポーネントを使用して、リスナーを登録しているバックエンド・ソースからの在庫の更新を表示するとします。
例37-5に、プッシュされたデータを受信するためにtable
コンポーネントのvalue
属性で使用される式言語を示します。
例37-5 アクティブ・データの表示
... <f:view> <af:document id="d1"> <af:form id="f1"> <af:panelStretchLayout topHeight="50px" id="psl1"> <f:facet name="top"> <af:outputText value="Oracle ADF Faces goes Push!" id="ot1"/> </f:facet> <f:facet name="center"> <!-- id="af_twocol_left_full_header_splitandstretched" --> <af:decorativeBox theme="dark" id="db2"> <f:facet name="center"> <af:panelSplitter orientation="horizontal" splitterPosition="100" id="ps1"> <f:facet name="first"> <af:outputText value="Some content here." id="menu"/> </f:facet> <f:facet name="second"> <af:decorativeBox theme="medium" id="db1"> <f:facet name="center"> <af:table value="#{stockManager}" var="row" rowBandingInterval="0" id="table1" emptyText="No data..."> <af:column sortable="false" headerText="Name" id="column1"> <af:outputText value="#{row.name}" id="outputText1"/> </af:column> <af:column sortable="false" headerText="Value...." id="column2"> <af:outputText value="#{row.value}" id="outputText2" /> </af:column> </af:table> </f:facet> </af:decorativeBox> </f:facet> </af:panelSplitter> </f:facet> </af:decorativeBox> </f:facet> </af:panelStretchLayout> </af:form> </af:document> </f:view>