35 Using the Active Data Service with an Asynchronous Backend

This chapter provides information on registering an asynchronous backend to provide real-time data updates to ADF Faces components.

This chapter includes the following sections:

35.1 About the Active Data Service

The Fusion technology stack includes the Active Data Service (ADS), which is a server-side push framework that allows you to provide real-time data updates for ADF Faces components. You bind ADF Faces components to a data source and ADS pushes the data updates to the browser client without requiring the browser client to explicitly request it. For example, you may have a table bound to attributes of an ADF data control whose values change on the server periodically, and you want the updated values to display in the table. You can create a Java bean to implement the ActiveModel interface and register it as an event listener to notify the component of a data event from the backend, and the component rerenders the changed data with the new value highlighted, as shown in Figure 35-1.

Figure 35-1 Table Displays Updated Data as Highlighted

Changed data is shown in blue highlight

35.1.1 Active Data Service Use Cases and Examples

Using ADS is an alternative to using automatic partial page rendering (PPR) to rerender data that changes on the backend as a result of business logic associated with the ADF data control bound to the ADF Faces component. Whereas automatic PPR requires sending a request to the server (typically initiated by the user), ADS enables changed data to be pushed from the data store as the data arrives on the server. Also, in contrast to PPR, ADS makes it possible for the component to rerender only the changed data instead of the entire component. This makes ADS ideal for situations where the application needs to react to data that changes periodically.

To use this functionality, you must configure the application to use ADS. If your application services do not support ADS, then you also need to create a proxy of the service so that the components can display the data as it updates in the source.

Any ADF Faces page can use ADS. However, you can configure only the following ADF Faces components to work with active data:

  • activeCommandToolbarButton

  • activeImage

  • activeOutputText

  • table

    Note:

    Do not use filtering on a table that will be using active data. Once a table is filtered at runtime, active data cannot be displayed. Currently, ADS supports table components with the outputText component contained within a column; other components are not supported inside the table column.

  • tree

  • treeTable

  • DVT graph, gauge, and geographical map components

For details about the active data service framework and important configuration information, see Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

35.2 Process Overview for Using Active Data Service

To use ADS, you can optionally configure your application to determine the method of data transport, as well as other performance options.

Before you begin:

Complete the following tasks:

  • Implement the logic to fire the active data events asynchronously from the data source. For example, this logic might be a business process that updates the database, or a JMS client that gets notified from JMS.

  • The Active Data framework does not support complicated business logic or transformations that require the ADF runtime context, such as a user profile or security. For example, the framework cannot convert an ADF context locale-dependent value and return a locale-specific value. Instead, you need to have your data source handle this before publishing the data change event.

  • Before users can run the ADF Faces page with ADS configured for the application, they must disable the popup blocker for their web browser. Active data is not supported in web browsers that have popup blockers enabled.

To use the Active Data Service:

  1. Optionally, configure ADS to determine the data transport mode, as well as to set other configurations, such as a latency threshold and reconnect information. Configuration for ADS is done in the adf-config.xml file.

    For details about configuring ADS, see Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework.

  2. Create a backing bean that implements the ActiveModel interface and register it as the listener for active data events from your backend.

  3. Create a class that extends the BaseActiveDataModel API to pass the Event object to the ADS framework.

  4. Register a data change listener for data change events from the backend.

  5. In the web page, configure the ADF Faces component to capture and display the pushed data by adding an expression to name the managed bean that implements the the ADF component that you use to capture and display the pushed data.

35.3 Implement the ActiveModel Interface in a Managed Bean

Create a backing bean that contains the active model implementation as its property. This class uses an ADS decorator class to wrap the JSF model. This class should also implement a callback from the backend that will push data into the ADS framework.

You need to create a Java class that subclasses one of the following ADS decorator classes:

  • ActiveCollectionModelDecorator class

  • ActiveDataModelDecorator class (for use with graphs)

  • ActiveGeoMapDataModelDecorator class

  • ActiveGaugeDataModelDecorator class

These classes are wrapper classes that delegate the active data functionality to a default implementation of ActiveDataModel. The ActiveDataModel class listens for data change events and interacts with the Event Manager.

