Sunday, June 14, 2015

An implementation on automatic data push in ADF using Active Data Service (ADS)

  From time to time when we develop applications, our application is not running on its own. Chances are the application is always interacting with other applications. 
  Let's assume you have this requirement for your application: your application has a page to display data from a database table, but another application will update the same table from outside of your application's context. What is the best approach to synchronize your application with the data that are modified from another application?
  There are the following ways to achieve such requirement:
    1) Your application keeps waiting on possible data change; this is not recommended, because your application acts like it is blocked, it won't respond to other requests. Use this approach if you have to. Like your application has to wait for certain change to proceed. For example your application is a payment gateway, it redirects your end user to a bank page for the end user to do some online transaction. Your application has to wait for the end user to finish and process the bank transaction response in your application.
    2) Your application add a 'Refresh' button onto your page, ask your end user to click on this refresh button to refresh data update onto your screen; using this approach the user experience is largely relying on if end user is OK to keep pressing button to refresh application data.
    3) Your application initialize a thread to poll the database table at certain frequency ( for example poll database table every 30 seconds ); this approach will save end user from keep clicking 'Refresh' button, but the down size is your application might have to refresh page each time the poll happens. Some user might complain that your page flashes or flickers. Another big drawback is polling database may cause database performance issue.
  4) Your application continues after displaying data; you will still have some logic after the data changes are pushed to your application and your application can process such data changes at the back ground while still servicing other requests.

  As you can see approach 4) above using automatic data push gives your application more flexibility in a cleaner way. Every other approach might be OK in a case by case scenario. 
  Fortunately there is a simple approach to achieve auto data push in ADF using Oracle database and ADF Active Data Service - ADS technology. The following are the steps to implement this from scratch.

Grant 'change notification' to your database user
  In my sample application I am using scott database user, log into your database and issue the following SQL command:
    grant change notification to scott
  You need to grant such permission as sys or system or any user having DBA privilege.

