Skip navigation

Oracle Process Cloud service (PCS) provides REST API enabling other applications to integrate with PCS. More details on REST API for Oracle Process Cloud service can be found here. Oracle Process Cloud Service accepts OAuth tokens as an alternative to basic authentication for the REST APIs. In this post we discuss how PCS REST API can be accessed using an OAuth token to create a new business process instance.

 

The scenario discussed in this blog involves a web application deployed in JCS-SX that triggers a business process (“Funds Transfer Process”) deployed in PCS. The “Funds Transfer” process considered in this blog is a simple business process which validates certain attributes of the incoming request and forwards the request for manual approval if needed. The web application obtains the OAuth token from the OAuth server and passes the token to PCS REST API for authentication.

 

The following diagram depicts the high level interactions between JCS-SX, PCS and the OAuth server:

pcs_oauth_blog_image.png

This use case considers that both JCS-SX and PCS instances have been provisioned in the same identity domain. When provisioned in the same identity domain, the resources and clients needed for communicating using OAuth are automatically configured along with an OAuth server which is used for obtaining the tokens. You can navigate to “OAuth Administration” tab from “My Services” and see the following OAuth resources and clients registered by default. Please refer to Managing OAuth Resources and Clients for more details.

 

Note: You need to have an Identity Administrator role to be able to access the "OAuth Administration" page.

pcs_oauth_blog_image_1.png

Note: Please note the client identifier (Id) (highlighted in red box above) and its respective “secret” (secret) that can be viewed by clicking on the “Show Secret” button of the JCS SX OAuth client. This information will be used by the web application to obtain an access token for the client and access PCS REST API.

 

The JCS-SX OAuth client is used to invoke the PCS REST API from the web application, hence ensure that the PCS resource is accessible for this client. You can manage the accessibility of the resources by clicking on the “Modify” menu option under the action palette in “Register Client” section against the JCS-SX OAuth client as shown below:

pcs_oauth_blog_image_2.png

Note: This blog assumes that the business process (“Funds Transfer Process”) is deployed in PCS, the export of the business process is provided in the appendix section for your reference.

 

With the prerequisites in place we can now proceed to obtain the client access token that would be used by the web application to invoke the PCS REST API. This sample uses the OAuth grant types – client_credentials and password to retrieve the client access token involving the following steps:

  1. Obtain client assertion using the client credentials.
  2. Obtain an access token using the client assertion obtained in the above step

 

Note: When it comes to making webservice calls within Oracle Platform Services one can use owsm policies for identity propagation and does not necessarily deal with OAuth tokens explicitly.This is just an illustration of how you could use OAuth token for authenticating with PCS without using owsm policies.

 

These steps are detailed further using specific code snippets.

 

Store the details of the business process to be invoked and the details required to access the OAuth token server in a HashMap.

 

Note: To keep things simple for the purpose of this blog, details like client secret, user name , password are stored in a java HashMap, however it is highly recommended to use Oracle Credential Store Framework (CSF) to ensure secure management of credentials. Please look at “References” section at the end of this blog for more information regarding this.

 

    public static HashMap populateMap() {
        HashMap map = new HashMap();
        // PCS
        map.put("PCS_URL", "https://<PCS_HOST>:443/bpm/api/3.0/processes");
        map.put("PCS_PROCESS_DEF_ID", "default~MyApplication!1.0~FundsTransferProcess");
        map.put("PCS_FTS_SVC_NAME", "FundsTransferProcess.service");
        // OAuth
        map.put("TOKEN_URL", "https://<ID_DOMAIN_NAME>.identity.<DATA_CENTER>.oraclecloud.com/oam/oauth2/tokens");
        map.put("CLIENT_ID", "<CLIENT_ID>");
        map.put("SECRET", "<SECRET>");
        map.put("DOMAIN_NAME", "<ID_DOMAIN_NAME>");
        map.put("USER_NAME","<PCS_USER_NAME>");
        map.put("PASSWORD","<PCS_USER_PWD>");
        return map;
    }
   
    public String getOAuthToken() throws Exception{
        String token = "";
        
        String authString = entryMap.get("CLIENT_ID")+":"+entryMap.get("SECRET");
                
        Map clientAssertionMap = getClientAssertion(authString);
        
        token = getAccessToken(authString,clientAssertionMap);
        
        return token;
    }

 

Note: The values specified in the above code for the keys – PCS_PROCESS_DEF_ID, PCS_FTS_SVC_NAME are only for reference. After you deploy the funds transfer business process in PCS, you can retrieve the details of the business process by executing the following curl command and replace the above values appropriately:

 

curl -u <PCS_USER_NAME>:<PCS_USER_PWD> -H "Content-Type:application/json" -H "Accept:application/json" -X GET https://<PCS_HOST>:443/bpm/api/3.0/process-definitions

 

The “getOAuthToken” method is implemented to retrieve the client assertion by accessing the OAuth server (token endpoint) and passing client_id:client_secret as a basic authorization header. These details can be obtained from OAuth Administration tab as mentioned in a note above. The following code snippet shows how this can be implemented:

 

    private Map<String,String> getClientAssertion(String authString) throws Exception{
        
        resource = client.resource( entryMap.get("TOKEN_URL")+"");
        
        ClientResponse res = null;
        String payload = "grant_type:client_credentials";
        
        MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));
        
        MultivaluedMap formData = new MultivaluedMapImpl();
        formData.add("grant_type", "client_credentials");
                
        try {
        res = 
            resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))
            .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))
            .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
            .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
            .accept(MediaType.APPLICATION_JSON_TYPE)
            .post(ClientResponse.class,formData);
        } catch (Exception e) {
            System.out.println("In catch: "+e);
            e.printStackTrace();
            throw e;
        }
        
        String output = res.getEntity(String.class);
        JSONObject newJObject = null;
        org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();
        try {
             
             newJObject = (JSONObject) parser.parse(output);
            
            } catch (org.json.simple.parser.ParseException e) {
                e.printStackTrace();
        }
              
        Map<String,String> assertionMap = new HashMap <String,String>();
        
        assertionMap.put("assertion_token",newJObject.get("access_token")+"");
        assertionMap.put("assertion_type",newJObject.get("oracle_client_assertion_type")+"");
        
        if (res != null && res.getStatus() != 200) {
            System.out.println("Server Problem (getClientAssertion): "+res.getStatusInfo());
            throw new Exception (res.getStatusInfo().getReasonPhrase());
        }
        return assertionMap;
    }

 

The above implementation uses a Jersey client to access the token server and obtain a client assertion and client assertion type. Also note that grant_type:client_credentials being passed in the payload. The following code snippet uses the password grant_type to obtain the client access token from the token server by passing in the client assertion obtained earlier along with user name and password.

 

    private String getAccessToken(String authString,Map clientAssertionMap) throws Exception{
        resource = client.resource(entryMap.get("TOKEN_URL")+"");
        
        String clientAssertionType = (String) clientAssertionMap.get("assertion_type");
        String clientAssertion = (String) clientAssertionMap.get("assertion_token");
                                              
        ClientResponse res = null;
        
        MultivaluedMap formData = new MultivaluedMapImpl();
        formData.add("grant_type", "password");
        formData.add("username", entryMap.get("USER_NAME"));
        formData.add("password", entryMap.get("PASSWORD"));
        formData.add("client_assertion_type", clientAssertionType);        
        formData.add("client_assertion", clientAssertion);        
        
        try {
        res = 
            resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))
            .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))
            .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
            .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
            .accept(MediaType.APPLICATION_JSON_TYPE)
            .post(ClientResponse.class,formData);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        
        String output = res.getEntity(String.class);
        
        JSONObject newJObject = null;
        org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();
        try {
           
           newJObject = (JSONObject) parser.parse(output);
        
        } catch (org.json.simple.parser.ParseException e) {
           e.printStackTrace();
        }
        
       String token = newJObject.get("access_token")+"";
        
        if (res != null && res.getStatus() != 200) {
            System.out.println("Server Problem (getAccessToken): "+res.getStatusInfo());
            throw new Exception (res.getStatusInfo().getReasonPhrase());
        }
        return token;
    }

 

The PCS resource can now be accessed using the client access token, the following code snippet invokes the PCS REST API to create a new process instance of the “Funds Transfer” Business Process. The payload consists of the details of the process to be created like process definition id, service name and the input values entered by the user in a JSP page. Please note that the OAuth token obtained in the previous step is being set to the “Authorization” header.

    public String invokeFundsTransferProcess(String token,FundsTransferRequest ftr) throws Exception {
     
        StringBuffer payload = new StringBuffer();
        payload.append("{");
        payload.append("\"processDefId\":\""+entryMap.get("PCS_PROCESS_DEF_ID").toString()+"\",");
        payload.append("\"serviceName\":\""+entryMap.get("PCS_FTS_SVC_NAME").toString()+"\",");
        payload.append("\"operation\":\"start\",");
        payload.append("\"params\": {");
        payload.append("\"incidentId\":\""+ftr.getIncidentId()+"\",");
        payload.append("\"sourceAcctNo\":\""+ftr.getSourceAcctNo()+"\",");
        payload.append("\"destAcctNo\":\""+ftr.getDestAcctNo()+"\",");
        payload.append("\"amount\":"+ftr.getAmount()+",");
        String tsfrType;
        if(ftr.getTransferType().equals("tparty"))
            tsfrType = "intra";
        else
            tsfrType = "inter";

        payload.append("\"transferType\":\""+tsfrType+"\"");
        payload.append("}, \"action\":\"Submit\"");
        payload.append("}");
     
        MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));

        resource = client.resource(entryMap.get("PCS_URL").toString());
        ClientResponse res = null;  
        try {
        res = 
            resource.header("Authorization", "Bearer " + token)
            .type("multipart/mixed")
            .accept(MediaType.APPLICATION_JSON)
            .post(ClientResponse.class, multiPart);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        
        if (res != null && res.getStatus() != 200) {
            System.out.println("Server Problem (PCSRestOAuthClient.invokeFundsTransferProcess): "+res.getStatusInfo() +" while invoking "+entryMap.get("PCS_URL").toString());
            throw new Exception (res.getStatusInfo().getReasonPhrase());
        }
    
    return res.getStatus()+"";
    }

 

A simple JSP page is used to capture user input and trigger the Funds Transfer Business Process.

pcs_oauth_blog_image_4.png

Upon successful initiation of the Funds Transfer process, you should be able to see an instance of the process getting created and processed in the "Tracking" page of PCS as shown below:

pcs_oauth_blog_image_3.png

 

Known Issues:

Depending on the JDK you use, you might see a "javax.net.ssl.SSLHandshakeException: server certificate change is restricted during renegotiation" error when trying to invoke the PCS REST API from JCS-SX. Please set the following system properties on your JCS-SX and restart the server to work around this issue:

  1. Set "weblogic.security.SSL.minimumProtocolVersion" to "TLSv1.2" in JCS - SaaS Extension and restart JCS - SX
  2. If the problem still persists, set "jdk.tls.allowunsafeservercertchange" to "true" and restart JCS - SaaS Extension

 

Appendix:

Funds Transfer business process (PCS export) for reference -  MyApplication.zip (attached)

 

References:

 

The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

This blog will demonstrate how to a build and run a WebSocket based microservice. Here is what the blog will cover at a high level

 

  • Overview of WebSocket and the sample Java application
  • Continuous Integration setup: from source code in the IDE to a build artifact in Oracle Developer Cloud
  • Continuous Deployment setup: from a build artifact in Developer Cloud Service to an application running in Oracle Application Container Cloud
  • Testing the application

 

 

Overview

 

WebSocket: the standard

 

WebSocket is an IETF standard recognized by RFC 6455 and has the following key characteristics which make it great fit for real time applications

  • Bi-directional: both server and client an initiate a communication
  • Full duplex: once the WebSocket session is established, both server and client can communicate independent of each other
  • Less verbose (compared to HTTP)

 

A deep dive into the protocol is out of scope of this blog. Please refer to the RFC for further details

 

Java Websocket API

 

A standard Java equivalent (API) for this technology is defined by JSR 356. It is backed by a specification which makes it possible to have multiple implementations of the same. JSR 356 is also included as a part of the Java Enterprise Edition 7 (Java EE 7) Platform. This includes a pre-packaged (default) implementation of this API as well as integration with other Java EE technologies like EJB, CDI etc.

 

Tyrus

 

Tyrus is the reference implementation of the Java Websocket API. It is the default implementation which is packaged with Java EE 7 containers like Weblogic 12.2.1 (and above) and Glassfish (4.x). It provides both server and client side API for building web socket applications.

 

Tyrus grizzly module

 

