17 Using Web Services in a MAF Application

This chapter describes how to access REST web services from a MAF application.

This chapter includes the following sections:

17.1 Introduction to Using Web Services in a MAF Application

MAF supports the consumption of REST web services with JSON objects (REST-JSON) in MAF applications. This type of web service is recommended by MAF over alternative web services as the smaller payloads that REST-JSON web services generate typically mean lower response times for communication between an application and the services that it accesses.

Using a REST-JSON web service in a MAF application requires you to configure a connection to the URL end point for the web service in your application. MAF stores this end point in the connections.xml file of your application. You also write an adapter (RESTServiceAdapter) that takes the value you configured in connections.xml and uses it to construct the request URI that you submit to the web service. You must also write Java classes to model the data that the web service returns. Use these classes to generate data controls that bind your application’s AMX pages to the data that the web service accesses. If your application accesses secured web services, you must associate a security policy with the connection to the URL end point for the web service. If your application must access services hosted outside your corporate firewall, you may also need to configure entries in your application’s maf.properties file.

Note:

As an alternative to writing a RESTServiceAdapter, use the design-time support provided by MAF to generate the client data model that accesses REST web services. For more information, see Creating the Client Data Model in a MAF Application.

The WorkBetter sample application provides examples of programmatically consuming REST services using the RESTServiceAdapter. For more information about how to access the source code of the WorkBetter sample application, see MAF Sample Applications.

17.2 Creating a Rest Service Adapter to Access Web Services

Use a rest service adapter RestServiceAdapter to access data sent using REST calls and to trigger execution of web service operations. The RestServiceAdapterFactory.createRestServiceAdapter() API from the oracle.maf.api.dc.ws.rest package creates adapters that implement RestServiceAdapter.

Ensure that the connection to the URL end point for the service exists in the connections.xml file, and then add your code to the bean class, as the following examples demonstrate.

Use the RestServiceAdapterFactory.createMcsRestServiceAdapter() API if you want to create an adapter that sends diagnostic information to Mobile Cloud Service, as described in Sending Diagnostic Information to Oracle Mobile Cloud Service.

For more information about RestServiceAdapterFactory and RestServiceAdapter, see Java API Reference for Oracle Mobile Application Framework.

....
import oracle.maf.api.dc.ws.rest.RestServiceAdapterFactory;
import oracle.maf.api.dc.ws.rest.RestServiceAdapter;
....
RestServiceAdapterFactory factory = RestServiceAdapterFactory.newFactory();
RestServiceAdapter restServiceAdapter = factory.createRestServiceAdapter();

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

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

// Specify the type of request
restServiceAdapter.setRequestMethod(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.clearRequestProperties();
restServiceAdapter.setConnectionName("RestServerEndpoint");
restServiceAdapter.setRequestMethod(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.clearRequestProperties();
restServiceAdapter.setConnectionName("RestServerEndpoint");
restServiceAdapter.setRequestMethod(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.clearRequestProperties();
restServiceAdapter.setConnectionName("RestServerEndpoint");
restServiceAdapter.setRequestMethod(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.

17.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 RestServiceAdapter'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.

....
import oracle.maf.api.dc.ws.rest.RestServiceAdapterFactory;
import oracle.maf.api.dc.ws.rest.RestServiceAdapter;
....

RestServiceAdapterFactory factory = RestServiceAdapterFactory.newFactory();
RestServiceAdapter restServiceAdapter = factory.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);
...

17.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.

RestServiceAdapterFactory factory = RestServiceAdapterFactory.newFactory();
RestServiceAdapter restServiceAdapter = factory.createRestServiceAdapter();

restServiceAdapter.clearRequestProperties();
restServiceAdapter.setConnectionName("JagRestServerEndpoint");
restServiceAdapter.setRequestMethod(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) {
         }
      }
   }
}

17.3 Accessing Secure Web Services

MAF supports both secured and unsecured web services. When a REST web service is secured, you must associate the REST connection with the predefined security policy that supports the REST web service, as described in How to Enable Access to Web Services.

Table 17-1 lists the predefined security policies that you can associate with connections to REST web services.

Table 17-1 Security Policies Supported for REST-Based Web Services

Authentication Type REST Policy Description

HTTP Basic

oracle/wss_http_token_over_ssl_client_policy

This policy includes credentials in the HTTP header for outbound client requests and authenticates users against the Oracle Platform Security Services identity store. This policy also verifies that the transport protocol is HTTPS. Requests over a non-HTTPS transport protocol are refused. This policy can be enforced on any HTTP-based client.

HTTP Basic

