Skip Headers
Oracle® Fusion Applications Developer's Guide
11g Release 5 (11.1.5)

Part Number E15524-10
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
PDF · Mobi · ePub

42 Implementing an Asynchronous Service Initiation with Dynamic UI Update

This chapter describes what to do when initiating asynchronous or long-running functionality from an Oracle ADF UI and, on completion, notifying users of the completion of that process by dynamically updating the UI. This provides a more dynamic experience for the end user and eliminates the need to constantly click a refresh button.

Notes:

When to implement: When initiating asynchronous or long-running functionality from an Oracle ADF UI and, on completion, notifying users of the completion of that process by dynamically updating the UI. This provides a more dynamic experience for the end user and eliminates the need to constantly click a refresh button.

Design Pattern Summary: Oracle ADF UI registers an Active Data control subscriber on top of a JMS queue. The Oracle ADF UI then raises a business event either via the default CRUD operations on the entity or programmatically via Java. This event initiates a BPEL process that performs work and, when completed, invokes a synchronous ADF Business Components service method to trigger pushing the message on the JMS queue, which then causes the Active Data Service control to refresh the component or area of the component.

Involved components:

42.1 Introduction to the Recommended Design Pattern

Asynchronous services cannot be invoked from Java code in Oracle Fusion applications. When notification of completion of asynchronous, long-running functionality is required in a UI, business events can be used for asynchrony. In addition, ADS triggered over JMS will cause the UI update when the BPEL process completes and invokes the ADF Business Components service to signal its completion.

This approach is recommended because supported technology is used. The approach also supports dynamic page updates if the user navigates away and later returns.

42.2 Potential Approaches

Other than the Oracle ADF UI > Event > BPEL > ADF Business Components > ADS approach, following are the potential approaches:

42.3 Example

The following is an example that illustrates the design pattern.

In an order workbench, an end user selects an order and submits it to a scheduling system for fulfillment. The scheduling system services take several seconds to several minutes to acknowledge scheduling and when the user clicks the button to initiate the scheduling process, needs to be notified in the UI upon successful scheduling for fulfillment without the need to repeatedly refresh the page by hand.

In this implementation, entering the UI data and clicking Schedule programmatically raises a business event, initiates a BPEL process which goes through processing and approvals as needed, then finally invokes an order ADF Business Components service to complete the process and publish the JMS message to trigger the ADS UI update.

42.4 How to Implement an Asynchronous Service Initiation with Dynamic UI Update

To enable the UI for dynamic update via ADS, you must first create the ADS handler, which uses a common set of JMS Queue handlers to broker the updates coming from the call to ADF Business Components services.

The main steps are as follows, as shown in Figure 42-1:

  1. A business event is raised to initiate BPEL, which can perform work asynchronously.

  2. The BPEL process updates the database and submits Oracle Enterprise Scheduler jobs.

  3. At the end of the BPEL process, a web service is invoked to publish the message.

  4. The web service publishes the message to JMS.

  5. JMS delivers the message to the ActiveDataCollectionModel.

  6. The ActiveDataCollectionModel decodes the message and updates the UI.

Figure 42-1 Technology Flow Diagram (with Optional Oracle Enterprise Scheduler Job Submission)

Technology Flow Diagram

Prerequisites (for Prototyping Only)

Create the JMS queue in your development environment. Use the prototype common library to build this functionality into your application with minimal changes once the dependent functionality is consumed by the infrastructure.

For prototyping only, take the following steps to set up JMS using Oracle WebLogic Server Console:

Note:

This procedure assumes that the JMS Module does not already exist.

  1. In the Oracle WebLogic Server Console, navigate to Messaging > JMS Modules.

  2. Click New to create the JMS Module.

  3. Name the module "FusionAppsADSJMSModule" and click Next.

  4. In the Targets panel, choose the AdminServer target and click Next.

  5. Choose "Would you like to add resources to this JMS system module?" and click Finish.

  6. In the Summary of Resources table, click New.

  7. Choose Connection Factory and click Next.

  8. Name the connection factory FusionAppsADSJMSConnectionFactory, provide the JNDI name jms/Prototype/MyQueueConnFactory, and click Next.

  9. Click Finish.

  10. Verify the new connection factory in the Summary of Resources table and click New.

  11. Choose Queue and click Next.

  12. Name the queue FusionAppsADSJMSQueue, provide the JNDI name jms/Prototype/MyQueue and click Finish.

42.4.1 Writing the Active Data Handler

The classes shown in Example 42-1, Example 42-2, and Example 42-3 are common to every implementation of this pattern, and are responsible for handling JMS integration with ADS supported events.

Example 42-1 DemoDataChangeEntry.java

package ads.demo.common;

import java.io.Serializable;

public class DemoDataChangeEntry implements Serializable {
    public enum ChangeType
    {
        /**
         * Indicates the change is row value updates
         */
        UPDATE,

        /**
         * Indicates the change is a new row insertion
         */
        INSERT,

        /**
         * Indicates the change is a new row insertion before a row
         */
        INSERT_BEFORE,

        /**
         * Indicates the change is a new row insertion after a row
         */
        INSERT_AFTER,

        /**
         * Indicates the change is a new row insertion inside a parent
         */
        INSERT_INSIDE,

        /**
         * Indicates the change is row deletion
         */
        REMOVE,

        /**
         * Indicates the change is range refresh
         */
        REFRESH
    }
    public DemoDataChangeEntry() {
       super();
    }

    public DemoDataChangeEntry(Object[] pk, ChangeType type,
                             String[] attributes, Object[] values) {
         _pk = pk;
         _type = type;
         _attributes = attributes;
         _values = values;
    }
   
    private Object[] _pk;
    private ChangeType _type;
    private String[] _attributes;
    private Object[] _values;
 
    public Object[] getPk() {
        return _pk;
    }
 
    public ChangeType getType() {
        return _type;
    }
 
    public String[] getAttributes() {
        return _attributes;
    }
 
    public Object[] getValues() {
        return _values;
    }
 
    public void setPk(Object[] _pk) {
        this._pk = _pk;
    }
 
    public void setType(ChangeType _type) {
        this._type = _type;
    }

    public void setAttributes(String[] _attributes) {
        this._attributes = _attributes;
    }
 
    public void setValues(Object[] _values) {
        this._values = _values;
    }
}

Example 42-2 DemoDataUpdateEvent.java

package ads.demo.common;
 
import java.io.Serializable;
 
import java.util.List;
 
public class DemoDataUpdateEvent implements Serializable {
    public DemoDataUpdateEvent() {
    }
   
    public DemoDataUpdateEvent(List<DemoDataChangeEntry> entries) {
        _entries = entries;
    }
    private List<DemoDataChangeEntry> _entries;
 
    public List<DemoDataChangeEntry> getEntries() {
        return _entries;
    }
 
    public void setEntries(List<DemoDataChangeEntry> _entries) {
        this._entries = _entries;
    }
}

Example 42-3 JMSHelper.java

package ads.demo.common;
 
import java.util.Hashtable;
 
 
import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
 
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
 
import javax.jms.Session;
 
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
 
public final class JMSHelper {
    private  static JMSHelper _instance = new JMSHelper();
    public static JMSHelper getInstance() {
        return _instance;
    }
   
    public ObjectMessage createObjectMessage() throws JMSException {
            return qsession.createObjectMessage();
    }
   
    public void sendMessage(ObjectMessage message) throws JMSException {
            qsender.send(message);
    }
   
    public QueueReceiver createQueueReceiver(MessageListener listener, 
      String messageFilter) throws JMSException {
        QueueReceiver qreceiver = qsession.createReceiver(queue, messageFilter);
        qreceiver.setMessageListener(listener);
        return qreceiver;       
    }
   
    private JMSHelper() {
        try {
            init();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
       
    // Defines the JMS context factory.
    private final static String JMS_QUEUE_FACTORY=
     "jms/Prototype/MyQueueConnFactory";
 
    // Defines the queue.
    private final static String ADS_QUEUE="jms/Prototype/MyQueue";
 
    private QueueConnectionFactory qconFactory;
    private QueueConnection qcon;
    private QueueSession qsession;
    private QueueSender qsender;
    private Queue queue;
 
    /**
     * Creates all the necessary objects for sending
     * messages to a JMS queue.
     *
     * @param ctx JNDI initial context
     * @param queueName name of queue
     * @exception NamingException if operation cannot be performed
     * @exception JMSException if JMS fails to initialize due to internal error
     */
    private void init()
      throws NamingException, JMSException
    {
      InitialContext ctx = new InitialContext();
      qconFactory = (QueueConnectionFactory) ctx.lookup(JMS_QUEUE_FACTORY);
      qcon = qconFactory.createQueueConnection();
      qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      queue = (Queue) ctx.lookup(ADS_QUEUE);
      qsender = qsession.createSender(queue);
      qcon.start();
    }
 
    /**
     * Closes JMS objects.
     * @exception JMSException if JMS fails to close objects due to internal error
     */
    private void close() throws JMSException {
      qsender.close();
      qsession.close();
      qcon.close();
    }
 
}

To implement the Active Data Collection Model:

The Active Data Collection Model, driven by the ADS infrastructure, manages the messages coming from the queue and propagates them to the UI as Oracle ADF Rich Events. Implement the Active Data Collection Model by extending the CollectionModel class in the org.apache.myfaces.trinidad.model package and overriding the startActiveData, stopActiveData and onMessage methods. The class must implement ActiveDataModel and MessageListener as the onMessage method accepts JMS messages (which is a list of update events) and runs them through the active data listener.

Note:

Instead of implementing all the logic for CollectionModel, delegate to the collection model returned by the tree binding.

What you need to know before you begin:

  • The following methods must be implemented for ActiveDataModel:

    • getActiveDataPolicy() always returns ActiveDataPolicy.ACTIVE;

    • startActiveData(Collection<Object> rowKeys, int startChangeCount, ActiveDataListener listener) is where you create a queue receiver of the topic subscriber in JMS. If you are not using JMS, this is where you register yourself with the event source as listener.

    • stopActiveDate(Collection<Object> rowKeys, ActiveDataListener listener) removes the queue receiver of the topic subscriber in JMS.

    • getCurrentChangeCount(): ADS expects the events to arrive in order. Keep a counter in the JavaBean, so that the counter increments when a new event is pushed.

  • For ActiveDataCollectionModel to be the queue receiver or topic subscriber, ActiveDataCollectionModel must implement the MessageListener interface using the onMessage method. Do the following:

    1. Get the payload from the message. It should be DataUpdateEvent.

    2. Convert DataUpdateEvent to ActiveDataEvent. so that ADS can process the event.

    3. Deliver ActiveDataEvent to ADS.

Example 42-4 shows a collection model returned by a tree binding.

Example 42-4 Collection Model Returned by Tree Binding

package ads.demo.view;
import ads.demo.common.DemoDataChangeEntry;
import ads.demo.common.DemoDataUpdateEvent;
import ads.demo.common.JMSHelper;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.QueueReceiver;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.model.binding.DCIteratorBinding;
import oracle.adf.share.ADFContext;
import oracle.adf.view.rich.event.ActiveDataEntry;
import oracle.adf.view.rich.event.ActiveDataListener;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;
import oracle.adf.view.rich.model.ActiveDataModel;
import oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding;
import oracle.jbo.Key;
import org.apache.myfaces.trinidad.model.CollectionModel;
...

public class ActiveDataCollectionModel extends CollectionModel implements ActiveDataModel,
...
    public void startActiveData(Collection<Object> rowKeys,
                                int startChangeCount,
                                ActiveDataListener listener) {
        _listeners.add(listener);
        _currEventId = startChangeCount;
        if (_listeners.size() == 1) {
            // register as receiver for JMS Queue, listening to change event
            try {
                String messageFilter = "JMSCorrelationID = '" + getUuid() + "'";
                qreceiver = JMSHelper.getInstance().createQueueReceiver(this,
                            messageFilter);
            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }
    public void stopActiveData(Collection<Object> rowKeys,
                               ActiveDataListener listener) {
        _listeners.remove(listener);
        if (_listeners.isEmpty()) {
            // clean JMS
            try {
                qreceiver.close();
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
    public int getCurrentChangeCount() {
        return _currEventId;
    }
    public void onMessage(Message message) {
        try {
            DemoDataUpdateEvent myEvent = null;
            if (message instanceof ObjectMessage) {
                myEvent =
                        (DemoDataUpdateEvent)((ObjectMessage)message).getObject();
                // Convert the event to ADS DataChangeEvent
            }
            List<ActiveDataEntry> dces = new ArrayList<ActiveDataEntry>(1);
            for (DemoDataChangeEntry entry : myEvent.getEntries()) {
                oracle.jbo.Key jboKey = new Key(entry.getPk());
                ActiveDataEntry.ChangeType newType = convertChangeType(entry.getType());
                Object[] path = new Object[] { Collections.singletonList(jboKey) };
                ActiveDataEntry dce =
                    new DemoActiveDataEntry(newType, path,
                                            new Object[0],
                                            entry.getAttributes(),
                                            entry.getValues());
                dces.add(dce);
            }
            _currEventId++;
            ActiveDataUpdateEvent event = new DemoActiveDataUpdateEvent(new Object(), _currEventId, 
                                              dces);
            _refreshControl = true;
           
            // Deliver event
            for (ActiveDataListener listener : _listeners) {
                try {
                    listener.dataChanged(event);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private int _currEventId = 0;
    private final List<ActiveDataListener> _listeners =
        new LinkedList<ActiveDataListener>();
    private boolean _refreshControl = false;
    private String _treeBindingName;
    private String _iterBindingName;
    private ActiveDataEntry.ChangeType         convertChangeType(DemoDataChangeEntry.ChangeType
        type) {
        if (type == DemoDataChangeEntry.ChangeType.UPDATE) {
            return ActiveDataEntry.ChangeType.UPDATE;
        } else if (type == DemoDataChangeEntry.ChangeType.REFRESH) {
            return ActiveDataEntry.ChangeType.REFRESH;
        } else {
            return ActiveDataEntry.ChangeType.UPDATE;
        }
       
        // Return ActiveDataEntry.ChangeType.UPDATE;
    }
    private CollectionModel getModel() {
        CollectionModel cm =
            (CollectionModel)ADFContext.getCurrent().getRequestScope().get("collectionModel_" +
                                                                           this.hashCode());
        DCBindingContainer bindings =
            (DCBindingContainer)ADFContext.getCurrent().getRequestScope().get("bindings");
        if (_refreshControl) {
            DCIteratorBinding iterBinding =
                bindings.findIteratorBinding(_iterBindingName);
            iterBinding.executeQuery();
            _refreshControl = false;
        }
        if (cm == null) {
            FacesCtrlHierBinding hierBinding =
                (FacesCtrlHierBinding)bindings.findCtrlBinding(_treeBindingName);
            cm = hierBinding.getCollectionModel();
            ADFContext.getCurrent().getRequestScope().put("collectionModel_" +
                                                          this.hashCode(), cm);
        }
        return cm;
    }
...

There are two reasons for implementing the getModel() method this way:

  • It is necessary to delegate all collection model-related logic to the model returned by the tree binding. Inside the collection handler, you must get a handle to the collection model returned by the tree binding by looking up the binding container. As you reference the collection model often, store it somewhere for optimal performance. Make sure the managed JavaBean has a view scope while the binding container, tree binding or collection model has a request scope. You cannot store the collection model on the JavaBean. Instead, store the collection model in the request scope. When accessing the collection model, look it up first in the request scope. If the value is null—for example, at the beginning of the request—retrieve the value from the binding container.

  • When pushing the ActiveDataEvent through ADS, only the UI is updated with the new value. The binding layer is not aware that the underlying data source has changed. If the page is refreshed at this time, the UI displays the old data from the binding layer. A workaround is to keep a refreshBinding flag on the ActiveDataCollectionModel to indicate whether the binding requires refreshing. The flag is initially set to false. When an event is received, the flag is set to true. When getting the collection model, check for this flag first. If the flag is set to true, programmatically refresh the related binding before returning the collection model. Example 42-5 shows sample ActiveDataCollectionHandler code.

    Example 42-5 From the ActiveDataCollectionHandler Code

    private CollectionModel getModel() {
            CollectionModel cm =
                (CollectionModel)ADFContext.getCurrent().getRequestScope().get("collectionModel_" +
                                                                               this.hashCode());
            DCBindingContainer bindings =
                (DCBindingContainer)ADFContext.getCurrent().getRequestScope().get("bindings");
            if (_refreshControl) {
                DCIteratorBinding iterBinding =
                    bindings.findIteratorBinding(_iterBindingName);
                iterBinding.executeQuery();
                _refreshControl = false;
            }
            if (cm == null) {
                FacesCtrlHierBinding hierBinding =
                    (FacesCtrlHierBinding)bindings.findCtrlBinding(_treeBindingName);
                cm = hierBinding.getCollectionModel();
                ADFContext.getCurrent().getRequestScope().put("collectionModel_" +
                                                              this.hashCode(), cm);
                System.out.println("CollectionModel: " + cm.hashCode());
            }
            return cm;
        }
    

42.4.2 Building the Supporting Active Data Entry Classes

The ActiveDataCollectionHandler uses Oracle ADF Rich Events to propagate the data updates and UI refresh in response to JMS queue updates. You must implement these event classes and register them as events from the CollectionHandler.

To create the Active Data Entry implementation:

The class shown in Example 42-6 extends the Oracle ADF class oracle.adf.view.rich.event.ActiveDataEntry and implements several methods in that interface.

Example 42-6 Active Data Entry Class

package ads.demo.view;
 
import java.util.HashMap;
import java.util.Map;
 
import oracle.adf.view.rich.event.ActiveDataEntry;
 
public class DemoActiveDataEntry extends ActiveDataEntry {
    public DemoActiveDataEntry(ActiveDataEntry.ChangeType change,
                               Object[] path, Object[] insertKeyPath,
                               String[] names, Object[] values) {
        super();
 
        if (names != null) {
            for (int i = 0; i < names.length; i++) {
                String attribute = names[i];
                Object value = values[i];
                _valuesMap.put(attribute, value);
            }
        }
 
        _attributes = names;
        _values = values;
        _changeType = change;
        _path = path;
        _insertPath = insertKeyPath;
 
    }
 
    public ActiveDataEntry.ChangeType getChangeType() {
        return _changeType;
    }
 
    public Object[] getKeyPath() {
        return _path;
    }
 
    public Object[] getInsertKeyPath() {
        return _insertPath;
    }
 
    public String[] getAttributeNames() {
        return _attributes;
    }
 
    public Object getAttributeValue(String name)
    {
      return _valuesMap.get(name);
    }
 
    public Object getFormattedAttributeValue(String name)
    {
      return getAttributeValue(name);
    }
 
    private final Map<String, Object> _valuesMap =
        new HashMap<String, Object>();
    private String[] _attributes = null;
    private Object[] _values = null;
    private ChangeType _changeType = null;
    private Object[] _path = null;
    private Object[] _insertPath = null;
 
}

To implement the Active Data Update Event:

The Active Data update event takes a list of Active Data entry events and performs them at once. The class extends from oracle.adf.view.rich.event.ActiveDataUpdateEvent and implements several methods, as shown in Example 42-7.

Example 42-7 Active Data Update Event

package ads.demo.view;
 
import java.util.Collections;
import java.util.List;
 
import oracle.adf.view.rich.event.ActiveDataEntry;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;
 
public class DemoActiveDataUpdateEvent extends ActiveDataUpdateEvent {
    public DemoActiveDataUpdateEvent(Object object) {
        super(object);
    }
 
    public DemoActiveDataUpdateEvent(Object source, int eventId,
                               List<ActiveDataEntry> changeList)
    {
      super(source);
 
      _changeList = changeList;
      _eventId = eventId;
    }
 
    /**
    * Get the change list of this event
    *
    * @return the change list of this event
    */
    public List<ActiveDataEntry> getChangeList()
    {
      return _changeList;
    }
 
    /**
    * Get the event ID
    * Return the event ID
    */
    public int getEventId()
    {
      return _eventId;
    }
 
    public long getEventTime()
    {
      return System.currentTimeMillis();
    }
 
    public String toString()
    {
      return super.toString() + " eventId:" + _eventId + " changeList:" + _changeList;
    }
 
    private List<ActiveDataEntry> _changeList =  Collections.emptyList();
    private int _eventId = 0;
}

42.4.3 Registering the Active Data Collection Model with the Oracle ADF UI Page

In order to enable the active data feature and "hook" your collection model, you need to register the class as a managed JavaBean.

ADS requires UI components to have the same model across requests. Therefore, register the ActiveDataCollectionModel as a view scoped managed JavaBean. As long as you stay on the same page, the table is based on the same model.

To register your collection model as a managed JavaBean:

  1. Open adfc-config.xml.

  2. Add the managed JavaBean named adsBean and provide the package to your collection model class, as shown in Example 42-8.

Example 42-8 adsBean Managed JavaBean

<managed-bean>
  <managed-bean-name>adsBean</managed-bean-name>
  <managed-bean-class>ads.demo.view.ActiveDataCollectionModel</managed-bean-class>
  <managed-bean-scope>view</managed-bean-scope>
  <managed-property>
      <property-name>treeBindingName</property-name>
      <property-class>java.lang.String</property-class>
      <value>EmpView1</value>
  </managed-property>
  <managed-property>
      <property-name>iterBindingName</property-name>
      <property-class>java.lang.String</property-class>
      <value>EmpView1Iterator</value>
  </managed-property>
</managed-bean>

42.4.4 Registering the Component Managed JavaBean for Supporting Method Actions

To trigger the synchronous functionality of the use case pattern, raise a business event in response to the click of an Oracle ADF button. In order to support a response to the click of a button, create a managed JavaBean with which you can associate methods as the action for these buttons.

To build your Oracle ADF component managed JavaBean:

In the prototype use case, there is a table that contains a list of employees and their entity object attributes. Add two buttons at the top of the table in a panel collection toolbar which, when clicked, uses the selected employee to initiate an approval process. When completed, the approval process dynamically updates the table, as shown in Figure 42-2.

Figure 42-2 Dynamically Updated Table

Sample UI

The table component requires the managed JavaBean shown in Example 42-9.

Example 42-9 Table Component Managed JavaBean

package ads.demo.view;
 
import java.util.ArrayList;
 
import java.util.Collection;
 
import java.util.Map;
 
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
 
import oracle.adf.model.OperationBinding;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.share.ADFContext;
import oracle.adf.view.rich.component.rich.data.RichTable;
 
import oracle.jbo.Key;
 
import org.apache.myfaces.trinidad.model.RowKeySet;
 
public class TableHandlerBean {
    private RichTable _table;
 
    public TableHandlerBean() {
        super();
    }
 
    public void setTable(RichTable _table) {
        this._table = _table;
    }
 
    public RichTable getTable() {
        return _table;
    }
 
    public void handleRaise(ActionEvent event) {
        String correlationId =        ((ActiveDataCollectionModel)ADFContext.getCurrent().getViewScope().get("adsBean")).getUuid();
        RowKeySet selectedRowKeys = getTable().getSelectedRowKeys();
        ArrayList<String> selectedEmp = new ArrayList<String>(selectedRowKeys.size());
        for (Object rowKey : selectedRowKeys) {
            Key jboKey = ((Collection<Key>)rowKey).iterator().next();
            String rowKeyString = ((Integer)jboKey.getKeyValues()[0]).toString();
            selectedEmp.add(rowKeyString);
            // Publish event
            try {
                DCBindingContainer bindings =                (DCBindingContainer)ADFContext.getCurrent().getRequestScope().get("bindings");
                OperationBinding action =                (OperationBinding)bindings.findCtrlBinding("publishEvent");
                Map params = action.getParamsMap();
                params.put("correlationId", correlationId);
                params.put("key", rowKeyString);
                params.put("eventType", "payRaise");
                action.execute();
                // addConfirmationMessage();
            } catch ( Exception e ) {
                log.severe("ASM: Failed to raise commission event for key: " + rowKeyString);
            } // try
        }
 
        // Invoke BPEL from here.
    }
 
    public void handleCommission(ActionEvent event) {
        String correlationId =       ((ActiveDataCollectionModel)ADFContext.getCurrent().getViewScope().get("adsBean")).getUuid();
        RowKeySet selectedRowKeys = getTable().getSelectedRowKeys();
        ArrayList<String> selectedEmp = new ArrayList<String>(selectedRowKeys.size());
        for (Object rowKey : selectedRowKeys) {
            Key jboKey = ((Collection<Key>)rowKey).iterator().next();
            String rowKeyString = ((Integer)jboKey.getKeyValues()[0]).toString();
            selectedEmp.add(rowKeyString);
            // Publish event
            try {
                DCBindingContainer bindings =                (DCBindingContainer)ADFContext.getCurrent().getRequestScope().get("bindings");
                OperationBinding action =                (OperationBinding)bindings.findCtrlBinding("publishEvent");
                Map params = action.getParamsMap();
                params.put("correlationId", correlationId);
                params.put("key", rowKeyString);
                params.put("eventType", "payCommission");
                action.execute();
                // addConfirmationMessage();
            } catch ( Exception e ) {
                log.severe("ASM: Failed to raise commission event for key: " + rowKeyString);
            } // try
        }
        // Invoke BPEL from here.
 
 
    private void addConfirmationMessage() {
        FacesMessage msg = new FacesMessage("You request is submitted for approval.");
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }
}

To register the component managed JavaBean:

As with the collection model, register the component managed JavaBean by adding an entry to adfc-config.xml, as shown in Example 42-10.

Example 42-10 adfc-config.xml Registration Code

<managed-bean>
    <managed-bean-name>tableBean</managed-bean-name>
    <managed-bean-class>ads.demo.view.TableHandlerBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

42.4.5 Referencing the Managed JavaBean in the Page UI

Modify the page component to reference the managed JavaBean from the earlier steps, as shown in Example 42-11.

Note:

You may notice that selectedRowKeys is not bound to any method. By default, it is bound to #{bindings.treeBinding.collectionModel.selectedRowKeys}. It will no longer work after using ActiveDataCollectionModel.

Example 42-11 Referencing the Managed JavaBean

<af:table value="#{viewScope.adsBean}" var="row"
  rows="#{bindings.EmpView1.rangeSize}"
  fetchSize="#{bindings.EmpView1.rangeSize}"
 
  rowBandingInterval="0"
 
  filterModel="#{bindings.EmpView1Query.queryDescriptor}"
 
  queryListener="#{bindings.EmpView1Query.processQuery}"
  filterVisible="true" varStatus="vs"
  selectionListener="#{bindings.EmpView1.collectionModel.makeCurrent}"
  rowSelection="multiple" id="t1" width="100%"
  binding="#{tableBean.table}">

42.4.6 Creating the Data Model and Adding Application Module Methods

The data model should exist before the page is built in order to simplify laying out the components required to display the data contained in that model. The application module needs additional methods to support incoming service methods and, optionally, the methods for raising the business event.

For more information about creating a data model with application modules, see the chapter "Implementing Business Services with Application Modules" in Oracle Fusion Middleware Fusion Developer's Guide for Oracle Application Development Framework (Oracle Fusion Applications Edition).

.

To extend the methods of your application module for the service interface:

Make sure to expose one or more application module methods in the application module service. This facilitates the callback from BPEL upon completion of the process, triggering the ADS UI update. These methods publish the message to the JMS queue following the message structure shown here.

The message payload should take the format of DataUpdateEvent, which comprises one or more DataChangeEntry items.

  • changeType: enum (UPDATE, REFRESH). Currently, there is no use case for INSERT.

  • key: Object[]

  • insertKey: Object[]

  • attributeNames: String[], a list of names of changed attributes

  • attributeValues: Object[], a list of new values for the changed attributes

In this pattern, payRaise and payCommision are supported for one or more selected employees. Use methods with simple string interfaces invoked by BPEL to complete the payRaise or payCommision event for each particular employee. Call the sendMessage method to publish the JMS message to notify ADS of the UI update. Sample BPEL methods are shown in Example 42-12.

Example 42-12 BPEL Methods

// Simplified interface method for service call per employee  
 
  public void performSingleRaise(String correlationId, String key) {
        ArrayList thelist = new ArrayList<String>();
        thelist.add(key);
        performRaise(correlationId, thelist);
    } //
 
 
  // List interface for call from UI and by Simplified Service Method
    public void performRaise(String correlationId, List<String> keyValues) {
        List<DemoDataChangeEntry> dces =
            new ArrayList<DemoDataChangeEntry>(keyValues.size());
        ViewObject empVO = getEmpView1();
        for (String keyVal : keyValues) {
            Key key = new Key(new Object[] { keyVal });
            Row row = empVO.findByKey(key, 1)[0];
            BigDecimal newSal = new
                BigDecimal(Math.round(((BigDecimal)row.getAttribute("Sal")).doubleValue()*(1+(new
                Random()).nextDouble()/10)));
            row.setAttribute("Sal", newSal);
            DemoDataChangeEntry dce =
                new DemoDataChangeEntry(new Object[] { new Integer(keyVal) },
                                        DemoDataChangeEntry.ChangeType.UPDATE,
                                        new String[] { "Sal" },
                                        new Object[] { newSal.toString() });
            dces.add(dce);
        }
        this.getDBTransaction().commit();
 
        DemoDataUpdateEvent event = new DemoDataUpdateEvent(dces);
        // Send a message
        sendMessage(correlationId, event);
    }
 
    // Simplified interface for Service method
 
    public void paySingleCommission(String correlationId, String key) {
        ArrayList<String> thelist = new ArrayList<String>();
        thelist.add(key);
        payCommission(correlationId, thelist);
    }
 
 
// List interface for calling from UI and by Simplified Service Method
 
public void payCommission(String correlationId, List<String> keyValues) {
        List<DemoDataChangeEntry> dces =
            new ArrayList<DemoDataChangeEntry>(keyValues.size());
        ViewObject empVO = getEmpView1();
        for (String keyVal : keyValues) {
            Key key = new Key(new Object[] { keyVal });
            Row row = empVO.findByKey(key, 1)[0];
 
            BigDecimal newComm = new BigDecimal((new Random()).nextInt(10000));
            row.setAttribute("Comm", newComm);
            DemoDataChangeEntry dce =
                new DemoDataChangeEntry(new Object[] { new Integer(keyVal) },
                                        DemoDataChangeEntry.ChangeType.REFRESH,
                                        new String[] { "Comm" },
                                        new Object[] { newComm.toString() });
            dces.add(dce);
        }
        this.getDBTransaction().commit();
 
        DemoDataUpdateEvent event = new DemoDataUpdateEvent(dces);
        // send message
        sendMessage(correlationId, event);
    }
 
 
// Private method to push ADS update to JMS queue
 
private void sendMessage(String correlationId, DemoDataUpdateEvent event) {
        try {
            JMSHelper helper = JMSHelper.getInstance();
            ObjectMessage message = helper.createObjectMessage();
            message.setObject(event);
            message.setJMSCorrelationID(correlationId);
            helper.sendMessage(message);
        } catch (JMSException e) {
            e.printStackTrace();
        } // try
    } // sendMessage
 

To define structure and compose event metadata:

The code that programmatically creates business event payloads and raises them through the business event APIs should be deliberately built around the namespace and event attributes defined in the appropriate EDL and XSD files.

For this pattern, a single event is used that supports multiple event types through an attribute value such as payRaise and payComission. However, support for additional event types only requires adding the UI facet, the programmatic method to raise that new event type and a conditional branch in BPEL. If the pattern requires completely separate event definitions, the code becomes more complex, the number of managed metadata source files increases, and the composite becomes more complex as well.

While this is a simpler approach, it is not as flexible from an integration perspective. Define your event types such that they support your current use case and potentially support additional integration in the future. Example 42-13 shows a simplified event definition, while Example 42-14 shows an event schema definition.

Example 42-13 Simplified Event Definition

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://schemas.oracle.com/events/edl"
             targetNamespace="http://xmlns.oracle.com/apps/ta/adsdemo/events/edl">
  <schema-import namespace="http://xmlns.oracle.com/apps/ta/adsdemo/events/schema"
             location="xsd/ADSDemoEventSchema.xsd"/>
  <event-definition name="ADSDemoEvent">
    <content xmlns:ns0="http://xmlns.oracle.com/apps/ta/adsdemo/events/schema"
             element="ns0:ADSDemoEventElement"/>
  </event-definition>
</definitions>

Example 42-14 Event Schema Definition

<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns="http://xmlns.oracle.com/apps/ta/adsdemo/events/schema"
            targetNamespace="http://xmlns.oracle.com/apps/ta/adsdemo/events/schema"
                   attributeFormDefault="unqualified"
            elementFormDefault="qualified">
  <xsd:element name="ADSDemoEventElement" type="ADSDemoEventElementType"/>
  <xsd:complexType name="ADSDemoEventElementType">
      <xsd:sequence>
        <xsd:element name="correlationId" type="xsd:long"/>
        <xsd:element name="key" type="xsd:long"/>
        <xsd:element name="eventType" type="xsd:string"/>
      </xsd:sequence>
</xsd:complexType>
 
</xsd:schema>

To extend the application module with publishEvent and supporting methods:

In the page bindings, add the method publishEvent that binds to the application module method of the same name. Use this binding in the handleRaise and handleCommission methods of the TableHandlerBean to publish the event for each employee to be updated.

For more information about extending the application module with the publishEvent method, see Section 32.5.1, "Using the Java Event API to Publish Events."

Note:

It is critical that the event name and namespace are consistent throughout the code and metadata definitions in the subscribing SOA composite.

42.4.7 Creating a SOA Composite that Subscribes to the Published Event

The creation of a SOA composite that subscribes to an event is covered in Section 32, "Initiating a SOA Composite from an Oracle ADF Web Application." A sample pattern composite is shown in Figure 42-3.

Figure 42-3 Pattern Composite

Prototype Composite

42.4.8 Constructing a BPEL Process to Perform Asynchronous Work

The creation of a BPEL process and human task activities is described in other sections. For more information, see Chapter 38, "Managing Tasks from an Oracle ADF Application."

A sample BPEL process is shown in Figure 42-4.

Figure 42-4 BPEL Flow

BPEL Flow

42.4.9 Invoking the ADF Business Components Service

Invoking an ADF Business Components service from a BPEL process is covered in another section. For more information, see Chapter 34, "Orchestrating ADF Business Components Services."

42.5 Securing the Design Pattern

The process of securing this design pattern is the same as that of securing an Oracle ADF UI application.

For more information, see Chapter 50, "Securing Web Services Use Cases."

42.6 Verifying the Deployment

Do the following to test functionality:

  1. Turn on the EDN-DB-LOG page by navigating to http://host:port/soa-infra/events/edn-db-log and ensure it reads "Log is Enabled." If it is not, click Enable.

  2. Open the UI page and interact with the UI components that you designed to trigger the event.

    The event should immediately display in the EDN-DB-LOG page.

  3. Check for the event payload shown in Example 42-15.

    Example 42-15 Event Payload

    Enqueing event: http://xmlns.oracle.com/apps/ta/adsdemo/events/edl::ADSDemoEvent from J
    Body: <business-event xmlns:ns="http://xmlns.oracle.com/apps/ta/adsdemo/events/edl" xmlns="http://oracle.com/fabric/businessEvent">
       <name>ns:ADSDemoEvent</name>
       <id>494ae921-4667-4a42-8190-5a5aaa428f7e</id>
       <content>
          <ADSDemoEventElement xmlns="http://xmlns.oracle.com/apps/ta/adsdemo/events/schema">
             <correlationId>3926ed2d-e023-4f05-85f9-bdf0b57099ae</correlationId>
             <key>7499</key>
             <eventType>payRaise</eventType>
          </ADSDemoEventElement>
       </content>
    </business-event>
     
    Subject name:
    Enqueing complete
     
    Starting EDN Agent for Event from Queue
    Dequeued event: http://xmlns.oracle.com/apps/ta/adsdemo/events/edl::ADSDemoEvent
    Subject name:
    Body: <business-event xmlns:ns="http://xmlns.oracle.com/apps/ta/adsdemo/events/edl" xmlns="http://oracle.com/fabric/businessEvent">
       <name>ns:ADSDemoEvent</name>
       <id>494ae921-4667-4a42-8190-5a5aaa428f7e</id>
       <content>
          <ADSDemoEventElement xmlns="http://xmlns.oracle.com/apps/ta/adsdemo/events/schema">
             <correlationId>3926ed2d-e023-4f05-85f9-bdf0b57099ae</correlationId>
             <key>7499</key>
             <eventType>payRaise</eventType>
          </ADSDemoEventElement>
       </content>
    </business-event>
    
  4. Check the console ($DOMAIN_HOME/as.log) or soa-diagnostic logs ($DOMAIN_HOME/servers/<serverName>logs/<serverName>.log) to see any Mediator activity that results from your event.

    INFO: MediatorServiceEngine received an event = {http://xmlns.oracle.com/apps/ta/adsdemo/events/edl}ADSDemoEvent
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.common.persistence.MediatorPersistor persistCallback
    INFO: No call back info set in incoming message
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.common.persistence.MediatorPersistor persistCallback
    INFO: Message properties :{id=041ecfcf-8b73-4055-b5c0-0b89af04f425, tracking.compositeInstanceId=50003, tracking.ecid=0000I2pqzVCBLA5xrOI7SY19uEYF00004g:47979}
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.dispatch.InitialMessageDispatcher dispatch
    INFO: Executing Routing Service..
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.dispatch.InitialMessageDispatcher processCases
    INFO: Unfiltered case list size :1
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.monitor.MediatorActivityMonitor createMediatorCaseInstance
    INFO: Creating case instance with name :ADEDemoProcess.adedemoprocess_client.process
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.dispatch.InitialMessageDispatcher processCase
    INFO: Immediate case {ADEDemoProcess.adedemoprocess_client.process} with case id :{5B52B4A02B9211DEAF64D3EF6E2FB21D} will be executed
    Apr 17, 2009 1:57:26 PM oracle.tip.mediator.service.filter.FilterFactory createFilterHandler
    INFO: No Condition defined
    
  5. Check Oracle Enterprise Manager at http://host:port/em for an instance of your SOA composite, and check for errors.

  6. If your process has no errors and is expecting a response from the human workflow notification, do the following:

    1. Navigate to the worklist at http://host:port/integration/worklistapp.

    2. Log in as the assigned approver.

    3. Approve or reject the notification per your design requirements.

At this point, the BPEL process should complete and invoke the ADF Business Components service to trigger the ADS push. The UI should promptly update. Check the Oracle ADF UI runtime console and diagnostic logs for stack traces and log messages.

42.7 Troubleshooting the Use Case

For the Oracle ADF UI functionality, use Fusion Middleware Control, Oracle Fusion Applications Logger, and server diagnostic logs for information about what is failing.

For the events functionality, use the Event Delivery Network database log page at http://host:port/soa-infra/events/edn-db-log.

For the SOA functionality, use the Oracle Enterprise Manager console for diagnostics and Oracle Fusion Applications Logger sensor variables for logging.

For the ADF Business Components service functionality, use BPEL fault handling and logging via Oracle Fusion Applications Logger sensor variables as well as the console, Oracle Fusion Applications Logger and server diagnostic logs for more detailed error messages.

42.8 What You May Need to Know About Initiating an Asynchronous Service with Dynamic UI Update

42.9 Known Issues and Workarounds

Known issues are as follows: