JBPM

오픈소스 비즈니스 컨설팅
이동: 둘러보기, 검색

BPM 솔루션인 JBoss jBPM을 정리 한다.


Eclipse 모델링 환경 구축

JBoss jBPM에 jpdl을 모델링하기 위한 Eclipse 모델링 환경을 구축해 보자.

  • 다운로드 사이트에서 jbpm-4.3.zip 을 다운로드 하여 c:/zzdir/ 폴더 아래에 압축을 풀어 둔다.
  • Eclipse에 GPD plugin을 설치 한다.
  • Eclipse에서 "Help -> Install New Software..." 메뉴를 선택 한다.
  • "Add..." 버튼을 누른 후 "Archive..." 버튼을 눌러 c:/zzdir/jbpm-4.3/install/src/gpd/jbpm-gpd-site.zip 파일을 선택 한다.
  • "OK" 버튼을 누르고, 목록에 표시된 것을 모두 선택한 후 설치 한다.
  • Eclipse에 jBPM 실행환경을 설정 한다.
  • "Window -> Preferences -> JBoss jBPM -> jBPM 4 --> Runtime Locations" 메뉴를 선택 한다.
  • "Add..." 버튼을 눌러 다음과 같이 등록 한다.
  • Name : "jbpm-4.0"
  • Location : "c:/zzdir/jbpm-4.3"
  • Eclipse에 jBPM 사용자 라이브러리를 등록 한다.
  • "Window -> Preferences -> Java -> Build Path -> User Libraries" 메뉴를 선택 한다.
  • "New..." 버튼을 선택한 후 User library name에 "jBPM Libraries"을 입력한 후 "OK" 버튼을 누른다.
  • "jBPM Libraries"을 선택한 후 "Add JARs..." 버튼을 눌러 jBPM에 c:/zzdir/jbpm-4.3/lib/ 폴더에 있는 모든 jar 파일을 등록 한다.
  • "jBPM Libraries"을 선택한 후 "Add JARs..." 버튼을 눌러 jBPM에 c:/zzdir/jbpm-4.3/jbpm.jar 파일을 등록 한다.
  • Eclipse에 jPDL 4 schema를 등록 한다.
  • "Window -> Preferences -> XML -> XML Catalog" 메뉴를 선택 한다.
  • "Add..." 버튼을 누른 후 "File System..." 버튼을 눌러 c:/zzdir/jbpm-4.3/src/jpdl-4.0.xsd 파일을 선택하여 등록 한다.

jBPM Command Line 실행 환경 구축

JBoss jBPM을 실행하기 위한 환경을 구축해 보자.

  • 다운로드 사이트에서 jbpm-4.3.zip 을 다운로드 하여 c:/zzdir/ 폴더 아래에 압축을 풀어 둔다.
  • Eclipse에서 jBPM 이라는 Java Project를 생성 한다. 앞으로 jBPM 프로젝트의 홈 디렉토리를 $JBPM_HOME 이라 한다.
  • "jBPM"을 선택한 후 오른쪽 마우스를 눌러 "Properties" 메뉴을 선택 한다.
  • "Java Build Path -> Libraries -> Add Library..."를 선택 한다.
  • "User Library"를 선택한 후 "jBPM Libraries"를 선택하여 등록 한다.
  • $JBPM_HOME/src/jbpm.cfg.xml 파일을 생성 한다.
<?xml version="1.0" encoding="UTF-8"?>
<jbpm-configuration>
  <import resource="jbpm.default.cfg.xml" />
  <import resource="jbpm.tx.hibernate.cfg.xml" />
  <import resource="jbpm.jpdl.cfg.xml" />
  <import resource="jbpm.identity.cfg.xml" />
</jbpm-configuration>
  • $JBPM_HOME/src/jbpm.hibernate.cfg.xml 파일을 생성 한다.
  • Database에 대한 접속 정보를 관리 한다. 여기서는 HSQLDB의 메모리 DB를 사용 한다.
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
         "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
  <session-factory>
     <property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
     <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
     <property name="hibernate.connection.url">jdbc:hsqldb:mem:.</property>
     <property name="hibernate.connection.username">sa</property>
     <property name="hibernate.connection.password"></property>
     
     <property name="hibernate.hbm2ddl.auto">create-drop</property>
     <property name="hibernate.format_sql">true</property>
     
     <mapping resource="jbpm.repository.hbm.xml" />
     <mapping resource="jbpm.execution.hbm.xml" />
     <mapping resource="jbpm.history.hbm.xml" />
     <mapping resource="jbpm.task.hbm.xml" />
     <mapping resource="jbpm.identity.hbm.xml" />
  </session-factory>
