Introduction
This article outlines support available in the DFC that relate to Workflows and related concepts. Before looking at the DFC classes, however, let us quickly review some important concepts about Workflows.
Workflows may be likened to “programs” that treat collections of documents as data. Workflows describe business processes that dictate when (under what circumstances) information (represented by documents) is sent to whom (users and groups), and how documents of interest change state (represented by lifecycles) as they progress through a workflow. Workflows are described using the following terminology:
- Package: A package is a collection of related documents, treated as a unit of information.
- Activity: A step that is performed as part of a workflow. A workflow will typically have multiple activities. Workflows have one or more start activities, one or more step activities and one end activity.
- Aliases: A named set of names that map onto defined users and groups. An alias is a user-friendly name representing users and groups.
- Ports: A port is logical point of information exchange in an activity. Ports exchange information via packages. A port may be an Input port (one that accepts packages for processing), an Output port (one that sends packages from this activity to the next), or a Revert port (one that accepts a package that is returned by the next activity).
This article discusses the DFC classes that manipulate workflow templates that are already created (using Workflow Manager). In the next installment of this article, we shall look at defining workflows programmatically. This article discusses some common tasks associated with starting and managing workflows and tasks programming using DFC.
Note: The examples given below are intended merely to show how to use DFC to address the task at hand (manipulating workflows). A real (production grade) application using this code would likely have robust error checking and logging.
Obtaining A Session
The examples below assume that a DFC Session object has already been created. The examples refer to an IDfSession object named session. You probably already know how to log into a docbase and create a session, but just as a refresher, we will go over it here.
To create a session, you must first create a DfLoginInfo object. The DfLoginInfo object contains the username and password that will be used to log into the docbase. In order to perform the actual log in that creates the session object, you must pass the DFLoginInfo object along with a docbase name to the newSession() method of the DfClient object.
Login Example
DfLoginInfo loginInfo = new DfLoginInfo();
loginInfo.setUser( "aUser" );
loginInfo.setPassword( "Password" );
IDfSession session = DfClient.getLocalClient()
.newSession("aDocbase", loginInfo );
Getting a List of All the Installed Workflows
Before a workflow template (called a Business Process in Documentum lingo) can be used to create a workflow instance, the workflow template must be “installed”, which means that all of its associated activities are all valid and installed. We’ll cover how to validate and install activities and workflows in a further installment of this article, but for now lets see how to get a list of the workflows that are already installed and ready to use.
The object in the docbase that represents a workflow template (business process) is called the dm_process object. It has an attribute, r_definition_state whose value will tell us if the business process is installed or not.
- 0 = Draft
- 1 = Validated
- 2 = Installed
In order to get a list of the installed workflows, we simply issue a query to find all of the dm_process objects where r_definition_state = 2.
Luckily for us, the IDfSession object has a method named getRunnableProcesses() that will do just this, saving us the hassle. The getRunnableProcesses() method returns a collection of all installed business process objects. By default, the collection contains the object_name and r_object_id of the installed business process objects, but you can also specify other attributes you want it to retrieve.
Listing Installed Workflows Example
The following code snippet shows how to get a list of all installed workflow templates using the getRunnableProcesses() method:
// Get a list of all installed workflows!
System.out.println( "The following workflows are ready to run:-" );
IDfCollection Workflows = session.getRunnableProcesses("");
while ( Workflows.next() )
{
IDfTypedObject Next = Workflows.getTypedObject();
// The following chunk of code has to de with
// obtaining the attributes of the
// IDfTypedObject we got back!
java.util.Enumeration e = Next.enumAttrs();
while ( e.hasMoreElements() )
{
IDfAttr NextAttr = (IDfAttr)e.nextElement();
System.out.print( NextAttr.getName() + " = " );
int AttrCount = 0;
if ( NextAttr.isRepeating() )
AttrCount = Next.getValueCount
( NextAttr.getName() );
else
AttrCount = 1;
for (int i=0; i<AttrCount; i++)
{
// Get the next value!
IDfValue NextAttrValue = Next.getRepeatingValue
( NextAttr.getName(), i );
System.out.print( NextAttrValue );
System.out.print( ", " );
}
}
System.out.println();
}
Creating a Workflow Instance (aka Using the Workflow Builder Object)
In order to use a workflow, a specific instance of a workflow must be created from the workflow template (business process). This is accomplished by calling the IDfSession object’s newWorkflowBuilder method. This method takes a single argument, the object ID of the business process object you are using as your workflow template. It returns an IDfWorkflowBuilder object, which is a bit of a strange beast.
The IDfWorkflowBuilder object is a helper class that converts a dm_process object to an actual dm_workflow object. Its most useful methods are:
- isRunnable() – Returns true if the dm_process is in the installed state
- getStartStatus() – Returns 0 if the user has permission to start the workflow
-
initWorkflow() – This method actually does the work of creating a workflow from the business
process - runWorkflow() – This method starts a workflow
- getStartActivityNames() – Returns a list of the names of every start activity in the workflow
- addPackage() – This method adds a package to the start activity of the workflow
- getWorkflow() – Returns the run-time workflow object created by initWorkflow()
Using the IDfWorkflowBuilder object to create and start a workflow is a multi-step process. You must first call the initWorkflow() method to create a dm_workflow object. You then call the runWorkflow() method to start the workflow. If the workflow is designed with a start activity that has no input ports, the activity will become active and appear in the recipient’s inbox. If the workflow is designed with a start activity that dows have an input port, the start activity will not become active until you attach a package to its input port. To do this, use the addPackage() method. If you don’t know the name of the start activity or want to give the user a list of all the valid start activities to choose from, you can use the getStartActivityNames() method. We will discuss adding packages in detail later in this article.
Once you have started your workflow, you can get rid of the workflow builder object. It serves no purpose other than to start the workflow. It does not need to stick around in order for the workflow to function properly.
Creating a Workflow Instance Example
IDfWorkflowBuilder
workflowBuilder = session.newWorkflowBuilder
( new DfId( "4b0f475b80002164" ) );
// Init the workflow!
IDfId NewWorkflowId = workflowBuilder.initWorkflow();
System.out.println( "Workflow initted! Id: " + NewWorkflowId );
// Start the workflow running!
IDfId NewWorkflowId2 = workflowBuilder.runWorkflow();
System.out.println( "Workflow started! Id: " + NewWorkflowId2 );
// You would add your package here if necessary
// Get the workflow object we created
IDfWorkflow workflow = workflowBuilder.getWorkflow();
Getting a Handle on Aliases
You may have noticed that there is no method of the Workflow Builder that allows you to specify what users or groups should be assigned to the various activities within the workflow. This assignment takes place when the Business Process is created, not when the workflow is created. However, this seems very limiting since many times you won’t know who is supposed to receive an activity until it comes time to start the workflow. Luckily, Documentum supports the ability to assign activities when a workflow is created by the use of Alias Sets.
An Alias Set is an object in the docbase that contains a mapping of roles to the user or group that should perform that role. For example, imagine a workflow that sends a document for review and approval. The document be reviewed by the department supervisor, Fred, and approved by the manager, Barney. But if Fred gets promoted or transferred to a different department, we don’t want to have to modify every business process in the docbase just to change from Fred to his replacement.
This is where alias sets can help. We can create a simple alias set, containing a “Reviewer” role and an “Approver” role. The alias set will map Fred to the Reviewer and Barney to the Approver. We can then create a business process with two tasks, the Review task and the Approve task, and associate the business process with the Fred/Barney alias set. When the workflow is created, the server will determine each activity recipient by looking up his name in the alias set. If Fred gets promoted, we just need to change the alias set, not all the workflows.
Although this is quite helpful, there is an even more exciting way to use alias sets that allows us to choose an activity’s recipient when the workflow is created. If you associate a business process with an alias set that has empty values in it, the server will let you fill in those values when the workflow is created. If your users are using the Desktop Client, they will automatically be prompted to choose the user that should perform the ownerless activity. But if you are writing your own program that starts workflows, you will have to fill this value in yourself. This is called populating an alias set’s missing values.
Populating the missing value in an alias set is pretty straightforward once you know the object ID of the alias set. When a workflow is created, the server will make a run-time copy of the alias set specified in the business process. It is this copy that you should modify to fill out any missing values. To get the workflow’s alias set, use the IDfWorkflow object’s getAliasSetId() method. This method returns the r_object_id of the dm_alias_set object.
Once you have the ID of the Alias Set, you can loop through its values looking for empty values. If you find any, you can prompt the user to choose the correct user or group to perform the given role. You can use the Name and Description attributes of the alias to help the user understand what the role is supposed to do. The IDfAliasSet object has several methods that you may find useful:
- getAliasCount() – Returns the number of alias values in the alias set
- getAliasName() – Takes an integer index into the repeating attribute and returns the name of the alias located at the specified position
- getAliasDescription() – Returns the description of the alias located at the specified position
- getAliasValue() – Returns the value of the alias located at the specified position
AliasSet Example
The following code snippet shows how to obtain the alias set for a workflow, loop through its values, and set the value of any empty aliases to the “/articles/dmin” user.
// Get the AliasSet for this workflow!
IDfId AliasSetId = workflow.getAliasSetId();
System.out.println( "AliasSetId: " + AliasSetId );
IDfAliasSet AliasSet = (IDfAliasSet)session.
getObject( AliasSetId );
int nAliases = AliasSet.getAliasCount();
System.out.println( "Aliases:-" );
boolean AliasValuesChanged = false;
for (int i=0; i<nAliases; i++)
{
System.out.println( "tAliasName: "
+ AliasSet.getAliasName(i)
+ ", "
+ "AliasValue = "
+ AliasSet.getAliasValue(i) );
if ( AliasSet.getAliasValue(i) == null )
{
AliasSet.setAliasValue( i, "/articles/dmin" );
AliasValuesChanged = true;
}
};
if ( AliasValuesChanged )
{
// Save the AliasSetId!
AliasSet.save();
}
Packages and Activities
A workflow is made up of one or more activities – the tasks that are to be performed. Activities are assigned to certain users or groups and only become active when a package is delivered to one of the activity’s input ports. In a normal workflow, a user completes an activity and starts the next one by taking a package from the current activity’s output port and routing it to the next activity’s input port. This deactivates the current activity and activates the next one. The process of routing packages from one activity to the next is performed by the server.
A package is just a fancy way of talking about the document that is being routed around from person to person. It’s called a package because it can contain multiple documents instead of just a single document. In fact, an activity can deal with multiple packages as well. This allows an activity to send the same package to multiple users (a distribution list) or have multiple people send different packages to one person (for example, if the recipient were consolidating many documents into one).
An activity receives packages through its input port and sends packages through its output port. The input and output ports of different activities are linked together to create the workflow. To send a document from one person to another, you need two activities, A and B. The output port of A should be connected to the input port of B. This indicates who is sending the document and who is receiving it.
Once a package is attached to an activity, it does not need to be manually attached to all the subsequent activities. It will simply flow from one activity to the next without intervention. Any additional packages will have to be manually attached, but again, once they are attached they will flow as designed in the business process.
When you start a workflow, you must take the package being routed and manually attach it to the workflow’s start activity. A start activity is the entry point that allows us to “kick off” a workflow. Even though a workflow is started, its start activity won’t become active until you give it a package. A workflow can have multiple start activities, but often has only one. In the most common case, we will will be routing a single document through the entire workflow. In this case, we’ll want to take a document attach it as a package to the workflow’s start activity This can be done by Workflow Builder’s addPackage() method.
In order to use the addPackage() method, you must know the name of the start activity and the name of its input port. It is often necessary to inspect a workflow to determine these values. Luckily, the Workflow Builder (IDfWorflowBuilder) gives us a helper method named getStartActivityNames() which will give us the names of any valid start activities. To get the name of the activity’s input port, you will need to inspect the activity itself. The Workflow Builder’s getStartActivityIDs() method will give you the ID of the activity (an IDfActivity object), and you can then use the IDfActivity objects getPortCount(), getPortType(), and getPortName() methods to loop through the ports and find the input port.
Once you know the name of the Activity and the Port, you can call the Workflow Builder’s addPackage() method. This method takes the following arguments:
- startActivityName – Name of the start activity to which you are attaching this package
- inputPortName – Name of the start activity’s input port
- packageName – Name of the package. Specify your own or use the name of the document you are attaching
- packageType – The object type of the components in the package
- noteText – A text message to attach to the package for the recipient
- notePersistent – A boolean indicating whether or not to show the note to all recipients of the package, anywhere in the workflow, or just the first recipient of the package.
- componentIds – A list of all the objects you are including in this package
When the attachment is accepted by the activity, the activity will become active and place a work item in the user’s inbox.
Attaching a Package Example
This example shows the steps necessary to attach a package to a workflow after it is has been started. The steps shown are:
- Get the Names and IDs of the workflow’s start activities
- Select the first start activity and determine the name of its first input port (in a real application, you would probably ask the user which start activity to use)
- Attach a package to the activity using a hard-coded object ID (in a real application you would probably ask the user what document to attach)
// Get lists of the start activity Names and IDs
IDfList StartActivityNames = workflowBuilder.getStartActivityNames();
IDfList StartActivities = workflowBuilder.getStartActivityIds();
// Get the first start activity's Name
String ActivityName = StartActivityNames.getString(i);
// Get the first start activity ID
IDfId ActivityId = (IDfId)StartActivities.get(i);
// Construct an IDfActivity from the start activity ID
IDfActivity TheActivity = (IDfActivity)session.getObject( ActivityId );
// Get the number of ports and loop through them until you find an input port
int nPorts = TheActivity.getPortCount();
String InputPortName = null;
for (int j=0; j<nPorts; j++)
{
System.out.println( "Port Name: " + TheActivity.getPortName(j)
+ ", " + "Port Type = " + TheActivity.getPortType(j) );
if ( TheActivity.getPortType(j).equals( "INPUT" ) )
InputPortName = TheActivity.getPortName(j);
}
// With AnActivity and InputPort, we can now attach a package
// First create a list of components (even though we only have one component)
DfList List = new DfList();
List.append( new DfId( "090f475b80000140" ) );
IDfId PackageId = w.addPackage(
AnActivityName,
InputPort,
"Package Name",
"dm_document",
"This is a message to the first recipient",
false,
List );
System.out.println( "The id of the newly created package is: " +PackageId );
// Now that we have “delivered” a package to a start activity
// of the workflow, this activity will be started
Inbox Processing
When an activity becomes active, it usually results in a work item being queued up in someone’s inbox. This inbox is a Documentum-specific inbox, not your email inbox. The inbox contains a list of work items that have been forwarded to the user as well as a list of event notifications. Work items are generally workflow-related while event notifications are simply information messages generated by the server.
A user’s inbox is really just a list of the dmi_queue_item objects that are associated with that user. Documentum has created a view that you can query against that makes it easy to see what items are in a particular user’s inbox. The following query will return the items in Fred Flintstone’s inbox. In this query, the item_id is the r_object_id of the document in the inbox and the stamp is the r_object_id of the dmi_queue_item that represents the work item.
SELECT stamp, item_id, item_name, date_sent, priority FROM dm_queue WHERE name = 'Fred Flintstone'
DFC provides some methods that are useful for manipulating your inbox.
- IDfSession.getEvents() – Returns a collection of unread items in the current user’s inbox. This isn’t as useful as it sounds since most users will want to see all the items in their inbox, not just the unread ones. It’s best to call IDfSession.getEvents() to get a list of all the items in a user’s inbox.
- IDfSession.hasEvents() – Returns true if there are any new items in the user’s inbox since the last time you called hasEvents().
- IDfSession.getTasks() – Returns a collection of all the items in a user’s inbox. Use the DF_TASKS filter to get only workflow-related tasks. The getTasks() method only retreives these attributes: sent_by, date_sent, task_name, due_date, priority, task_state, r_object_id, item_id. If you want to get other attributes such as the name of the document, you must specify them in the additionalAttributes argument.
- IDfWorkItem.acquire() – Acquires the task/work item. Acquiring a work item indicates that you intend to work on it. If the task was routed to a group, acquiring the task will cause it to disappear from the other group members’ inboxes.
- IDfWorkItem.complete() – Tells the server that you are finished working on the work item. This will cause the task to be forwareded to the next person in the workflow.
- IDfWorkItem.delegateTask() – Delegates the work item to someone else.
In a production application, processing an inbox would probably be a manual process. A user would view the contents of his inbox and select an item to work on. He would acquire the work item to show that he was working on it and would probably need to view or checkout the document attached to the work item. When he finished modifying the document, he would mark the work item as completed and it would be removed from his inbox. If he refreshed his inbox, he would see that the work item was no longer there.
Viewing Inbox Example
The following example shows how to query the docbase for the contents of the current user’s inbox. It will print the name of the document, the r_object_id of the document (needed if the user is going to view or check out the document), the person who sent the document and that date/time it was sent, the work item’s ID (needed for the user to aquire and complete the work item), the name of the task, the due date requested by the sender, and the message sent by the sender.
// Use getTasks to get all the tasks in the user’s inbox
// Use the DF_TASKS filter so that we don’t get any event notifications
// Specify these addtionalAttributes: item_name, due_date, message
// Order the results by the date_sent
IDfCollection Tasks = d.getTasks( "/articles/dmin",
IDfSession.DF_TASKS,
"item_name, due_date, message",
"date_sent" );
while ( Tasks.next() )
{
IDfTypedObject Next = Tasks.getTypedObject();
java.util.Enumeration e = Next.enumAttrs();
while ( e.hasMoreElements() )
{
IDfAttr NextAttr = (IDfAttr)e.nextElement();
System.out.print( NextAttr.getName() + " = " );
int AttrCount = 0;
if ( NextAttr.isRepeating() )
AttrCount = Next.getValueCount(
NextAttr.getName() );
else
AttrCount = 1;
for (int i=0; i<AttrCount; i++)
{
// Get the next value!
IDfValue NextAttrValue =
Next.getRepeatingValue(
NextAttr.getName(),
i );
System.out.print( NextAttrValue );
System.out.print( ", " );
}
}
System.out.println();
}
}
Acquiring Work Items Example
This example assumes that you know the r_object_id of the work item (which you would have retrieved from the previous example). It shows how to acquire the work item.
IDfPersistentObject pObj = sess.getObjectByQualification(
"dmi_queue_item where r_object_id='"
+ TaskId.toString() + "'");
// Make sure the task(s) for this user are associated with the workflow
if (pObj.getId("router_id").toString().equals( wfId.getId() ))
{
IDfWorkitem wi = (IDfWorkitem)d.getObject(tasks.getId("item_id"));
wi.acquire();
// Do some processing on the work item here
// ...
}
Completing Work Items Example
This example assumes that you know the r_object_id of the work item (which you would have retrieved from the previous example). It shows how to complete the work item, which will remove it from the users inbox and forward it to the next user in the workflow.
IDfPersistentObject pObj = sess.getObjectByQualification(
"dmi_queue_item where r_object_id='"
+ TaskId.toString() + "'");
// Make sure the task(s) for this user are associated with the workflow
if (pObj.getId("router_id").toString().equals( wfId.getId() ))
{
IDfWorkitem wi = (IDfWorkitem)d.getObject(tasks.getId("item_id"));
// Acquire the work item here.
// Done some processing on the work item: ready to complete
wi.complete();
}
To be continued
I would like to acknowledge Michael Trafton’s help in writing this article. Mikey reworked a lot of the text in this article to make it more readable and insightful, especially in the section on Alias Sets. In a future article, I hope to work through the steps of creating a workflow template and using DFC to write a complete program to bring all this information together.