Saturday, December 5, 2009

ADF Data push with Active Data Service

With Patch Set 1 of JDeveloper 11G we can push data to the JSF page. This is called Active Data Service and this works with the following JSF component
  • activeCommandToolbarButton
  • activeImage
  • activeOutputText
  • table
  • tree
  • All DVT components
One requirement for this feature is you need to have a Datasource who supports data push. For example the BAM server of the Soa Suite support this. For other cases you need to use the Active Data Proxy framework. In this blog I made a demo with 3 different working examples , The first is an example of Oracle this in the fusion demo and can be downloaded here , the second is the one of Matthias Wessendorf and the last is the one of the Oracle ADS documentation example which works with a EJB datacontol.

In the next weeks I will make an ADS example with EJB and JMS, The ADS Page components will then listen on a topic for EJB data change events and refreshes the page with push .

But first let me show what you need to do.
Configure the adf-config.xml , located in the .adf\META-INF folder ( workspace level). With this ADF file you can configure the push parameters, study these parameters because they can influence the application performance.


<?xml version="1.0" encoding="windows-1252" ?>
<adf-config xmlns="http://xmlns.oracle.com/adf/config"
xmlns:sec="http://xmlns.oracle.com/adf/security/config"
xmlns:ads="http://xmlns.oracle.com/adf/activedata/config">
<sec:adf-security-child xmlns="http://xmlns.oracle.com/adf/security/config">
<CredentialStoreContext credentialStoreClass="oracle.adf.share.security.providers.jps.CSFCredentialStore"
credentialStoreLocation="../../src/META-INF/jps-config.xml"/>
</sec:adf-security-child>
<ads:adf-activedata-config xmlns="http://xmlns.oracle.com/adf/activedata/config">
<!--
transport allows three settings:
* streaming (default)
* polling
* long-polling
-->
<transport>long-polling</transport>
<latency-threshold>10000</latency-threshold>
<keep-alive-interval>10000</keep-alive-interval>
<polling-interval>3000</polling-interval>
<max-reconnect-attempt-time>1800000</max-reconnect-attempt-time>
<reconnect-wait-time>10000</reconnect-wait-time>
</ads:adf-activedata-config>
</adf-config>

Then create in the .adf\META-INF folder a new folder called services and put a new file in called adf-config.properties and add the following content.
http\://xmlns.oracle.com/adf/activedata/config=oracle.adfinternal.view.faces.activedata.ActiveDataConfiguration$ActiveDataConfigCallback