</hibernate-configuration>


  • 자 여기까지 진행이 되었으면 JBoss jBPM을 실행할 환경의 구성은 완료 되었다. JBoss jBPM에서도 HelloWrold를 만들어 정상적으로 동작하는지 확인해 보자.
  • $JBPM_HOME/src/printHelloWorld.jpdl.xml 파일을 생성 한다.
  • JBoss jBPM에서 실행할 프로세스 정의 파일을 생성 한다.
<?xml version="1.0" encoding="UTF-8"?>
<process name="helloWorld" xmlns="http://jbpm.org/4.0/jpdl">
	<start>
		<transition to="printHelloWorld" />
	</start>
	<java class="com.jopenbusiness.jbpm.cli.Printer" 
	      method="printHelloWorld" name="printHelloWorld">
		<transition to="theEnd" />
	</java> 
	<end name="theEnd" />
</process>
  • $JBPM_HOME/src/com/jopenbusiness/jbpm/cli/Printer.java 파일을 생성 한다.

package com.jopenbusiness.jbpm.cli;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.jbpm.api.Configuration;
import org.jbpm.api.ExecutionService;
import org.jbpm.api.ProcessEngine;
import org.jbpm.api.RepositoryService;

//--- http://www.javaplex.com/blog/getting-started-with-jbpm/
public class Printer {
       //--- http://docs.jboss.com/jbpm/v4/userguide/html_single/#services
	public static void main(String args[]) {
		ProcessEngine processEngine = null;
		RepositoryService repositoryService = null;
		ExecutionService executionService = null;
 
		processEngine = new Configuration().setResource("jbpm.cfg.xml").buildProcessEngine();
		repositoryService = processEngine.getRepositoryService();
		executionService = processEngine.getExecutionService();
 		
		repositoryService.createDeployment().addResourceFromClasspath("printHelloWorld.jpdl.xml").deploy();
		executionService.startProcessInstanceByKey("helloWorld");
	}
 	
       //--- JBoss jBPM의 프로세스 정의 파일에 정의되어 호출되는 함수
	public void printHelloWorld() {
		File tmpFile = null;
		BufferedWriter out = null; 
		SimpleDateFormat formatDate = null;
 
		tmpFile = new File("c:/jbpm.out");
		try {
			formatDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			out = new BufferedWriter(new FileWriter(tmpFile));
			out.append("jBPM: printHelloWorld - " + formatDate.format(new Date()));
 		} catch (FileNotFoundException e) { 
		    e.printStackTrace();
		} catch (IOException e) {
		    e.printStackTrace();
		} finally {
		    if (out != null) {
		        try {
		        	out.close();
		        } catch (IOException e) {
		            e.printStackTrace();
		        }
		    }
		    out = null;
		}
	}
}

  • 작성된 Print.java를 실행하여 c:/jbpm.out 파일이 잘 생성 되는지 확인해 보자.
  • Eclipse에서 Print.java를 선택한 후 오른쪽 마우스를 누른다.
  • "Run AS -> 2 Java Application" 메뉴을 선택 한다.
  • c:/jbpm.out 파일이 잘 생성 되었는지 확인 한다.

사용자 가이드

jBPM service API

RepositoryService
  • 프로세스 정의 데이터를 관리
  • JPDL로 정의된 프로세스를 등록할 수 있음
IdentityService
  • User와 Group 관리
ExecutionService
  • 프로세스 실행 및 관리
  • 실행되고 있는 Process Instance 목록 등을 제공
TaskService
  • 프로세스의 각 단위 작업을 관리
  • 사용자에 의해서 실행되는 Task를 관리하는 서비스
  • Process Instance에서 사용자별로 할당된 Task 목록 등을 제공