oracle/wss_http_token_client_policy

This policy includes credentials in the HTTP header for outbound client requests. This policy can be enforced on any HTTP-based or HTTPS-based client.

HTTP Basic

oracle/wss_http_token_over_ssl_client_policy

This policy includes credentials in the HTTP header for outbound client requests and authenticates users against the Oracle Platform Security Services identity store. This policy also verifies that the transport protocol is HTTPS. Requests over a non-HTTPS transport protocol are refused. This policy can be enforced on any HTTP-based client.

HTTP Basic

Web SSO

oracle/http_cookie_client_policy

This policy injects cookies obtained after authentication in the HTTP request header, e.g: accessing OAM Webgate resources. This policy also sets response cookies. This policy can be enforced on any REST-based client.

OAuth

oracle/http_oauth2_token_mobile_client_policy

This policy injects bearer token (OAuth access token) in the HTTP request header while communicating with the endpoint. This token can be obtained from any OAuth2 server. This policy can be enforced on any REST-based client.

For more information on these policies and their usage, see the Determining Which Predefined Policies to Use and Predefined Policies chapters in Securing Web Services and Managing Policies with Oracle Web Services Manager.

17.3.1 How to Enable Access to Web Services

When a web service is secured and expects an authentication token, you must associate the login connection with the predefined security policy that supports the web service. For a list of predefined security policies are supported for the authentication type available for REST-based web services, see Accessing Secure Web Services.

You create the login server connection using the Create MAF Login Connection dialog in the maf-application.xml overview editor. For details about creating the login server connection, see How to Create a MAF Login Connection.

To associate a security policy with a web service:

  1. In the Applications window, expand the Descriptors node and then ADF META-INF, and double-click maf-application.xml. Then, in the overview editor for the maf-application.xml file, expand the Security - Web Services Security Policies section and choose the web service connection that you created in the Name field.
  2. In the Login Server Connection field, choose the login server connection that you defined.
  3. In the Policy field, double-click the Edit Policy icon button and in the Edit Data Control Policies dialog, select the policy that you want to associate with the service for the current web service connection and click OK.
    Figure 17-1 shows the policy associated with RESTConnection1 and RESTConnection2.

    For a list of the security policies that you may select in the Edit Data Control dialog and associate with a REST-based web service, see Accessing Secure Web Services.

    Figure 17-1 Associating a Security Policy with a Web Service Connection

    This image is described in the surrounding text

17.3.2 What Happens When You Enable Access to Web Services

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

You can view the security policy already associated with the REST web service using the Edit Data Control Policies dialog shown in Figure 17-2. Click Override Properties to invoke a dialog where you can specify alternative values for properties that the selected policy permits you to override.

Figure 17-2 Editing Web Service Data Control Policies

This image is described in the surrounding text.

17.3.3 What You May Need to Know About Accessing Web Services and Containerized MAF Applications

When the MAF application is containerized at deployment time, access to secured web services behind the corporate firewall will rely on the Mobile Security Access Server (MSAS), a component in the Oracle Mobile Security Suite (OMSS), to provide a central access point for securing traffic from mobile devices to corporate resources. In this case, the MSAS instance is configured to enforce an authentication endpoint for use in the initial authentication of the user.

Additionally, the backend service endpoints must be associated 1.) with a MSAS proxy application that ensures the URL of the resource is protected by an access policy that MSAS enforces and 2.) a client policy that MSAS adds to the proxied request, for example Single Sign-On (SSO).

In order to allow the MAF application to communicate with MSAS, the user installs and registers a Secure Workspace app for the type of authentication that has been configured for the MSAS instance. Then when the user attempts to access a protected resource, the MAF application and the Secure Workspace rely on a MSAS-generated Proxy Auto-Configuration file to determine which requests to proxy using the MSAS AppTunnel.

For more information about the role of MSAS AppTunnel in the authentication process of containerized MAF applications, see Overview of the Authentication Process for Containerized MAF Applications.

For an overview of OMSS support for containerized MAF applications, see Containerizing a MAF Application for Enterprise Distribution.

In the OMSS documentation library, see Policy and Assertion Template Reference for Oracle Mobile Security Access Server for reference information about MSAS predefined security and management policies.

17.3.4 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.

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. 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 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 Configuring End Points Used in MAF Applications .

The 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.

17.3.5 What You May Need to Know About Cookie Injection

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 execute cookie injection for the login connection associated with the URL endpoint of the REST web service. This is handled at runtime without configuration of the MAF application by the MAF developer.

17.4 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 the platform where you deploy the application. 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.