Oracle Bpm 11g Approval Performance Optimization

background

The project coordinates office work and workflow using Oracle Bpm products. Recently, it took a long time for the approval of complaints by Party A's father. We hope to reach the average time of 1 second. Party A's dad asked for it, but there was no way to do so, so we started a painful optimization process.Keep a record of this optimization process in Purdue's thoughts, hoping to reduce similar pain.

Problem Description

Environment Description

  1. Oracle Bpm 11.1.1.6 (hereinafter referred to as BPM)
  2. Oracle ESB 12.1.3 (hereinafter referred to as OSB), Oracle SOA Suit 11g (hereinafter referred to as SOA)

Code Call Path

Business Code-->OSB-->SOA-->JAVA Approval Code-->Call the approval API provided by bpm

This article mainly describes how to optimize the JAVA approval code

Related Codes

The following code should be familiar to students who are familiar with the approval API provided by Oracle Bpm, if they have not been exposed to it, for reference:
Oracle Official Tutorial

Approval code on project:

package cn.com.utility.bpm.utils;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import oracle.bpel.services.workflow.StaleObjectException;
import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.task.ITaskService;
import oracle.bpel.services.workflow.task.model.CommentType;
import oracle.bpel.services.workflow.task.model.ObjectFactory;
import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.verification.IWorkflowContext;

public class TaskService {

    private IWorkflowServiceClient wfSvcClient = null;
    private ITaskQueryService taskQueryService = null;
    private ITaskService taskService = null;
    private IWorkflowContext wfContext = null;

    private static final String BPM_MANAGER_UN = "weblogic";
    private static final String BPM_MANAGER_PW = "weblogic1";
    private static final String SOA_URL = "t3://bpmtest1.wlj.com.cn:8001";

    private static final String BPM_CLIENT_TYPE = "EJB";
    private static final String BPM_LDAP_DOMAIN = "jazn.com";

    private String username;

    public TaskService() {
        super();
    }

    public IWorkflowServiceClient getWorkflowServiceClient() {

        if (wfSvcClient == null) {
            Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String> properties =
                new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String>();
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,
                           BPM_CLIENT_TYPE);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,
                           SOA_URL);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,
                           BPM_MANAGER_UN);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,
                           BPM_MANAGER_PW);
            wfSvcClient =
                    WorkflowServiceClientFactory.getWorkflowServiceClient(WorkflowServiceClientFactory.REMOTE_CLIENT,
                                                                          properties,
                                                                          null);
        }

        return wfSvcClient;
    }

    public ITaskQueryService getTaskQueryService() {

        if (taskQueryService == null) {
            taskQueryService =
                    this.getWorkflowServiceClient().getTaskQueryService();
        }

        return taskQueryService;
    }

    public ITaskService getTaskService() {

        if (taskService == null) {
            taskService = this.getWorkflowServiceClient().getTaskService();
        }

        return taskService;
    }

    public IWorkflowContext getWorkflowContext() {
        long start = System.currentTimeMillis();
        if (wfContext == null) {
            try {
                wfContext =
                        getTaskQueryService().authenticate(BPM_MANAGER_UN, BPM_MANAGER_PW.toCharArray(),
                                                           BPM_LDAP_DOMAIN);
                wfContext =
                        this.getTaskQueryService().authenticateOnBehalfOf(wfContext,
                                                                          username);
            } catch (WorkflowException e) {
                return null;
            }
        }
        return wfContext;
    }
    
    /**
     * Approval operation
     * @param taskId
     * @param outcome
     * @param comments
     * @param params
     * @return
     * @throws StaleObjectException
     * @throws WorkflowException
     */
    public Task updateTaskOutcome(String taskId, String outcome,
                                  String comments,
                                  Map params) throws StaleObjectException,
                                                     WorkflowException {
        Task task = this.getTaskById(taskId);
        if (params != null) {
            Iterator iter = params.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry)iter.next();
                if (entry.getKey() != null && entry.getValue() != null) {
                    String key = entry.getKey().toString();
                    String value = entry.getValue().toString();
                    this.updatePayloadElement(task, key, value);
                }
            }
        }

        ObjectFactory factory = new ObjectFactory();
        CommentType commentType = factory.createCommentType();
        commentType.setComment(comments);
        commentType.setCommentScope("TASK");
        commentType.setTaskId(taskId);
        commentType.setAction(outcome);
        task.addUserComment(commentType);
        this.getTaskService().updateTask(this.getWorkflowContext(), task);
        task = this.getTaskById(taskId);
        
        this.getTaskService().updateTaskOutcome(this.getWorkflowContext(),
                                                task, outcome);

        return task;
    }

    public void updatePayloadElement(Task task, String name, String value) {
        task.getPayloadAsElement().getElementsByTagName(name).item(0).setTextContent(value);
    }

    public Task getTaskById(String taskId) {
        Task task = null;
        try {
            task = this.getTaskQueryService().getTaskDetailsById(this.getWorkflowContext(),
                                              taskId);
        } catch (WorkflowException e) {
            e.printStackTrace();
        }
        return task;
    }

}

Problem investigation

The process is painful:...

(PS: Don't know if there's a big man who can provide a way to reduce the pain of this process?Welcome to Big Guy!

thinking

Record ideas for sorting out:

  1. Source check
  2. Printing each piece of code that may take a long time takes time
  3. Deployment, Monitoring
  4. Export log, statistical analysis

Result

The approval interface on a project implements two main things:

  1. Approval
  2. Record approval history

Analyzing from the log, recording the approval history (this step may not be necessary, but due to various reasons on the project, the official approval history table (WFHISTORY) of BPM cannot be used, and the approval history needs to be written in a separate history table) can be ignored for the time being.

So the following section will focus on descriptions such as optimizing approval operations.

Optimized Processing

Log Analysis

  1. Combined with the source code, it can be seen that the total time taken to get the context is about 4s
  2. Apparently a BPM-approved API was called repeatedly during approval
[getWorkflowContext]Obtain context Time taken for tests cost(ms):918
[getWorkflowContext]Obtain context Time taken for tests cost(ms):700
[getWorkflowContext]Obtain context Time taken for tests cost(ms):1957
[getWorkflowContext]Obtain context Time taken for tests cost(ms):300
[BpmService]taskId :0a28e3e0-8c75-4eac-ae0d-78a29783be83 Approval operation cost(ms):1917
[BpmService]taskId :0a28e3e0-8c75-4eac-ae0d-78a29783be83 Record approval history cost(ms):8
[BpmService]taskId :0a28e3e0-8c75-4eac-ae0d-78a29783be83 Time taken for tests cost(ms):5892[MHM.MHM_RECEIPT_HEADERS_T]

optimization

  1. Cache context
  2. Code to handle duplicate API calls

    • Add parameters (system/process identification) to skip processes that do not have Outcomes Require Comment set.
    • After testing, it is found that if Outcomes Require Comment is set for BPM process modeling, the updateTaskOutcome() method cannot be called directly, and the following code needs to be called first:
    this.getTaskService().updateTask(this.getWorkflowContext(), task);

Set Outcomes Require Comment on the human Task when modeling Bpm:

Code examples:

 //Identify if OUTCOME must be commented on the process model
Boolean comment = true;
if (funName != null && funName.startsWith("CRM.")) {
    //CRM system does not comment
    comment = false;
}
//Omit some code
if (comment) {
    this.getTaskService().updateTask(this.getWorkflowContext(), task);
    task = this.getTaskById(taskId);
}

Related Codes

package cn.com.utility.bpm.utils;

import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import oracle.bpel.services.workflow.StaleObjectException;
import oracle.bpel.services.workflow.WorkflowException;
import oracle.bpel.services.workflow.client.IWorkflowServiceClient;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;
import oracle.bpel.services.workflow.query.ITaskQueryService;
import oracle.bpel.services.workflow.task.ITaskService;
import oracle.bpel.services.workflow.task.model.CommentType;
import oracle.bpel.services.workflow.task.model.ObjectFactory;
import oracle.bpel.services.workflow.task.model.Task;
import oracle.bpel.services.workflow.verification.IWorkflowContext;

public class TaskService2 {

    private IWorkflowServiceClient wfSvcClient = null;
    private ITaskQueryService taskQueryService = null;
    private ITaskService taskService = null;
    private IWorkflowContext wfContext = null;

    private static final String BPM_MANAGER_UN = "weblogic";
    private static final String BPM_MANAGER_PW = "weblogic1";
    private static final String SOA_URL = "t3://bpmtest1.wlj.com.cn:8001";

    private static final String BPM_CLIENT_TYPE = "EJB";
    private static final String BPM_LDAP_DOMAIN = "jazn.com";
    private static IWorkflowContext managerWfContext = null;
    private static Map<String, TokenCache> tokenCache =
        new Hashtable<String, TokenCache>();

    private String username;

    public TaskService2() {
        super();
    }

    public IWorkflowServiceClient getWorkflowServiceClient() {

        if (wfSvcClient == null) {
            Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String> properties =
                new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String>();
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,
                           BPM_CLIENT_TYPE);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,
                           SOA_URL);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,
                           BPM_MANAGER_UN);
            properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,
                           BPM_MANAGER_PW);
            wfSvcClient =
                    WorkflowServiceClientFactory.getWorkflowServiceClient(WorkflowServiceClientFactory.REMOTE_CLIENT,
                                                                          properties,
                                                                          null);
        }

        return wfSvcClient;
    }

    public ITaskQueryService getTaskQueryService() {

        if (taskQueryService == null) {
            taskQueryService =
                    this.getWorkflowServiceClient().getTaskQueryService();
        }

        return taskQueryService;
    }

    public ITaskService getTaskService() {

        if (taskService == null) {
            taskService = this.getWorkflowServiceClient().getTaskService();
        }

        return taskService;
    }
    
    private IWorkflowContext getIWorkflowContext() {
        String logTag = "[getWorkflowContext]";
        long totalStartTime = System.currentTimeMillis();
        long totalEndTime = 0;
        long startTime = 0;
        long endTime = 0;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        TokenCache token = tokenCache.get(username);
        if (token == null || token.isTimeout()) {
            System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                               "create new context cache:" + username);
            try {
                if (managerWfContext == null) {
                    startTime = System.currentTimeMillis();
                    managerWfContext =
                            getTaskQueryService().authenticate(BPM_MANAGER_UN,
                                                               BPM_MANAGER_PW.toCharArray(),
                                                               BPM_LDAP_DOMAIN);
                    endTime = System.currentTimeMillis();
                    System.out.println(logTag + "[" + df.format(new Date()) +
                                       "]" + " Obtain managerWfContext cost(ms):" +
                                       (endTime - startTime));
                }
                startTime = System.currentTimeMillis();
                wfContext =
                        this.getTaskQueryService().authenticateOnBehalfOf(managerWfContext,
                                                                          username);
                endTime = System.currentTimeMillis();
                System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                                   " Obtain wfContext cost(ms):" +
                                   (endTime - startTime));
                startTime = System.currentTimeMillis();
                if (token == null) {
                    token = new TokenCache();
                    token.setWorkflowContext(wfContext);
                    tokenCache.put(username, token);
                } else {
                    token.updateToken(wfContext);
                }
                endTime = System.currentTimeMillis();
                System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                                   " Processing Cache cost(ms):" + (endTime - startTime));
                System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                                   "WorkflowContext Initialization Completed!");
            } catch (WorkflowException e) {
                System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                                   "Obtain IWorkflowContext Report errors");
                System.out.println(e.getMessage());
                return null;
            }
        } else {
            System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                               "get context from cache:" + username);
            wfContext = token.getWorkflowContext();
        }
        totalEndTime = System.currentTimeMillis();
        System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                           " Obtain context Time taken for tests cost(ms):" +
                           (totalEndTime - totalStartTime));
        return wfContext;
    }
    
    public IWorkflowContext getWorkflowContext() {
        String logTag = "[getWorkflowContext]";
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        IWorkflowContext context = getIWorkflowContext();
        //Add a retry after an error or timeout
        if (context == null) {
            System.out.println(logTag + "[" + df.format(new Date()) + "]" +
                               "Retrieve IWorkflowContext");
            managerWfContext = null;
            context = getIWorkflowContext();
        }
        return context;
    }
    
    /**
     * Approval operation
     * @param taskId
     * @param outcome
     * @param comments
     * @param params
     * @param funName System Identification
     * @return
     * @throws StaleObjectException
     * @throws WorkflowException
     */
    public Task updateTaskOutcome(String taskId, String outcome,
                                  String comments, Map params,
                                  String funName) throws StaleObjectException,
                                                         WorkflowException {
        Task task = this.getTaskById(taskId);
        if (params != null) {
            Iterator iter = params.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry)iter.next();
                if (entry.getKey() != null && entry.getValue() != null) {
                    String key = entry.getKey().toString();
                    String value = entry.getValue().toString();
                    this.updatePayloadElement(task, key, value);
                }
            }
        }
        
        //Identify if OUTCOME must be commented on the process model
        Boolean comment = true;
        if (funName != null && funName.startsWith("CRM.")) {
            //CRM does not comment
            comment = false;
        }
        ObjectFactory factory = new ObjectFactory();
        CommentType commentType = factory.createCommentType();
        commentType.setComment(comments);
        commentType.setCommentScope("TASK");
        commentType.setTaskId(taskId);
        commentType.setAction(outcome);
        task.addUserComment(commentType);
        if (comment) {
            this.getTaskService().updateTask(this.getWorkflowContext(), task);
            task = this.getTaskById(taskId);
        }
        this.getTaskService().updateTaskOutcome(this.getWorkflowContext(),
                                                task, outcome);

        return task;
    }
    
