SyncWSHumanTaskHandler&LocalTaskServiceが一番手堅い実装だと信じてスタート。後で意見を翻す可能性あり。
とりあえず Servlet/EJB で動かす
staticにksession、localTaskServiceを持ち、プロセスを通して使いまわす。JUnitのテストに近いやりかた。現実的ではない。staticにしてるのはもちろん手抜き。EJBのメソッド毎にトランザクションは切れる。
LocalTaskService.complete()でタスクをcomplete。
Daemon Thread [http-127.0.0.1-8080-1] (Suspended (breakpoint at line 316 in SyncWSHumanTaskHandler$TaskCompletedHandler)) SyncWSHumanTaskHandler$TaskCompletedHandler.execute(Payload) line: 316 LocalTaskService$SimpleEventTransport.trigger(Payload) line: 287 MessagingTaskEventListener.taskCompleted(TaskCompletedEvent) line: 73 TaskEventSupport.fireTaskCompleted(long, String) line: 47 TaskServiceSession.postTaskCompleteOperation(Task) line: 502 TaskServiceSession.taskOperation(Operation, long, String, String, ContentData, List<String>) line: 469 LocalTaskService.complete(long, String, ContentData) line: 76 HelloBean.retrieveTaskByJohn(int) line: 98 ...
abortWorkItem??ともかく、(completeWorkItemでも同様の動きになるはず)
processInstance.signalEvent("workItemAborted", workItem);
が飛び、
HumanTaskNodeInstance.triggerCompleted()からプロセスを継続する。よって次のノードのHumanTaskNodeInstance.createWorkItem()へ進む。
Daemon Thread [http-127.0.0.1-8080-1] (Suspended (breakpoint at line 36 in HumanTaskNodeInstance)) HumanTaskNodeInstance.createWorkItem(WorkItemNode) line: 36 HumanTaskNodeInstance(WorkItemNodeInstance).internalTrigger(NodeInstance, String) line: 96 HumanTaskNodeInstance(NodeInstanceImpl).trigger(NodeInstance, String) line: 122 HumanTaskNodeInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 196 HumanTaskNodeInstance(NodeInstanceImpl).triggerCompleted(String, boolean) line: 155 HumanTaskNodeInstance(ExtendedNodeInstanceImpl).triggerCompleted(String, boolean) line: 47 HumanTaskNodeInstance(StateBasedNodeInstance).triggerCompleted(String, boolean) line: 162 HumanTaskNodeInstance(StateBasedNodeInstance).triggerCompleted() line: 143 HumanTaskNodeInstance(WorkItemNodeInstance).triggerCompleted(WorkItem) line: 239 HumanTaskNodeInstance.triggerCompleted(WorkItem) line: 90 HumanTaskNodeInstance(WorkItemNodeInstance).workItemAborted(WorkItem) line: 293 HumanTaskNodeInstance(WorkItemNodeInstance).signalEvent(String, Object) line: 279 RuleFlowProcessInstance(WorkflowProcessInstanceImpl).signalEvent(String, Object) line: 338 JPAWorkItemManager.abortWorkItem(long) line: 154 AbortWorkItemCommand.execute(Context) line: 56 AbortWorkItemCommand.execute(Context) line: 29 DefaultCommandService.execute(Command<T>) line: 36 SingleSessionCommandService.execute(Command<T>) line: 355 CommandBasedStatefulKnowledgeSession$1.abortWorkItem(long) line: 156 SyncWSHumanTaskHandler$TaskCompletedHandler.handleCompletedTask(long) line: 378 SyncWSHumanTaskHandler$TaskCompletedHandler.execute(Payload) line: 319 LocalTaskService$SimpleEventTransport.trigger(Payload) line: 287 MessagingTaskEventListener.taskCompleted(TaskCompletedEvent) line: 73 TaskEventSupport.fireTaskCompleted(long, String) line: 47 TaskServiceSession.postTaskCompleteOperation(Task) line: 502 TaskServiceSession.taskOperation(Operation, long, String, String, ContentData, List<String>) line: 469 LocalTaskService.complete(long, String, ContentData) line: 76 HelloBean.retrieveTaskByJohn(int) line: 98 ...
JPAKnowledgeService.loadStatefulKnowledgeSession()
JPAKnowledgeService.loadStatefulKnowledgeSession()を使って、セッションIDからksessionをリストアする。SyncWSHumanTaskHandlerも毎回設定。org.jbpm.task.service.TaskServiceはstaticにキャッシュ。LocalTaskServiceも毎回作成。
public TaskService getTaskService(StatefulKnowledgeSession ksession) { if (taskService == null) { taskService = new org.jbpm.task.service.TaskService( emf, SystemEventListenerFactory.getSystemEventListener()); } SyncWSHumanTaskHandler humanTaskHandler = new SyncWSHumanTaskHandler( new LocalTaskService(taskService), ksession); humanTaskHandler.setLocal(true); humanTaskHandler.connect(); ksession.getWorkItemManager().registerWorkItemHandler("Human Task", humanTaskHandler); return new LocalTaskService(taskService); }
LocalTaskService.complete()のトランザクションコミット時にStaleObjectStateExceptionが出る。なんでやねん。
11:09:25,831 ERROR [AbstractFlushingEventListener] Could not synchronize database state with session org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.jbpm.persistence.processinstance.ProcessInstanceInfo#12]
デバッグ。。。
Daemon Thread [http-127.0.0.1-8080-1] (Suspended (breakpoint at line 36 in HumanTaskNodeInstance)) HumanTaskNodeInstance.createWorkItem(WorkItemNode) line: 36 HumanTaskNodeInstance(WorkItemNodeInstance).internalTrigger(NodeInstance, String) line: 96 HumanTaskNodeInstance(NodeInstanceImpl).trigger(NodeInstance, String) line: 122 HumanTaskNodeInstance(NodeInstanceImpl).triggerNodeInstance(NodeInstance, String) line: 196 HumanTaskNodeInstance(NodeInstanceImpl).triggerCompleted(String, boolean) line: 155 HumanTaskNodeInstance(ExtendedNodeInstanceImpl).triggerCompleted(String, boolean) line: 47 HumanTaskNodeInstance(StateBasedNodeInstance).triggerCompleted(String, boolean) line: 162 HumanTaskNodeInstance(StateBasedNodeInstance).triggerCompleted() line: 143 HumanTaskNodeInstance(WorkItemNodeInstance).triggerCompleted(WorkItem) line: 239 HumanTaskNodeInstance.triggerCompleted(WorkItem) line: 90 HumanTaskNodeInstance(WorkItemNodeInstance).workItemAborted(WorkItem) line: 293 HumanTaskNodeInstance(WorkItemNodeInstance).signalEvent(String, Object) line: 279 RuleFlowProcessInstance(WorkflowProcessInstanceImpl).signalEvent(String, Object) line: 338 JPAWorkItemManager.abortWorkItem(long) line: 154 AbortWorkItemCommand.execute(Context) line: 56 AbortWorkItemCommand.execute(Context) line: 29 DefaultCommandService.execute(Command<T>) line: 36 SingleSessionCommandService.execute(Command<T>) line: 355 CommandBasedStatefulKnowledgeSession$1.abortWorkItem(long) line: 156 SyncWSHumanTaskHandler$TaskCompletedHandler.handleCompletedTask(long) line: 378 SyncWSHumanTaskHandler$TaskCompletedHandler.execute(Payload) line: 319 LocalTaskService$SimpleEventTransport.trigger(Payload) line: 287 MessagingTaskEventListener.taskCompleted(TaskCompletedEvent) line: 73 TaskEventSupport.fireTaskCompleted(long, String) line: 47 TaskServiceSession.postTaskCompleteOperation(Task) line: 502 TaskServiceSession.taskOperation(Operation, long, String, String, ContentData, List<String>) line: 469 LocalTaskService.complete(long, String, ContentData) line: 76 HelloBean.retrieveTaskByJohn(int) line: 101 ...
全く同じスタックで、HumanTaskNodeInstance.createWorkItemが4回実行される。そんなバカな。
MessagingTaskEventListener.taskCompleted()でループしている。MessagingTaskEventListenerのkeysにorg.jbpm.process.workitem.wsht.SyncWSHumanTaskHandler$TaskCompletedHandlerが6個入っていた。ksessionに
蓄積されているのだろう。つまりloadStatefulKnowledgeSessionの後にSyncWSHumanTaskHandlerのregisterWorkItemHandlerはいらんかったんや!
SyncWSHumanTaskHandlerをJPAKnowledgeService.newStatefulKnowledgeSession()直後だけにしたら、普通に動いた。
org.jbpm.task.service.TaskService の謎
org.jbpm.task.service.TaskServiceのstatic持ちを止め、毎回EntityManagerFactoryから生成するようにすると、2つ目のタスクが作成されなくなった。
デバッグ。。
MessagingTaskEventListener.taskCompleted()で keys.getTargets( key );の結果がnullだった。はて。
humanTaskHandler.connect();から、LocalTaskService.registerForEvent()が呼ばれ、responseHandlerをEventKeysにセットする。
public void registerForEvent(EventKey key, boolean remove, EventResponseHandler responseHandler) { SimpleEventTransport transport = new SimpleEventTransport(session, responseHandler, remove); service.getEventKeys().register(key, transport); }
つまり、最初に登録するSyncWSHumanTaskHandlerが参照するLocalTaskServiceが参照するTaskServiceと、後でタスクオペレーションに使うLocalTaskServiceが参照するTaskServiceは同じインスタンスでないとダメー。ここの依存関係が肝か。(訂正:SyncWSHumanTaskHandlerが参照するLocalTaskServiceとタスクオペレーションに使うLocalTaskServiceを同じインスタンスにすればいい)(さらに追記。LocalTaskServiceを同じインスタンスにしないと冒頭のabortWorkItemになるので、同じにしないとだめ!JbpmJUnitTestCaseの実装がミスリーディングだと思うぞ!)
くっそ、じゃあやっぱりSyncWSHumanTaskHandlerの再設定が必要だろ。
再挑戦
loadStatefulKnowledgeSession()の度にSyncWSHumanTaskHandlerをregisterするように戻す。。。。あれ、動いた。待て待て。。。そうか
- TaskServiceをキャッシュ、ksessionもキャッシュ -> 正常
- TaskServiceをキャッシュ、毎回SyncWSHumanTaskHandlerを設定 -> responseHandlerが重複登録されてStaleObjectStateException
- TaskServiceをキャッシュ、最初のみSyncWSHumanTaskHandlerを設定 -> 正常
- キャッシュなし、最初のみSyncWSHumanTaskHandlerを設定 -> responseHandlerが無いので、プロセスが進まない
- キャッシュなし、毎回SyncWSHumanTaskHandlerを設定 -> 正常
OK、納得した。最後のパターンが汎用性の高い使い方のはず。
締め
依存性を減らすため、ksessionも毎回作成する。startProcess時にprocessInstanceIdをreturnするが、今回は使わない。