Going Mobile with ADF: Programmatically Invoking SOAP Web Services with Complex Types

Introduction

This 5th article in the “Going Mobile with ADF” series explains how you programmatically invoke a SOAP web service from an ADF Mobile application. It illustrates how to handle in Java the complex type that might be returned by the web service, as well as how to set up the complex types that might be needed as a parameter for a web service call. The techniques are illustrated by building an ADF Mobile CRUD application against an ADF Business Components SDO service interface, but are applicable to any SOAP web service with complex types defined in the payload XSD.

Main Article

You might wonder why we need programmatic invocation of a SOAP web service at all. After all, it is easy and fast to create a web service data control and then use simple drag and drop actions from the data control palette to build the interface. Well, here are some common reasons that justify programmatic invocation of (SOAP) web services:

  • The structure of the web service payload might not map 1:1 to the data object structure needed to build the user interface.
  • Multiple web service calls might be needed to set up one data object instance.
  • You want to implement caching to reduce the number of web service calls and/or to allow for working in offline mode
  • You do not like the tight coupling between the web service payload and the user interface that is created when using drag and drop with the web service data control. By creating an intermediate layer of data objects, you can change the structure of the web service payload, or even the protocol (moving from SOAP-XML to REST-JSON for example) without the ADF mobile user interface being affected.

This article presents a pure Java solution for invoking web services with complex types. An alternative technique to invoking a web service with a complex parameter is illustrated in the video Web Service With Complex Parameter in ADF Mobile by ADF product manager Shay Shmeltzer. Shay delegates the construction of the complex type to the ADF binding layer. This techniques works equally fine, but in some use cases a 100% Java approach will be easier and faster. For example, when using drag and drop, the so-called “generic type” returned by a “read” call cannot be changed directly and used as input for an “update” call. So, you will need to copy over all the values to the value bindings of the update parameter type.

Setting up the ADF BC SDO Departments Service Interface

In this article we will use a SOAP web service that we created by using the SDO service interface on an ADF BC application module. More information on setting up such a service can be found in chapter 11 Integrating Service-Enabled Application Modules in the Fusion Developers’s Guide. Here is a screen shot of the service-enabled HRService application module that we are going to use in this article.

AdfbcSdoService

Note there is one (funny) error you easily might run into when testing your ADF BC SDO SOAP service: what do I do: seems an odd quirck of the EJB spec: The exception is: java.lang.StackOverflowError

SdoStackOverflow

The reason for this error is that by default the view link accessors are also traversed by the ADF BC SDO service interface. You then can get an endless loop resulting in a StackOverflow error with a department that has employees, but is also managed by an employee, and employees managed by other employees. The solution is to uncheck the checkbox Generate Property in SDO in the appropriate ViewLink definition, as shown below.

GeneratePropertyInSDO

Invoking the findDepartments Web Service Method

With the ADF BC SDO service up and running, we can start developing our ADF Mobile application. We first need to create a Department data object to store the values returned by the findDepartments web service call:

package oracle.ateam.mobile.soapdemo.model;

public class Department {

    private Integer departmentId;
    private String departmentName;
    private Integer managerId;
    private Integer locationId;

    public Department() {
        super();
    }

    public void setDepartmentId(Integer departmentId) {
        this.departmentId = departmentId;
    }

    public Integer getDepartmentId() {
        return departmentId;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setLocationId(Integer locationId) {
        this.locationId = locationId;
    }

    public Integer getLocationId() {
        return locationId;
    }
}

Next, we create a DepartmentService service object that invokes the findDepartments web service method, and create a list of Department objects from the result returned by the web service. Here is the skeleton code for this class:

package oracle.ateam.mobile.soapdemo.model;

import java.util.ArrayList;
import java.util.List;

public class DepartmentService {

    List departments = new ArrayList();

    public DepartmentService() {
        super();
        findAll();
    }

    public Department[] getDepartments() {
        Department[] departmentArray = (Department[])departments.toArray(new Department[departments.size()]);
        return departmentArray;
    }

    public void findAll() {
        // todo: add code to call findDepartments web service method and populate departments list
    }
}

We turn this class into a Data Control, so we can use the “departments” collection to create department list page and department edit page.

DepartmentServiceDC

To invoke the web service, we first need to create a data control for the web service. This might sound confusing as we are not going to use the web service data control to create the user interface. However, we will programmatically invoke the data control methods using a utility class provided by ADF Mobile. So, we first run the web service data control wizard and enter the WSDL of our ADF BC SDO service:

WSWizard

The resulting HRSOAPService data control looks like this

datacontrol

We will first invoke the findDepartments method without passing in values for the findCriteria or findControl arguments. The code to do this looks like this:

public void findAll() {
    try {
        GenericType result =
            (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("HRSOAPService", null, "findDepartments",
                                                                    new ArrayList(), new ArrayList(),
                                                                    new ArrayList());
        // todo: convert result in list of departments
    } catch (Exception e) {
        throw new AdfException(e.getLocalizedMessage(), AdfException.ERROR);
    }
}

To get a better understanding of the result returned by the web service, it helps to run the app in debug mode and inspect the result using the debug data window:

DebugData

As you can see, the result contains a vector of nested attributes. Complex attribute values are returned as a GenericType, Simple payload types are returned as String or Integer attribute values. Metadata information about each attribute can be obtained by inspecting the attributeInfo vector. The top-level vector represents 27 attributes which means 27 departments are returned by the web service.

The easiest way to convert this result into a list of departments is by using the GenericTypeBeanSerializationHelper class as illustrated below:

public void findAll() {
    try {
        GenericType result =
            (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("HRSOAPService", null, "findDepartments",
                                                                    new ArrayList(), new ArrayList(),
                                                                    new ArrayList());
        for (int i = 0; i < result.getAttributeCount(); i++) {
            // Get each individual GenericType instance that holds the attribute key-value pairs of department
            GenericType entityGenericType = (GenericType)result.getAttribute(i);
            // Now create Department instance out of this GenericType
            // this works fine if payload attr names match department attr names
            Department department =
                (Department)GenericTypeBeanSerializationHelper.fromGenericType(Department.class,
                                                                               entityGenericType);
            departments.add(department);
        }
    } catch (Exception e) {
        throw new AdfException(e.getLocalizedMessage(), AdfException.ERROR);
    }
}

This technique is easy and fast and works when the payload attributes map one-to-one to attributes in your data object.Note that even child collections are populated correctly. So, if we create a new Employee class with (some of) the employee attributes as included in the payload, and we add the following code to our Department data object:

private Employee[] employeesView;

public void setEmployeesView(Employee[] employeesView) {
    this.employeesView = employeesView;
}

public Employee[] getEmployeesView() {
    return employeesView;
}

then the EmployeesView array attribute will be populated as well, which allows us to create a “department and its employees” master-detail page.

However, as discussed above, one of the common reasons for programmatic invocation is that the payload data structure does not match 1:1 to the data object structure you want to use for your user interface. In such a case, it is unlikely you can use the GenericTypeBeanSerializationHelper class. Here is sample code that create the list of departments (and employees) without using this utility class:

for (int i = 0; i < result.getAttributeCount(); i++) {
    // Get each individual GenericType instance that holds the attribute key-value pairs of department
    GenericType depGenericType = (GenericType)result.getAttribute(i);
    Department department = new Department();
    department.setDepartmentId((Integer)depGenericType.getAttribute("DepartmentId"));
    department.setDepartmentName((String)depGenericType.getAttribute("DepartmentName"));
    department.setManagerId((Integer)depGenericType.getAttribute("ManagerId"));
    department.setLocationId((Integer)depGenericType.getAttribute("LocationId"));
    if (depGenericType.getAttributeCount()>4) {
        // additional attributes must be EmployeesView attributes, where each instance represents an
        // employee working in the department
        for (int j = 4; j < depGenericType.getAttributeCount(); j++) {
            GenericType empGenericType = (GenericType)depGenericType.getAttribute(j);
            Employee employee = new Employee();
            employee.setEmployeeId((Integer)empGenericType.getAttribute("EmployeeId"));
            employee.setFirstName((String)empGenericType.getAttribute("FirstName"));
            employee.setLastName((String)empGenericType.getAttribute("LastName"));
            department.addEmployee(employee);
        }
    }
    departments.add(department);
}

You might be a bit puzzled by the code to add the employees. it helps when you understand that the employee generic type instances are not wrapped in one “vector attribute” of the department generic type. Instead, each employee generic type is directly added as an attribute to the department generic type, so the number of attributes varies depending on the number of employees working in the department.

We also changed the Department class to make it easier to add individual employees:

private List employees = new ArrayList();

public void addEmployee(Employee employee) {
    employees.add(employee);
}

public Employee[] getEmployeesView() {
    Employee[] employeeArray = (Employee[])employees.toArray(new Employee[employees.size()]);
    return employeeArray;
}

Invoking the findDepartments Web Service Method with FindCriteria Argument Specified

So far, we have seen how we can handle and convert complex types returned by the web service to Java objects. Now, we will look at the reverse: setting up a complex type that is passed as argument into the web service call. We will specify the findCriteria argument of the findDepartments method to filter on DepartmentName field. First, we need to understand how the findCriteria argument is composed. One way to do this is by expanding the findDepartments_parameters object in the data control palette, as shown below.

findCriteriaParam

However, this doesn’t show all the information that resides in the XSD. The value attribute under the item attribute is defined within a choice element, it can be either a simple type or a nested ViewCriteria. This information is revealed when looking at the HRServiceService.xsd in JDeveloper (this xsd file is added to the Resources folder after running the web service data control wizard):

findCriteriaXSD

Now that we have a complete picture of the findCriteria object,we can construct this argument in Java. For each complex type, we need to create a GenericType instance, and the attribute of a GenericType instance is either a simple type, or a nested GenericType, similar to the structure of  the result returned by the findDepartments method. Here is the Java code of the find method that performs the web service call with this argument:

public void find(String searchValue) {
    try {
        Department[] oldDeps = getDepartments();
        departments.clear();
        List paramNames = new ArrayList();
        paramNames.add("findCriteria");
        // set up the findCriteria generic type
        GenericVirtualType findCriteria = new GenericVirtualType(null, "findCriteria");
        GenericVirtualType filter = new GenericVirtualType(null, "filter");
        findCriteria.defineAttribute(null, "filter", GenericType.class, filter);
        GenericVirtualType group = new GenericVirtualType(null, "group");
        filter.defineAttribute(null, "group", GenericType.class, group);
        GenericVirtualType item = new GenericVirtualType(null, "item");
        group.defineAttribute(null, "item", GenericType.class, item);
        item.defineAttribute(null, "attribute", String.class, "DepartmentName");
        item.defineAttribute(null, "operator", String.class, "like");
        item.defineAttribute(null, "value", String.class, searchValue + "%");
        item.defineAttribute(null, "upperCaseCompare", Boolean.class, Boolean.TRUE);

        List paramValues = new ArrayList();
        paramValues.add(findCriteria);
        List paramTypes = new ArrayList();
        paramTypes.add(GenericType.class);
        GenericType result =
            (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("HRSOAPService", null, "findDepartments",
                                                                    paramNames, paramValues, paramTypes);
        for (int i = 0; i < result.getAttributeCount(); i++) {
            GenericType entityGenericType = (GenericType)result.getAttribute(i);
            Department department =
                (Department)GenericTypeBeanSerializationHelper.fromGenericType(Department.class,
                                                                               entityGenericType);
            departments.add(department);
        }
        Department[] newDeps = getDepartments();
        propertyChangeSupport.firePropertyChange("departments", oldDeps, newDeps);
        providerChangeSupport.fireProviderRefresh("departments");
    } catch (Exception e) {
        throw new AdfException(e.getLocalizedMessage(), AdfException.ERROR);
    }
}

As you can see in line 26, we now pass in three array lists, all with one entry: the name of the parameter, the value and the type. In lines 8-18 we create the findCriteria generic type and the nested complex attributes filter, group and item by instantiating instances of GenericVirtualType. This class implements the GenericType interface. We also keep track of the old and new departments array so we can notify the user interface to update the list after the find method has been invoked.

We can now drag and drop this find method onto the department list page as an ADF Mobile Parameter Form, and use the Find button to invoke the web service with the value of the searchValue field passed in as search value for department name.As you can see below the search is case insensitive because we have set the value for the upperCaseCompare attribute to Boolean.TRUE.

SearchDepartment2

Some complex types are defined as enumeration, this is for example the case with the conjunction type, which can be inspected in the BC4JService.xsd, also located in the Resources folder.

conjunction

In Java code, this type should not be defined using a GenericVirtualType instance, instead it should be defined as an attribute of type Enumeration.class. Here is some code where we use the conjunction to extend the above findCriteria to also query the department with id 60, regardless of the search value entered for department name:

GenericVirtualType item2 = new GenericVirtualType(null, "item");
group.defineAttribute(null, "item", GenericType.class, item2);
item2.defineAttribute(null, "attribute", String.class, "DepartmentId");
item2.defineAttribute(null, "operator", String.class, "=");
item2.defineAttribute(null, "value", Integer.class, new Integer(60));
item.defineAttribute(null, "conjunction", Enumeration.class, "Or");

Invoking the mergeDepartments Web Service Method

Now that we learned how to create the complex nested findCriteria type in Java, calling the createDepartments, updateDepartments or mergeDepartments methods becomes straightforward.In the sample app we will call the mergeDepartments method so we can use one button in the “edit department” page to both insert a new department or update an existing department. The mergeDepartments XSD snippet looks like this:

mergeDepartmentsXSD

So, we can create the mergeDepartments method in our DepartmentService class as follows:

public void mergeDepartment(Department department) {
    try {
        List paramNames = new ArrayList();
        paramNames.add("departments");
        List paramValues = new ArrayList();
        GenericVirtualType depGenericType = new GenericVirtualType(null, "departments");
        depGenericType.defineAttribute(null, "DepartmentId", Integer.class, department.getDepartmentId());
        depGenericType.defineAttribute(null, "DepartmentName", String.class, department.getDepartmentName());
        depGenericType.defineAttribute(null, "ManagerId", Integer.class, department.getManagerId());
        depGenericType.defineAttribute(null, "LocationId", Integer.class, department.getLocationId());
        paramValues.add(depGenericType);
        List paramTypes = new ArrayList();
        paramTypes.add(GenericType.class);
        GenericType result =
            (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("HRSOAPService", null, "mergeDepartments",
                                                                    paramNames, paramValues, paramTypes);
        // result is genericType that holds attributes of department inserted/updated
        // of there are server-derives values, you could set those on department instance passed
        // into this method.

    } catch (Exception e) {
        throw new AdfException(e.getLocalizedMessage(), AdfException.ERROR);
    }
}

As you can see, the code is similar to the way we constructed the findCriteria object. However, if the data object structure matches the payload structure, which is the case in our example, we can make it even simpler by using the GenericTypeBeanSerializationHelper class, just like we did when processing the result from the findDepartments method call. This time, we use the toGenericType method rather than the fromGenericType method. This allows us to create the department generic type with just one line of code:

        // this works fine if payload attr names match department attr names
        GenericVirtualType depGenericType =
                (GenericVirtualType) GenericTypeBeanSerializationHelper.toGenericType("HRSOAPService.Types.mergeDepartments.departments", department);
        paramValues.add(depGenericType);

We can now drag and drop the mergeDepartments method as a button onto our “edit department” page and label the button with “Save”. With this information it is trivial to implement the deleteDepartments method as well. If you download the sample app (see links at the bottom of this article), you will have the full CRUD functionality available.

Adding the HRSOAPService as Data Control Usage

If you followed the above steps and created the department list and department edit pages using drag and drop actions from the DepartmentService data control, you will get the following error when running the application: Unable to read DataControl Usages, on loadDataControl for id: HRSOAPService.

DcUsageError

This error occurs because we only invoke the HRSOAPService data control programmatically. Since we did not use it for drag and drop actions, we need to manually add it as a data control usage in DataBindings.cpx file:

<dataControlUsages>
    <dc id="DepartmentService" path="oracle.ateam.mobile.soapdemo.ui.DepartmentService"/>
    <dc id="HRSOAPService" path="oracle.ateam.mobile.soapdemo.ui.HRSOAPService"/>
</dataControlUsages>

With the entry for HRSOAPService inside the dataControlUsages element, the error will be gone.

Using the SQLite Database for On-Device Data Caching

So far. we implemented a layer of data objects that holds all information coming from the web service calls. The Amx pages we created are nicely decoupled from the web service calls, they work against our layer of data objects. So, after initial data load, we can use the application in offline mode. However, if we exit the application, we loose all the data cached in data objects. So, the next step is to store the data in the on-device SQLite database, which allows us to create data object instances from the SQLite data at application start up when working in offline mode. It would also allow us to implement the search functionality locally against the SQLite database, rather than invoking a more expensive web service call. The Oracle A-team has developed a JDeveloper extension that you can install, which makes it fast and easy to implement this on-device persistence and data caching. It also removes the need for most of the CRUD-style coding explained in this article, as it contains a generic SOAPPersistenceManager class that you can plug in. For more information, and download of the extension, please see the article Going Mobile with ADF – Implementing Data Caching and Syncing for Working Offline.

Sample Download and Install

The downloads are JDeveloper 11.1.2.4 applications. The ADF Mobile app requires you have installed the latest ADF Mobile extension (build 11.1.2.4.39.64.51) from the update center which has become available in october 2013.

  • HrDemoSOAP.zip: the server-side ADF application that contains the ADF BC SDO service. To run the service, open the workspace in JDeveloper, right-mouse-click on the HRServiceServiceImpl.java class and choose Run. This will launch the weblogic server and deploys the ADF BC service as a SOAP web service.
  • HRProgSoap.zip: the ADF Mobile CRUD application that uses the ADF BC SDO web service. Before you can run the application, you need to modify the IP address in connections.xml to point to the machine where you are running the SOAP web service.To preserve the current row in the edit page when clicking on a department in the list page, the sample uses the technique as explained in article ADF Mobile – Preserving the Current Row Across Pages. As explained in that article you need to install the CustomBeanDCDefinition extension jar file in the [jdev_home]\jdev\extensions directory.

Sample

Comments

  1. manish pandey says:

    Hi,

    i am getting AdfInvocation Exception when i run find method.

    but findAll method is running well
    GenericType result =
    (GenericType)AdfmfJavaUtilities.invokeDataControlMethod(“HRSOAPService”, null, “findDepartments”,
    new ArrayList(), new ArrayList(),
    new ArrayList());

    please help me. i have stuck on this.

  2. Veeresh Gandhaveedi says:

    Hi Steven,

    I am trying to creat a Java Data control from web service data control and trying to cast the response to POJO class.

    I am using below code to cast values from GenericTpe to UserInfo bean, After doing this still my bean attribute values are “null”.

    GenericType result =
    (GenericType)AdfmfJavaUtilities.invokeDataControlMethod(“RS”, null, “A_USER_INFORMATION”,
    pnames, params,
    ptypes);

    for (int i = 0; i < result.getAttributeCount(); i++) {
    // Get each individual GenericType instance that holds the attribute key-value pairs of department
    GenericType depGenericType = (GenericType)result.getAttribute(i);
    userInfo =
    (UserInfo)GenericTypeBeanSerializationHelper.fromGenericType(UserInfo.class, depGenericType);
    }

    Am I missing anything here? Please suggest how to resolve this. My webservice response for UserInfo method is as follows;

    Generic Type: [http://xmlns.oracle.com/apps/csf/soaprovider/plsql/xxxxxxxxxx/a_user_information/,
    OutputParameters] namespace= http://xmlns.oracle.com/apps/csf/soaprovider/plsql/xxxxxxxxxx/a_user_information/name=
    X_USER_TBL_ITEM value= Generic Type: [null, X_USER_TBL_ITEM] namespace=
    http://xmlns.oracle.com/apps/csf/soaprovider/plsql/xxxxxxxxxx/a_user_information/name= USER_NAME value= VEERESH
    namespace=http://xmlns.oracle.com/apps/csf/soaprovider/plsql/xxxxxxxxxx/a_user_information/name= USER_ID value= 111111
    namespace=http://xmlns.oracle.com/apps/csf/soaprovider/plsql/xxxxxxxxxx/a_user_information/name= APPL_ID value= 000001
    namespace=http://xmlns.oracle.com/apps/csf/soaprovider/plsql/xxxxxxxxxx/a_user_information/name= RESPONSIBILITY_ID value= 000002

    thanks,
    Veeresh

Add Your Comment