Go to your JSF page and drag and drop for example departments from your datacontrol to your page. Choose a readonly table ( when you want to use inputtext instead of outputtext JSF components then keep in mind you need to reset the uicomponent else it won't get the new value ) and don't use Filtering on the table.
This is how your JSF can look like.

<af:table value="#{bindings.departmentsFindAll.collectionModel}"
var="row"
rows="#{bindings.departmentsFindAll.rangeSize}"
emptyText="#{bindings.departmentsFindAll.viewable ? 'No data to display.' : 'Access Denied.'}"
fetchSize="#{bindings.departmentsFindAll.rangeSize}"
rowBandingInterval="0"
selectedRowKeys="#{bindings.departmentsFindAll.collectionModel.selectedRow}"
selectionListener="#{bindings.departmentsFindAll.collectionModel.makeCurrent}"
rowSelection="single" id="t3">
<af:column sortProperty="departmentId" sortable="true"
headerText="#{bindings.departmentsFindAll.hints.departmentId.label}"
id="c8">
<af:outputText value="#{row.departmentId}" id="ot9">
<af:convertNumber groupingUsed="false"
pattern="#{bindings.departmentsFindAll.hints.departmentId.format}"/>
</af:outputText>
</af:column>
<af:column sortProperty="departmentName" sortable="true"
headerText="#{bindings.departmentsFindAll.hints.departmentName.label}"
id="c9">
<af:outputText value="#{row.departmentName}" id="ot8"/>
</af:column>
</af:table>

Here the pagedef of the page ,we need to have a handle to the departmentsFindAll tree in our Active Data Proxy framework.

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="11.1.1.55.36" id="ADStablePageDef"
Package="nl.whitehorses.app.view.pageDefs">
<parameters/>
<executables>
<iterator Binds="root" RangeSize="25" DataControl="CountrySessionEJBLocal"
id="CountrySessionEJBLocalIterator"/>
<accessorIterator MasterBinding="CountrySessionEJBLocalIterator"
Binds="departmentsFindAll" RangeSize="25"
DataControl="CountrySessionEJBLocal"
BeanClass="nl.whitehorses.model2.Departments"
id="departmentsFindAllIterator"/>
</executables>
<bindings>

<tree IterBinding="departmentsFindAllIterator" id="departmentsFindAll">
<nodeDefinition DefName="nl.whitehorses.model2.Departments"
Name="departmentsFindAll0">
<AttrNames>
<Item Value="departmentId"/>
<Item Value="departmentName"/>
</AttrNames>
</nodeDefinition>
</tree>
</bindings>
</pageDefinition>

Now we can make our own DepartmentModel where we add Active Data Service to it. We need the departmentsFindAll tree attribute from the pagedef.

package nl.whitehorses.app.ads;

import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.view.rich.model.ActiveCollectionModelDecorator;

import oracle.adf.view.rich.model.ActiveDataModel;

import oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding;


import org.apache.myfaces.trinidad.model.CollectionModel;

public class DepartmentModel extends ActiveCollectionModelDecorator {

private MyActiveDataModel _activeDataModel = new MyActiveDataModel();
private CollectionModel _model = null;


public CollectionModel getCollectionModel() {
if (_model == null) {
DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
FacesCtrlHierBinding treeData = (FacesCtrlHierBinding)dcBindings.getControlBinding("departmentsFindAll");
_model = treeData.getCollectionModel();
}
return _model;
}

public ActiveDataModel getActiveDataModel() {
return _activeDataModel;
}

public MyActiveDataModel getMyActiveDataModel() {
return _activeDataModel;
}

}

Make your own active ActiveDataModel in which we start a thread where we fire change events.


package nl.whitehorses.app.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 MyActiveDataModel extends BaseActiveDataModel
{
protected void startActiveData(Collection<Object> rowKeys, int startChangeCount)
{
_listenerCount.incrementAndGet();
if (_listenerCount.get() == 1)
{
System.out.println("start up");

Runnable dataChanger = new Runnable()
{
public void run()
{
System.out.println("MyThread starting.");
try
{
Thread.sleep(2000);
System.out.println("thread running");
Change chg = new Change();
chg.triggerDataChange(MyActiveDataModel.this);
}
catch (Exception exc)
{
System.out.println("MyThread exceptioned out.");
}
System.out.println("MyThread terminating.");
}
};
Thread newThrd = new Thread(dataChanger);
newThrd.start();
}
}

protected void stopActiveData(Collection<Object> rowKeys)
{
_listenerCount.decrementAndGet();
if (_listenerCount.get() == 0)
{
System.out.println("tear down");
}
}

public int getCurrentChangeCount()
{
return _currEventId.get();
}

public void bumpChangeCount()
{
_currEventId.incrementAndGet();
}

public void dataChanged(ActiveDataUpdateEvent event)
{
fireActiveDataUpdate(event);
}

private final AtomicInteger _listenerCount = new AtomicInteger(0);
private final AtomicInteger _currEventId = new AtomicInteger();
}




For demo purposes we send some change events. For this we need to know the Key of the department iterator and the attribute which changed. See the ADS help page for other events like insert ,delete etc..


package nl.whitehorses.app.ads;

import oracle.adf.view.rich.event.ActiveDataEntry;

import oracle.adf.view.rich.event.ActiveDataUpdateEvent;

import oracle.adfinternal.view.faces.activedata.ActiveDataEventUtil;
import oracle.adfinternal.view.faces.activedata.JboActiveDataEventUtil;


public class Change {
public Change() {

}

public void triggerDataChange(MyActiveDataModel model) throws Exception {


for ( int i = 0 ; i < 10 ; i++) {
try {
Thread.sleep(4000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}


model.bumpChangeCount();

ActiveDataUpdateEvent event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
model.getCurrentChangeCount(),
JboActiveDataEventUtil.convertKeyPath(new Object[] { new Long(10) , new Integer(0) }),
null,
new String[] { "departmentName" },
new Object[] { "Administration" });

model.dataChanged(event);

try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}

model.bumpChangeCount();
event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
model.getCurrentChangeCount(),
JboActiveDataEventUtil.convertKeyPath(new Object[] { new Long(30), new Integer(0) }),
null,
new String[] { "departmentName" },
new Object[] { "Purchasing" });

model.dataChanged(event);

}
}
}

