Monday, February 2, 2015

BAM: Creating editable report in ADF on top of BAM DOs.

BAM provides a way to create an editable report on top of BAM DOs. You just need to select editable fields but problem is you can only create free text fields. What if you want to have LOV (choice list) for some editable field. There seems to be no option in BAM. 

In this blog we are going to create an ADF page,which allows editing a BAM report.

Effectively we need to do following stuff
  1. Query records from BAM DO and show it in tabular format
  2. Make few fields editable in table.
  3. Edit values and click Save. 
  4. All changes should be saved to BAM DO.


To query data from BAM we have three options
  • Use BAM datacontrol: This is the recommended way of querying data from BAM DOs and create a UI out of it. But when it comes to editable report there seems to be few problems.
    • Problem-1: Due to active data behavior table will get refreshed after few seconds and because of that any changes done will be lost after few seconds. We can disable active data behavior but still if we need to perform a PPR on table it will start showing data from directly from DO and again we will lose changes.
    • Problem-2: I don't think there is any setter for binding attributes. If you edit a value in UI and then try to get same value from bindings you will get DO value not edited value. Actually data is not getting pushed to bindings. Binding is still same and if any PPR happens you will lose data also. 
    • Problem-3: Row selection does not work on a table, which is directly based on BAM datacontrol. In UI it appears like row is selected but if you try to get currently selected row in bean, you will see that binding iterator is still pointing to first row. Because of this if you edit a record you would not know which record is actually edited. 
    • Because of above 3 problems I can say its difficult to use BAM data control for editing data.
  • Use BAM webservice: BAM provides the webservice and DataObjectOperationsByName provide a GET service to get data from BAM DOs. But we can have following problems
    • Problem-1: Service only returns one row at a time. If we want to query all employees of certain department that might not be possible. Service will give only one employee record.
    • Problem-2: I could not find a better way to propagate ADF security while calling BAM webservice. Because of which I need to execute BAM webservice using a predefined username/password. Yes we need not to hardcode username/password, we can use credential store to access username/password. If there is any data security at DO level we may miss that and all users will see same data. 
    • Because of above 2 problems I can say its not possible to use BAM webservice to edit (query) multiple rows. 
  • Use BAM database connection: We can directly create EO/VO etc directly on top of BAM tables. This looks like a feasible solution but we have following problems here
    • Problem-1: We need direct access to database and client may not agree.
    • Problem-2: If there is any data security at DO level we may miss that and all users will see same data. 
    • Because of above 2 problems I can say its not a good solution to directly query database.
So we have kind of discarded all possible solution of data query. Now what?
Here is the approach that we are going to do in this blog

  • Query data (all rows) from DO using datacontrol and push them in programatic VO. 
  • Show data from programatic VO to user. 
  • User can freely update data and data will get posted on VO without any issue. PPR will also show edited data.
  • On Submit call DO webservice to update it


NOTE:

  • Because you are showing data from VO to end user, you can use all VO features like LOV creation, control hints etc.
  • As we are querying data using datacontrol so ADF security can be propagated and DO level security will be taken care. [ We have a checkbox on BAM datacontrol wizard to propagate ADF security.]
  • Only downside I see is if you have huge number of records you need to query them all from DO and push to programmatic VO. It may be a bit performance hit. 
Here are the steps to implement this approach
Create datacontrol based on BAM DO: Follow https://docs.oracle.com/cd/E23943_01/dev.1111/e10224/bam_data_control.htm to create a datacontrol based on a BAM DO.
    In general you need to have SOA extension in your jdev. You need to create a BAM connection. You need to search for your DO under connection and then right click and create data control.

For this blog we assume that we have created a data control based on 
Create an ADF page to show data from BAM datacontrol: For this we just need to create a page and drag-drop datacontrol on it. Create a table out of it. Run the page. You should see BAM report in tabular format.
Disable active data behavior: To remove active behavior open page definition and set  ChangeEventPolicy and ChangeEventRate for bindings as shown below
Also keep fetchSize very high so that all rows of DO gets fetched at page load itself.

