IBM Support

Launching an external workflow via the Case Information widget

Technical Blog Post


Abstract

Launching an external workflow via the Case Information widget

Body

IBM Case Manager allows documents to be filed in case folders, and presents these documents to a case worker through a Case Information widget. Out-of-the-box menu actions included with the Navigator Framework on which ICM is built allow a case worker to view, print, or comment on documents. Workers can move a document from one case to another case, using the “Cut” and “Paste” actions.

However, this copy/paste pattern requires multiple clicks, page switching, and most importantly, that the target case already exists. In a more complicated scenario, imagine that a case worker needs to move a document from one Policy case to another Policy. The requirements given to the developer state that the case worker must type a new policy number, which must be validated, and then the system should move the document from the old Policy case to a new one. A new Policy case must be created it if one doesn’t yet exist for the entered policy number.

No out of the box action exists to accomplish this. Fortunately, Case Information widget allows a developer to customize the right-click popup menu for the Documents view, to invoke a script action that can fulfil the requirement.

The custom script action I’ll describe in this post will use several Navigator Framework tools to fulfill the requirement:

  • Launch a custom dialog so a case worker may enter a new policy number for the selected document
  • Validate the keyed policy number using a custom Navigator Framework service
  • After the dialog completes, launch an external P8 workflow that will either create a new case or file the document in an existing case.

I’ll do this all within one deployable JAR file. I’ll go through the widget configuration, script action JavaScript, external workflow, custom dialog JavaScript, and custom service Java code one piece at a time. Start by launching Case Builder.

Widget Configuration

Using Case Builder, create or edit a solution that needs a ‘Move Document’ function. Click the Pages tab, then edit the page that contains the Case Information widget. Typically this is a Case Details page or a Work Details page. Click the widget Settings button and the Menus tab. Then click the button to add an item to the Documents view menu, and choose Script Action. Enter “Move Document” as the label for the button.

I could put code directly into the Script Action box in Case Builder, but this script will be more than 50 lines long, and long scripts pasted directly there are very difficult to debug. Instead, I’ll use the pattern shown in the blog post entitled “Best Practices for Debugging and Organizing the Scripting Code”, and add only one line to the Script Action box in Case Builder, and the balance of the JavaScript in a plugin for Navigator Framework that will be built next. Here’s the line to add:

blogscripts.doMoveDocumentDialog(this);

In this case, blogscripts refers to the global object that our plugin will create, and doMoveDocumentDialog is the method on the object that will be called. When a script is called from a Script Action, this refers to an ecm.model.Action object full of useful context that the script will require. The screenshot in Figure 1 shows the settings pane of the Case Information widget.

imageFigure 1: Case Information widget settings

 

Script Action Javascript

I’ll be using Eclipse Luna to build the plugin for Content Navigator. To set up the Eclipse environment, it’s best to read chapter 3 of the IBM Redbooks publication, Extending and Customizing IBM Content Navigator. Before starting Eclipse, visit that RedBook’s web page, click the Additional Materials link and download the file SG248055.zip. Unzip it, and copy the two JAR files from the ‘usecase/Chapter 3’ folder into the Eclipse plugins directory.

From the Eclipse ‘File’ menu, create a new Content Navigator Plug-in project. I’ll call it BlogPlugin. The project template will automatically create and update a suitable blogplugin.java file to wrangle all of the plugin parts, a WebContent directory to hold the JavaScript, and a blogplugin.js file in the WebContent directory. Right-click the blobpluginDojo package and create an additional javascript file there named “MoveDocumentDialog.js”. I’ll fill in the contents of that file later.

Next, right-click the ‘src’ folder and choose “IBM Content Navigator”, “Service Extensions”, “New Service…” from the popup menus. Place the service in the same package as the plugin and name the class PolicyValidator. The project structure should look like Figure 2, below, when this is completed.

 

image

Figure 2: Project structure in eclipse

 

Open the blogplugin.js file in the WebContent package and replace the template with the code below. I’m adding a global object here, named “blogscripts”.

require(["dojo/_base/lang", "blogpluginDojo/MoveDocumentDialog"], function(lang, MoveDocumentDialog){
    lang.setObject("blogscripts", {    
        doMoveDocumentDialog : function(action) {
            var solution = action.getActionContext("Case")[0].caseObject.caseType.solution;
            var document = action.getActionContext("Document")[0];
 
            var callback=function(args) {
                if(!args.cancel) {
                    var cb1=function() {
                        //Launch the workflow
                        var workItem = new ecm.model.WorkItem({id:"{2F4A4239-5642-4D53-8B96-DD2AEF0D38C1}",
                            name:"Move Policy Document",
                            repository:solution.targetObjectStore,
                            connectionPoint:solution.connectionPoint,
                            objectStore:solution.targetObjectStore.objectStore,
                            workclass_name: "Move Policy Document"});
 
                        workItem.retrieveProcessorInformation("launch", dojo.hitch(this, function(workItem) {
                            workItem.retrieveLaunchParameters(dojo.hitch(this, function(workItem) {
                                var attachmentItems= [];
                                var type=3;
                                if(document.isFolder())
                                    type=2;
 
                                attachmentItems.push({ "attachment_id":document.id.split(",")[2],
                                    "attachment_name":document.name,
                                    "attachment_library_type":3,
                                    "attachment_repository_id":document.repository.id,
                                    "attachment_library_name":document.repository.objectStoreDisplayName,
                                    "attachment_version":"-1",
                                    "attachment_vsid":document.vsId,
                                    "attachment_type":type,
                                    "attachment_description":document.repository.domainId});
 
                                var properties = [];
                                properties.push({dataType:"xs:string", displayValue:args.policyNumber,
                                    label:"policyNumber", name:"policyNumber", value:args.policyNumber});
                                properties.push({dataType:"xs:attachment", label:"initiatingDoc",
                                    name:"initiatingDoc", value:attachmentItems});
 
                                workItem.completeStep(properties, true, function(item, response) {console.log("launch complete.");});
                            }));//retrieveLaunchParameters
                        })//dojo.hitch
                        , false, null, false);//retreiveProcessorInformation
                    }
                    /*first unfile it from the case it's filed in*/
                    document.parent.removeFromFolder (document,dojo.hitch(this,cb1));
                }//args.cancel
            }//callback
            var mdDialog = new MoveDocumentDialog({documentName:document.name, callback:dojo.hitch(this,callback)});
            mdDialog.show();
        }//doMoveDocumentDialog
    });//setObject
});//require

 

Listing 1: blogplugin.js

Let’s step through this code. First, it must be mentioned that the function as shown will work only with a P8 Object Store repository, so if the repository in use is a Content Manager or CMIS repository, the library type and a few other properties of the attachment object will need adjusting. Because several callbacks are used, the javascript code doesn’t execute in the order seen in Listing 1. I’ll explore it in the order that it actually executes. First, I grab the solution and document contexts from the action object that Case Client passes in when the case worker clicks the custom menu option. Then I create the dialog box and show it. I’ll explore the dialog in detail later, for now just note that the dialog takes a callback as an argument, which is invoked when the case worker clicks “OK” or “Cancel”.

    var solution = action.getActionContext("Case")[0].caseObject.caseType.solution;
    var document = action.getActionContext("Document")[0];
 ...
    var mdDialog = new MoveDocumentDialog({documentName:document.name, callback:dojo.hitch(this,callback)});
    mdDialog.show();

This code gets the dialog to pop. After a case worker enters a valid policy number and clicks “OK”, the callback is invoked. The first thing the callback does is to unfile the document from the old case using ecm.model.ContentItem’s removeFromFolder method. This method takes a callback argument, ‘cb1’ in this case, which is the function that I’ll use to launch the workflow.

    document.parent.removeFromFolder (document,dojo.hitch(this,cb1));

Because the Case Information widget subscribes to changes to the case folder, the document disappears from the widget immediately. Next I want to launch a P8 workflow that exists outside of Case Manager. The workflow’s launch step takes an attachment and policy number as parameters. It searches for existing cases, and if it finds one, files the attachment there. If it doesn’t find one, it creates a new case. Let’s look at how the workflow is created:

    var workItem = new ecm.model.WorkItem({id:"{2F4A4239-5642-4D53-8B96-DD2AEF0D38C1}",
        name:"Move Policy Document",
        repository:solution.targetObjectStore,
        connectionPoint:solution.connectionPoint,
        objectStore:solution.targetObjectStore.objectStore,
        workclass_name: "Move Policy Document"});

To get the ID needed here, locate the workflow in the Content Navigator browse view, right-click it, and expand the System Properties. Also, be sure the workflow has been transferred to the Process Engine database. Now that I have a workItem object, I can start filling it out. I start by retrieving the template for the launch step processor. This function takes a callback as an argument, which is invoked when the step template is retrieved.

    workItem.retrieveProcessorInformation("launch", dojo.hitch(this, function(workItem) {

The callback then fetches the launch step parameters:

    workItem.retrieveLaunchParameters(dojo.hitch(this, function(workItem) {

Now that I have the step and its parameters, I can fill the parameters in. I’ll be passing in a new policy number (that the case worker keys in the dialog), and the document that they’ve selected from the Case Information widget. But I can’t simply throw the ecm.model.ContentItem that I get from the Action at PE. I’ve got to create an attachment that the step processor can understand:

    attachmentItems.push({ "attachment_id":document.id.split(",")[2],
        "attachment_name":document.name,
        "attachment_library_type":3,
        "attachment_repository_id":document.repository.id,
        "attachment_library_name":document.repository.objectStoreDisplayName,
        "attachment_version":"-1",
        "attachment_vsid":document.vsId,
        "attachment_type":type,
        "attachment_description":document.repository.domainId});

Next I set the launch step parameters: the new policy number that the case worker entered and the attachment that I created.

    properties.push({dataType:"xs:string", displayValue:args.policyNumber, label:"policyNumber", name:"policyNumber", value:args.policyNumber});
    properties.push({dataType:"xs:attachment", label:"initiatingDoc", name:"initiatingDoc", value:attachmentItems});

Finally I complete the step and log the accomplishment to the console!

    workItem.completeStep(properties, true, function(item, response) {console.log("launch complete.");});

External Workflow

The workflow I’m using is created outside of Case Manager, using Process Designer. The beauty of this pattern is that the workflow can do absolutely anything required before, during, and after the case creation. To get the workflow to launch properly, ensure that the workflow data fields (above named policyNumber and initiatingDoc) are set as read/write parameters of the launch step, and that initiatingDoc is set as the workflow’s Initiating Attachment. The workflow’s name, (I’m using “Move Policy Document” here), may be changed as long as it matches the name and workclass name that’s passed to the constructor of ecm.model.WorkItem.  Ensure that the workflow gets transferred to the PE database using the same name. Finally, the workflow’s launch step processor must be set to Navigator Launch Processor. See figures 3 and 4 below.

image

Figure 3: Set the Workflow Name

image

Figure 4: Set the Launch Step Parameters and Step Processor

The steps in the workflow map will vary according to the application. Below is an example of a simple map that does what I described earlier in this article: searches for an existing case, and if it finds one, files the document there. If it doesn’t find one, it creates a new case and files the document in it.

Figure 5 shows the workflow map. Component steps invoking ce_operations are used extensively in this workflow map. The “Update Doc Property” step uses the setStringProperty operation. “Search For Policy Case” uses the searchForOne operation with a stored search that searches for folders of the given case type with a policy number matching the validated number that the case worker keyed in the dialog box. “Create New Case” simply creates a new folder of the desired case folder class, while “Move To Case” uses the move operation to move the document from whatever folder it may be filed in to the new case folder.

image

Figure 5: Example workflow map

Custom Dialog

The custom Dialog I’ll use extends ecm.widget.dialog.BaseDialog. Most of Listing 2 is mundane JavaScript and Dojo. The most important section is the custom service invocation, ecm.model.Request.invokePluginService(). This method takes as arguments the service’s plugin name (“blogplugin”), the service name (“PolicyValidator”), and an object containing parameters to pass into the service as well as callbacks for success or failure.

I send the typed policy number to the service to be validated, and receive its response via callback. I toggle the enabledness of the OK button based on whether or not the service returns to us a valid policyHolder for the policyNumber that I send it. If it returns an exception, I notify the case worker that they need a good policy number to proceed.

define(["dojo/_base/declare", "dojo/_base/lang", "dojo/dom-geometry", "dojo/dom-style", "ecm/widget/dialog/BaseDialog"],
        function (declare, lang, domGeom, domStyle, BaseDialog) {
    return declare( [BaseDialog], {
        contentString:
            "<div class=\"paddingDiv\"><label dojoAttachPoint=\"docLabel\"></label></div><div><div class=\"paddingDiv\">" +
            "<div class=\"paddingDiv\"><label for=\"${id}_commentText\">To Policy Number:</label></div>" +
            "<input class=\"taskTypesInput\" name=\"policyNumber\" id=\"${id}_policyNumber\" dojoType=\"dijit.form.ValidationTextBox\" dojoAttachPoint=\"policyNumberInput\">" +
            "</input></div><div class=\"paddingDiv\"><label dojoAttachPoint=\"userLabel\">Policy Holder: <i>Tab to validate</i></label></div>",
            resourceBundle:null,
            constructor:function (arguments) {
                this._callback = arguments.callback;
                this._documentName = arguments.documentName;
                this._isOK = false;
            }, postCreate:function () {
                this.inherited(arguments);
                this.setTitle("Move Document");
                this.setResizable(false);
                this.setSize(420, 240);
                var okButton = this.addButton("Move", "_onOK", true, true);
                this.docLabel.innerHTML = "Moving Document: <i>" + this._documentName + "</i>";
 
                this.policyNumberInput.domNode.onchange = dojo.hitch(this, function (id) {
                    var policyNumber = this.policyNumberInput.attr("value");
                    var serviceParams = {policyNumber:policyNumber};
 
                    ecm.model.Request.invokePluginService("blogplugin", "PolicyValidator", {
                        requestParams: serviceParams,
                        backgroundRequest: false,
                        requestCompleteCallback: dojo.hitch(this, function(response) { // success
                            if(response.properties["Exception"] != null) {
                                this.userLabel.innerHTML = "Policy not found. Correct and tab to validate.";
                                okButton.attr("disabled", true);
                            }
                            if(response.properties["policyHolder "] != null) {
                                this.userLabel.innerHTML = "Policy Holder: " + response.properties["policyHolder"];
                                okButton.attr("disabled", false);
                            }
                        }),
                        requestFailedCallback: dojo.hitch(this, function(response)      { // failed
                            console.log("request failed: " + response);
                            this.userLabel.innerHTML = "Lookup failed. Correct and tab to validate.";
                            okButton.attr("disabled", true);
                        })
                    });
                });
            }, show:function () {
                this.inherited(arguments);
                this._isOK = false;
            }, resize:function () {
                this.inherited(arguments);
                var width = domGeom.getContentBox(this.domNode).w - 50;
                domStyle.set(this.policyNumberInput.domNode, "width", width + "px");
            }, _onOK:function () {
                this._isOK = true;
                this.hide();
            }, hide:function () {
                this.inherited(arguments);
                if(!this._isOK)
                    this._callback({cancel: true});
                else
                    this._callback({cancel: false,
                        policyNumber: this.policyNumberInput.attr("value")
                    })
            }
    }
    );}
);
//@ sourceURL=MoveDocumentDialog.js

Listing 2: MoveDocumentDialog.js

Custom Navigator Service

The Eclipse plugin automatically created a skeleton service for us when I added the PolicyValidator.java file to our project. I only have to implement the execute method to validate the policy number that the dialog sends it. Typically this would involve calling a system of record database to validate the number and return the policy holder’s name. This example will just use a dummy function. The key is to return JSON in a format that ecm.model.Request.invokePluginService() expects.

    public void execute(PluginServiceCallbacks callbacks,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
       
        String model = "{\"properties\": {";
        String policyNumber = request.getParameter("policyNumber");
       
        //The book is a little thin...
        if(policyNumber.equals("1234567890"))
            model +=  "\"policyHolder\":\"Jane Example\"";
        else
            model += "\"Exception\":\"Not Found\"";
       
        model += "}}";
       
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        out.println(model);
    }

Listing 3: PolicyValidator.java (partial)

Deployment

From the Eclipse Package Explorer pane, right-click the build.xml file and chose Run As->Ant Build from the popup menu to generate a JAR file and deploy it to Content Navigator as a plugin or folder, as described in the blog post entitled “Best Practices for Debugging and Organizing the Scripting Code”. Deploy the solution and create a case. Add a document to the case, and right-click it in the Case Information Widget. Choose “Move Document” from the popup menu and the new custom Move Document Dialog should appear.

image

Figure 6: Completed custom dialog

Enter a valid policy number (there’s only one valid number in the example) and click the Move button. The document gets removed from the Case Information widget immediately. The launched workflow may be found in Process Administrator, but might complete too quickly to find it there. Otherwise, after it completes, locate the moved document in a new case or in an existing case, if there was one.

[{"Business Unit":{"code":"BU053","label":"Cloud & Data Platform"},"Product":{"code":"SSCTJ4","label":"IBM Case Manager"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":"","Line of Business":{"code":"LOB45","label":"Automation"}}]

UID

ibm11281100