External Task Worker for TAXII-springboot-bpmn.
This is a generic task worker that can execute scripts in different polymorphic languages as determined by the execution env and worker configurations. Scripts can be injected during runtime allowing a flexible architecture.
Example of a BPMN that would be executed on the TAXII-springboot-bpmn Server, and the worker(s) would Fetch and Lock, and then complete the work as orchestrated by Camunda.
Using External workers allow execution of TAXII and STIX data evaluations and actions to be done in any language.
This worker acts as a universal bridge example allowing a unified worker framework to execute scripting in various polymorphic languages providing flexibility to downstream Cyber Analysts.
FetchAndLock falConfig = FetchAndLock.builder()
.addTopic(FetchAndLockTopic.builder()
.topicName("mytopic")
.build())
.addTopic(FetchAndLockTopic.builder()
.topicName("someOtherSimilarTopic")
.build())
.maxTasks(50)
.usePriority(true)
.workerId("central-worker")
.build();
/**
* Single Use Fetch and Lock. After tasks are fetched the future is completed and will not loop.
* @param fetchAndLockModel
* @return
*/
private Future<Object> fetchAndLock(FetchAndLockModel fetchAndLockModel) {
Future<Object> future = Future.future();
System.out.println("Attempting to Fetch and Lock Tasks... " + new Date().toString());
externalTaskService.fetchAndLock(fetchAndLockModel).setHandler(result -> {
if (result.succeeded()) {
List<FetchAndLockResponseModel> tasks = result.result().getFetchedTasks();
if (!tasks.isEmpty()) {
tasks.forEach(this::doSomeWork);
future.complete(tasks.size() + " Tasks found and executed");
} else {
future.complete("No tasks found.");
}
} else {
future.fail(result.cause());
}
});
return future;
}
externalTaskService.complete(completeModel).setHandler(completeResult -> {
if (completeResult.succeeded()) {
System.out.println(String.format("Task %s completed.", completeModel.getId()));
} else {
System.out.println(completeResult.cause().getMessage());
}
});
externalTaskService.handleBpmnError(handleBpmnErrorModel)
externalTaskService.handleFailure(handleFailureModel)
All methods return a Future<>
so you can implement the .setHandler()
method to action when a result is returned by the async code:
externalTaskService.complete(completeModel).setHandler(completeResult -> {
if (completeResult.succeeded()) {
System.out.println(String.format("Task %s completed.", completeModel.getId()));
} else {
System.out.println(completeResult.cause().getMessage());
}
});
As long as the Web Client was able to make a successful http connection, the Future will complete.
It is up to your implementations to evaluate if the Future was a success or failure. In the case of a Failure, the returned Throwable
will be a instance of CamundaErrorResponse.class.
The HTTP Status Code, the raw response body, and a in-code defined message are provided for context about what went wrong.
Example usage is, if you were using the fetchAndLock(), and camunda returned a status code other than 200, then you will get failed Future
, as 200
is the defined status code for a successful request as per Camunda API spec docs.
...
CircuitBreaker breaker = new ExternalTaskCircutBreaker(vertx, null).getCircuitBreaker();
/**
* Implements Circuit Breaker with looping so as long as the Fetch and Lock is not throwing errors then the breaker will remain closed and endlessly loop.
* @param breaker
* @param falConfig
*/
private void fetchAndLockUsingBreaker(CircuitBreaker breaker, FetchAndLockModel falConfig){
breaker.execute(future -> {
Future<Object> tasksCheckResult = fetchAndLock(falConfig);
tasksCheckResult.setHandler(result -> {
if (result.succeeded()) {
future.complete();
fetchAndLockUsingBreaker(breaker, falConfig);
} else if (result.failed()) {
future.fail(result.cause());
}
});
});
}
/**
* Single Use Fetch and Lock. After tasks are fetched the future is completed and will not loop.
* @param fetchAndLockModel
* @return
*/
private Future<Object> fetchAndLock(FetchAndLockModel fetchAndLockModel) {
Future<Object> future = Future.future();
System.out.println("Attempting to Fetch and Lock Tasks... " + new Date().toString());
externalTaskService.fetchAndLock(fetchAndLockModel).setHandler(result -> {
if (result.succeeded()) {
List<FetchAndLockResponseModel> tasks = result.result().getFetchedTasks();
if (!tasks.isEmpty()) {
tasks.forEach(this::doSomeWork);
future.complete(tasks.size() + " Tasks found and executed");
} else {
future.complete("No tasks found.");
}
} else {
future.fail(result.cause());
}
});
return future;
}
The usage of vertx should be that the Vertx Event Bus is leveraged to pass tasks around. All the Immutables has been setup to be serializable as Json using vertx's jackson Json.encode()
.
A verticle can be setup to collect tasks and send them to other verticles through the event bus to process the work.
This enables other verticles to not to have worry about Camunda specific information and details.
Further you could also have each verticle long poll or jitter poll camunda and process everything within the verticle it self.
The current thinking is that centralized polling with pushing the work to individual verticles through the event bus is optimial as it scales the best without having to increase the load of polling on the Camunda server.
CAM-9562 - Camunda Versions: 7.10.0, 7.9.6, 7.11.9: Long Polling that has a connection terminated will cause tasks to be locked automatically without the worker receiving them. This is a bug with Camunda and not TAXII-Worker.
GraalVM is used to provide polymorphic language execution support with languages such as (LLVM, C, Java, Groovy, Other JVM based scripting languages, Ruby, JavaScript and Node, Python, R, etc).
This support is experimental at this stage but will grow as the worker is enhanced.
A working example can be see with the ...graal.python.PythonExecutor
class.
Currently tested against GraalVM 1.0.0 rc9. This is also the version used in the pom.xml for the GraalSDK.
You must execute The worker in a GraalVM environment, and it must have the Python language installed.
You can install GraalVM with SDKMAN. Then you can execute ~/.sdkMan/candidates/java/1.0.0-rc9-graal/jre/bin/gu install python