Tyrus has a modular architecture i.e. it has different modules for server, client implementations, a SPI etc. It supports the notion of containers (you can think of them as connectors) for specific runtime support (these build on the modular setup). Grizzly is one of the supported containers which can be used for server or client (or both) modes as per your requirements (the sample application leverages the same)

 

About the sample application

 

The sample is a chat application – a canonical use case for WebSockets (this by no means a full-blown chat service). Users can

  • Join the chat room (duplicate usernames not allowed)
  • Get notified about new users joining
  • Send public messages
  • Send private messages
  • Leave the chat room (other users get notified)

 

The application is quite simple

  • It has a server side component which is a (fat) JAR based Java application deployed to Application Container Cloud
  • The client can be any component which has support for the WebSocket API e.g. your browser . The unit tests use the Java client API implementation of Tyrus

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Code

 

Here is a summary of the various classes and their roles

 

Class(es)

Category

Description

 

 

 

ChatServer

Core

It contains the core business logic of the application

WebSocketServerManager

Bootstrap

Manages bootstrap and shutdown process of the WebSocket container

ChatMessage,

DuplicateUserNotification, LogOutNotification,

NewJoineeNotification,

Reply,

WelcomeMessage

Domain objects

Simple POJOs to model the application level entities

ChatMessageDecoder

Decoder

Converts chats sent by users into Java (domain) object which can be used within the application

DuplicateUserMessageEncoder, LogOutMessageEncoder,

NewJoineeMessageEncoder,

ReplyEncoder,

WelcomeMessageEncoder

Encoder(s)

Converts Java (domain) objects into native (text) payloads which can be sent over the wire using the WebSocket protocol

 

Here is the WebSocket endpoint implementation (ChatServer.java)

 

@ServerEndpoint(
        value = "/chat/{user}/",
        encoders = {ReplyEncoder.class, 
                    WelcomeMessageEncoder.class, 
                    NewJoineeMessageEncoder.class, 
                    LogOutMessageEncoder.class,
                    DuplicateUserMessageEncoder.class},
        decoders = {ChatMessageDecoder.class}
)


public class ChatServer {


    private static final Set<String> USERS = new ConcurrentSkipListSet<>();
    private String user;
    private Session s;
    private boolean dupUserDetected;


    @OnOpen
    public void userConnectedCallback(@PathParam("user") String user, Session s) {
        if (USERS.contains(user)) {
            try {
                dupUserDetected = true;
                s.getBasicRemote().sendText("Username " + user + " has been taken. Retry with a different name");
                s.close();
                return;
            } catch (IOException ex) {
                Logger.getLogger(ChatServer.class.getName()).log(Level.SEVERE, null, ex);
            }


        }
        this.s = s;
        s.getUserProperties().put("user", user);
        this.user = user;
        USERS.add(user);


        welcomeNewJoinee();
        announceNewJoinee();
    }