//    //Old Code
//    public IWorkflowContext getWorkflowContext() {
//        long start = System.currentTimeMillis();
//        if (wfContext == null) {
//            try {
//                wfContext =
//                        getTaskQueryService().authenticate(BPM_MANAGER_UN, BPM_MANAGER_PW.toCharArray(),
//                                                           BPM_LDAP_DOMAIN);
//                wfContext =
//                        this.getTaskQueryService().authenticateOnBehalfOf(wfContext,
//                                                                          username);
//            } catch (WorkflowException e) {
//                return null;
//            }
//        }
//        System.out.println("getWorkflowContext cost:" +
//                           (System.currentTimeMillis() - start) + "ms");
//        return wfContext;
//    }
    
//    Old Code
//    /**
//     * Approval operation
//     * @param taskId
//     * @param outcome
//     * @param comments
//     * @param params
//     * @return
//     * @throws StaleObjectException
//     * @throws WorkflowException
//     */
//    public Task updateTaskOutcome(String taskId, String outcome,
//                                  String comments,
//                                  Map params) throws StaleObjectException,
//                                                     WorkflowException {
//        Task task = this.getTaskById(taskId);
//        if (params != null) {
//            Iterator iter = params.entrySet().iterator();
//            while (iter.hasNext()) {
//                Map.Entry entry = (Map.Entry)iter.next();
//                if (entry.getKey() != null && entry.getValue() != null) {
//                    String key = entry.getKey().toString();
//                    String value = entry.getValue().toString();
//                    this.updatePayloadElement(task, key, value);
//                }
//            }
//        }
//
//        ObjectFactory factory = new ObjectFactory();
//        CommentType commentType = factory.createCommentType();
//        commentType.setComment(comments);
//        commentType.setCommentScope("TASK");
//        commentType.setTaskId(taskId);
//        commentType.setAction(outcome);
//        task.addUserComment(commentType);
//        this.getTaskService().updateTask(this.getWorkflowContext(), task);
//        task = this.getTaskById(taskId);
//        
//        this.getTaskService().updateTaskOutcome(this.getWorkflowContext(),
//                                                task, outcome);
//
//        return task;
//    }
    
    

    public void updatePayloadElement(Task task, String name, String value) {
        task.getPayloadAsElement().getElementsByTagName(name).item(0).setTextContent(value);
    }

    public Task getTaskById(String taskId) {
        Task task = null;
        try {
            task = this.getTaskQueryService().getTaskDetailsById(this.getWorkflowContext(),
                                              taskId);
        } catch (WorkflowException e) {
            e.printStackTrace();
        }
        return task;
    }

}

Result

  1. Processes with Outcomes Require Comment have an average time reduction of 2 seconds due to the addition of a caching mechanism
  2. Average process time without Outcomes Require Comment set is 1 second

Last

After making the cache and system identification, I have met Dad A at last.

Other Optimizations

  1. Modify approval scenario: Consider using message queues such as MQ for asynchronous processing
  2. Optimizing approval history: If approval history needs to be recorded separately, it is recommended to use message queues such as MQ for asynchronous processing

Tags: Oracle Java Weblogic

Posted on Sun, 08 Dec 2019 13:41:06 -0500 by MattG