HistoryService
  • 프로세스의 실행 내역 관리
ManagementService
  • jBPM을 관리하기 위해서 jBPM 관리 콘솔에서 사용하는 서비스

RepositoryService

import org.jbpm.api.Configuration;
import org.jbpm.api.ProcessEngine;
import org.jbpm.api.RepositoryService;

ProcessEngine processEngine = null;
RepositoryService repositoryService = null;
String deploymentId = null;

processEngine = new Configuration().setResource("jbpm.cfg.xml").buildProcessEngine();
repositoryService = processEngine.getRepositoryService();

//--- jPDL deploy
deploymentId = repositoryService.createDeployment().addResourceFromClasspath("printHelloWorld.jpdl.xml").deploy();
//--- repositoryService.deleteDeployment(deploymentId);

IdentityService

import org.jbpm.api.IdentityService;

IdentityService identityService = processEngine.getIdentityService();

identityService.createGroup("sales-dept"); 

identityService.createUser("johndoe", "firstName", "lastName", "email");
identityService.createMembership("johndoe", "sales-dept");

identityService.createUser("joesmoe", "joesmoe", "Joe", "Smoe");
identityService.createMembership("joesmoe", "sales-dept");
//--- groupid를 반환 한다.
String createGroup(String groupName);
String createGroup(String groupName, String groupType)
String createGroup(String groupName, String groupType, String parentGroupId);

ExecutionService

ExecutionService

import org.jbpm.api.ExecutionService;
import org.jbpm.api.ProcessInstance;

//--- Process 관리
ExecutionService executionService = null;
ProcessInstance processInstance = null;
String pid = null;

executionService = processEngine.getExecutionService();

Map<String,Object> variables = new HashMap<String,Object>();
variables.put("customer", "홍길동");         //--- jpdl에서 #{customer} 라는 이름으로 지정 가능
variables.put("type", "Accident");
variables.put("amount", new Float(283.2));

//--- Start new process
//--- processInstance = executionService.startProcessInstanceByKey("hello");
processInstance = executionService.startProcessInstanceById("hello-1", variables);
pid = processInstance.getId();

State

<state name="a">
    <transition to="b" />
</state>
<state name="wait for response">
    <transition name="accept" to="submit document" />
    <transition name="reject" to="try again" />
</state>

Execution executionInA = processInstance.findActiveExecutionIn("a");
assertNotNull(executionInA);
processInstance = executionService.signalExecutionById(executionInA.getId());

processInstance = executionService.signalExecutionById(executionId, "accept");

Decision

  • 사례 1
<decision name="evaluate document">
   <transition to="submit document">
     <condition expr="#{content=="good"}" />
   </transition>
   <transition to="try again">
     <condition expr="#{content=="not so good"}" />
   </transition>
   <transition to="give up" />
 </decision>

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("content", "good");
ProcessInstance processInstance = 
executionService.startProcessInstanceByKey("DecisionConditions", variables);
assertTrue(processInstance.isActive("submit document"));
  • 사례 2
<decision name="evaluate document" expr="#{content}" >
   <transition name="good" to="submit document"  />
   <transition name="bad"  to="try again"  />
   <transition name="ugly" to="give up"  />
 </decision>

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("content", "good");
ProcessInstance processInstance = 
executionService.startProcessInstanceByKey("DecisionExpression", variables);
  • 사례 3
<decision name="evaluate document">
   <handler class="org.jbpm.examples.decision.handler.ContentEvaluation" />
   <transition name="good" to="submit document" />
   <transition name="bad" to="try again" />
   <transition name="ugly" to="give up" />
 </decision>

public class ContentEvaluation implements DecisionHandler {
 public String decide(OpenExecution execution) {
   String content = (String) execution.getVariable("content");
   if (content.equals("you're great")) {
     return "good";
   }
   if (content.equals("you gotta improve")) {
     return "bad";
   }
   return "ugly";
 }
}

sub-process

  • 사례 1
<sub-process name="review" sub-process-key="SubProcessReview">
   <parameter-in var="document" subvar="document" />
   <parameter-out var="reviewResult" subvar="result" />
   <transition to="wait" />
</sub-process>

<process name="SubProcessReview" xmlns="http://jbpm.org/4.3/jpdl">
</process>
  • 사례 2