    private void welcomeNewJoinee() {
        try {
            s.getBasicRemote().sendObject(new WelcomeMessage(this.user));
        } catch (Exception ex) {
            Logger.getLogger(ChatServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


    private void announceNewJoinee() {
        s.getOpenSessions().stream()
                .filter((sn) -> !sn.getUserProperties().get("user").equals(this.user))
                //.filter((s) -> s.isOpen())
                .forEach((sn) -> sn.getAsyncRemote().sendObject(new NewJoineeNotification(user, USERS)));
    }


    public static final String LOGOUT_MSG = "[logout]";


    @OnMessage
    public void msgReceived(ChatMessage msg, Session s) {
        if (msg.getMsg().equals(LOGOUT_MSG)) {
            try {
                s.close();
                return;
            } catch (IOException ex) {
                Logger.getLogger(ChatServer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        Predicate<Session> filterCriteria = null;
        if (!msg.isPrivate()) {
            //for ALL (except self)
            filterCriteria = (session) -> !session.getUserProperties().get("user").equals(user);
        } else {
            String privateRecepient = msg.getRecepient();
            //private IM
            filterCriteria = (session) -> privateRecepient.equals(session.getUserProperties().get("user"));
        }


        s.getOpenSessions().stream()
                .filter(filterCriteria)
                //.forEach((session) -> session.getAsyncRemote().sendText(msgContent));
                .forEach((session) -> session.getAsyncRemote().sendObject(new Reply(msg.getMsg(), user, msg.isPrivate())));


    }


    @OnClose
    public void onCloseCallback() {
        if(!dupUserDetected){
            processLogout();
        }
        
    }


    private void processLogout() {
        try {
            USERS.remove(this.user);
            s.getOpenSessions().stream()
                    .filter((sn) -> sn.isOpen())
                    .forEach((session) -> session.getAsyncRemote().sendObject(new LogOutNotification(user)));


        } catch (Exception ex) {
            Logger.getLogger(ChatServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


}

 

Setting up Continuous Integration & Deployment

 

The below sections deal with the configurations to made within the Oracle Developer Cloud service

 

Project & code repository creation

 

Please refer to the Project & code repository creation section in the Tracking JUnit test results in Developer Cloud service blog or check the product documentation for more details

 

Configure source code in Git repository

 

Push the project from your local system to your Developer Cloud Git repo you just created. We will do this via command line and all you need is Git client installed on your local machine. You can use Git or any other tool of your choice

 

cd <project_folder> 
git init  
git remote add origin <developer_cloud_git_repo>  
//e.g. https://john.doe@developer.us.oraclecloud.com/developer007-foodomain/s/developer007-foodomain-project_2009/scm/acc-websocket-sample.git//john.doe@developer.us.oraclecloud.com/developer007-foodomain/s/developer007-foodomain-project_2009/scm/acc-websocket-sample.git   
git add .  
git commit -m "first commit"  
git push -u origin master  //Please enter the password for your Oracle Developer Cloud account when prompted

 

Configure build

 

Create a New Job

 

 

Select JDK

 

 

 

Continuous Integration (CI)

 

Choose Git repo

 

 

 

Set build trigger - this build job will be triggered in response to updated within the Git repository (e.g. via git push)

 

 

 

Add Maven Build Step

 

 

 

Activate the following post build actions

  • Archive the Maven artifacts (contains deployable zip file)
  • Publish JUnit test result reports

 

 

 

Execute Build & check JUnit test results

 

Before configuring deployment, we need to trigger the build in order to produce the artifacts which can be referenced by the deployment configuration

 

 

 

After the build is complete, you can

  • Check the build logs
  • Check JUnit test results
  • Confirm archived Maven artifacts

 

 

 

 

Test results

 

 

 

Build logs

 

 

 

 

Continuous Deployment (CD) to Application Container Cloud

 

Create a New Confguration for deployment

 

 

 

Enter the required details and configure the Deployment Target

 

 

 

Configure the Application Container Cloud instance

 

 

 

 

 

Configure Automatic deployment option on the final confirmation page

 

 

 

Confirmation screen

 

 

 

 

Test the CI/CD flow

 

Make some code changes and push them to the Developer Cloud service Git repo. This should

 

  • Automatically trigger the build, which once successful will
  • Automatically trigger the deployment process

 

 

 

 

 

 

 

Check your application in Application Container Cloud

 

 

 

 

 

Here is the detailed view

 

 

 

 

Test

 

You would need a WebSocket client for this example. I would personally recommend using the client which can be installed into Chrome browser as a plugin – Simple WebSocket Client. See below snapshot for a general usage template of this client

 

 

 

The following is a template for the URL of the WebSocket endpoint

 

wss://<acc-app-url>/chat/<user-handle>/
e.g. wss://acc-websocket-chat-domain007.apaas.em2.oraclecloud.com/chat/abhi/

 

 

Test transcript

 

Here is a sequence of events which you can execute to test things out

 

Users foo and bar join the chatroom

 

wss://acc-websocket-chat-domain007.apaas.em2.oraclecloud.com/chat/foo/
wss://acc-websocket-chat-domain007.apaas.em2.oraclecloud.com/chat/bar/

 

   

 

 

foo gets notified about bar

 

 

 

 

User john joins

 

wss://acc-websocket-chat-domain007.apaas.em2.oraclecloud.com/chat/john/

 

 

 

foo and bar are notified

 

 

     

 

 

foo sends a message to everyone (public)

 

 

 

Both bar and john get the message

             

 

bar sends a private message to foo

 

 

Only foo gets it

 

 

In the meanwhile, john gets bored and decides to leave the chat room

 

 

 

Both foo and bar get notified

 

         

 

That's all folks !

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.