We need to add the DepartmentModel class as backing bean in the adfc-config or faces-config xml.

<managed-bean>
<managed-bean-name>DepartmentModel</managed-bean-name>
<managed-bean-class>nl.whitehorses.app.ads.DepartmentModel</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>


And at last change the value attribute of the af:table to our managed bean
af:table value="#{DepartmentModel}" var="row"

Here you can download my demo workspace

30 comments:

  1. Hi Edwin,

    ADS is nice stuff. I first worked with it with the GoogleTalk demo (http://www.oracle.com/technology/pub/articles/jellema-googletalk.html) However, what I never got to work - and perhaps you have - is the use of other components besides table, DVT and treetable for ADS - primarily the activeOutputText and activeOutputImage that you mention. Do you know how to use those? (I would also like to have a client side event listener for ADS events, to allow you to execute additional processing when a push event occurs).

    best regards,

    Lucas

    ReplyDelete
  2. Hi Lucas,

    the activeOutputText and activeOutputImage will only work now with a data store that publishes events. like BAM. So they still have to do some work.

    for the rest like table, tree, Graph, geo map, and gauge DVT components. You need to create a Java class that subclasses one of the following decorator classes:

    ActiveCollectionModelDecorator ,
    ActiveDataModelDecorator ,
    ActiveGeoMapDataModelDecorator ,
    ActiveGaugeDataModelDecorator

    thanks Edwin

    ReplyDelete
  3. Thats very informative. Can you also throw some light on how to use a managed bean as a data source for my TreeTable with ADS?

    ReplyDelete
  4. Hi

    my example workspace contains an example of Matthias Wessendorf, this is a bean example.

    thanks Edwin

    ReplyDelete
  5. Thanks, I have written code for ADS Treetable by extending the 'ActiveCollectionModelDecorator'. But nothing is getting rendered on the page. Do I need to extend a different class for TreeTable?

    ReplyDelete
  6. Hi,

    That should work, for a treetable demo you can take a look at the rich fusion demo.

    possible you got an unique key in the tree problem. are you using a row integer as key or do you have jbo key.

    thanks Edwin

    ReplyDelete
  7. Hi Edwin,

    Nice Article. Thanks for the same.

    Here you make use of the ActiveDataEventUtil.buildActiveDataUpdateEvent() method to create the Event.This event is passed to fireActiveDataUpdate() to update the UI Component.

    Now, if the inserts/updates to the tables are made by a different process or person, I will not have a handle to the event. So, how do I create that event that can be used with fireActiveDataUpdate() ?

    For ex: I want to enable ADS for DVT Graphs based on DB tables through Business components? But, I have no control over when or what changes happen to tables.
    In such a case, how I enable ADS for my DVT Graphs ?

    Best Regards,
    Sivan

    ReplyDelete
  8. Hi,

    you can try push the insert & update to a jms topic and the ADS has to listen to this topic and when it receive a message it can update the ADS component.

    Thanks

    ReplyDelete
  9. Hi Edwin,
    yr article is nice.this one is based on bindings, collection etc.but how i implement this push if i m only using ADF components with managed beans. Since i ve a application which is only based on pure adf components & beans. i want to implement server side push on some of my af:table in my application.
    Can u put any example related to this case?

    Best Regards

    ReplyDelete
  10. Hi,

    this should also work without adf bindings, Matthias and I made some pojo demo's

    http://matthiaswessendorf.wordpress.com/2009/12/05/adf-faces-and-server-side-push/

    take a look at the demo workspace.

    thanks Edwin

    ReplyDelete
  11. Sorry Edwin, which one is demo for pojo. can u please specify heading & date of post.

    Good luck for yr team in WC.

    Best Regards

    ReplyDelete
  12. Hi,

    in my demo workspace you can take a look at the adstable.jspx and follow the matthias or oracle datapush tables.

    thanks

    ReplyDelete
  13. Hi Edwin,
    I followed the Matthias sample & changed it to connect database query.it is working nice for row updates in db table. but for new row insert,when i tried 'ActiveDataEntry.ChangeType.INSERT', the page is running similar to when polling happens.that is not a desired behaviour.
    And i know that i m not doing properly when new row inserted in db table.
    Edwin yr guidance is needed for new row insert.

    Best Regards,

    ReplyDelete
  14. Hi Edwin,

    How i implement the data push for insert events. Can u specify what i need to adjust in Matthias sample.

    Best Regards,

    ReplyDelete
  15. Hi,

    this is how it works you need to make a insert method to the DepartmentManager bean which does the event stuff.

    public void onDepartmentInsert(Integer rowKey, Department dept)
    {

    if (rowKey != null) {
    System.out.println("insertEvent "+rowKey);

    MyActiveDataModel asm = getMyActiveDataModel();

    // start the preparation for the ADS insert
    asm.prepareDataChange();

    // create an ADS event, using an _internal_ util.....
    // this class is not part of the API
    ActiveDataUpdateEvent event =
    ActiveDataEventUtil.buildActiveDataUpdateEvent(
    ActiveDataEntry.ChangeType.INSERT_AFTER, // type
    asm.getCurrentChangeCount(), // changeCount
    new Object[] {rowKey}, // rowKey
    new Object[] {null}, //insertKey (null as we don't insert stuff;
    new String[] {"id","name"}, // attribue/property name that changes
    new Object[] { dept.getId(), dept.getName()} // the payload for the above attribute
    );
    // deliver the new Event object to the ADS framework
    asm.notifyDataChange(event);
    }
    }

    then add insertData to the DepartmentBackend bean



    public void insertData() {
    // Add event code here...
    System.out.println("insertData");
    Random randomGenerator = new Random();
    int randomInt = randomGenerator.nextInt(1000);
    Long deptId = new Long(randomInt);
    Department dept = new Department("dept "+randomInt , deptId);
    Integer newRowKey = departments.size();
    departments.add(dept);
    listener.onDepartmentInsert(newRowKey, departments.get(newRowKey));
    }

    and put insertData to the thread and see the new records in the table

    thanks

    ReplyDelete
  16. Thanks Edwin,

    I attached my database query here, & it is working fine for new insert. sorry, but there is one task remains. that i think you can assist me.
    I want to trigger a popup programmatically after ADS is inserting new record.
    I tried this in onDepartmentInsert() method in DepartmentManager.java but failed.
    How it should be implement?

    Best Regards,

    ReplyDelete
  17. Hi,

    I don't know, can you make a testcase then I can take a look. send it to biemond at gmail dot com

    thanks

    ReplyDelete
  18. Hi Edwin,

    I sent my testcase to your gmail id. Please find the ADSPojo.zip file with "Subject: ADSPojo - TestCase, Sender: ADSPojo".

    Best Regards,

    ReplyDelete
  19. Hi Edwin,

    I was wondering if you had the chance to write the ADS example with EJB and JMS.
    I am new (very new) to ADF... I am trying to find out how can an ADF app can subscribe and listen to a JMS topic (in a remote machine) and then have the UI/page refreshed...
    Thanks for any help!

    ReplyDelete
  20. Hi,

    Oracle is making an ADS JMS Implementation, so it is coming but I don't know when.

    thanks

    ReplyDelete
  21. Hi Edwin,

    I am the same who sent you ADSPojo.zip to yr gmail id.

    In my real application af:table, one column contains af:commandLink.
    On inserting new row by datapush,
    this column remains blank but all others column populated.others column have af:outputtext. after replacing af:commandLink with af:outputtext this column populated.

    but i cannot replace af:commandLink in my app. I replaced only for testing.

    What i do so that af:commandLink also appears in column on new row insert?

    Best Regards,

    ReplyDelete
  22. Hi Edwin,
    thanks for great information. I've been trying to make ADS push to UI which consists of table and summary label at the same time. I still can't make it work. I assume issue is in the fact that I am trying to INSERT_AFTER in table and UPDATE on the label. Do you have any hints or sample that does this?

    ReplyDelete
  23. Edwin,
    Thanks for the tremendous info and samples you leave on your blogs! In your/Matthias sample,I see that you are simulating database updates.Can you throw some light on how this is done real time.I tried to follow http://download.oracle.com/docs/cd/E14072_01/java.112/e10589/dbchgnf.htm
    but I am not able to use a custom method like public void onDeptartmentUpdate(Integer rowKey, Department dept) in the listener, as in your example. Because of this I cant get the updated row info.
    Kindly advise,
    Krishna

    ReplyDelete
  24. Hi,

    the easy way is to use a oracle 11g database, else it wont work and you can use a simple adf bc model project with a application modele in shared mode ( I think)

    put this data in a table on a page
    then you can add some records in the table.

    else you need to implement your own ActiveDataModel which reads an AQ for the update or polls for changes.

    thanks

    ReplyDelete
  25. Hi Edwin,

    Thanks for this useful post. We are trying something similar but using coherence to read the data in a POJO class that we are exposing as data control. We wants to display the BAM data object through coherence in our application using ADF DVT graphs.Can you please suggest some key changes that are required in order to achieve that.

    Thanks.

    ReplyDelete
    Replies
    1. Hi,

      I don't know if this works with an ADF data control, try it first with a managed bean and direct on the jsf component. Don't know what property of the DVT component you should use and what to implement.

      good luck.

      Delete
  26. Hi Edwin,

    Thanks for this post.
    But, I am not able to get ADS for "UPDATE" while using a dynamic table.
    The scenario is like, I am creating dynamic tables (based on List).
    Now when an updated list comes, I need to refresh the corresponding dynamic table, I created.

    Thanks.

    ReplyDelete
    Replies
    1. Hi,

      You must implement DepartmentModel with ActiveDataModel and add this as value of af:table
      value="#{DepartmentModel}"

      that is the only way.

      thanks

      Delete
  27. I guess I don't fully understand the process here.
    I have a MySQL database which ADF Business Component is connected to.
    I want to see the ADF table automtatically refreshed when there is a change to the database table (like insert/update/delate). I guess this is what ADS is for, right?
    So I followed your example and it didn't work. Any extra thing?
    I noticed you had fired some events in your Change object. Maybe MySQL database doesn't fire these events when there is a change?

    ReplyDelete
    Replies
    1. Hi,

      this works with oracle 11g r2 , oracle jdbc driver, add change logging to the table and use a shared adf bc application module.
      else you need to program your own DepartmentModel ( which publish your changes ) with ActiveDataModel and add this as value of af:table value="#{DepartmentModel}"

      thanks

      Delete