Skip to Content
Technical Articles

Extract CustomAttributes into an own data source

In a typically end user scenario with a custom inbox it is sometimes necessary to display custom attributes with context specific content of the tasks. For this exists a feature inside of the BPM Framework that allows to set CustomAttributes to the BPM Task directly. This is a nice feature, but it runs into issues in high volume situations (task lists with more than 1000 – 2000 tasks). This phenomenon can be observed inside public BPM API and oData requests. A lot of time for one request will be spend for the internal selections of the custom attributes and not to get the whole list the task abstracts for an specific end user.

In this article you will learn to extract CustomAttributes into an own data source and you get a hint how you can fill the data source for still existing tasks.

Prerequisite

Before the CustomAttributes can filled or extracted into an own data source, a implementation for a data source in necessary. Please look at this blog article Persist additional task data with a small DB footprint to build a persistency layer where the data can be saved.

Realization

After creating a persistency layer it is required to fill this data source with the relevant content of your CustomAttributes.

For new tasks this can realized very easy with one of the BPM Events USERTASK_ACTIVATED or USERTASK_CREATED. If you don’t use the aBPM Framework you must implement an event listener bean (please see help.sap.com/javadocs to get the relevant information of your Netweaver release). If you use the aBPM Framework scenario you must overwrite the operations onTaskActivated() or onTaskCreated() inside of your <aBPMScenario>ExtBase class.

@Override
public void onTaskActivated(CallbackContext ctx, URI task) {
    logger.infoT(MessageFormat.format("Start create task notification for task inside onTaskActivated(): 
    {0}", task.toString()));
    super.onTaskActivated(ctx, task);

    try {
        TaskProvider.persistCustomAttributes(ctx, task);
    } catch (TaskDataServiceException e) {
        SimpleLogger.trace(Severity.ERROR, logger, "persistCustomAttributes(): Error when trying to 
        persist taskdata with URI " + task.toString() + "", e);
    }

    ...

    logger.infoT(MessageFormat.format("Finished task notification for task inside onTaskActivated(): {0}", 
    task.toString()));
}

The TaskProvider is another class inside the scenario implementation that reads and prepare the list of CustomAttributes before the persistency service will be called:

public class TaskProvider {

    private static TaskDataServiceLocal taskDataService = 
     JndiUtils.getEjbByInterface(TaskDataServiceLocal.class);
    private static BPMIdHelper idHelper = BPMFactory.getBPMIdHelper();

    /**
     * persist Custom Attributes
     */
    public static void persistCustomAttributes(CallbackContext ctx, URI task) throws 
    TaskDataServiceException {

        <TypedBO> typeBo = new <TypedBO>(ctx.getBusinessObject());
        String taskId = idHelper.convertToHexString(task);

        Map<String, List<String>> customAttr = new HashMap<String, List<String>>();

        List<String> customAttrList = new ArrayList<String>();
        customAttrList.add(typeBo.getControl_DocumentCategory());
        customAttr.put(Constants.CUSTOM_ATTRIBUTE_DOCUMENT_CATEGORY, customAttrList);

        customAttrList = new ArrayList<String>();
        customAttrList.add(typeBo.getInvoiceHeader_SupplierName());
        customAttr.put(Constants.CUSTOM_ATTRIBUTE_SUPPLIER_NAME, customAttrList);

        customAttrList = new ArrayList<String>();
        CurrencyAmount ca = typeBo.getInvoiceHeader_SupplierNetAmountLC();
        if (ca != null) {
            customAttrList.add(ca.getValue().toString());
        } else {
            customAttrList.add("");
        }
        customAttr.put(Constants.CUSTOM_ATTRIBUTE_NET_AMOUNT_LC, customAttrList);

        customAttrList = new ArrayList<String>();
        Date date = typeBo.getInvoiceHeader_DiscountDate();
        if (date != null) {
            customAttrList.add(date.toString());
        } else {
            customAttrList.add("");
        }
        customAttr.put(Constants.CUSTOM_ATTRIBUTE_DISCOUNT_DATE, customAttrList);

        ... and other custom attributes

        taskDataService.createAndUpdateTaskDataByTaskInstanceIDAndHashMap(taskId, customAttr);
    }

}

For existing tasks it is a little bit more complex. The reason is that these tasks still exists inside the system and the BPM Events for TaskActivate or TaskCreate were executed in the past.

For this there are two options to enrich the task persistency layer:

  1. Check by every access to the task if the additional CustomAttributes exists inside of the custom persistency layer
    or
  2. Create a Job that scan the system for BPM Tasks and enrich the custom persistency layer

So the first option is much performance intensive because this must be done by every access via CustomInbox and/or Task access (onAfterTaskClaimed/onBeforeOutput) -> at the end this not so optimal. The Job has the advantage to run in the background and fill the custom persistency layer step by step. At the end all CustomAttributes for all relevant tasks are inside the custom persistency layer and you must not check again and again if something is missing.

Hint: The tasks that have to be check by a job should reduce to the tasks that you really need. In case your custom inbox has only a filter for status Ready or InProgress tasks in makes sense to search inside the job only for tasks that has status Ready & Suspended (BPM Task LifecycleStatus = 20, Task with status Erroneous and Failed are also be covered with this lifecycle status) and InProgress (BPM Status = 30). In case you need more (e.g. Completed (BPM Task LifecycleStatus = 40) or Cancelled (BPM Task LifecycleStatus = 50)) the selection of your job must be adapted.

After the implementation to enrich an own data source with CustomAttributes an adaption of the implementation where you read the task list for a custom inbox must be changed. Typically it is a kind of a TaskProviderFacade. The reason for a change it is the CustomAttributes were read beforehand via BPM API calls, now it must be read from your datasource. Technically the API call must be maintained so that CustomAttributes don’t be read the from the BPM framework.
Tip: Use a flag to control inside of your TaskProviderFacade implementation if the CustomAttributes should be read from public BPM API or from your data source. This makes it easier to compare the changes and runtime behaviour before and after the switch. Additionally you have the option to switch back if the internal BPM API call was fixed to use the default functionality or to switch if you see issues with your persistency layer.

Conclusion

The extraction of CustomAttributes into an own data source is interesting when you have the requirement to access a lot of custom specific data in combination with high volumes of tasks with a fast response time. In case you have only less tasks with less or no custom attributes and you can spend some time to execute the public BPM API the BPM feature can be used furthermore. But keep in mind that the public BPM API selects internally the CustomAttributes more sequentially (discovered behavior inside productive projects) while your selects on the own data source can be done via a bulk execution.

Be the first to leave a comment
You must be Logged on to comment or reply to a post.