All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vanillabp.camunda8.service.Camunda8ProcessService Maven / Gradle / Ivy

The newest version!
package io.vanillabp.camunda8.service;

import io.camunda.zeebe.client.ZeebeClient;
import io.vanillabp.camunda8.Camunda8AdapterConfiguration;
import io.vanillabp.camunda8.Camunda8VanillaBpProperties;
import io.vanillabp.camunda8.LoggingContext;
import io.vanillabp.springboot.adapter.AdapterAwareProcessService;
import io.vanillabp.springboot.adapter.ProcessServiceImplementation;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Transactional(propagation = Propagation.MANDATORY)
public class Camunda8ProcessService
        implements ProcessServiceImplementation {

    private static final Logger logger = LoggerFactory.getLogger(Camunda8ProcessService.class);
    
    private final CrudRepository workflowAggregateRepository;

    private final Class workflowAggregateClass;

    private final Function getWorkflowAggregateId;

    private final Camunda8VanillaBpProperties camunda8Properties;

    private final ApplicationEventPublisher publisher;

    private AdapterAwareProcessService parent;

    private ZeebeClient client;

    public Camunda8ProcessService(
            final Camunda8VanillaBpProperties camunda8Properties,
            final ApplicationEventPublisher publisher,
            final CrudRepository workflowAggregateRepository,
            final Function getWorkflowAggregateId,
            final Class workflowAggregateClass) {
        
        super();
        this.camunda8Properties = camunda8Properties;
        this.publisher = publisher;
        this.workflowAggregateRepository = workflowAggregateRepository;
        this.workflowAggregateClass = workflowAggregateClass;
        this.getWorkflowAggregateId = getWorkflowAggregateId;
                
    }
    
    @Override
    public void setParent(
            final AdapterAwareProcessService parent) {
        
        this.parent = parent;
        
    }
    
    public void wire(
            final ZeebeClient client,
            final String workflowModuleId,
            final String bpmnProcessId,
            final boolean isPrimary,
            final Collection messageBasedStartEventsMessageNames,
            final Collection signalBasedStartEventsSignalNames) {

        if (parent == null) {
            throw new RuntimeException("Not yet wired! If this occurs dependency of either "
                    + "VanillaBP Spring Boot support or Camunda8 adapter was changed introducing this "
                    + "lack of wiring. Please report a Github issue!");
            
        }

        this.client = client;
        parent.wire(
                Camunda8AdapterConfiguration.ADAPTER_ID,
                workflowModuleId,
                bpmnProcessId,
                isPrimary,
                messageBasedStartEventsMessageNames,
                signalBasedStartEventsSignalNames);
        
    }

    @Override
    public Class getWorkflowAggregateClass() {

        return workflowAggregateClass;

    }
    
    @Override
    public CrudRepository getWorkflowAggregateRepository() {

        return workflowAggregateRepository;

    }

    @Override
    public String getPrimaryBpmnProcessId() {
        return parent.getPrimaryBpmnProcessId();
    }

    @Override
    public DE startWorkflow(
            final DE workflowAggregate) throws Exception {

        return runInTransaction(
                workflowAggregate,
                attachedAggregate -> {
                    final var tenantId = camunda8Properties.getTenantId(parent.getWorkflowModuleId());
                    final var command = client
                            .newCreateInstanceCommand()
                            .bpmnProcessId(parent.getPrimaryBpmnProcessId())
                            .latestVersion()
                            .variables(attachedAggregate);

                    try {
                        (tenantId == null
                                ? command
                                : command.tenantId(tenantId))
                                .send()
                                .get(10, TimeUnit.SECONDS);
                    } catch (Exception e) {
                        throw new RuntimeException(
                                "Starting workflow '"
                                + parent.getPrimaryBpmnProcessId()
                                + "‘ for aggregate '"
                                + attachedAggregate
                                + "' failed!",
                                e);
                    }
                },
                "startWorkflow");
        
    }

    @Override
    public DE correlateMessage(
            final DE workflowAggregate,
            final String messageName) {

        return runInTransaction(
                workflowAggregate,
                attachedAggregate -> {
                    final var correlationId = getWorkflowAggregateId
                            .apply(workflowAggregate);

                    doCorrelateMessage(
                            workflowAggregate,
                            messageName,
                            correlationId.toString());
                },
                "correlateMessage");

    }
    
    @Override
    public DE correlateMessage(
            final DE workflowAggregate,
            final Object message) {
        
        return correlateMessage(
                workflowAggregate,
                message.getClass().getSimpleName());
        
    }

    @Override
    public DE correlateMessage(
            final DE workflowAggregate,
            final String messageName,
            final String correlationId) {

        return runInTransaction(
                workflowAggregate,
                attachedAggregate -> doCorrelateMessage(
                        attachedAggregate,
                        messageName,
                        correlationId),
                "correlateMessage-by-correlationId");

    }

    private void doCorrelateMessage(
            final DE attachedAggregate,
            final String messageName,
            final String correlationId) {

        final var tenantId = camunda8Properties.getTenantId(parent.getWorkflowModuleId());
        final var command = client
                .newPublishMessageCommand()
                .messageName(messageName)
                .correlationKey(correlationId)
                .variables(attachedAggregate);

        final var messageKey = (tenantId == null
                ? command
                : command.tenantId(tenantId))
                .send()
                .join()
                .getMessageKey();
        
        logger.trace("Correlated message '{}' using correlation-id '{}' for process '{}' as '{}'",
                messageName,
                correlationId,
                parent.getPrimaryBpmnProcessId(),
                messageKey);
        
    }
    
    @Override
    public DE correlateMessage(
            final DE workflowAggregate,
            final Object message,
            final String correlationId) {
        
        return correlateMessage(
                workflowAggregate,
                message.getClass().getSimpleName(),
                correlationId);
        
    }

    @Override
    public DE completeTask(
            final DE workflowAggregate,
            final String taskId) {

        return runInTransaction(
                workflowAggregate,
                taskId,
                attachedAggregate -> {
                    client
                            .newCompleteCommand(Long.parseLong(taskId, 16))
                            .variables(attachedAggregate)
                            .send()
                            .join();

                    logger.trace("Complete task '{}' of process '{}'",
                            taskId,
                            parent.getPrimaryBpmnProcessId());
                },
                "completeTask");

    }
    
    @Override
    public DE completeUserTask(
            final DE workflowAggregate,
            final String taskId) {

        return runInTransaction(
                workflowAggregate,
                taskId,
                attachedAggregate -> {
                    client
                            .newCompleteCommand(Long.parseLong(taskId, 16))
                            .variables(attachedAggregate)
                            .send()
                            .join();

                    logger.trace("Complete user task '{}' of process '{}'",
                            taskId,
                            parent.getPrimaryBpmnProcessId());
                },
                "completeUserTask");
        
    }
    
    @Override
    public DE cancelTask(
            final DE workflowAggregate,
            final String taskId,
            final String errorCode) {

        return runInTransaction(
                workflowAggregate,
                taskId,
                attachedAggregate -> {
                    client
                            .newThrowErrorCommand(Long.parseLong(taskId))
                            .errorCode(errorCode)
                            .send()
                            .join();

                    logger.trace("Complete task '{}' of process '{}'",
                            taskId,
                            parent.getPrimaryBpmnProcessId());
                },
                "cancelTask");

    }
    
    @Override
    public DE cancelUserTask(
            final DE workflowAggregate,
            final String taskId,
            final String errorCode) {

        return cancelTask(workflowAggregate, taskId, errorCode);

    }

    private DE runInTransaction(
            final DE workflowAggregate,
            final Consumer runnable,
            final String methodSignature) {

        return runInTransaction(
                workflowAggregate,
                null,
                runnable,
                methodSignature);

    }

    private DE runInTransaction(
            final DE workflowAggregate,
            final String taskIdToTestForAlreadyCompletedOrCancelled,
            final Consumer runnable,
            final String methodSignature) {

        try {

            // persist to get ID in case of @Id @GeneratedValue
            // or force optimistic locking exceptions before running
            // the workflow if aggregate was already persisted before
            final var attachedAggregate = workflowAggregateRepository
                    .save(workflowAggregate);

            final var aggregateId = getWorkflowAggregateId.apply(attachedAggregate);
            final var bpmnProcessId = parent.getPrimaryBpmnProcessId();
            LoggingContext.setLoggingContext(
                    Camunda8AdapterConfiguration.ADAPTER_ID,
                    camunda8Properties.getTenantId(parent.getWorkflowModuleId()),
                    parent.getWorkflowModuleId(),
                    aggregateId.toString(),
                    bpmnProcessId,
                    taskIdToTestForAlreadyCompletedOrCancelled,
                    null,
                    null,
                    null);

            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                if (taskIdToTestForAlreadyCompletedOrCancelled != null) {
                    publisher.publishEvent(
                            new Camunda8TransactionProcessor.Camunda8TestForTaskAlreadyCompletedOrCancelled(
                                    methodSignature,
                                    () -> client
                                            .newUpdateTimeoutCommand(Long.parseUnsignedLong(taskIdToTestForAlreadyCompletedOrCancelled, 16))
                                            .timeout(Duration.ofMinutes(10))
                                            .send()
                                            .join(5, TimeUnit.MINUTES), // needs to run synchronously
                                    () -> "aggregate: " + aggregateId + "; bpmn-process-id: " + bpmnProcessId));
                }
                publisher.publishEvent(
                        new Camunda8TransactionProcessor.Camunda8CommandAfterTx(
                                methodSignature,
                                () -> runnable.accept(attachedAggregate),
                                () -> "aggregate: " + aggregateId + "; bpmn-process-id: " + bpmnProcessId));
            } else {
                runnable.accept(attachedAggregate);
            }

            return attachedAggregate;

        } finally {
            LoggingContext.clearContext();
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy