Skip to content

Commit 7a1a79f

Browse files
committed
Add External Task Listener Plugin
1 parent 03cdc70 commit 7a1a79f

File tree

14 files changed

+553
-0
lines changed

14 files changed

+553
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Camunda External Task Listener Plugin
2+
A Process Engine Plugin for [Camunda BPM](http://docs.camunda.org) that allows to listen to newly created External Tasks.
3+
It also provides a way to complete External Tasks without locking them via `ExternalTaskService.fetchAndLock()`.
4+
This could be used to signal an External Task Worker that is running inside the same JVM
5+
and to save the database transaction of `ExternalTaskService.fetchAndLock()`.
6+
7+
This project has been generated by the Maven archetype
8+
[camunda-archetype-engine-plugin-7.11.1](http://docs.camunda.org/latest/guides/user-guide/#process-applications-maven-project-templates-archetypes).
9+
10+
## Show me the important parts!
11+
![BPMN Process](src/main/resources/process.png)
12+
13+
## How does it work?
14+
15+
## How to use it?
16+
To get started refer to `InMemoryH2Test` and `camunda.cfg.xml`.
17+
For using it in production you have to [integrate the plugin into your Camunda BPM configuration](https://docs.camunda.org/manual/latest/user-guide/process-engine/process-engine-plugins/).
18+
19+
## Environment Restrictions
20+
Built and tested against Camunda BPM version 7.11.0.
21+
22+
## Known Limitations
23+
24+
## Improvements Backlog
25+
26+
## License
27+
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>org.camunda.bpm.consulting.snippet</groupId>
6+
<artifactId>engine-plugin-external-task-listener</artifactId>
7+
<version>0.0.1-SNAPSHOT</version>
8+
9+
<packaging>jar</packaging>
10+
11+
<name>External Task Listener Plugin for Camunda BPM</name>
12+
<description>A Process Engine Plugin for [Camunda BPM](http://docs.camunda.org) that allows to listen to newly created External Tasks. [The project has been generated by the Maven archetype 'camunda-archetype-engine-plugin-7.11.1']</description>
13+
14+
<properties>
15+
<camunda.version>7.11.0</camunda.version>
16+
<!--
17+
Adjust if you want to use Camunda Enterprise Edition (EE):
18+
<camunda.version>7.11.0-ee</camunda.version>
19+
Make sure you also switch to EE repository below
20+
-->
21+
<maven.compiler.source>1.8</maven.compiler.source>
22+
<maven.compiler.target>1.8</maven.compiler.target>
23+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
24+
</properties>
25+
26+
<dependencyManagement>
27+
<dependencies>
28+
<dependency>
29+
<groupId>org.camunda.bpm</groupId>
30+
<artifactId>camunda-bom</artifactId>
31+
<version>${camunda.version}</version>
32+
<scope>import</scope>
33+
<type>pom</type>
34+
</dependency>
35+
</dependencies>
36+
</dependencyManagement>
37+
38+
<dependencies>
39+
<dependency>
40+
<!-- process engine, needs to be provided -->
41+
<groupId>org.camunda.bpm</groupId>
42+
<artifactId>camunda-engine</artifactId>
43+
<scope>provided</scope>
44+
</dependency>
45+
46+
<dependency>
47+
<!-- AssertJ Testing Library -->
48+
<groupId>org.camunda.bpm.extension</groupId>
49+
<artifactId>camunda-bpm-assert</artifactId>
50+
<version>1.2</version>
51+
<scope>test</scope>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>junit</groupId>
56+
<artifactId>junit</artifactId>
57+
<version>4.12</version>
58+
<scope>test</scope>
59+
</dependency>
60+
61+
<dependency>
62+
<!-- Needed for InMemoryH2Test -->
63+
<groupId>com.h2database</groupId>
64+
<artifactId>h2</artifactId>
65+
<version>1.4.197</version>
66+
<scope>test</scope>
67+
</dependency>
68+
69+
<dependency>
70+
<!-- Used to generate test coverage reports, see https://github.com/camunda/camunda-consulting/tree/master/snippets/camunda-bpm-process-test-coverage -->
71+
<groupId>org.camunda.bpm.extension</groupId>
72+
<artifactId>camunda-bpm-process-test-coverage</artifactId>
73+
<version>0.3.2</version>
74+
<scope>test</scope>
75+
</dependency>
76+
77+
<dependency>
78+
<!-- use logback as logger -->
79+
<groupId>ch.qos.logback</groupId>
80+
<artifactId>logback-classic</artifactId>
81+
<version>1.1.3</version>
82+
<scope>test</scope>
83+
</dependency>
84+
85+
<dependency>
86+
<groupId>org.slf4j</groupId>
87+
<!-- apache commons logging => slf4j -->
88+
<artifactId>jcl-over-slf4j</artifactId>
89+
<version>1.7.25</version>
90+
<scope>test</scope>
91+
</dependency>
92+
93+
<dependency>
94+
<!-- java util logging => slf4j -->
95+
<groupId>org.slf4j</groupId>
96+
<artifactId>jul-to-slf4j</artifactId>
97+
<version>1.7.25</version>
98+
<scope>test</scope>
99+
</dependency>
100+
101+
</dependencies>
102+
103+
<repositories>
104+
<repository>
105+
<id>camunda-bpm-nexus</id>
106+
<name>Camunda Maven Repository</name>
107+
<url>https://app.camunda.com/nexus/content/groups/public</url>
108+
</repository>
109+
<!-- enable this for EE dependencies (requires credentials in ~/.m2/settings.xml)
110+
<repository>
111+
<id>camunda-bpm-nexus-ee</id>
112+
<name>Camunda Enterprise Maven Repository</name>
113+
<url>https://app.camunda.com/nexus/content/repositories/camunda-bpm-ee</url>
114+
</repository>
115+
-->
116+
</repositories>
117+
118+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import org.camunda.bpm.engine.ProcessEngine;
4+
import org.camunda.bpm.engine.ProcessEngineConfiguration;
5+
import org.camunda.bpm.engine.externaltask.ExternalTask;
6+
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
7+
import org.camunda.bpm.engine.impl.cmd.ExternalTaskCmd;
8+
import org.camunda.bpm.engine.impl.persistence.entity.ExternalTaskEntity;
9+
10+
public class CompleteExternalTaskWithoutLockCmd extends ExternalTaskCmd {
11+
12+
CompleteExternalTaskWithoutLockCmd(String externalTaskId) {
13+
super(externalTaskId);
14+
}
15+
16+
@Override
17+
protected void execute(ExternalTaskEntity externalTask) {
18+
externalTask.complete(null, null);
19+
}
20+
21+
@Override
22+
protected void validateInput() {
23+
}
24+
25+
public static void completeExternalTaskWithoutLock(ProcessEngine processEngine, ExternalTask externalTask) {
26+
completeExternalTaskWithoutLock(processEngine.getProcessEngineConfiguration(), externalTask);
27+
}
28+
29+
public static void completeExternalTaskWithoutLock(ProcessEngine processEngine, String externalTaskId) {
30+
completeExternalTaskWithoutLock(processEngine.getProcessEngineConfiguration(), externalTaskId);
31+
}
32+
33+
public static void completeExternalTaskWithoutLock(ProcessEngineConfiguration configuration, ExternalTask externalTask) {
34+
completeExternalTaskWithoutLock(configuration, externalTask.getId());
35+
}
36+
37+
public static void completeExternalTaskWithoutLock(ProcessEngineConfiguration configuration, String externalTaskId) {
38+
((ProcessEngineConfigurationImpl) configuration).getCommandExecutorTxRequired()
39+
.execute(new CompleteExternalTaskWithoutLockCmd(externalTaskId));
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import static org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener.CompleteExternalTaskWithoutLockCmd.completeExternalTaskWithoutLock;
4+
5+
import java.util.concurrent.CompletableFuture;
6+
7+
import org.camunda.bpm.engine.externaltask.ExternalTask;
8+
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
9+
import org.camunda.bpm.engine.impl.context.Context;
10+
11+
public class CompleteWithoutLockExternalTaskListener implements ExternalTaskListener {
12+
13+
@Override
14+
public void notify(ExternalTask externalTask) {
15+
// TODO try to lock the task in the same TX that creates it
16+
String externalTaskId = externalTask.getId();
17+
System.out.println("New External Task: " + externalTaskId);
18+
ProcessEngineConfigurationImpl processEngineConfiguration = Context.getProcessEngineConfiguration();
19+
20+
CompletableFuture.runAsync(() -> {
21+
// give the database enough time to commit the TX that creates the external task
22+
try {
23+
Thread.sleep(200L); // TODO use ScheduledExecutorService and or Queue
24+
} catch (InterruptedException e) {
25+
e.printStackTrace();
26+
}
27+
completeExternalTaskWithoutLock(processEngineConfiguration, externalTaskId);
28+
}).exceptionally(e -> {
29+
System.out.println("Oops! We have an exception - " + e.getMessage());
30+
return null;
31+
});
32+
}
33+
34+
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import org.camunda.bpm.engine.externaltask.ExternalTask;
4+
5+
public interface ExternalTaskListener {
6+
7+
public void notify(ExternalTask externalTask);
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import static org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener.CompleteExternalTaskWithoutLockCmd.completeExternalTaskWithoutLock;
4+
5+
import java.util.concurrent.CompletableFuture;
6+
7+
import org.camunda.bpm.engine.externaltask.ExternalTask;
8+
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
9+
import org.camunda.bpm.engine.impl.context.Context;
10+
import org.camunda.bpm.engine.impl.db.entitymanager.cache.CachedDbEntity;
11+
import org.camunda.bpm.engine.impl.db.entitymanager.cache.DbEntityCache;
12+
import org.camunda.bpm.engine.impl.db.entitymanager.cache.DbEntityState;
13+
import org.camunda.bpm.engine.impl.interceptor.Command;
14+
import org.camunda.bpm.engine.impl.interceptor.CommandInterceptor;
15+
import org.camunda.bpm.engine.impl.persistence.entity.ExternalTaskEntity;
16+
17+
public class ExternalTaskListenerCommandInterceptor extends CommandInterceptor {
18+
19+
private ExternalTaskListener externalTaskListener;
20+
21+
public ExternalTaskListenerCommandInterceptor(ExternalTaskListener externalTaskListener) {
22+
this.externalTaskListener = externalTaskListener;
23+
}
24+
25+
@Override
26+
public <T> T execute(Command<T> command) {
27+
// execute command first
28+
final T result = next.execute(command);
29+
30+
// after that find all new External Tasks
31+
DbEntityCache cache = Context.getCommandContext().getDbEntityManager().getDbEntityCache();
32+
for (ExternalTaskEntity externalTaskEntity : cache.getEntitiesByType(ExternalTaskEntity.class)) {
33+
CachedDbEntity cachedEntity = cache.getCachedEntity(externalTaskEntity);
34+
if (DbEntityState.TRANSIENT.equals(cachedEntity.getEntityState())) {
35+
externalTaskListener.notify(externalTaskEntity);
36+
}
37+
}
38+
39+
return result;
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import java.util.logging.Logger;
4+
5+
import org.camunda.bpm.engine.delegate.TaskListener;
6+
import org.camunda.bpm.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
7+
import org.camunda.bpm.engine.impl.bpmn.parser.AbstractBpmnParseListener;
8+
import org.camunda.bpm.engine.impl.bpmn.parser.BpmnParseListener;
9+
import org.camunda.bpm.engine.impl.pvm.delegate.ActivityBehavior;
10+
import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl;
11+
import org.camunda.bpm.engine.impl.pvm.process.ScopeImpl;
12+
import org.camunda.bpm.engine.impl.util.xml.Element;
13+
14+
public class ExternalTaskListenerParseListener extends AbstractBpmnParseListener implements BpmnParseListener {
15+
16+
private final Logger LOGGER = Logger.getLogger(ExternalTaskListenerParseListener.class.getName());
17+
18+
@Override
19+
public void parseStartEvent(Element startEventElement, ScopeImpl scope, ActivityImpl startEvent) {
20+
LOGGER.info("Parsing Start Event "
21+
+ ", activityId=" + startEvent.getId()
22+
+ ", activityName='" + startEvent.getName() + "'"
23+
+ ", scopeId=" + scope.getId()
24+
+ ", scopeName=" + scope.getName());
25+
}
26+
27+
@Override
28+
public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) {
29+
LOGGER.info("Adding Task Listener to User Task:"
30+
+ " activityId=" + activity.getId()
31+
+ ", activityName='" + activity.getName() + "'"
32+
+ ", scopeId=" + scope.getId()
33+
+ ", scopeName=" + scope.getName());
34+
ActivityBehavior behavior = activity.getActivityBehavior();
35+
if (behavior instanceof UserTaskActivityBehavior) {
36+
((UserTaskActivityBehavior) behavior).getTaskDefinition().addTaskListener(TaskListener.EVENTNAME_CREATE, new ExternalTaskListenerTaskListener());
37+
}
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.camunda.bpm.engine.ProcessEngine;
7+
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
8+
import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;
9+
import org.camunda.bpm.engine.impl.interceptor.CommandInterceptor;
10+
11+
public class ExternalTaskListenerProcessEnginePlugin implements ProcessEnginePlugin {
12+
13+
@Override
14+
public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
15+
List<CommandInterceptor> postCommandInterceptors = processEngineConfiguration.getCustomPostCommandInterceptorsTxRequired();
16+
if (postCommandInterceptors == null) {
17+
postCommandInterceptors = new ArrayList<>();
18+
processEngineConfiguration.setCustomPostCommandInterceptorsTxRequired(postCommandInterceptors);
19+
}
20+
postCommandInterceptors.add(new ExternalTaskListenerCommandInterceptor(new CompleteWithoutLockExternalTaskListener()));
21+
}
22+
23+
@Override
24+
public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
25+
26+
}
27+
28+
@Override
29+
public void postProcessEngineBuild(ProcessEngine processEngine) {
30+
31+
}
32+
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.camunda.bpm.consulting.snippet.engine_plugin_external_task_listener;
2+
3+
import java.util.logging.Logger;
4+
5+
import org.camunda.bpm.engine.delegate.DelegateTask;
6+
import org.camunda.bpm.engine.delegate.TaskListener;
7+
8+
public class ExternalTaskListenerTaskListener implements TaskListener {
9+
10+
private final Logger LOGGER = Logger.getLogger(ExternalTaskListenerTaskListener.class.getName());
11+
12+
@Override
13+
public void notify(DelegateTask task) {
14+
LOGGER.info("Event '" + task.getEventName() + "' received by Task Listener for Task:"
15+
+ " activityId=" + task.getTaskDefinitionKey()
16+
+ ", name='" + task.getName() + "'"
17+
+ ", taskId=" + task.getId()
18+
+ ", assignee='" + task.getAssignee() + "'"
19+
+ ", candidateGroups='" + task.getCandidates() + "'");
20+
}
21+
22+
}

0 commit comments

Comments
 (0)