JBPM
둘러보기로 가기
검색하러 가기
mail
BPM 솔루션인 JBoss jBPM을 정리 한다.
- 홈페이지 : http://sourceforge.net/projects/jbpm/, http://www.jboss.org/jbpm/
- 다운로드 : http://sourceforge.net/projects/jbpm/files/
- 라이선스 : GNU LGPL
- 플랫폼 : Java
목차
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 파일을 선택하여 등록 한다.
- 이때 Key Type은 "Namespace Name"이 Key는 "http://jbpm.org/4.3/jpdl"이 자동으로 설정 된다.
jBPM Command Line 실행 환경 구축
JBoss jBPM을 실행하기 위한 환경을 구축해 보자.
- 다운로드 사이트에서 jbpm-4.3.zip 을 다운로드 하여 c:/zzdir/ 폴더 아래에 압축을 풀어 둔다.
- Eclipse에서 jBPM 이라는 Java Project를 생성 한다. 앞으로 jBPM 프로젝트의 홈 디렉토리를 $JBPM_HOME 이라 한다.
- Eclipse 모델링 환경 구축에서 생성한 "jBPM Libraries"를 Java Build Path에 아래와 같이 등록 한다.
- "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 |
|
IdentityService |
|
ExecutionService |
|
TaskService |
|
HistoryService |
|
ManagementService |
|
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="이름" |
|
class="클래스명" |
|
method="함수명" |
|
var="반환되는 객체명" |
|
g="x,y,width,height" |
|
<arg>인수</arg> |
|
<field name="state">변수값</field> |
|
<transition to="다음 단계 이름"/> |
|
- 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>
- 사례 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="이름" |
|
assignee="사용자 아이디" |
|
candidate-users="userid1,userid2" |
|
candidate-groups="groupid1,groupid2" |
|
- 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 엔진 및 도구
- Runtime Engine (LGPL)
- Eclipse Designer (EPL, 준비중)
- Web Modeller (MIT, Signavio)
참고 문헌
- 매뉴얼
- Evaluations : Workflow 솔루션 비교
- Quick Getting Started with jBPM 4.0 Tutorials, 2009.7