Code your database change listener
  Your ADF code must have a database change listener registered to listen on the backend database table. The following code snippet is an example:
    private void startListenForDBChanges() throws Exception {
        OracleConnection conn = connect();
        Properties prop = new Properties();
        prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
        dcr = conn.registerDatabaseChangeNotification(prop);

        try {
            dbChangeListener = (new DatabaseChangeListener() {
                    public void onDatabaseChangeNotification(DatabaseChangeEvent dce) {
                        String rowId =
                            dce.getTableChangeDescription()[0].getRowChangeDescription()[0].getRowid().stringValue();
                        System.out.println("Changed row id : " + rowId);
                        String changeDetail = formChangeDetail(rowId);
                        listener.onDBChangeListener(changeDetail);
                    }
                });

            dcr.addListener(dbChangeListener);

            System.err.println(dcr + " " + dbChangeListener);

            Statement stmt = conn.createStatement();
            ((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);

            ResultSet rs =
                stmt.executeQuery("SELECT ename, job, sal, comm FROM bonus");

            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException ex) {
            if (conn != null) {
                conn.unregisterDatabaseChangeNotification(dcr);
                conn.close();
            }
            throw ex;
        }
    }

  In the above code, the "SELECT ename, job, sal, comm FROM bonus" is to let your code listen on ename, job, sal and comm columns of bonus table. Any changes related to thse 4 columns will be intercepted by your database change listener.

Construct your change notification message
  Your can have a method to construct the change notification message that might be displayed onto your application page.

    private String formChangeDetail(String rowId) {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        String ename = "";
        String job = "";
        BigDecimal sal = null;
        BigDecimal comm = null;
        OracleConnection connection = connect();
        try {
            stmt = connection.prepareStatement("SELECT ename, job, sal, comm FROM bonus WHERE ROWID=?");
            stmt.setString(1, rowId);
            rs = stmt.executeQuery();
            while (rs.next()) {
                ename = rs.getString(1);
                job = rs.getString(2);
                sal = rs.getBigDecimal(3);
                comm = rs.getBigDecimal(4);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                rs.close();
                stmt.close();
                connection.close();
            } catch (Exception ex) {
                ex.printStackTrace();
                System.err.println("Cannot close connection.");
            }
        }
        return "Emp name: " + ename + " job: " + job + " salary: " + sal + " commission: " + comm + ".";
    } 

 Code your AdsController class
  Your AdsController java class should have this declaration:
    public class AdsController extends BaseActiveDataModel implements DBChangeListener {}
  You will register your AdsController class in your application same as you do as backing bean class.
  
  Add active model statement in your AdsController constructor method:
    public AdsController() {
        ActiveModelContext vActiveModelContext =
            ActiveModelContext.getActiveModelContext();
        Object[] vKeyPath = new String[0];
        vActiveModelContext.addActiveModelInfo(this, vKeyPath, "Status");
    }
  Important: "Status" is the name of your change notification event name. When I was first implemented ADS, I was really confused by what the 3rd parameter means in addActiveModelInfo(), it is just a name. You should use such name consistently in your application. Later in triggerActiveDataUpdateEvent() we will use this same name again. You need to make sure they are the same.

Override startActiveData method in AdsController
    protected void startActiveData(Collection<Object> coll, int i) {
        System.err.println("Starting active data thread for: " + this);

        dbPushController = new DBPushController();
        try {
            dbPushController.addChangeListener(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

  In the startActiveData method, you add change listener into your ADS controller class.
  
Override onDBChangeListener method in AdsController
    public void onDBChangeListener(String changeDetail) {
        triggerActiveDataUpdateEvent(changeDetail);
    }
...
    public void triggerActiveDataUpdateEvent(String changeDetail) {
        counter.incrementAndGet();

        System.err.println("Trigger active data update event: " +
                           counter.get() + " " + this);
        ActiveDataUpdateEvent event =
            ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
                                                           counter.get(),
                                                           new String[0], null,
                                                           new String[] { "Status" },
                                                           new Object[] { changeDetail });
        fireActiveDataUpdate(event);
        j++;
    } 
 

  Since we declared AdsController as implement DBChangeListener, we have to implement onDBChangeListener() method, in it we fire the active data update event. Notice "Status" in bold in triggerActiveDataUpdateEvent() method, it is the same data change name we used in AdsController constructor method.

  That's pretty much it. You need to define a connection in your ADF application model project. I am using scott and bonus table for this sample.
  Please see the screenshot of running this application below.

When application first runs, no data in scott.bonus table yet


Now add a record for SMITH and give SMITH 1.5% commission

Now modify SMITH's commission from 1.5% to 2%



At last we add 5% commission to Jones



  Congratulations! You now know all it about to implement Oracle ADF automatic data push via ADS and database change listener! 
  Please use the following link to download the source code of this sample:
Data Push Sample Source Code 

   

Friday, June 5, 2015

How to implement 100% Dynamic ADF table with inputText having value change listeners.

  I have a use case that has the following business requirements: 

    1) Have multiple database tables and display them on the page; 
   2) The application needs to have the capability to track end user changes;
  
  The easiest way in ADF is to create the number of Entity Objects and same number of View Objects. Then user needs to create an ADF page and drag drops the same number of times on the page. After that user needs to have a drop down to allow end user to choose which table to work on and based on this selection the corresponding table will be rendered and all the others will be hidden. User needs to define a value change listener for every field for each table.
  This solution seems OK for a handful of tables in the application, how about the developer needs to work with a subset of Oracle E-Business Suite Inventory module tables which can be several tens of thousands of tables!
  There is a better solution. Use dynamic view, dynamic tables. Along with my implementation, I referred to the following blog post: Eugene Fedorenko's ADF Practice blog - "Dynamic Table Declarative Component" and Andrejus Baranovskis Blog - "ADF Generator for Dynamic ADF BC and ADF UI". Engene and Andrejus has given a working sample respectively. My job is to understand the code and put them together, extend and tweak to meet my specific requirements.
  The following are the break-down of the design and implementation steps towards the completion of finishing this application.

 Choose refreshing mechanism
  No matter what approach you select, you will have a drop down containing all the table names. Then after each selection the table display area needs to be refreshed. One can put everything on a single page and use ppr appropriately to refresh. Or one can put table display area in a region. Such region has a Bounded Task Flow behind it and the BTF has its refresh set to 'ifNeeded' in page definition file and when its parameters change, the BTF will be automatically refreshed. In my sample I am using the latter.

 Dynamically construct view object
  The constructVOInstance() method in AppModuleImpl.java file is the one which construct the view object at run time on the fly. It retrieves all the Entity Object's attribute names and define the view definition file on top of them, then calls createViewObject() to create the view object.

         ViewDefImpl viewDef = new ViewDefImpl(viewDefName);
            viewDef.addEntityUsage(shortEoName, eoName, false, false);
            viewDef.addAllEntityAttributes(shortEoName);            

            String select = ViewDefImpl.buildDefaultSelect(viewDef.getAttributeDefs());
            viewDef.setSelectClause(select);

            viewDef.resolveDefObject();
            viewDef.registerDefObject();

            String from = viewDef.buildDefaultFrom();
            viewDef.setFromClause(from);
            System.out.println("vo Name : " + voName);

            ViewObject vo = createViewObject(voName, viewDef);


 Build your dynamic table on page
   Developer needs to drag the DummyVO onto his/her page/fragment and drops it as:
    a) ADF dynamic table with dynamic:table tag; 
    b) ADF dynamic table with af:table tag; 

I later found out that dynamic:table is not applicable in my case because developer cannot add anything inside it. So here is what I am using on my page fragment as the definition of ADF table:
    <af:table value="#{pageFlowScope.TableBean.tree.collectionModel}" var="row" rows="#{pageFlowScope.TableBean.tree.rangeSize}"
                      emptyText="#{pageFlowScope.TableBean.tree.viewable ? 'No data to display.' : 'Access Denied.'}"  fetchSize="#{pageFlowScope.TableBean.tree.rangeSize}" rowBandingInterval="0" id="t2">
                

                <af:forEach items="#{pageFlowScope.currentViewDefs}" var="def">
                <af:column sortProperty="#{def.name}" sortable="false" headerText="#{def.name}" id="c1">
                        <af:inputText value="#{row[def.name]}" id="it2" autoSubmit="true"
                                      valueChangeListener="#{mainBean.fieldValueChange}"/>
                    </af:column>
                </af:forEach>
            </af:table>

  We will deal with the tricky part later, but basically I am achieving dynamic by embed af:forEach underneath af:table to get dynamic.

  Notice that the "value" attribute has something like: #{pageFlowScope.TableBean.tree.collectionModel}. This is one of the tricky part. The dynamic is coming from the logic in backing bean getTree() method.

 Construct JUCtrlHierBinding dynamically
   You see what is in the getTree() method in TableBackingBean class.
       JUCtrlHierBinding chb = (JUCtrlHierBinding)dcb.findCtrlBinding(TREE_NAME);

        if (chb != null) {
            dcb.removeControlBinding(TREE_NAME);
        }

        //Looking for the VEmpIterator iterator
        JUIteratorBinding iter = (JUIteratorBinding)getIterator(ITERATOR_NAME, VO_NAME);

        //Create and init a tree binding definition
        JUCtrlHierDef hierDef = new FacesCtrlHierDef();
        HashMap initValues = new HashMap();
        initValues.put(JUCtrlHierDef.PNAME_IterBinding, iter.getName());
        JUCtrlHierTypeBinding typeBinding = new JUCtrlHierTypeBinding();
        initValues.put(JUCtrlHierDef.PNAME_TypeBindings, new JUCtrlHierTypeBinding[] { typeBinding });
        hierDef.init(initValues);

        //Create a tree binding instance
        chb = (JUCtrlHierBinding)hierDef.createControlBinding(dcb);

        //Add the instance to the current binding container
        dcb.addControlBinding(TREE_NAME, chb);   

   Same as when constructing ViewObject dynamically, when you try to construct the JUCtrlHierBinding, you need to construct its Defition first.
  You may already know that the corresponding Java class for af:table is RichTable and inside it there is something called wrapped data, which is the collectionmodel normally in af:table's value attribute. And it is the data set for af:table's var attribute to iterator through such data set. This wrapped data or collectionmodel has its corresponding Java class too which is JUCtrlHierBinding. These are the page UI layer objects, and at the page definition layer there is Iterator Binding or JUIteratorBinding Java class. And down the chain at the ADF Model layer it is ViewObject in Application Module or ViewObjectImpl Java class. These are pretty much what Oracle ADF data binding is all about and how the data flows during data binding. 
  Lastly I drag drop the TableNamesVO onto my main page as a Select One Choice. This view object contains the SQL query for all the table names that need to be managed in this application. When the value of this drop down changes, the table name will be the parameter for the table display area bounded task flow and cause it to be processed as illustrated in 'Dynamically construct view object' section.
  In this sample I am using Oracle's scott schema. There are 4 tables from scott schema will be shown in the table selection drop down. In order to correctly run the sample application, please make sure scott schema is unlocked and is connectable. See the following screen shot for the resulting page:
 
  If you need to add another 100 tables to be managed for this application, all you need to do is make sure TableNamesVO SQL has the new 100 table names, that's it!
  These are all you need to know in order to have a very dynamic, flexible, extensible and sustainable approach to achieve 100% Oracle dynamic table.
  Please find the working sample code below in the attachment of this post. Enjoy! 

 DynamicEditableAFTable.rar