Fetch all records of DO and Push them to programatic VO:
This is an important step. We want to query BAM DO and push its data to programmatic VO. Our approach is to have bean method which will get data using bindings created in previous step and then call an AM method which will populate VO. We can follow these steps
    a. Create programmatic VO and add it in application module
          
                               Add appropriate attribute to VO
     



    b. Add following method in AM and expose it to client
    public void initEmployeePG(List<Map> employeeList){
      
        ViewObjectImpl vo = this.getEmployeeVO();
        Iterator it = employeeList.iterator();
        while(it.hasNext()){
           Map empMap = (Map) it.next();
          
            Row row = vo.createRow();
            vo.insertRow(row);
            row.setAttribute("id", empMap.get("id"));
            row.setAttribute("Salesperson", empMap.get("Salesperson"));
            row.setAttribute("SalesArea", empMap.get("SalesArea"));
            row.setAttribute("SalesNumber", empMap.get("SalesNumber"));
           
           row.setAttribute("ChangeFlag", "N");
        }
       
        vo.setSortBy("Salesperson");
        vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);
        vo.executeQuery();
    }


above method accepts a list of map, which is used to initialize programmatic VO. Our aim is to somehow create query data from DO using datacontrol and create a list of map based on that data and pass that to this method. This method will insert that data into VO and later we will show UI based on this VO.

To construct a list of map based on DO data we can do following.

    a. Add AM method in pageDef file

    b. Create bean class. Register it in backing bean scope and write following method
     public void initializeEmployeePG(){
        AdfFacesContext facesCtx=  AdfFacesContext.getCurrentInstance();
        Map pageFlow = facesCtx.getPageFlowScope();
        String pageInitialized = (String)pageFlow.get("pPageInitialized");
        if(!"Y".equals(pageInitialized)){
            List<Map> emps = new ArrayList<Map>();
            DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
            DCIteratorBinding treeData =    (DCIteratorBinding)dcBindings.get("QueryIterator");
            Row[] rows = treeData.getAllRowsInRange();

            for (Row row1 : rows) {
                BAMDataControlDataRow row = (BAMDataControlDataRow)row1;
                Map pn = (HashMap)row.getDataProvider();
                Map empMap = new HashMap();
                empMap.put("id",pn.get("ID"));
                empMap.put("Salesperson",pn.get("_Salesperson"));
                empMap.put("SalesArea",pn.get("_Sales_Area"));
                empMap.put("SalesNumber",pn.get("_Sales_Number"));
                emps.add(empMap);
            }         
          
            Map params = new HashMap();
            params.put("employeeList",emps);
            ADFUtil.executeOperationBinding("initEmployeePG", params);
                  
            pageFlow.put("pPageInitialized", "Y");
        }
    }

    c. Create a new java class (EmployeeDC.java)and put following method in it.
    public void initializeEmployeePage() {
          
            EmployeePGBean bean =
                (EmployeePGBean)ADFUtil.evaluateEL("#{backingBeanScope.EmployeePGBean}");
            bean.initializeEmployeePG();
          
        }

    d. Expose  EmployeeDC.java as datacontrol
    e. Add initializeEmployeePage method of EmployeeDC in pagedef file. Also add an executable to invoke this method automatically on page load.
           
Below diagram shows complete diagram of page initialization

Remove database connection and make AM non database dependent:
As we only want to access BAM server using BAM datacontrol there is no need to have a database connection in our application. We can follow blog http://andrejusb.blogspot.in/2012/03/use-case-for-adf-bc-with-no-database.html to make AM non database dependent.

Create editable table and show data from programmatic VO:
Now you can comment existing af:table entry in on page and and drop new VO datacontrol on page to create a new table. Remember when you are removing existing af:table bindings should not get removed. In new table you can add inputText, LOV whatever you want as you are only working with programmatic VO

Save data using BAM webservices:
Once user clicks on Save button we may need to call BAM webservice to post data. We can follow blog: http://sanjeev-technology.blogspot.in/2014/11/executing-bam-webservices-using-adf.html