<sub-process name="review"
              sub-process-key="SubProcessReview"
              outcome="#{result}">
   <transition name="ok" to="next step" />
   <transition name="nok" to="update" />
   <transition name="reject" to="close" />
 </sub-process>
  • 사례 3
<sub-process name="review"
              sub-process-key="SubProcessReview">
   <transition name="ok" to="next step" />
   <transition name="nok" to="update" />
   <transition name="reject" to="close" />
 </sub-process>
 
<task name="get approval"
       assignee="johndoe">
              
   <transition name="ok" to="ok"/>
   <transition name="nok" to="nok"/>
   <transition name="reject" to="reject"/>
 </task>
  
 <end name="ok" />
 <end name="nok" />
 <end name="reject" />

java

  • JBoss jBPM에 의해서 자동으로 실행되는 단계로 특정 Java Class의 Method를 호출 한다.
name="이름"
  • 이 단계의 이름, 분기(transition)시 사용 한다.
class="클래스명"
  • 실행할 Java 클래스 지정
  • class 대신 expr="#{변수}" 를 사용하여 실행할 Java 클래스를 지정할 수도 있다.
method="함수명"
  • Java 클래스에서 실행될 함수명(Method)을 지정 한다.
var="반환되는 객체명"
  • 반환되는 객체가 저장될 변수명을 정의 한다.
  • 다른 단계에서 "#{반환되는 객체명}" 으로 지정하여 사용할 수 있다.
g="x,y,width,height"
  • 이 단계를 그림으로 그릴 때 표시되는 위치
<arg>인수</arg>
  • Java Method에 인수로 전달하는 값을 정의
  • 문자열 : <string value="Hi, how are you?"/> -> String
  • 숫자 : <int value="25"/> -> Integer
  • 객체 : <object expr="#{processInfo}"/> -> Object 또는 해당 객체의 타입
<field name="state">변수값</field>
  • 함수에 전달되는 값이 아닌 Java 클래스에 선언된 변수에 전달되는 값을 정의 한다.
  • 변수값은 위 <arg>에 전달되는 값과 동일한 문법을 사용하여 정의 한다.
<transition to="다음 단계 이름"/>
  • Java 클래스의 함수를 실행한 후 분기할 다음 단계를 명시 한다.
  • JBoss jBPM의 java 실행 사례
   //--- com.groupware.approve.manager.ApproveWorkflowManager 클래스의 documentMove 함수가 자동 실행 된다.
   //--- processInfo는 Process를 처음 실행할 때 전달된 HashMap
   <java name="step_2_1"
         class="com.groupware.approve.manager.ApproveWorkflowManager" 
         method="documentMove" 
         var="answer"
   	  g="200,300,200,50" >
  		<arg><string value="2-1. documentMove : Drafter document move"/></arg>
  		<arg><object expr="#{processInfo}"/></arg>
  		
  		<arg><object expr="#{processInfo.currentUserSeq}"/></arg>
  		<arg><object expr="#{processInfo.currentBox}"/></arg>
  		<arg><object expr="#{processInfo.currentUserSeq}"/></arg>
  		<arg><string value="Approve/Processing"/></arg>
       <transition to="step_2_2"/>
   </java>

   import com.groupware.approve.manager;
   public class ApproveWorkflowManager {
       public String documentMove(String stepInfo, HashMap<String, Object> processInfo,
               String beforeUser, String beforeBox, String afterUser, String afterBox) {
           processInfo.put("result", "ok");
           return "result";
       }
   }

script

  • 참고 문헌
  • 사례 1
 <script name="invoke script" 
         expr="Send packet to #{person.address}"
         var="text">
   <transition to="wait" />
 </script>
  • 사례 2
 <script name="invoke script" 
         var="text">
   <text>
     Send packet to #{person.address}
   </text>
   <transition to="wait" />
 </script>

hql

  • 사례 1
<hql name="get process names"
      var="activities with o">
   <query>
     select activity.name
     from org.jbpm.pvm.internal.model.ActivityImpl as activity
     where activity.name like :activityName
   </query>
   <parameters>
     <string name="activityName" value="%o%" />
   </parameters>
   <transition to="count activities" />
 </hql>
  
 <hql name="count activities"
      var="activities"
      unique="true">
   <query>
     select count(*)
     from org.jbpm.pvm.internal.model.ActivityImpl
   </query>
   <transition to="wait" />
 </hql>

