Showing posts with label ADF Mobile. Show all posts
Showing posts with label ADF Mobile. Show all posts

Tuesday, January 26, 2016

MAF: Invoking WebServices (Which way to choose)

In MAF there are actually multiple ways to create UI based on webservice.

Your choices are
1. If service is SOAP based, you can create a datacontrol and use it directly to build UI
2. If service is SOAP based, you can create a datacontrol, Call datacontrol from POJO using ADFmfJavaUtilities.invokeDataControlMethod, Expose POJO as datacontrol and use it to build UI.
3. If service is REST based and output in XML, you can create a datacontrol and use it directly to build UI.
4. If service is REST based and output is XML, you can create a datacontrol, call datacontrol from POJO using ADFmfJavaUtilities.invokeDataControlMethod, expose POJO as datacontrol and use it to build UI.
5. If service is REST based and output is JSON, you can create a POJO, call webservice directly using RESTServiceAdapter class. Expose POJO as datacontrol and use it to build UI.

Below diagram depicts above mentioned choices a flow

Although we have multiple choices to invoke webservices, but I only go with two approaches. It reduces my confusion and provide me best options.

1. I do not drag and drop webservice datacontrol on UI. It creates few problems
     a. I lose ability to pre-process/validate/post-process/cache-result capabilities. These capabilities comes only if we invoke webservice datacontrol using POJO. So my preferred way is always have a POJO wrapper around service.

   b. If you directly drag and drop webservice datacontrol on UI, it may get executed more frequently than you want. At times you need to provide logic on UI rendered property or binding iterator to stop auto execution of webservice. It complicates thing.

  So my first suggestion do not drag and drop webservice datacontrol directly on UI, instead wrap it around a POJO and expose POJO as datacontrol. Inside POJO you can do multiple things and invoke service using AdfmfJavaUtilities.InvokeDataControlMethod if needed.

2. For REST service I do not go datacontrol way at all. I prefer simple POJO with RESTServiceAdapter class.
   a. If REST is XML based I have ability to create a datacontrol and then invoke it using AdfmfJavaUtilities but I do not prefer this approach. Its like completely defining your REST service at client side, what is possible url, what are output their attributes etc. If anything changes, I need to recreate datacontrol and it may be lots of testing again. But if I use RESTServiceAdapter, its either no impact or minimal impact on my code.


Effectively my way of invoking services in MAF is pretty straigt forward
1. If service is SOAP
       a. Create SOAP datacontrol
       b. Create POJO and use AdfmfJavaUtilities class to invoke webservice. Also implement pre-processing/validation-of-input/post-processing/caching-of-result logic in POJO.
       c. Expose POJO as datacontorl
       d. Create UI using POJO datacontrol

2. If service is REST
     a. Create POJO (No need to create datacontrol using REST service)
     b. Use RESTServiceAdapter class to invoke rest service from POJO. Also implement pre-processing/validation-of-input/post-processing/caching-of-result logic in POJO.
     c. Expose POJO as datacontrol
     d. Create UI using POJO datacontrol


Above two approaches are my preferred way.

Thursday, February 19, 2015

MAF (ADF Mobile): Calling an HTTP basic secured REST service

We can follow MAF tutorial http://docs.oracle.com/cd/E53569_01/tutorials/tut_jdev_maf_json/tut_jdev_maf_json.html to invoke a REST service using MAF infrastructure.

In this blog I am just trying to add how we can call SECURED REST service, which requires HTTP Basic authentication.

Before going for actual blog, few lines about tutorial api to execute REST.
Basic approach in tutorial is
a. MAF page will invoke a datacontrol method
b. Create URL for REST request
c. Call REST service
d. Populate POJO objects using REST response
e. Return POJO objects



Main code to invoke REST service looks like
private String invokeRestRequest(String httpMethod, String requestURI, String payload){
   
   String restPayload = "";
   RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
   restServiceAdapter.clearRequestProperties();
   restServiceAdapter.setConnectionName("REST-Public"); //Change it as per your connection name
       
   //set GET, POST, DELETE, PUT
   restServiceAdapter.setRequestType(httpMethod);
       
   //this sample uses JSON only. Thus the media type can be hard-coded in this class
   //the content-type tells the server what format the incoming payload has
   restServiceAdapter.addRequestProperty("Content-Type", "application/json");
   //the accept header indicates the expected payload fromat to the server
   restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");
   restServiceAdapter.setRequestURI(requestURI);       
   restServiceAdapter.setRetryLimit(0);   
       
   //variable holding the response
   String response = "";
       
   //set payload if there is payload passed with the request
   if(payload != null){  
             //send with empty payload
             restPayload  = payload;
   }

   try {
       response = (String)restServiceAdapter.send(restPayload);
    } catch (Exception e) {
        //log error
        Trace.log("REST_JSON",Level.SEVERE, this.getClass(),"invokeRestRequest", "Invoke of REST Resource failed for "+httpMethod+" to "+requestURI);
        Trace.log("REST_JSON",Level.SEVERE, this.getClass(),"invokeRestRequest", e.getLocalizedMessage());
        }
  return response;

};

