39 Using the Active Data Service with an Asynchronous Backend

This chapter describes how to register an asynchronous backend with Active Data Service (ADS) to provide real-time data updates to ADF Faces components.

This chapter includes the following sections:

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

Figure 39-1 Table Displays Updated Data as Highlighted

This image is described in the surrounding text

39.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 and ADF Data Visualization (DVT) components to work with active data:

  • activeImage

  • activeOutputText

  • chart (all types)

  • gauge (all types)

  • pivotTable

  • tree

  • treeTable

  • geoMap (mapPointTheme only)

  • sunburst

  • treemap

For specific information about ADS support for DVT components, see Active Data Support.

Additionally, note that collection-based components (such as table, tree, and pivotTable) support ADS only when the outputText component or sparkChart is configured to display the active data; other components are not supported inside the collection-based component.

For details about the active data service framework and important configuration information, see Developing Fusion Web Applications with Oracle Application Development Framework.

39.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.
  2. Optionally, configure a servlet parameter to specify the duration of the active session before it times out due to user inactivity. Configuration for the client-side servlet timeout parameter is done in the web.xml file.

    For details about configuring the servlet timeout parameter, see Developing Fusion Web Applications with Oracle Application Development Framework.

  3. Create a backing bean that implements the ActiveModel interface and register it as the listener for active data events from your backend.
  4. Create a class that extends the BaseActiveDataModel API to pass the Event object to the ADS framework.
  5. Register a data change listener for data change events from the backend.
  6. 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 ADF component that you use to capture and display the pushed data.

39.3 Implementing 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

  • ActiveGeoMapDataModelDecorator 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 the following example):

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

    The following sample 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.

    The following sample 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.

    The following example 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.

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

Register the class as a managed bean in the faces-config.xml file. The following example 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.

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

39.4 What You May Need to Know About Maintaining 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. For information about how you can use your implementation of the ActiveDataModel class to maintain read consistency, see Passing the Event Into the Active Data Service.

39.5 What You May Need to Know About Navigating Away From the ADS Enabled Page

When the user attempts to close the browser that displays an ADS-enabled page that they are viewing, ADS will stop on the browser native window.unload event. For those applications that want to allow the user to return to the page, the application needs to display a confirmation prompt so the user can cancel the action. In this case, the application should use the ADF client listener beforeunload and not the browser native window.onbeforeunload listener.

For an example of a client listener that can handle the ADF beforeunload event, see How to Use an ADF Client Listener to Control Navigating Away From a JSF Page.

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

The following examples shows the notifyDataChange() method of the model passes the Event object to the ADS framework, by placing the object into the fireActiveDataUpdate() method.

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

39.7 Registering the Data Update Event Listener

You need to register a data change listener for data change events from the backend. The following example 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.

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

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

The following example shows the expression language used on the table component value attribute to receive the pushed 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>