mail

  • 사례 1
<mail name="send birthday reminder note">
   <to addresses="johnDoe@some-company.com" />
   <subject>Reminder: ${person} celebrates his birthday!</subject>
   <text>Do not forget: ${date} is the birthday of ${person} </text>
   <transition to="end" />
 </mail>

event

<process name="EventListener" xmlns="http://jbpm.org/4.3/jpdl">

 <on event="start">
   <event-listener class="org.jbpm.examples.eventlistener.LogListener">
     <field name="msg"><string value="start on process definition"/></field>
   </event-listener>
 </on>

 <start>
   <transition to="wait"/>
 </start>

 <state name="wait">
   <on event="start">
     <event-listener class="org.jbpm.examples.eventlistener.LogListener">
       <field name="msg"><string value="start on activity wait"/></field>
     </event-listener>
   </on>
   <on event="end">
     <event-listener class="org.jbpm.examples.eventlistener.LogListener">
       <field name="msg"><string value="end on activity wait"/></field>
     </event-listener>
   </on>
   <transition to="park">
     <event-listener class="org.jbpm.examples.eventlistener.LogListener">
       <field name="msg"><string value="take transition"/></field>
     </event-listener>
   </transition>
 </state>
  
 <state name="park"/>

</process>

public class LogListener implements EventListener {
	
 // value gets injected from process definition
 String msg;
 
 public void notify(EventListenerExecution execution) {
   List<String> logs = (List<String>) execution.getVariable("logs");
   if (logs==null) {
     logs = new ArrayList<String>();
     execution.setVariable("logs", logs);
   }
    
   logs.add(msg);

   execution.setVariable("logs", logs);
 }
}

timer

  • jbmp.cfg.xml
<process-engine-context>
     <business-calendar>
       <monday    hours="9:00-12:00 and 12:30-17:00"/>
       <tuesday   hours="9:00-12:00 and 12:30-17:00"/>
       <wednesday hours="9:00-12:00 and 12:30-17:00"/>
       <thursday  hours="9:00-12:00 and 12:30-17:00"/>
       <friday    hours="9:00-12:00 and 12:30-17:00"/>
       <holiday period="01/07/2010 - 31/08/2010"/>
     </business-calendar>
 </process-engine-context>
  • Dudate 문법
quantity [business] {second | seconds | minute | minutes | 
                    hour | hours | day | days | week | 
                    weeks | month | months | year | years}
  • 사례 1
<state name="guardedWait">
   <transition name="go on" to="next step" />
   <transition name="timeout" to="escalation">
     <timer duedate="10 minutes" />
   </transition>
 </state>
  • 사례 2
<state name="guardedWait" >
   <on event="timeout">
     <timer duedate="9 business hours"/>
     <event-listener class="org.jbpm.examples.timer.event.Escalate" />
   </on>
   <transition name="go on" to="next step" />
 </state>

group

  • group
  • group timer

TaskService

import org.jbpm.api.TaskService;
import org.jbpm.api.task.Task;

TaskService taskService = null;
List<Task> taskList = null;
Task task = null;
String taskId = null;

taskService = processEngine.getTaskService();

//--- 사용자의 Task 목록을 반환
taskList = taskService.findPersonalTasks("johndoe");
//--- 그룹의 Task 목록을 반환
//--- taskService.findGroupTasks(groupid);
task = taskList.get(0);
taskId = task.getId();

Set<String> variableNames = taskService.getVariableNames(taskId);
Map<String, Object> variables = taskService.getVariables(taskId, variableNames);

variables = new HashMap<String, Object>();
variables.put("category", "small");
variables.put("lires", 923874893);
taskService.setVariables(taskId, variables);

String outcome = null;
taskService.completeTask(taskId);
taskService.completeTask(taskId, variables);
taskService.completeTask(taskId, outcome);
taskService.completeTask(taskId, outcome, variables);

Task

  • 사용자에게 할당되어 실행되는 단계