OK, now what if service is secured and needs HTTP authentication.

When a REST service needs authentication we need to add 'Authrization' entry in http header.

Authorization: Basic a3Zlcm1hQGtiYWNlLmNvbTpLYmFjZUAwMDE=
It means if we know username and password we can concatenate them as username:password and then somehow get base64 encoding. Once we have that we can append it with word 'Basic' and set complete string as a value for 'Authorization'. Simple

Only tricky part is getting base64 encoding. We have multiple ways to get base64 encoded string.

Solution 1: Using Java API:

We can use below lines of code to add 'Authorization' in request


String base64 = null;
String cred = "sanjeev" + ":" + "myPassword";
try {
           
    base64 = Base64.getEncoder().encodeToString(cred.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            //Handle your exception
        }
                restServiceAdapter.addRequestProperty("Authorization", "Basic " + base64);



After this you should be able to call HTTP basic secured REST service. You will require to import java.util.Base64 class.

I think this class is added in java8 so it may not work if have older version of java installed in your mobile.


Solution 2: Using JavaScript api

Another solution could be to use javascript api to get base64 encoding. We have javascript api btoa available that can encode a string. For this approach we need to following these steps
a. Create js file: Create a javascript file and add following lines in it
        base64encode = function (){
             var input = arguments[0];
             return window.btoa(input);
        }

b. Register js against a feature: In feature.xml file add js file against the feature which needs base64 encoding

c. Call js from java code


String cred = "sanjeev" + ":" + "myPassword";
String base64 = (String)AdfmfContainerUtilities.invokeContainerJavaScriptFunction("Test",
                 "base64encode", new Object[] {cred});
        restServiceAdapter.addRequestProperty("Authorization", "Basic " + base64);



For simplicity I have hard coded username/password within method but you can accept them as a parameter to make method more generic.



Friday, November 7, 2014

ADF Tree: Searching child nodes using af:query panel

Problem Description:
We all know how to create a ADF table and provide a query panel for that table. Even we can create a query panel for ADF Tree but it only works with parent most VO. For example if we have Department/Employee tree we can provide a query panel that search only on department. At times we may want to include few employee related attributes like JOB on search panel and when user search based on JOB we just want to show those employees having same Job. By this way filtering is on child nodes and not on parent nodes.

In this blog I will be creating a tree showing department and employee. I will be creating a search panel with Department and Job field. When user search based on department, system will filter department nodes and when user search based on employee, system will filter employee nodes.

We will perform following steps
1. Create Department/Employee VO with a view link and create normal search panel
2. Add Employee Job field in search panel
3. Search Employee rowsets based on Job


1. Create Department/Employee VO with a view link and create normal search panel: This step is simple one and we all know how to create normal search panel. Here are the steps
      a. Create view object DepartmentVO (have department-id as primary key)
      b. Create view object EmployeeVO
      c. Create view link DepartmentVOToEmployeeVO. (Join based on department-id)
      d. Create a view criteria "search" in DepartmentVO and select Department-Name as view-criteria-item.
      e. Add DepartmentVO and EmployeeVO in AM using view link.
      f. Drag view-criteria from data control and drop it on page to create a search panel with adf tree.

At the end of this step you should be ready with a normal search panel on Department-Employee Tree. Search field is just department-name and when user enters department-name system filters Department (parentvo). Within department all employees are visible.

2. Add Employee Job field in search panel: Follow these steps to do it

  a. Create a transient attribute JobId in DepartmentVO (NOTE: Its DepartmentVO no EmployeeVO)

     

  b.  Include this attribute in search view-criteria

     


   Now if you run the page, you will see a new field JobId but it would not do anything at this time. We will make it working in next step.


3. Search Employee rowsets based on Job
Follow these steps

  a. Write following java method in AMImpl.java

        /**
     * This method is used to execute childrowset of employeevo based on job-id mentioned on department vo's view criteria.
     * Logic:
     *   1. Select JobId value/operator from 'search' view criteria of DepartmentVO
     *   2. Create and apply a new dynamic viewcriteria on employeeVO based on JobId value/operator
     *   3. Iterate of all department rows and find out their employeeVO row-set. Execute that rowset.
     */
    public void queryDepartment(){
        ViewObjectImpl deptVO = this.getDepartmentVO1();
        ViewCriteria vc = deptVO.getViewCriteria("search");
        ViewCriteriaRow vcr = (ViewCriteriaRow)vc.get(0);
        String jobId = (String)vcr.getAttribute("JobId");
        String operator = vcr.getOperator("JobId");

       
        if(deptVO.first() != null){
            ViewObjectImpl empVO = (ViewObjectImpl) ((RowSet)deptVO.first().getAttribute("EmployeeVO")).getViewObject();
           
           
             ViewCriteria vc1 = empVO.createViewCriteria();
             ViewCriteriaRow vcRow1 = vc1.createViewCriteriaRow();
            
             vcRow1.setAttribute("JobId", jobId);
             vcRow1.setOperator("JobId", operator);
             vc1.addRow(vcRow1);
            
             empVO.applyViewCriteria(vc1);
        }
       
        RowSetIterator rsi = null;
        try{
            rsi = deptVO.findRowSetIterator("iter");
            if(rsi == null){
                rsi = deptVO.createRowSetIterator("iter");
            }
            rsi.reset();
            while(rsi.hasNext()){
               Row row = rsi.next();
              RowSet empRs = ((RowSet)row.getAttribute("EmployeeVO"));
               empRs.executeQuery();
                             
            } 
            }finally{
            if(rsi != null){
                rsi.closeRowSetIterator();
            }
        }
       
    }


   b. Expose this method to client



   c. Create a method action binding for this exposed method in pageDef file.

   

    d. Create a bean for page and register it in backing bean scope

  

   e. Write a method in searchEmployeeAndDepartment in SearchBean.java. It should perform following tasks

  •          execute department search programmatically.
  •          call AM method queryDepartment which executes all rowsets of employee VO
  •          refresh tree ui component


 Code of  SearchBean.java should look like

import java.util.Map;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.view.rich.component.rich.data.RichTree;
import oracle.adf.view.rich.component.rich.data.RichTreeTable;
import oracle.adf.view.rich.context.AdfFacesContext;
import oracle.adf.view.rich.event.QueryEvent;
import oracle.binding.BindingContainer;
import oracle.binding.OperationBinding;

public class SearchBean {

    private RichTree deptTree;

    public SearchBean() {
        super();
    }


    public void searchEmployeeAndDepartment(QueryEvent queryEvent) {
        // Add event code here...
        FacesContext fctx = FacesContext.getCurrentInstance();
                ELContext elctx = fctx.getELContext();
                Application app = fctx.getApplication();
                ExpressionFactory exprFactory = app.getExpressionFactory();
              
                MethodExpression methodExpr = exprFactory.createMethodExpression(elctx, "#{bindings.searchQuery.processQuery}", Object.class, new Class[] {QueryEvent.class});
         methodExpr.invoke(elctx, new Object[] {queryEvent});
        
         this.executeOperationBinding("queryDepartment", null);
        
        AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
        adfFacesContext.addPartialTarget(deptTree);
    }
   
    public static Object executeOperationBinding(String methodAction,
                                                               Map param) {
       
            OperationBinding ob =  getDCBindingContainer().getOperationBinding(methodAction);

            if (param != null) {
                Map paramOps = ob.getParamsMap();
                paramOps.putAll(param);
            }
            Object result = ob.execute();
            return result;
        }
   
    /**
        * Return the Binding Container as a DCBindingContainer.
        * @return current binding container as a DCBindingContainer
        */
       public static DCBindingContainer getDCBindingContainer() {
           return (DCBindingContainer)getBindingContainer();
       }
   
    /**
        * Return the current page's binding container.
        * @return the current page's binding container
        */
       public static BindingContainer getBindingContainer() {
           return (BindingContainer)evaluateEL("#{bindings}");
       }
   
    /**
    * Programmatic evaluation of EL.
    *
    * @param el EL to evaluate
    * @return Result of the evaluation
    */
    public static Object evaluateEL(String el) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ELContext elContext = facesContext.getELContext();
        ExpressionFactory expressionFactory =
        facesContext.getApplication().getExpressionFactory();
        ValueExpression exp =
        expressionFactory.createValueExpression(elContext, el,
        Object.class);
   
        return exp.getValue(elContext);
    }


    public void setDeptTree(RichTree deptTree) {
        this.deptTree = deptTree;
    }

    public RichTree getDeptTree() {
        return deptTree;
    }
}






   f. Bind tree with bean's deptTree property

      

   g. Change af:query's querylistener and point it to searchEmployeeAndDepartment method of bean.
   


That's it. You can run your page now. You should see following result



The logic is
1. On search button click execute a bean method
2. Bean method will excute department search as normal
3. Bean method will also call AM method which will execute each employee rowsets based on Job-id
4. Bean method will refresh tree component.


Few things that we can try
a. Do not execute all row-set of employee vos but only those which are already open. Yes set criteria in all row-sets so that if user opens other, criteria is applied.

b. If we only specify JobId and there are departments which does not have employee with that job, we should not be showing those departments. We can use Exist operator (view-link) in DepartmentVO for that but we need to change AM code to refer job-id from nested row. Planning to implement it in next blog.

Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.

Friday, August 8, 2014

ADF Mobile: Issues faced while migrating from 11.1.2.4 to 12.1.3.0

Till 11.1.2.4 it was ADF Mobile and from 12.1.3.0 it is Mobile Application Framework (MAF). In this blog I am just writing few issues that I have faced while migrating one of my ADF mobile (11.1.2.4) project to MAF.

====================================================================
Issue 1:
Error: Unable to copy to output directory, D:\<file-path>\ApplicationController\src\META-INF\adfmf-skins.xml not found    ApplicationController.jpr    D:\<file-path>\ApplicationController    ApplicationController.jpr


Error: Unable to copy to output directory, D:\<file-path>\ViewController\src\META-INF\adfmf-feature.xml not found    ViewController.jpr    D:\<file-path>\ViewController    ViewController.jpr

Solution: https://community.oracle.com/thread/3579575
a. Remove .data directory
b. Do a clean build as there is no adfmf-skins.xml. Its changed to maf-skins.xml

====================================================================

Issue 2: Skin (css file) developed in 11.1.2.4 is not picked
Solution: Use version Id with 12.1.3 to define your skins and also to refer oracle provided base skins, which you are extending.
See bottom of this blog to set correct entries of maf-skin.xml and maf-config.xml

====================================================================

Issue 3: Error (Line 22, Column 48): Attribute allowDeviceAccess not defined on element adfmf:featureReference
Solution: remove allowDeviceAccess attribute from maf-application.xml

====================================================================

Issue 4:
Unable to load resource JSON file
/data/data/com.company.AbsenceManagemetn/files/baseProfiles/profile.json
Empty Response
Error: a profile is required to set up the base page

Solution: Problem with skin configuration in maf-skins.xml file. Start using skin versioning
See bottom of this blog to set correct entries of maf-skin.xml and maf-config.xml


====================================================================

Issue 5: KXmlUtil.loadFromXml Unexpected element - HTML Expected schema
Solution: Redeploy apk and use that.

====================================================================

Issue 6: The custom authentication page does not have credentials field.
Solution: This issue only occurs if we have created custom login page in 11.1.2.4 by copying adf-mobile-provided-login-pages-and-js. We need to again copy new login page and customize that as per our need. 


=========================================================
Correct way of extending skins in MAF

Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.


Saturday, May 17, 2014

ADF Mobile Dependent (Cacading) LOV

This blog is to show how we can create depending (or cascading) LOVs using ADF Mobile

Steps are
1. Create client classes to store data that needs to appear in LOVs
2. Create datacontrol class to hold LOV data in a list (array) and provide mechanism to refresh LOV data
3. Create select-one-choice on page using datacontrol
4. Set valuechange listner of LOV1 to refresh dataprovider of LOV2.



In this example I will create a Country/State LOVs

Country LOV will have India, US values. and State LOV will show states of India or US.

==============================

Step 1. Create client classes to store data that needs to appear in LOVs

To represent country and states we need two classes Country, State

Country.java 
package com.san.mobile.client;

public class Country {
    private String countryName;
  
    public Country( String countryName) {
        super();
        this.countryName = countryName;
    }


    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }

    public String getCountryName() {
        return countryName;
    }

}




State.java
package com.san.mobile.client;

public class State {
    private String stateName;
    public State(String stateName) {
        super();
        this.stateName = stateName;
    }

    public void setStateName(String stateName) {
        this.stateName = stateName;
    }

    public String getStateName() {
        return stateName;
    }
}




========================================================

Step 2. Create datacontrol class to hold LOV data in a list (array) and provide mechanism to refresh LOV data 

package com.san.mobile.dc;

import com.san.mobile.client.Country;
import com.san.mobile.client.State;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.ProviderChangeListener;
import oracle.adfmf.java.beans.ProviderChangeSupport;

public class DependentLOVDC {
    private List countries = new ArrayList();
    private List states = new ArrayList();
  
    protected transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);
    public DependentLOVDC() {
        super();
      
        //NOTE: You can call a webservice here to get actual country data.
      
        countries.add(new Country("India"));
        countries.add(new Country("US"));
    }
  
    public void addProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.addProviderChangeListener(l);
       }
       public void removeProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.removeProviderChangeListener(l);
       }
     
     
    public Country[] getCountries(){      
        return (Country[])countries.toArray(new Country[countries.size()]);      
    }
  
  public State[] getStates(){
       
        states = new ArrayList();
            Map pageFlow = (Map)AdfmfJavaUtilities.evaluateELExpression("#{pageFlowScope}");
            String countryName = (String)pageFlow.get("pCountryName");
                      
           
            if(countryName == null || "India".equals(countryName)){
                //NOTE: You can call a webservice here to get actual country data.
                states.add(new State("Delhi"));
                states.add(new State("Karnataka"));
            }
            else  if("US".equals(countryName)){
                //NOTE: You can call a webservice here to get actual country data.
               states.add(new State("California"));
                states.add(new State("Georgia"));
            }
           
           
            return (State[])states.toArray(new State[states.size()]);
           
        }
  
  
        public void refresh(){
            providerChangeSupport.fireProviderRefresh("states");
        }
      
    }


In above class I mainly have two getters, which returns list of countries and states. State getter is returning based on pageFlow variable pCountryName. It means if on selection of country we can set pCountryName in pageFlow. Which is easy.
I have also added provider change support on this class by adding 

protected transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this); 

and 


  public void addProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.addProviderChangeListener(l);
       }
       public void removeProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.removeProviderChangeListener(l);
       }  



There are two big problems we need to resolve
1. How would we ask framework to execute getStates once we have selected a project.

2. How would we ask framework to refresh UI LOV if getStates returns new set of data. 

Both of this can be done using providerChangeSuuport. You just need to add a refresh method on this class as 


public void refresh(){
            providerChangeSupport.fireProviderRefresh("states");
        }


Expose this class as a datacontrol. For this right click on
DependentLOVDC and select 'Create data control'.
You will wee following structure in Data Controls. 



 











===================================================================
Step 3. Create select-one-choice on page using datacontrol

 Drag and drop countries/states on your page and create select-one-choice as shown below.




















Select OK on next popup.

You will get following code in your amx

    <amx:selectOneChoice value="#{bindings.countries.inputValue}" label="#{bindings.countries.label}" id="soc1">
      <amx:selectItems value="#{bindings.countries.items}" id="si1"/>
    </amx:selectOneChoice>
    <amx:selectOneChoice value="#{bindings.states.inputValue}" label="#{bindings.states.label}" id="soc2">
      <amx:selectItems value="#{bindings.states.items}" id="si2"/>
    </amx:selectOneChoice>


AS we know in ADF when you select a choice list value and try to use valueChangeEvent.getNewValue(), it returns index but not the actual value. To avoid using valueChangeEvent.getNewValue() we can create an attribute binding in pageDef as 



================================================================
4. Set valuechange listner of LOV1 to refresh dataprovider of LOV2.


Create method action binding for refresh method of DependentLOVDC in pageDef.























Create a bean and register it as usual. Write valuechangelistener on country LOV as

    public void handleCountrySelection(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
       
        Map pageFlow = (Map)AdfmfJavaUtilities.evaluateELExpression("#{pageFlowScope}");
        String countryName = (String)valueChangeEvent.getNewValue();
       
        pageFlow.put("pCountryName", countryName);
       
        MethodExpression me = AdfmfJavaUtilities.getMethodExpression("#{bindings.refresh.execute}", Object.class, new Class[] {});
        me.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[]{});
    }


======================================================

Thats it.

Code flow is like

1. System create both lovs using datacontrol data provider
2. On selection of country, valuechange listener runs. Listener executes a bean method, which sets pageFlow variable pCountryName and calls refresh method of binding. Binding refresh points to refresh method of datacontrol.
3. Refresh method asks providerChangeSupport to refresh "states" dataprovider, which is nothing but getStates method of DependentLOVDC. Once data is changed, providerchangesupport will refresh on UI components showing data from that dataprovider.

Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.