Specifically, when you implement the ActiveModel interface, you accomplish the following:

  • Wraps the JSF model interface. For example, the ActiveCollectionModelDecorator class wraps the CollectionModel class.

  • Generates active data events based on data change events from the data source.

To implement the ActiveModel interface, you need to implement methods on your Java class that gets the model to which the data is being sent and registers itself as the listener of the active data source (as illustrated in Example 35-1):

  1. Create a Java class that extends the decorator class appropriate for your component.

    Example 35-1 shows a StockManager class that extends ActiveCollectionModelDecorator. In this case, the data is displayed for an ADF Faces table component.

  2. Implement the methods of the decorator class that will return the ActiveDataModel class and implement the method that returns the scalar model.

    Example 35-1 shows an implementation of the getCollectionModel() method that registers with an existing asynchronous backend. The method returns the list of stocks collection from the backend.

  3. Implement a method that creates application-specific events that can be used to insert or update data on the active model.

    Example 35-1 shows the onStockUpdate() callback method from the backend, which uses the active model (an instance of ActiveStockModel) to create ActiveDataUpdateEvent objects to push data to the ADF Faces component.

Example 35-1 Extend the Decorator Class

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 our 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();
}

Register the class as a managed bean in the faces-config.xml file. Example 35-2 shows the bean StockManager is registered. Defining the managed bean allows you to specify the managed bean in an expression for the ADF Faces component's value property.

Example 35-2 Register as a Managed Bean

...
<managed-bean>
  <managed-bean-name>stockManager</managed-bean-name>
  <managed-bean-class>
      oracle.afdemo.view.feature.rich.StockManager
  </managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

35.3.1 What You May Need to Know About Read Consistency

Using active data means that your component has two sources of data: the active data feed and the standard data fetch. Because of this, you must make sure your application maintains read consistency.

For example, say your page contains a table and that table has active data enabled. The table has two methods of delivery from which it updates its data: normal table data fetch and active data push. Say the back end data changes from foo to bar to fred. For each of these changes, an active data event is fired. If the table is refreshed before those events hit the browser, the table will display fred because standard data fetch will always get the latest data. But then, because the active data event might take longer, some time after the refresh the data change event would cause foo to arrive at the browser, and so the table would update to display foo instead of fred for a period of time. Therefore, you must implement a way to maintain the read consistency.

To achieve read consistency, the ActiveDataModel has the concept of a change count, which effectively timestamps the data. Both data fetch and active data push need to maintain this changeCount object by monotonically increasing the count, so that if any data returned has a lower changeCount, the active data event can throw it away. Example 35-3 shows how you can use your implementation of the ActiveDataModel class to maintain read consistency.

35.4 Pass the Event Into the Active Data Service

You need to create a class that extends BaseActiveDataModel class to pass the event created by your managed bean. The ActiveDataModel class listens for data change events and interacts with the Event Manager. Specifically, the methods you implement do the following:

  • Optionally, starts and stops the active data and the ActiveDataModel object, and registers and unregisters listeners to the data source.

  • Manages listeners from the Event Manager and pushes active data events to the Event Manager.

Example 35-3 shows the notifyDataChange() method of the model passes the Event object to the ADS framework, by placing the object into the fireActiveDataUpdate() method.

Example 35-3 Pass the Event Object into ADS

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();
}

35.5 Register the Data Update Event Listener

You need to register a data change listener for data change events from the backend. Example 35-4 shows the listener bean StockBackEndSystem is registered in the faces-config.xml file. Note that for this example, expression language is used to inject a listener to the backend.

Example 35-4 Register the Data Update Event Listener

...
<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>

35.6 Configure the ADF Component to Display Active Data

ADF components that display collection-based data can be configured to work with ADS and require no extra setup in the view layer. Once the listener is registered, you can use ADS to stream the data to the view layer. For example, imagine that your JSPX page uses a table component to display stock updates from a backend source on which you register a listener.

Example 35-5 shows the expression language used on the table component value attribute to receive the pushed data.

Example 35-5 Display the Active Data

...
<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>