15 Using Web Services in MAF AMX

This chapter describes how to integrate a third-party web service into the MAF AMX application feature implementation.

This chapter includes the following sections:

15.1 Introduction to Using Web Services in MAF Applications

Web services allow applications and their features to exchange data and information through defined application programming interfaces. Using web services you can expose business functionality irrespective of the platform or language of the originating application because the business functionality is exposed in such a way that it is abstracted to a message composed of standard XML constructs that can be recognized and used by other applications.

In a MAF application, you use web services to interact with remote data sources. In particular:

  • To query data in remote data sources.

  • To write data to and from remote data sources.

Some of the most typical use cases for employing web services in MAF applications are:

  • To add functionality that is readily available as a web service, but which would be time-consuming to develop within the application.

  • To provide access to an application that runs on a different architecture.

Using web services in your MAF application enables you to do the following:

  • Choose from a subset of functionality exposed on the web service. Since you cannot request more enterprise data than offered by the web services, you can deal with the limitation of which enterprise data can and cannot be accessed by reducing the number of application features exposed in a web service data control.

  • Provide functionality that is too computationally intensive for the mobile device's resources. This could be due to either the actual amount of work the device would need to perform, or the fact that the functionality is based on a much larger data set than the one that is locally available on the device.

    Note:

    You may consider outsourcing the computationally intensive functionality to the service (the server side).

MAF supports consumption of both SOAP and REST web services. Before deciding which type to use, consider the following:

  • SOAP with its large payloads and verbose XML schemas can have an impact on performance of your MAF application. It is recommended to either use REST web services (if available) or inject a middleware solution such as Oracle Service Bus (see http://www.oracle.com/technetwork/middleware/service-bus/overview/index.html) to transform payloads from SOAP to REST JSON.

  • Since MAF is not an extract, transform and load tool (ETL), you would have to write code to accommodate complex web service payloads, which would result in decreased performance as well as code complexity. Using a middleware solution such as Oracle Service Bus can help with shaping the data payloads suitable for mobile environment.

The following web service scenarios usage demonstrate the data access (scenario 1) as well as computational and data-driven functionality (scenarios 2 and 3):

  • Fetch a set of Opportunity data from the enterprise data store to enable the end user to manipulate it on the device, and then post changes back to the enterprise data store through the web service.

  • Request a report be generated on some enterprise data, and then fetch the report.

  • Obtain a map image of a route to a customer site.

The most common way of using web services in an application feature developed with MAF is to create a data control for an external web service. For more information, see the following:

15.2 Creating a Web Service Data Control Using REST

JDeveloper lets you create a data control for an existing REST web service. This REST web service returns an XML response.

Note:

If you are working behind a firewall and you want to use a web service that is outside the firewall, you must configure the Web Browser and Proxy settings in JDeveloper. For more information, see Section 15.10, "Configuring the Browser Proxy Information."

You can associate a REST web service data control with one or more HTTP methods using the same connection. You should be able to access custom operations exposed by a REST service. These custom operations mat to one of the HTTP methods and allow you to create a data control to expose these custom operations on the client.

To use security and notifications functionality on mobile devices, you can add custom headers and custom values to standard HTTP headers for use with specific operations exposed by the REST data control.

Before you begin:

Ensure that you have access to the REST web service that the data control is to access.

To create a REST web service data control:

  1. In the Applications window, right-click the application name, and then select File > New > From Gallery from the main JDeveloper menu.

  2. In the New Gallery dialog, expand the Business Tier node on the left and select Web Services. From the Items list on the right select Web Service Data Control (SOAP/REST) (see Figure 15-4), and then click OK.

  3. On the Data Source page of the Create Web Service Data Control wizard, select REST, as Figure 15-1 shows.

    Figure 15-1 Defining Data Source for REST Web Service Data Control

    This image is described in the surrounding text
  4. Follow the Create Web Service Data Control wizard instructions to complete creation of the data control, keeping in mind the following:

    • MAF supports only basic authentication for web services (see Section 15.7, "Accessing Secure Web Services"). When creating a new connection, select Basic in the Authentication Type field on the Create URL Connection dialog.

    • MAF supports all HTTP method types: GET, POST, PUT, and DELETE. You can select any of these method types when completing the Method Display Name fields on the Resources page, as Figure 15-2 shows.

      Note:

      You can include all four methods using the same connection and the same REST web service data control.

      Figure 15-2 Defining Resources for REST Web Service Data Control

      This image is described in the surrounding text
    • For each resource method on the Method Details page (see Figure 15-3), you must provide an XSD file reference for the XML payload and response content. The XSD has to be available as a file in the ViewController project.

      Figure 15-3 Defining Method Details for REST Web Service Data Control

      This image is described in the surrounding text

      Note:

      Since MAF creates internal definitions for the XSD structures at compile time, the XSD should not change after the application has been compiled. Therefore, it is recommended to reference the XSD file locally, which also provides a benefit of allowing to use a MAF application offline.

      Using the remote XSD negatively affects performance because MAF retrieves the XSD with each run of the application.

After the REST web services data control has been created by following the preceding steps, it behaves identically to its counterparts provided by other technologies available through JDeveloper.

A MAF sample application called RESTDemo (located in the PublicSamples.zip file within the jdev_install/jdeveloper/jdev/extensions/oracle.maf/Samples directory on your development computer) demonstrates how to use REST web services in a MAF application.

For information on how to use REST web services through Java bypassing data controls, see Section 15.8.2, "How to Use REST Web Services Adapter."

15.3 Creating a Web Service Data Control Using SOAP

JDeveloper lets you create a data control for an existing SOAP web service using only the Web Services Description Language (WSDL) file for the service. You can either browse to a WSDL file on the local file system, locate one in a Universal Description, Discovery and Integration (UDDI) registry, or enter the WSDL URL directly.

Note:

If you are working behind a firewall and you want to use a web service that is outside the firewall, you must configure the Web Browser and Proxy settings in JDeveloper. For more information, see Section 15.10, "Configuring the Browser Proxy Information."

To create a SOAP web service data control:

  1. In the Applications window, right-click the application name, and then select File > New > From Gallery from the main JDeveloper menu.

  2. In the New Gallery dialog, expand the Business Tier node on the left and select Web Services. From the Items list on the right select Web Service Data Control (SOAP/REST) (see Figure 15-4), and then click OK.

    Figure 15-4 Creating a New SOAP Web Service Data Control

    This image is described in the surrounding text
  3. On the Data Source page of the Create Web Service Data Control wizard, select SOAP.

  4. Follow the wizard instructions to complete creation of the data control.

Note:

MAF supports the following encoding styles for both SOAP 1.1 and 1.2 versions:
  • Document/literal

  • Document/wrapped

  • RPC

15.3.1 How to Customize SOAP Headers

MAF allows you to specify a custom provider class in your DataControls.dcx file. This custom class extends oracle.adfinternal.model.adapter.webservice.provider.soap.SOAPProvider. You can use it to specify an implementation of the SoapHeader[] getAdditionalSoapHeaders() method.

The following example shows how to extend the SOAPProvider and create a custom header demonstrated in the next example.

package provider.ebs.soap; 

import oracle.adfinternal.model.adapter.webservice.provider.soap.SOAPProvider;
import oracle.adfinternal.model.adapter.webservice.provider.soap.SoapHeader;

public class EBSSOAPProvider extends SOAPProvider {

public SoapHeader[] getAdditionalSoapHeaders() {
   SoapHeader header[] = new SoapHeader[2]; 
   SoapHeader token = null; 
   SoapHeader user = null; 
   SoapHeader pass = null; 

   header[0] = new SoapHeader("http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/",
                              "SOAHeader"); 
   header[0].addChild(new SoapHeader(
                              "http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/",
                              "Responsibility",
                              "SYSTEM_ADMINISTRATOR"));
   header[0].addChild(new SoapHeader(
                              "http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/",
                              "RespApplication",
                              "SYSADMIN")); 
   header[0].addChild(new SoapHeader(
                              "http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/",
                              "SecurityGroup",
                              "STANDARD"));
   header[0].addChild(new SoapHeader(
                              "http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/",
                              "NLSLanguage", 
                              "AMERICAN")); 
   header[0].addChild(new SoapHeader(
                              "http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/",
                              "Org_Id", 
                              "0"));

   header[1] = new SoapHeader(
               "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
               "Security");
   token = new SoapHeader(
              "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
              "UsernameToken");
   user = new SoapHeader(
              "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
              "Username",
              "sysadmin");
   pass = new SoapHeader(
              "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
              "Password",
              "sysadmin");

   header[1].addChild(token);
   token.addChild(user);
   token.addChild(pass); 

   return header;
   }
}

The following example shows the new custom header.

<soap:Header xmlns:ns1="http://xmlns.oracle.com/apps/fnd/soaprovider/plsql/fnd_user_pkg/">
   <ns1:SOAHeader> 
      <ns1:Responsibility>SYSTEM_ADMINISTRATOR</ns1:Responsibility>
      <ns1:RespApplication>SYSADMIN</ns1:RespApplication>
      <ns1:SecurityGroup>STANDARD</ns1:SecurityGroup>
      <ns1:NLSLanguage>AMERICAN</ns1:NLSLanguage>
      <ns1:Org_Id>0</ns1:Org_Id>
   </ns1:SOAHeader>
   <wsse:Security xmlns:wsse=
      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
      xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
      xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" 
      soap:mustUnderstand="1">
      <wsse:UsernameToken xmlns:wsse=
      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
      xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <wsse:Username>sysadmin</wsse:Username>
         <wsse:Password Type=
          http://docs.oasis-open.org/wss/2004/01/
          oasis-200401-wss-username-token-profile-1.0#PasswordText">sysadmin</wsse:Password>
      </wsse:UsernameToken>
   </wsse:Security>
</soap:Header>

@return

Note:

There is no predefined mechanism for passing variables into a class that extends SOAPProvider. Standard Java techniques should be sufficient. For example, you can add member variables and accessors to the class; then you can refer to them when setting the SoapHeader[] content returned by the getAdditionalSoapHeaders() method.

The following example demonstrates a sample DataControls.dcx file entry with the SOAPProvider registration.

<definition xmlns="http://xmlns.oracle.com/adfm/adapter/webservice"
                   name="SoapService" version="1.0"
                   provider="provider.ebs.soap.EBSSOAPProvider"
                   wsdl="http://@SRG_WS_HOST@:@SRG_WS_PORT@/SoapService/SoapServicePort?wsdl" >
   <service name="SoapService" namespace="http://model/"
            connection="SoapService"> 
      <port name="SoapServicePort">
         <operation name="echoSoapHeader"/>
      </port> 
   </service>
</definition>

Note:

You cannot specify dynamic SOAP headers using MAF.

15.3.2 How to Access Objects Returned by SOAP Calls

JDeveloper adds a dcStructureVersion property to the DataControls.dcx file for every web service data control created from a SOAP service reference. By default, this property is set to 2.

Tip:

If your application was developed using one of the previous versions of MAF and you intend to continue using the same code to access the response from Java, you should remove this property or change its value to 1.

The dcStructureVersion property serves customization purposes and impacts the structure of the result returned by the web service data control.

To access an object returned from a SOAP call, you should use code similar to the following:

result = (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("WeatherSOAP",
                                         null,
                                         "GetCityWeatherByZIP",
                                         pnames,
                                         pvals,
                                         ptypes);
// access SOAP object to obtain information about the returned object
result.get("City");

When working with collections, consider using code similar to the following:

result = (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("WeatherSOAP", 
                                         null,
                                         "GetCityWeatherByZIP", 
                                         pnames, 
                                         pvals, 
                                         ptypes);
result = result!=null && result.getParent()!=null ? result.getParent() : result;

15.4 What You May Need to Know About Web Service Data Controls

After the web service data control has been created, the web service operations and return values of the operations are displayed in the Data Controls window, as illustrated in Figure 15-5.

Figure 15-5 Web Service Data Control

The surrounding text describes this image.

Like other data controls, you can drag and drop the objects returned by the web service operations to create user interface components in a MAF AMX page. For more information, see Section 12.3.3, "How to Add Data Controls to a MAF AMX Page." When data returned from a web service operation is displayed, the following object types are handled:

  • Collections

  • Complex objects returned by a web service operation

  • Nested complex objects returned by a web service operation

Using a web service operation, both standard and complex data types can be updated and deleted.

As illustrated by Figure 15-5, each data control object is represented by an icon. Table 15-1 describes what each icon represents, where it appears in the Data Controls panel hierarchy, and what components it can be used to create. For more information see Section 14.6, "Creating Databound UI Components from the Data Controls Panel."

Table 15-1 Data Controls Panel Icons and Object Hierarchy for Web Services

Icon Name Description Used to Create...

The surrounding text describes this image.

Data Control

Represents a data control. You cannot use the data control itself to create UI components, but you can use any of the child objects listed under it. Typically, there is one data control for each web service.

Serves as a container for other objects and is not used to create anything.

The surrounding text describes this image.

Collection

Represents a data collection returned by an operation on the service. Collections also appear as children under method returns, other collections, or structured attributes. The children under a collection may be attributes, other collections, custom methods, and built-in operations that can be performed on the collection.

Forms, tables, graphs, trees, range navigation components, and master-detail components. See also Section 13.5, "Providing Data Visualization."

The surrounding text describes this image.

Attribute

Represents a discrete data element in an object (for example, an attribute in a row). Attributes appear as children under the collections or method returns to which they belong.

Label, text field, date, list of values, and selection list components. See also Section 13.2, "Designing the Page Layout."

The surrounding text describes this image.

Structured Attribute

Represents a returned object that is a complex type but not a collection. For example, a structured attribute might represent a single user assigned to the current service request.

Label, text field, date, list of values, and selection list components. See also Section 13.2, "Designing the Page Layout."

The surrounding text describes this image.

Method

Represents an operation in the data control or one of its exposed structures that may accept parameters, perform some business logic and optionally return single value, a structure or a collection of those.

Command components.

For methods that accept parameters: command components and parameterized forms. See also Section 13.3, "Creating and Using UI Components."

The surrounding text describes this image.

Method Return

Represents an object that is returned by a web service method. The returned object can be a single value or a collection.

A method return appears as a child under the method that returns it. The objects that appear as children under a method return can be attributes of the collection, other methods that perform actions related to the parent collection, and operations that can be performed on the parent collection.

When a single-value method return is dropped, the method is not invoked automatically by the framework. You should either drop the corresponding method as a button to invoke the method, or if working with task flows you can create a method activity for it.

The same components as for collections and attributes and for query forms.

The surrounding text describes this image.

Operation

Represents a built-in data control operation that performs actions on the parent object. Data control operations are located in an Operations node under collections. If an operation requires one or more parameters, they are listed in a Parameters node under the operation.

The following operations for navigation and setting the current row are supported: First, Last, Next, Previous, setCurrentRowWithKey, and SetCurrentRowWithKeyValue. Execute is supported for refreshing queries. Create and Delete are available as applicable, depending on the web service operation. Because the web service data controls are not transactional, Commit and Rollback are not supported.

User interface command components, such as buttons, links, and menus. For more information, see Section 13.3, "Creating and Using UI Components."

The surrounding text describes this image.

Parameter

Represents a parameter value that is declared by the method or operation under which it appears. Parameters appear in the Parameters node under a method or operation.

Array and structured parameters are exposed as updatable structured attributes and collections under the data control, which can be dropped as an ADF form or an updatable table on the UI. You can use the UI to build a parameter that is an array or a complex object (not a standard Java type).

Label, text, and selection list components. For more information, see Section 13.3.15, "How to Use List View and List Item Components."


15.5 Creating a New Web Service Connection

The connection information for the web service is stored in the connections.xml file along with the other connections in your application. You do not need to explicitly create this file, as it is generated in the.adf/META-INF directory by the New Web Service Data Control wizard at the time when the web service data control is created (see Section 15.2, "Creating a Web Service Data Control Using REST" and Section 15.3, "Creating a Web Service Data Control Using SOAP").

You modify the connection settings by editing the connections.xml file.

15.6 Adjusting the End Point for a Web Service Data Control

After creating a web service data control, you can modify the end point of the URI. This is useful in such cases as when you migrate an application feature from a test to production environment.

You modify the end point by editing the connections.xml file.

15.7 Accessing Secure Web Services

MAF supports both secured and unsecured web services. For more information, see Chapter 29, "Securing MAF Applications."

To access secured web services from your MAF application, you may need to configure web service data controls included in the application.

15.7.1 How to Enable Access to SOAP-Based Web Services

The following predefined security policies are supported for SOAP-based web services:

  • oracle/wss_http_token_client_policy

  • oracle/wss_http_token_over_ssl_client_policy

  • oracle/http_basic_auth_over_ssl_client_policy

  • oracle/wss_username_token_client_policy

  • oracle/wss_username_token_over_ssl_client_policy

For descriptions of these policies and their usage, see the "Determining Which Predefined Policies to Use" and "Predefined Policies" chapters in Oracle Fusion Middleware Securing Web Services and Managing Policies with Oracle Web Services Manager.

If a SOAP web service is secured, you can access it by configuring the web service data control with either oracle/wss_http_token_over_ssl_client_policy or oracle/wss_http_token_client_policy. To do so, use the Edit Data Control Policies dialog that Figure 15-6 shows. You can open this dialog as follows:

  • In the Applications window, select the .dcx file located in the application's view controller project.

  • In the Structure window, right-click the web service data control that you would like to configure, and then select Define Web Service Security from the context menu.

Figure 15-6 Editing Web Service Data Control Policies

This image is described in the surrounding text

Note:

JDeveloper stores the web service policy definitions in the wsm-assembly.xml file (located in META-INF directory of the application workspace).

15.7.2 How to Enable Access to REST-Based Web Services

Each time a MAF application requests a REST web service for cookie-based authorization, MAF's security framework enables the transport layer of the REST web service to check if cookie injection is enabled for the login connection associated with the URL endpoint of the REST web service. That is, the connections.xml file must include <injectCookiesToRESTHttpHeader value="true"/>. For details about configuring MAF application login to enable the login server cookie in REST web service calls, see Section 29.4.3, "How to Configure Authentication Using Oracle Mobile and Social Identity Management."

This is also true for an Oracle Access Management Mobile and Social (OAMMS) authentication context and the web resource is protected by an Oracle Access Manager server 10g WebGate, where the access token is injected as the encrypted cookie called the ObSSOCookie. For more information, see Section 29.4.13, "What Happens When You Enable Cookie Injection into REST Web Service Calls."

When a REST web service is expecting an OAuth access token, you must associate the REST connection with the predefined security policy that supports REST web services: oracle/oauth2_config_client_policy. Currently, the design time in JDeveloper does not support associating the policy. You must edit the REST connection to manually attach the policy set to the web service, where URI is set to oracle/oauth2_config_client_policy and someapi is the connection name, as illustrated in the following example.

<sca11:policySet xmlns:sca11="http://docs.oasis-open.org/ns/opencsa/sca/200912"
                 name="policySet"
                 attachTo="MODULE('someapi')"
   <wsp:PolicyReference xmlns:wsp="http://www.w3.org/ns/ws-policy"
                        DigestAlgorithm="http://www.w3.org/ns/ws-policy/Sha1Exc"
                        URI="oracle/oauth2_config_client_policy"
                        orawsp:status="enabled" orawsp:id="1"/>
</sca11:policySet>

For example, you would configure this policy when an OAMMS server is configured to authenticate against a social authentication server and the MAF application needs to invoke social service provider's REST API.

15.7.3 What You May Need to Know About Credential Injection

For secured web services, the user credentials are dynamically injected into a web service request at the time when the request is invoked. This process is similar for SOAP and REST web services.

MAF uses Oracle Web Services Manager (OWSM) Mobile Agent to propagate user identity through web service requests.

Before web services are invoked, the user must respond to an authentication prompt triggered by the user trying to invoke a secured MAF application feature or to start the application controlled by the access control service (ACS). In the latter case, the application must define a default login server with ACS URL, as well as to have at least one feature with a constraint that depends on the user.roles setting. The user credentials are stored in a credential store—a device-native and local repository used for storing credentials associated with the authentication provider's server URL and the user. At runtime, MAF assumes that all credentials have already been stored in the IDM Mobile credential store before the time of their usage.

In the connections.xml file, you have to specify the login server connection's adfCredentialStoreKey attribute value in the adfCredentialStoreKey attribute of the web service connection reference in order to associate the login server to the web service security (see the following two examples).

Note:

Since JDeveloper does not provide an Overview editor for the connections.xml file, you can use the Properties window to update the <Reference> element's adfcredentialStoreKey attribute with the name configured for the adfCredentialStoreKey attribute of the login server connection. Alternatively, you can add or update the attribute using the Source editor.

The following example shows the definition of the web service connection referenced as adfCredentialStoreKey="MyAuth", where MyAuth is the name of the login connection reference.

<Reference name="URLConnection1"
           className="oracle.adf.model.connection.url.HttpURLConnection"
           adfCredentialStoreKey="MyAuth"
           xmlns="">
   <Factory className="oracle.adf.model.connection.url.URLConnectionFactory"/>
   <RefAddresses>
      <XmlRefAddr addrType="URLConnection1">
         <Contents>
            <urlconnection name="URLConnection1"
                           url="http://myhost.us.example.com:7777/
                                SecureRESTWebService1/Echo">
               <authentication style="challange">
                  <type>basic</type>
                  <realm>myrealm</realm>
               </authentication>
            </urlconnection>
         </Contents>
      </XmlRefAddr>
      <SecureRefAddr addrType="username"/>
      <SecureRefAddr addrType="password"/>
   </RefAddresses>
</Reference>

The following example shows the definition of the login connection, where MyAuth is used as the credential store key value in the login server connection.

<Reference name="MyAuthName"
           className="oracle.adf.model.connection.adfmf.LoginConnection"
           adfCredentialStoreKey="MyAuth"
           partial="false"
           manageInOracleEnterpriseManager="true"
           deployable="true"
           xmlns="">
   <Factory className="oracle.adf.model.connection.adfmf.LoginConnectionFactory"/>
   <RefAddresses>
      <XmlRefAddr addrType="adfmfLogin">
         <Contents>
            <login url="http://172.31.255.255:7777/
                        SecuredWeb1-ViewController-context-root/faces/view1.jsf"/>
            <logout url="http://172.31.255.255:7777/
                        SecuredWeb1-ViewController-context-root/faces/view1.jsf"/>
            <accessControl url="http://myhost.us.example.com:7777/
                           UserObjects/jersey/getUserObjects" />
            <idleTimeout value="10"/>
            <sessionTimeout value="36000"/>
            <userObjectFilter>
               <role name="testuser1_role1"/>
               <role name="testuser2_role1"/>
               <privilege name="testuser1_priv1"/>
               <privilege name="testuser2_priv1"/>
               <privilege name="testuser2_priv2"/>
            </userObjectFilter>
         </Contents>
      </XmlRefAddr>
   </RefAddresses>
</Reference>

If a web service request is rejected due to the authentication failure, MAF returns an appropriate exception and invokes an appropriate action (see Section 30.4, "Using and Configuring Logging"). If none of the existing exceptions correctly represent the condition, a new exception is added.

The connections.xml file is deployed and managed under the Configuration Service. For more information, see Chapter 16, "Configuring End Points Used in MAF Applications."

connections.xml files in FARs are aggregated when the MAF application is deployed. The credentials represent deployment-specific data and are not expected to be stored in FARs.

15.7.4 Limitations of Secure WSDL File Usage

Since a MAF application must make a WSDL file accessible at run time without authentication, you cannot use a secure WSDL file with a SOAP web service secured by the basic authentication.

If your intention is to secure the WSDL, consider the following: since the WSDL file is fetched by the GET method of the web service, if you secure each web service method, except the GET method, you can use a secure WSDL. If you secure the GET method, you should not secure the WSDL.

15.8 Invoking Web Services From Java

In your MAF application, you can invoke the web services layer (both REST and SOAP) from the Java code and use the results in Java methods.

MAF provides the GenericTypeBeanSerializationHelper utility class that you can use to perform conversions between POJOs (JavaBeans objects) and MAF's GenericType objects based on the following set of conversion rules:

  1. When converting from POJO to GenericType objects:

    • Standard JavaBeans reflection rules are used for determining properties.

    • Transient properties are ignored in the conversion process.

    • Readable properties are converted into a GenericType attribute.

    • Array properties are represented as repeated attributes in the GenericType.

    • Map properties are represented as individual attributes in the GenericType.

    • Non-primitive properties are represented as nested GenericType objects.

  2. When converting from GenericType objects to POJO:

    • Standard JavaBeans reflection rules are used for determining properties.

    • Transient properties are ignored in the conversion process.

    • Writable properties are converted from GenericType attributes.

    • Repeated attributes in the GenericType are converted into an array object.

    • If the POJO implements the Map interface, then any properties that cannot be set through standard accessors are set in the POJO through the set method of the Map.

    • Non-primitive attributes are represented as nested POJO objects.

The advantage of using this helper API is that it allows you to take the response received from a web service and convert it to a JavaBean in a single call.

For example, a web service passes back and forth an Employee object that needs to be reused throughout the business logic. This object has the following set of properties:

  • name of type String

  • address: a complex object with street, city, state, and zipcode attributes

  • id of type long

  • salary of type float

  • phone of type String, and there could be more than one phone

  • password of type String, and the password should never be transmitted to the back-end web service

The following example shows a potential code for the Employee object.

public class Employee {

   protected String name;
   protected Address address;
   protected long id;
   protected float salary;
   protected String[] phone;
   protected transient String password;

   public String getName() {
      return name; 
   }

   public void setName(String name) {
      this.name = name;  
   }

   public Address getAddress() {
      return address;
   }

   public void setAddress(Address address) {
      this.address = address; 
   }

   public long getId() {
      return id; 
   }

   public void setId(long id) {
      this.id = id;
   }

   public float getSalary() {
      return salary; 
   }

   public void setSalary(float salary) {
      this.salary = salary; 
   }

   public String getPassword() {
      return password;
   }

   public void setPassword(String password) {
      this.password = password;
   }

   public void setPassword(String password) {
      this.password = password; 
   }

   public String[] getPhone() {
      return phone;  
   }

   public void setPhone(String phone) {
      this.phone = phone;  
   }

The following example shows the potential code for the Address object of the Employee class.

public class Address {

   protected String street;
   protected String city;
   protected String state;
   protected String zipcode;

   public String getStreet() {
      return street; 
   }

   public void setStreet(String street) {
      this.street = street;  
   }

   public String getCity() {
      return city; 
   }

   public void setCity(String city)  {
      this.city = city;  
   }

   public String getState() {
      return state; 
   }

   public void setState(String state) {
      this.state = state;  
   }

   public String getZipcode() {
      return zipcode; 
   }

   public void setZipcode(String zipcode)  {
      this.zipcode = zipcode;  
   }

Keeping in mind the conversion rules, note the following:

  1. Since the password is defined as transient, it is ignored with respect to the conversion algorithm.

  2. Since name, address, id, and salary all have get and set methods, they will all be converted to and from the GenericType.

  3. Based on the property type, properties can be coerced between types, as defined in the coerceToType(Object, Class) method of the oracle.adfmf.misc.Converter class.

  4. Complex objects, such as address, are recursed by the conversion algorithm to either build the child GenericType or to create and populate the POJO complex object depending on the direction of the conversion.

  5. Since phone is an array of String objects each representing a unique phone number and since the cardinality of this element is greater than one, the conversion algorithm will find all matches of the phone attribute in the GenericType object, present them as an array, and invoke the setPhone method on the bean. The toGenericType method of the GenericTypeBeanSerializationHandler will take each array element and append it to the toGenericType as an individual phone attribute.

With the following defined:

final String EMPLOYEE_VIRTUAL_BEAN_NAME = "EmployeeDC.Types.Employee";
Employee emp = getEmployee();
GenericType gt = null;
  • The Employee object is converted to the GenericType as:

    gt = GenericTypeBeanSerializationHelper.toGenericType
                                     (EMPLOYEE_VIRTUAL_BEAN_NAME, emp);
    
  • The GenericType is converted to the Employee object as:

    emp = GenericTypeBeanSerializationHelper.fromGenericType
                                      (Employee.class, gt, null);
    

For successful conversion, consider the following:

  • Typically, POJOs closely follow their associated GenericType structure.

  • When deviating from the GenericType structure, one of the following strategies should be followed:

    1. Additional bean properties should be declared transient.

    2. Optional properties can be excluded from the POJO, provided that the backing service can handle the missing data if used as an operation parameter.

  • The GenericType is only exposed in SOAP data controls. The virtual types have an associated virtual bean name that is passed into the toGenericType method. You can access the virtual bean name by hovering over the virtual type in the Data Controls window of JDeveloper. The typical name format is <DCName>.Types.<methodName>.<argName>.

For more information, see Oracle Fusion Middleware Java API Reference for Oracle Mobile Application Framework.

15.8.1 How to Add and Delete Rows on Web Services Objects

MAF allows you to insert and delete rows from a web services object programmatically by accessing the iterator on that object. To do so, you use the createRow and deleteRow methods of the oracle.adfmf.bindings.iterator.BasicIterator class.

The following example shows how to add a row to a web services object.

String keyFieldNames[] = {"EMPLID","ACAD_CAREER","INSTITUTION"};
String keyFieldValues[] = {"SR12030","UGRD","PSUNV"};
AmxAccessorIteratorBinding  acIter =
   (AmxAccessorIteratorBinding)((AdfmfJavaUtilities.getValueExpression
                                ("#{bindings.KEYIterator}", Object.class))
                                .getValue(AdfmfJavaUtilities.getAdfELContext()));

BasicIterator iter = acIter.getIterator(); 
GenericType key = (GenericType)iter.getDataProvider();
key.setAttribute("FIELDNAME", keyFieldNames[0]); 
key.setAttribute("FIELDVALUE", keyFieldValues[0]);

int totalRowCount = 0;
int currIndex = 0;

for (int i = 1; i < keyFieldNames.length; i++) {
   totalRowCount = iter.getTotalRowCount();
   currIndex = iter.getCurrentIndex();

   System.out.println("Starting to add rows.. \n\t Total Row Count: " +
                      totalRowCount + "\n\t Current Row Index: " + currIndex);

   // Create rows for key iterator
   iter.createRow();

   totalRowCount = iter.getTotalRowCount();
   System.out.println("\t Total Row Count after creating row: " + totalRowCount);

   if (iter.hasNext()) {
      iter.next();    
   }

   currIndex = iter.getCurrentIndex();
   System.out.println("\t Current Row Index after setting current 
                      index to newly added row: " + currIndex);

   GenericType key1 = (GenericType)iter.getDataProvider();
   key1.setAttribute("FIELDNAME", keyFieldNames[i]);
   key1.setAttribute("FIELDVALUE", keyFieldValues[i]);
}

// Execute method

// Add call to execute the action binding to execute the action. 
// If this is a web service call, the modified GenericType object 
// will be sent to the server and the server will respond appropriately

Note:

The createRow method signatures are available with and without a boolean input parameter. When this method is used without parameters, it produces the result identical to using the createRow with its boolean parameter value set to true: both createRow() and createRow(true) create a new row and insert it in the iterator.

15.8.2 How to Use REST Web Services Adapter

You can use the oracle.adfmf.dc.ws.rest.RestServiceAdapter interface to access data (that could be presented as JavaScript Object Notation, for example) sent across a REST call. The RestServiceAdapter interface lets you trigger execution of web service operations without the need to create a web service data control or interact with it directly.

To use the RestServiceAdapter interface in your MAF application, ensure that the connection exists in the connections.xml file (see Section 15.5, "Creating a New Web Service Connection"), and then add your code to the bean class, as the following examples show.

The following example demonstrates the use of the RestServiceAdapter for the GET request.

RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

// Clear any previously set request properties, if any
restServiceAdapter.clearRequestProperties();

// Set the connection name
restServiceAdapter.setConnectionName("RestServerEndpoint");

// Specify the type of request
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);

// Specify the number of retries
restServiceAdapter.setRetryLimit(0);

// Set the URI which is defined after the endpoint in the connections.xml.
// The request is the endpoint + the URI being set
restServiceAdapter.setRequestURI("/WebService/Departments/100");

String response = "";

// Execute SEND and RECEIVE operation
try {
   // For GET request, there is no payload
   response = restServiceAdapter.send("");
}
catch (Exception e) {
   e.printStackTrace();
}

The following example demonstrates the use of the RestServiceAdapter for the POST request.

String id = "111";
String name = "TestName111";
String location = "TestLocation111";
RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

restServiceAdapter.clearRequestProperties();
restServiceAdapter.setConnectionName("RestServerEndpoint");
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_POST);
restServiceAdapter.setRetryLimit(0);
restServiceAdapter.setRequestURI("/WebService/Departments");

String response = "";

// Execute SEND and RECEIVE operation
try {
   String postData = makeDepartmentPost("DEPT", id, name, location);
   response = restServiceAdapter.send(postData);
}
catch (Exception e) {
   e.printStackTrace();
}
System.out.println("The response is:  " + response);

private String makeDepartmentPost(String rootName, String id, 
                                  String name, String location) {
   String ret = "<" + rootName + ">";
   ret += "<DEPTID>" + id + "</DEPTID>";
   ret += "<NAME>" + name + "</NAME>";
   ret += "<LOCATION>" + location + "</LOCATION>";
   ret += "</" + rootName + ">";
   return ret;
}

The following example demonstrates the use of the RestServiceAdapter for the PUT request.

String id = "111";
String name = "TestName111";
String location = "TestLocation111";
RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

restServiceAdapter.clearRequestProperties();
restServiceAdapter.setConnectionName("RestServerEndpoint");
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_PUT);
restServiceAdapter.setRetryLimit(0);
restServiceAdapter.setRequestURI("/WebService/Departments");

String response = "";

// Execute SEND and RECEIVE operation
try {
   String putData = makeDepartmentPut("DEPT", id, name, location);
   response = restServiceAdapter.send(putData);
}
catch (Exception e) {
   e.printStackTrace();
}
System.out.println("The response is:  " + response);

private String makeDepartmentPut(String rootName, String id, 
                                  String name, String location) {
   String ret = "<" + rootName + ">";
   ret += "<DEPTID>" + id + "</DEPTID>";
   ret += "<NAME>" + name + "</NAME>";
   ret += "<LOCATION>" + location + "</LOCATION>";
   ret += "</" + rootName + ">";
   return ret;
}

The following example demonstrates the use of the RestServiceAdapter for the DELETE request.

RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

restServiceAdapter.clearRequestProperties();
restServiceAdapter.setConnectionName("RestServerEndpoint");
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_DELETE);
restServiceAdapter.setRetryLimit(0);
restServiceAdapter.setRequestURI("/WebService/Departments/44");

String response = "";

// Execute SEND and RECEIVE operation
try {
   // For DELETE request, there is no payload
   response = restServiceAdapter.send("");
}
catch (Exception e) {
   e.printStackTrace();
}

System.out.println("The response is:  " + response);

When you use the RestServiceAdapter, you should set the Accept and Content-Type headers to ensure that your request and response payloads are not deemed invalid due to mismatched MIME type.

Note:

The REST web service adapter only supports UTF-8 character set on MAF applications. UTF-8 is embedded in the adapter program.

15.8.2.1 Accessing Input and Output Streams

You can use the following RestServiceAdapter methods to obtain and customize the javax.microedition.io.HttpConnection, as well as access and interact with the connection's input and output streams which allows you to read data from the HttpConnection and write to it for further upload to the server:

  • Get an HttpConnection:

    HttpConnection getHttpConnection(String requestMethod, 
                                     String request, 
                                     Object httpHeadersValue)
    
  • Get the HttpConnection's OutputStream:

    OutputStream getOutputStream(HttpConnection connection) 
    
  • Get the HttpConnection's InputStream:

    InputStream getInputStream(HttpConnection connection)
    
  • Close the HttpConnection:

    void close (HttpConnection connection)
    
  • Look up a connection name in the connections.xml file and return the connection's end point:

    String getConnectionEndPoint(String connecionName)
    

These methods, while accomplishing the same functionality as the RestServiceAdaper's send and sendReceive methods, provide opportunity for customization of the connection and the process of sending requests and receiving responses.

The following example initializes and returns an HttpConnection using the provided request method, request, and HTTP headers value. In addition, it injects basic authentication into the request headers from the credential store, obtains the input stream and closes the connection.

RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

restServiceAdapter.clearRequestProperties();

// Specify the type of request
String requestMethod = RestServiceAdapter.REQUEST_TYPE_GET;

// Get the connection end point from connections.xml
String requestEndPoint = restServiceAdapter.getConnectionEndPoint("GeoIP");

// Get the URI which is defined after the end point
String requestURI = "/xml/" + someIpAddress;

// The request is the end point + the URI being set
String request = requestEndPoint + requestURI;

// Specify some custom request headers
HashMap httpHeadersValue = new HashMap();
httpHeadersValue.put("Accept-Language", "en-US");
httpHeadersValue.put("My-Custom-Header-Item", "CustomItem1");

// Get the connection
HttpConnection connection = 
                  restServiceAdapter.getHttpConnection(requestMethod,
                                                       request,
                                                       httpHeadersValue);

// Get the input stream
InputStream inputStream = restServiceAdapter.getInputStream(connection);

// Define data
ByteArrayOutputStream byStream = new ByteArrayOutputStream();

int res = 0;
int bufsize = 0, bufread = 0;

byte[] data = (bufsize > 0) ? new byte[bufsize] : new byte[1024];

// Use the input stream to read data
while ((res = inputStream.read(data)) > 0) {
   byStream.write(data, 0, res);
   bufread = bufread + res;
}
data = byStream.toByteArray();

// Use data 
...

restServiceAdapter.close(connection);
...

15.8.2.2 Support for Non-Text Responses

You can use the RestServiceAdapter to handle binary (non-text) responses received from web service calls. These responses can include any type of binary data, such as PDF or video files. The RestServiceAdapter method to use is sendReceive.

The following example shows how to send a request for a file to a REST server, and then save the file to a disk.

RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

restServiceAdapter.clearRequestProperties();
restServiceAdapter.setConnectionName("JagRestServerEndpoint");
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
restServiceAdapter.setRetryLimit(0);
restServiceAdapter.setRequestURI("/ftaServer/v1/kpis/123/related/1");

// Set credentials needed to access the REST server 
String theUsername = "hr_spec_all";
String thePassword = "Welcome1"; 
String userPassword = theUsername + ":" + thePassword;
String encoding = new sun.misc.BASE64Encoder().encode(userPassword.getBytes());

restServiceAdapter.addRequestProperty("Authorization", "Basic " + encoding);

// Execute the SEND and RECEIVE operation.
// Since it is a GET request, there is no payload.
try {
   this.responseRaw = restServiceAdapter.sendReceive("");
}
catch (Exception e) {
   e.printStackTrace();
}
System.out.println("The response is:  " + this.responseRaw);

// Write the response to a file
writeByteArrayToFile(this.responseRaw);

The following example demonstrates a method that is called by the code from the preceding example. This method saves a byte[] response to a file on disk:

public void writeByteArrayToFile(byte[] fileContent) {
   BufferedOutputStream bos = null;
   try {
      FileOutputStream fos = new FileOutputStream(new File(fileToSavePath));
      bos = new BufferedOutputStream(fos);
      // Write the byte [] to a file 
      System.out.println("Writing byte array to file");
      bos.write(fileContent);
      System.out.println("File written");
   }
   catch(FileNotFoundException fnfe) {
      System.out.println("Specified file not found" + fnfe);
   }
   catch (IOException ioe) {
      System.out.println("Error while writing file" + ioe);
   }
   finally {
      if(bos != null) {
         try {
            // Flush the BufferedOutputStream
            bos.flush();
            // Close the BufferedOutputStream
            bos.close();
         }
         catch (Exception e) {
         }
      }
   }
}

15.8.3 How to Enable Strict Validation of REST Responses

Using the maf-config.xml file, you can specify how MAF is to behave when a REST response contains a child or attribute that is not expected or defined in the XSD:

  • If the strict validation is enabled, MAF throws an exception.

  • If the strict validation is disabled, the condition is logged and the execution continues without exceptions thrown.

The following example shows how to set the validated parameter. If you define the value of this parameter as true, the validation proceeds as strict. The value of false (default) means that the validation is not strict.

<generic-type>
   <conversion>
      <validated>true</validated>
   </conversion> 
</generic-type>

15.8.4 How to Process JSON Responses

In addition to XML, a REST web service that you use in your MAF application can accommodate a message specified in a JavaScript Object Notation (JSON) format.

A MAF sample application called RESTDemo (located in the PublicSamples.zip file within the jdev_install/jdeveloper/jdev/extensions/oracle.maf/Samples directory on your development computer) includes the RESTJSONBean.java file that demonstrates how the RestServiceAdapter described in detail in Section 15.8.2, "How to Use REST Web Services Adapter" enables the use of JSON as a message format for REST web services

The RESTJSONResponse.java file that is also included in the RESTDemo represents the JSON message and is converted to an appropriate format using the fromJSON method of the JSONBeanSerializationHelper class.

The RESTJSONBean's loadData method shown in the following example demonstrates how to use the relevant APIs.

public class RESTJSONBean {
...
   public void setResponse(RESTJSONResponse response) {
      RESTJSONResponse oldResponse = this.response;
      this.response = response;
      propertyChangeSupport.firePropertyChange("response", oldResponse, response);
   }
   
   public void loadData() {
      RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
   
      // Clear previously set request properties, if any
      restServiceAdapter.clearRequestProperties();
   
      // Set the connection name
      restServiceAdapter.setConnectionName("GeoIP");
   
      // Specify the type of request
      restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
   
      // Specify the number of retries
      restServiceAdapter.setRetryLimit(0);
   
      // Set the URI which is defined after the endpoint in the connections.xml
      // The request is the endpoint + the URI being set
      restServiceAdapter.setRequestURI("/json/" + getSearchIp());
   
      setJsonResponse("");
   
      // Execute SEND and RECEIVE operation
      try {
         // For GET request, there is no payload
         setJsonResponse(restServiceAdapter.send(""));
   
         // Create a new RESTJSONResponse object and 
         // parse the returned JSON string into this class
         RESTJSONResponse res = new RESTJSONResponse();
         res = (RESTJSONResponse)JSONBeanSerializationHelper.
                fromJSON(RESTJSONResponse.class, getJsonResponse());
         setResponse(res);
      }
      catch (Exception e) {
         e.printStackTrace();
      }
   }
...
}

For additional information and examples, see the tutorial called Consuming REST-JSON Web Services in Mobile Applications with Oracle Mobile Application Framework.

15.8.5 What You May Need to Know About Invoking Data Control Operations

You can use the invokeDataControlMethod method of the AdfmfJavaUtilities to invoke a data control operation which does not have to be explicitly added as a methodAction in a PageDef file.

For more information and examples, see Oracle Fusion Middleware Java API Reference for Oracle Mobile Application Framework.

15.9 Understanding Limitations Related to MAF Support for JavaScript

Since MAF REST web service client does not support JavaScript, REST exchanges that require execution of JavaScript cannot be successfully processed by MAF applications. That is, a server response that expects the client to execute JavaScript cannot be completed. Your MAF application cannot include code to indirectly retrieve a web page that requires JavaScript support in order to complete the original request through redirection.

The following is an acceptable scenario on web browsers because most of them support JavaScript. However, it would not produce the desired outcome if used within a MAF application:

An application requests for data using REST web service and the application server redirects the request to the authentication server. The authentication server serves a web page that, in turn, redirects to another page, and then redirects back to the original REST end point. In the redirect sequence, one of the web pages that is served by the authentication server expects the client to execute a JavaScript and authenticates the end user against the application server.

15.10 Configuring the Browser Proxy Information

If the web service you are to call resides outside your corporate firewall, you need to ensure that you have set the appropriate Java system properties to configure the use of an HTTP proxy server.

By default, MAF determines the proxy information using the system settings on iOS and Android platforms. For example, if the proxy information is set using the Settings utility on an iOS-powered device, then JVM automatically absorbs it.

Note:

It is possible to define a different proxy for each MAF application.

If you do not want to obtain the proxy information from the device settings, first you need to add the -Dcom.oracle.net.httpProxySource system property. The default value of this property is native, which means that the proxy information is to be obtained from the device settings. You need to disable it by specifying a different value, such as user, for example: -Dcom.oracle.net.httpProxySource=user

JVM uses two different mechanisms for enabling the network connection:

  1. The generic connection framework (GCF). If this mechanism is used, the proxy is defined through the system property -Dcom.sun.cdc.io.http.proxy=<host>:<port>

  2. java.net API. If this mechanism is used, the proxy is defined through the standard http.proxyHost and http.proxyPort.

In either case, it is recommended to define all three properties in the maf.properties file, which would look similar to the following:

java.commandline.argument=-Dcom.oracle.net.httpProxySource=user
java.commandline.argument=-Dcom.sun.cdc.io.http.proxy=www-proxy.us.mycompany.com:80
java.commandline.argument=-Dhttp.proxyHost=www-proxy.us.mycompany.com
java.commandline.argument=-Dhttp.proxyPort=80

Note:

These properties affect only the JVM side of network calls.