Hi Guys,

Thanks for visiting my blog !!!

This is my first blog, that is why I decided to write about JBPM because it was the first open source tool on which I worked.

JBPM is workflow management tool available as a open source. It has many features,which one could expect in any workflow management tool. JBPM is getting more and more popular among developers and consultants. Many organizations are now recommanding JBPM as workflow management solutions for their clients.

The process engine which is core of JBPM is purely written in JAVA, which makes it easier for customization. Last year I got a chance to work on JBPM. My job was to customize the features of JBPM. Some times I got success and some times I spent sleepless night to find solution. Here I am going discuss process to customize identity service of JBPM.

 JBPM comes with its own identity management which we can install using ant build script which comes with jbpm distribution. You may download JBPM distribution file from here JBPM 4.4.

Most of the organization has their own user management database like LDAP.  We also had similar system to deal with the user and group management task. Clients wanted to use their own user management instead of out of the box functionality. So we decided to plug-in custom user managment tool with the JBPM's process engine.

Here I am going to describe the entire process in simple way. To demonstrate the solution I need custom identity components like  users, groups and one database which contains all the active users and groups information.

I have created my own user class which extends JBPM's User Interface.

import org.jbpm.api.identity.User;

public class CustomUser implements User {

	private String name;

	private String businessemail;

	private String familyname;

	private String id;

	public CustomUser(final String id, final String name,
			final String businessemail, final String familyname) {
		this.name = name;
		this.businessemail = businessemail;
		this.familyname = familyname;
		this.id = id;

	}
	@Override
	public String getBusinessEmail() {
		return businessemail;
	}

	@Override
	public String getFamilyName() {
		return familyname;
	}

	@Override
	public String getGivenName() {
		// TODO Auto-generated method stub
		return name;
	}

	@Override
	public String getId() {
		// TODO Auto-generated method stub
		return id;
	}

}

Similarly I have created custom group class which extends JBPM's Group Interface. 


import org.jbpm.api.identity.Group;
import org.jbpm.api.identity.User;

public class CustomGroup implements Group {

	private String id;

	private String name;

	private String type;

	private Map<String, CustomUser> memberList = new HashMap<String, CustomUser>();

	public CustomGroup(final String groupid, final String groupname,
			final String type) {
		id = groupid;
		name = groupname;
		this.type = type;
	}

	@Override
	public String getId() {
		// TODO Auto-generated method stub
		return id;
	}

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return name;
	}

	@Override
	public String getType() {
		// TODO Auto-generated method stub
		return type;
	}

	public void addUser(final String role, final CustomUser user) {
		this.memberList.put(role, user);
	}

	public List<User> getMembers() {
		List<User> memberlist = new ArrayList<User>();
		for (CustomUser member : memberList.values()) {
			memberlist.add(member);
		}
		return memberlist;
	}

}

Now for database I created a cache using Java HashMap collection.


CustomUser user1 = new CustomUser("atul1", "Atul Kotwale", "Atul.kotwale@gmail.com", "@tul");
CustomUser user2 = new CustomUser("atul2", "Atul Kotwale", "Atul.kotwale@gmail.com", "@tul");
customUserCashe.put("atul1",user1);
customUserCashe.put("atul2",user2);
CustomGroup group1 = new CustomGroup("auditors", "A Group of Auditors", "Admin");
group1.addUser("Lead Auditor", user1);
group1.addUser("Substitute Auditor", user2);
customGroupCashe.put("auditors", group1);


After that I created CustomIdentitySession class which extends IdentitySession interface of JBPM. This class contains custom implementation of Service methods which Process Engine uses to retrieve user and group information. I have implemented all the methods using my custom user, group classes  and cache which I built using Java HashMap. See the bellow class constructor for cache logic.


package org.jbpm.examples.identity.customidentitycomponent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jbpm.api.identity.Group;
import org.jbpm.api.identity.User;
import org.jbpm.pvm.internal.identity.spi.IdentitySession;

public class CustomIdentitySession implements IdentitySession {


private static final Map<String, CustomUser> customUserCashe = new HashMap<String, CustomUser>();
private static final Map<String, CustomGroup> customGroupCashe = new HashMap<String, CustomGroup>();


public CustomIdentitySession()
{
CustomUser user1 = new CustomUser("atul1", "Atul Kotwale", "Atul.kotwale@gmail.com", "@tul");
CustomUser user2 = new CustomUser("atul2", "Atul Kotwale", "Atul.kotwale@gmail.com", "@tul");
customUserCashe.put("atul1",user1);
customUserCashe.put("atul2",user2);
CustomGroup group1 = new CustomGroup("auditors", "A Group of Auditors", "Admin");
group1.addUser("Lead Auditor", user1);
group1.addUser("Substitute Auditor", user2);

customGroupCashe.put("auditors", group1);
}


@Override
public String createGroup(String id, String name, String type) {
// TODO Auto-generated method stub
CustomGroup group = new CustomGroup(id, name, type);
customGroupCashe.put(id, group);
return null;
}

@Override
public void createMembership(String arg0, String arg1, String arg2) {
// TODO Auto-generated method stub

}

@Override
public String createUser(String arg0, String arg1, String arg2, String arg3) {
// TODO Auto-generated method stub
return null;
}

@Override
public void deleteGroup(String arg0) {
// TODO Auto-generated method stub

}

@Override
public void deleteMembership(String arg0, String arg1, String arg2) {
// TODO Auto-generated method stub

}

@Override
public void deleteUser(String arg0) {
// TODO Auto-generated method stub

}

@Override
public Group findGroupById(String id) {
// TODO Auto-generated method stub
return (Group)this.customUserCashe.get(id);
}

@Override
public List<group> findGroupsByUser(final String id) {
// TODO Auto-generated method stub
List<group> groupList = new ArrayList<group>();
for(CustomGroup group : customGroupCashe.values())
{
for(User user : group.getMembers())
{
if(user.getId().equalsIgnoreCase(id))
{
groupList.add(group);
}
}
}
return groupList;
}

@Override
public List<group> findGroupsByUserAndGroupType(String arg0, String arg1) {
// TODO Auto-generated method stub
return null;
}

@Override
public User findUserById(String arg0) {
// TODO Auto-generated method stub
return (CustomUser)customUserCashe.get(arg0);
}

@Override
public List<user> findUsers() {
Set<Entry<String, CustomUser>> entryList = customUserCashe.entrySet();
List<user> allUserList = new ArrayList<user>();
for(Entry<String, CustomUser> entry : entryList)
{
allUserList.add((User)entry.getValue());
}
return allUserList;
}

@Override
public List<user> findUsersByGroup(String id) {
Object group = this.customUserCashe.get(id);
List<user> memberlist = null;
if(group instanceof CustomGroup)
{
memberlist = ((CustomGroup) group).getMembers();        
}

return memberlist;
}

@Override
public List<user> findUsersById(String... arg0) {
// TODO Auto-generated method stub				
return null;
}
}

Now we have completed development of our custom identity components.  Next task is to tell Process Engine to use Custom Identity components. JBPM framework has different modules. Each module has their own configuration file. JBPM's process engine plugs all the modules together using one centralize configuration file, The file name is "jbpm.cfg.xml". You may find jbpm.cfg.xml in the class path of your jbpm installation. In order to plug-in our custom identity component we have to make following changes in jbpm.cfg.xml file.
  •  Remove entry of jbpm.identity.cfg.xml file. This will unplug the default implementation of identity service.
  • Add following code to plug-in our custom identity session class. Which plug-in our custom component.
   <transaction-context>
    <object class="your.package.CustomIdentitySessionImpl" />
  </transaction-context>

After change your file should looks like this.


       

  Now we have done with configuration part. In order to test the implementation, I have created a simple workflow.



Before I explain you about sample workflow, I would like to give you some knowledge about types of JBPM task. In JBPM we have two type of task.

  • Interactive Task : This type of task required human intervention of we can say that workflow will wait on that task unless and until user mark it complete.
  • Automatic Task :- A task which do not required user intervention, workflow pass through the task and complete it automatically.
 In my example I have added one human task (Interactive task) and assign it to group. The group has name "Auditor" and it has two user atul1 and atul2.










I have used the same junit example setup which comes with JBPM distribution to test my code. I have created new package in same workspace with the name  "org.jbpm.examples.identity.customidentitycomponent".
My package contains following files.

  •  audit.process.jpdl.xml :- JBPM process defination files contains workflow defination.
  • CustomIdentityComponentTest :- A Junit test class contains logic to test the integation of custom identity component with JBPM core process engin. The test class will perform following task. 
  • It deloy workflow and intentiates process instance. After starting process, task will be assigned to group ("Audit"). 
  • The group has two user. The user will take the task. and complete. This will ensure that our custom identity component has plugged-in correctly.
Bellow is the code of my test class.
    public class CustomIdentityComponentTest extends JbpmTestCase {
    
      String deploymentId;
      
      String dept;
      
    
      protected void setUp() throws Exception {
        super.setUp();
            
        // deploy process
        deploymentId = repositoryService.createDeployment()
            .addResourceFromClasspath("org/jbpm/examples/identity/customidentitycomponent/audit.process.jpdl.xml")
            .deploy();
        
      }
    
      protected void tearDown() throws Exception {
        // delete process deployment
        repositoryService.deleteDeploymentCascade(deploymentId);
    
        super.tearDown();
      }
    
      public void testTestCustomIdentityComponent() {    
        executionService.startProcessInstanceByKey("AuditProcess");
    
        List taskList = taskService.findGroupTasks("atul1");
        assertEquals("Expected a single task in atul1's task list", 1, taskList.size());
        Task task = taskList.get(0);
        String taskId = task.getId();
        
        assertEquals("Audit", task.getName());
        assertNull(task.getAssignee());
    
        assertEquals(0, taskService.findPersonalTasks("atul1").size());
    
        // lets assume that atul1 takes the task
        taskService.takeTask(taskId, "atul1");
    
        // we'll check that the group task lists for atul1 and joesmoe are empty
        assertEquals(0, taskService.findGroupTasks("atul1").size());
    
        // and that the task is directly assigned to atul1
        taskList = taskService.findPersonalTasks("atul1");
        assertEquals(1, taskList.size());
        task = taskList.get(0);
        assertEquals("Audit", task.getName());
        assertEquals("atul1", task.getAssignee());
    
        // submit the task
        taskService.completeTask(taskId);
    
        taskList = taskService.findPersonalTasks("atul1");
        assertEquals(0, taskList.size());
    
      }
    }
    
      That's all I wanted to tell you about the identity process customization. I hope you like it. Please share your valuable comments, which help me to make this article more useful.