name="이름"
  • 이 단계의 이름, 분기(transition)시 사용 한다.
assignee="사용자 아이디"
  • Task 단계에 할당된 사용자 아이디
candidate-users="userid1,userid2"
  • Task 단계를 종료할 수 있도록 할당된 사용자 아이디 목록
candidate-groups="groupid1,groupid2"
  • Task 단계를 종료할 수 있도록 할당된 그룹 아이디 목록
  • jPDL에서 Task 정의
<task name="review"
      assignee="#{order.owner}">
    <transition to="wait" />
</task>
  • 사례
//--- 특정 Process에서 특정 사용자의 특정 Task를 가져 온다.
public Task getTask(String processid, String userid, String taskname) {
	TaskService taskService = null;
	TaskQuery query = null;
	List<Task> taskList = null;
	
	taskService = WorkflowManager.getProcessEngine().getTaskService();
	query = taskService.createTaskQuery();
	taskList = query.processInstanceId(processid).assignee(userid).activityName(taskname).list();
	if (0 < taskList.size()) {
		return taskList.get(0);
	} 
	return null;
}
  • 사례 1
<task name="review" assignee="#{order.owner}">
    <transition to="wait" />
</task>

public class Order implements Serializable {
 String owner;

 public Order(String owner) {
   this.owner = owner;
 }

 public String getOwner() {
   return owner;
 }

 public void setOwner(String owner) {
   this.owner = owner;
 }
}

Map<String, Object> variables = new HashMap<String, Object>(); 
variables.put("order", new Order("johndoe"));
ProcessInstance processInstance = executionService
   .startProcessInstanceByKey("TaskAssignee", variables);

List<Task> taskList = taskService.findPersonalTasks("johndoe");
  • 사례 2
<task name="review" candidate-groups="sales-dept">
     <transition to="wait" />
</task>

taskService.getAssignedTasks("johndoe");
taskService.findGroupTasks("johndoe");

taskService.takeTask(task.getDbid(), "johndoe");
  • 사례 3
<task name="review" g="96,16,127,52">
   <assignment-handler class="org.jbpm.examples.task.assignmenthandler.AssignTask">
     <field name="assignee">
       <string value="johndoe" />
     </field>
   </assignment-handler>
   <transition to="wait" />
 </task>

public class AssignTask implements AssignmentHandler {
 String assignee;

 public void assign(Assignable assignable, OpenExecution execution) {
   assignable.setAssignee(assignee);
 }
}
  • 사례 4
<swimlane name="sales representative"
           candidate-groups="sales-dept" />

<task name="enter order data"
       swimlane="sales representative">
   <transition to="calculate quote"/>
 </task>
  • 사례 5
<task name="review" 
     assignee="#{order.owner}"
    <notification/>
    <reminder duedate="2 days" repeat="1 day"/>
</task>

HistoryService

import org.jbpm.api.HistoryService;
import org.jbpm.api.history.HistoryProcessInstance;
import org.jbpm.api.history.HistoryProcessInstanceQuery;

HistoryService historyService = null;
List<HistoryProcessInstance> historyProcessInstances = null;

historyService = processEngine.getHistoryService();
historyProcessInstances = historyService.createHistoryProcessInstanceQuery()
    .processDefinitionId("hello-1")
    .orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME)
    .list();

ManagementService

import org.jbpm.api.ManagementService;

ManagementService managementService = null;

managementService = processEngine.getManagementService();

Migration

  • foobar.jpdl.xml
<process name="foobar">
    <migrate-instances versions="2..3" />
    <migrate-instances action="end"/>

    <migrate-instances>
        <activity-mapping old-name="b" new-name="a"/>
        <activity-mapping old-name="c" new-name="d"/>
    </migrate-instances>

    <migrate-instances>
        <migration-handler class="org.jbpm.test.migration.TestHandler"/>
    </migrate-instances>
</process>
  • Migration in Java program
ProcessDefinition pd1 = deployProcessDefinition("foobar", originalVersion);
ProcessDefinition pd5 = deployProcessDefinition("foobar", versionWithAbsoluteVersionRange);

관리자 가이드

jBPM 엔진 및 도구

참고 문헌

  • 매뉴얼