io.vanillabp.camunda8.wiring.Camunda8TaskWiring Maven / Gradle / Ivy
The newest version!
package io.vanillabp.camunda8.wiring;
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.worker.JobWorkerBuilderStep1.JobWorkerBuilderStep3;
import io.camunda.zeebe.model.bpmn.impl.BpmnModelInstanceImpl;
import io.camunda.zeebe.model.bpmn.instance.BaseElement;
import io.camunda.zeebe.model.bpmn.instance.Process;
import io.camunda.zeebe.model.bpmn.instance.UserTask;
import io.camunda.zeebe.model.bpmn.instance.zeebe.ZeebeFormDefinition;
import io.camunda.zeebe.model.bpmn.instance.zeebe.ZeebeLoopCharacteristics;
import io.camunda.zeebe.model.bpmn.instance.zeebe.ZeebeTaskDefinition;
import io.vanillabp.camunda8.Camunda8VanillaBpProperties;
import io.vanillabp.camunda8.deployment.Camunda8DeploymentAdapter;
import io.vanillabp.camunda8.deployment.DeployedBpmn;
import io.vanillabp.camunda8.service.Camunda8ProcessService;
import io.vanillabp.camunda8.wiring.Camunda8Connectable.Type;
import io.vanillabp.camunda8.wiring.parameters.Camunda8MethodParameterFactory;
import io.vanillabp.camunda8.wiring.parameters.ParameterVariables;
import io.vanillabp.spi.service.WorkflowTask;
import io.vanillabp.springboot.adapter.SpringBeanUtil;
import io.vanillabp.springboot.adapter.SpringDataUtil;
import io.vanillabp.springboot.adapter.TaskWiringBase;
import io.vanillabp.springboot.parameters.MethodParameter;
import jakarta.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
public class Camunda8TaskWiring extends TaskWiringBase, Camunda8MethodParameterFactory>
implements Consumer {
/*
* timeout can be set to Long.MAX_VALUE but this will cause a subsequent error
* (see https://github.com/camunda/camunda/issues/11903). Therefor the timeout
* is set to 100 years.
*/
private static final long TIMEOUT_100YEARS = 100L * 365 * 24 * 3600 * 1000;
private final String workerId;
private final SpringDataUtil springDataUtil;
private final ObjectProvider taskHandlers;
private final Collection> connectableServices;
private final Camunda8UserTaskHandler userTaskHandler;
private ZeebeClient client;
private List workers = new LinkedList<>();
private List handlers = new LinkedList<>();
private Set userTaskTenantIds = new HashSet<>();
private final Camunda8VanillaBpProperties camunda8Properties;
public Camunda8TaskWiring(
final SpringDataUtil springDataUtil,
final ApplicationContext applicationContext,
final SpringBeanUtil springBeanUtil,
final String workerId,
final Camunda8VanillaBpProperties camunda8Properties,
final Camunda8UserTaskHandler userTaskHandler,
final ObjectProvider taskHandlers,
final Collection> connectableServices) {
super(applicationContext, springBeanUtil, new Camunda8MethodParameterFactory());
this.workerId = workerId;
this.springDataUtil = springDataUtil;
this.taskHandlers = taskHandlers;
this.userTaskHandler = userTaskHandler;
this.connectableServices = connectableServices;
this.camunda8Properties = camunda8Properties;
}
@Override
protected Class getAnnotationType() {
return WorkflowTask.class;
}
/**
* Called by
* {@link Camunda8DeploymentAdapter#processBpmnModel(String, Map, DeployedBpmn, BpmnModelInstanceImpl, boolean)} to
* ensure client is available before using wire-methods.
*/
@Override
public void accept(
final ZeebeClient client) {
this.client = client;
handlers.forEach(handler -> handler.accept(client));
}
public void openWorkers() {
// fetch all usertasks spawned
userTaskTenantIds
.stream()
.map(workflowModuleId -> {
final var tenantId = camunda8Properties.getTenantId(workflowModuleId);
final var userTaskWorker = client
.newWorker()
.jobType("io.camunda.zeebe:userTask")
.handler(userTaskHandler)
.timeout(TIMEOUT_100YEARS) // user-tasks should not be fetched more than once
.name(workerId);
if (tenantId != null) {
userTaskWorker.tenantId(tenantId);
}
final var workerProperties = camunda8Properties.getUserTaskWorkerProperties(workflowModuleId);
workerProperties.applyToUserTaskWorker(userTaskWorker);
return userTaskWorker;
})
.forEach(workers::add);
workers
.forEach(JobWorkerBuilderStep3::open);
}
public Stream connectablesForType(
final Process process,
final BpmnModelInstanceImpl model,
final Class extends BaseElement> type) {
final var kind = UserTask.class.isAssignableFrom(type) ? Type.USERTASK : Type.TASK;
final var stream = model
.getModelElementsByType(type)
.stream()
.filter(element -> getOwningProcess(element).equals(process))
.map(element -> new Camunda8Connectable(
process,
element.getId(),
kind,
getTaskDefinition(kind, element),
element.getSingleExtensionElement(ZeebeLoopCharacteristics.class)))
.filter(connectable -> connectable.isExecutableProcess());
if (kind == Type.USERTASK) {
return stream;
}
return stream.filter(connectable -> connectable.getTaskDefinition() != null);
}
private String getTaskDefinition(
final Type kind,
final BaseElement element) {
if (kind == Type.USERTASK) {
final var formDefinition = element.getSingleExtensionElement(ZeebeFormDefinition.class);
if (formDefinition == null) {
return null;
}
return formDefinition.getFormKey();
}
final var taskDefinition = element.getSingleExtensionElement(ZeebeTaskDefinition.class);
if (taskDefinition == null) {
return null;
}
return taskDefinition.getType();
}
static Process getOwningProcess(
final ModelElementInstance element) {
if (element instanceof Process) {
return (Process) element;
}
final var parent = element.getParentElement();
if (parent == null) {
return null;
}
return getOwningProcess(parent);
}
@Override
protected Camunda8ProcessService> connectToBpms(
final String workflowModuleId,
final Class workflowAggregateClass,
final String bpmnProcessId,
final boolean isPrimary,
final Collection messageBasedStartEventsMessageNames,
final Collection signalBasedStartEventsSignalNames) {
final var processService = connectableServices
.stream()
.filter(service -> service.getWorkflowAggregateClass().equals(workflowAggregateClass))
.findFirst()
.get();
processService.wire(
client,
workflowModuleId,
bpmnProcessId,
isPrimary,
messageBasedStartEventsMessageNames,
signalBasedStartEventsSignalNames);
return processService;
}
@Override
protected void connectToBpms(
final String workflowModuleId,
final Camunda8ProcessService> processService,
final Object bean,
final Camunda8Connectable connectable,
final Method method,
final List parameters) {
final var repository = processService.getWorkflowAggregateRepository();
final var idPropertyName = getWorkflowAggregateIdPropertyName(
processService.getWorkflowAggregateClass());
final var tenantId = camunda8Properties.getTenantId(workflowModuleId);
final var taskHandler = taskHandlers.getObject(
springDataUtil,
repository,
connectable.getType(),
connectable.getTaskDefinition(),
bean,
method,
parameters,
idPropertyName,
tenantId,
workflowModuleId,
processService.getPrimaryBpmnProcessId());
if (this.client != null) {
taskHandler.accept(this.client);
} else {
handlers.add(taskHandler);
}
if (connectable.getType() == Type.USERTASK) {
userTaskHandler.addTaskHandler(
tenantId,
connectable.getBpmnProcessId(),
connectable.getElementId(),
taskHandler);
userTaskTenantIds.add(workflowModuleId);
return;
}
final var variablesToFetch = getVariablesToFetch(idPropertyName, parameters);
final var worker = client
.newWorker()
.jobType(connectable.getTaskDefinition())
.handler(taskHandler)
.name(workerId)
.fetchVariables(variablesToFetch);
final var workerProperties = camunda8Properties.getWorkerProperties(
workflowModuleId,
connectable.getBpmnProcessId(),
connectable.getTaskDefinition());
workerProperties.applyToWorker(worker);
workers.add(
tenantId != null
? worker.tenantId(tenantId)
: worker);
}
private String getWorkflowAggregateIdPropertyName(
final Class> workflowAggregateClass) {
if (workflowAggregateClass == null) {
return null;
}
return Arrays
.stream(workflowAggregateClass.getDeclaredFields())
.filter(field -> field.getAnnotation(Id.class) != null)
.findFirst()
.map(Field::getName)
.orElse(Arrays
.stream(workflowAggregateClass.getDeclaredMethods())
.filter(method -> method.getAnnotation(Id.class) != null)
.findFirst()
.map(this::propertyName)
.orElse(getWorkflowAggregateIdPropertyName(workflowAggregateClass.getSuperclass())));
}
private String propertyName(
final Method method) {
if (method.getName().startsWith("get")) {
if (method.getName().length() < 4) {
return method.getName();
}
return
method.getName().substring(3, 4).toLowerCase()
+ method.getName().substring(4);
} else if (method.getName().startsWith("is")) {
if (method.getName().length() < 3) {
return method.getName();
}
return
method.getName().substring(2, 3).toLowerCase()
+ method.getName().substring(3);
} else {
return method.getName();
}
}
private List getVariablesToFetch(
final String idPropertyName,
final List parameters) {
final var result = new LinkedList();
// the aggregate's id aka the business key
result.add(idPropertyName);
parameters
.stream()
.filter(parameter -> parameter instanceof ParameterVariables)
.flatMap(parameter -> ((ParameterVariables) parameter).getVariables().stream())
.forEach(result::add);
return result;
}
public void wireTask(
final String workflowModuleId,
final Camunda8ProcessService> processService,
final Camunda8Connectable connectable) {
super.wireTask(
connectable,
false,
(method, annotation) -> methodMatchesTaskDefinition(connectable, method, annotation),
(method, annotation) -> methodMatchesElementId(connectable, method, annotation),
(method, annotation) -> validateParameters(processService, method),
(bean, method, parameters) -> connectToBpms(workflowModuleId, processService, bean, connectable, method, parameters));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy