Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions experimental/operaton-engine-bpmn/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apply from: "${project.rootDir}/gradle/in-test-generated.gradle"

dependencies {
annotationProcessor project(":config:config-annotation-processor")

compileOnly project(":database:database-jdbc")

implementation project(":config:config-common")
implementation libs.fasterxml.uuidgenerator

api project(":common")
api(libs.operaton.engine) {
exclude group: 'org.springframework', module: 'spring-beans'
exclude group: 'org.apache.tomcat', module: 'catalina'
}

testImplementation libs.jdbc.postgresql
testImplementation project(":database:database-jdbc")
testImplementation project(":internal:test-logging")
testImplementation project(":internal:test-postgres")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.koraframework.bpmn.operaton.engine;

import org.operaton.bpm.engine.impl.jobexecutor.JobExecutor;
import org.jspecify.annotations.Nullable;
import io.koraframework.common.readiness.ReadinessProbe;
import io.koraframework.common.readiness.ReadinessProbeFailure;

public final class JobExecutorReadinessProbe implements ReadinessProbe {

private final JobExecutor jobExecutor;

public JobExecutorReadinessProbe(JobExecutor jobExecutor) {
this.jobExecutor = jobExecutor;
}

@Nullable
@Override
public ReadinessProbeFailure probe() {
if (jobExecutor.isAutoActivate()) {
return null;
} else {
return new ReadinessProbeFailure("Operaton BPMN Engine JobExecutor is not active");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.koraframework.bpmn.operaton.engine;

import org.operaton.bpm.engine.ArtifactFactory;
import org.operaton.bpm.engine.delegate.JavaDelegate;
import org.operaton.bpm.engine.impl.DefaultArtifactFactory;

import java.util.HashMap;
import java.util.Map;

public final class KoraArtifactFactory implements ArtifactFactory {

private final ArtifactFactory defaultArtifactFactory = new DefaultArtifactFactory();
private final Map<String, Object> componentByKey;

public KoraArtifactFactory(KoraDelegateWrapperFactory wrapperFactory,
Iterable<KoraDelegate> koraDelegates,
Iterable<JavaDelegate> javaDelegates) {
this.componentByKey = new HashMap<>();
for (JavaDelegate delegate : javaDelegates) {
JavaDelegate wrapped = wrapperFactory.wrap(delegate);
this.componentByKey.put(delegate.getClass().getCanonicalName(), wrapped);
}

for (JavaDelegate delegate : koraDelegates) {
JavaDelegate wrapped = wrapperFactory.wrap(delegate);
this.componentByKey.put(delegate.getClass().getCanonicalName(), wrapped);
}
}

@Override
public <T> T getArtifact(Class<T> clazz) {
@SuppressWarnings("unchecked")
var artifact = (T) componentByKey.get(clazz.getCanonicalName());
if (artifact != null) {
return artifact;
}

return defaultArtifactFactory.getArtifact(clazz);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.koraframework.bpmn.operaton.engine;

import org.operaton.bpm.engine.delegate.JavaDelegate;
import org.jspecify.annotations.NonNull;

public interface KoraDelegate extends JavaDelegate {

@NonNull
default String key() {
return getClass().getCanonicalName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.koraframework.bpmn.operaton.engine;

import org.operaton.bpm.engine.delegate.JavaDelegate;

public interface KoraDelegateWrapperFactory {

JavaDelegate wrap(JavaDelegate delegate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.koraframework.bpmn.operaton.engine;

import jakarta.el.ELContext;
import jakarta.el.ELResolver;
import org.operaton.bpm.engine.ProcessEngineException;
import org.operaton.bpm.engine.delegate.JavaDelegate;

import java.beans.FeatureDescriptor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class KoraELResolver extends ELResolver {

private final Map<String, Object> componentByKey;

public KoraELResolver(KoraDelegateWrapperFactory wrapperFactory,
Iterable<KoraDelegate> koraDelegates,
Iterable<JavaDelegate> javaDelegates) {
this.componentByKey = new HashMap<>();
for (JavaDelegate delegate : javaDelegates) {
JavaDelegate wrapped = wrapperFactory.wrap(delegate);
this.componentByKey.put(delegate.getClass().getSimpleName(), wrapped);
this.componentByKey.put(delegate.getClass().getCanonicalName(), wrapped);
}

for (KoraDelegate delegate : koraDelegates) {
JavaDelegate wrapped = wrapperFactory.wrap(delegate);
this.componentByKey.put(delegate.key(), wrapped);
this.componentByKey.put(delegate.getClass().getSimpleName(), wrapped);
this.componentByKey.put(delegate.getClass().getCanonicalName(), wrapped);
}
}

@Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null) {
// according to javadoc, can only be a String
String key = (String) property;
Object value = componentByKey.get(key);
if (value != null) {
context.setPropertyResolved(true);
return value;
}
}
return null;
}

@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}

@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base == null) {
String key = (String) property;
if (componentByKey.containsKey(key)) {
throw new ProcessEngineException(
"Cannot set value of '" + property + "', it resolves to a CamundaComponent defined in the Kora application."
);
}
}
}

@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return Object.class;
}

@Override
public Class<?> getType(ELContext context, Object base, Object property) {
return Object.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.koraframework.bpmn.operaton.engine;

import jakarta.el.*;
import org.operaton.bpm.engine.impl.el.JuelExpressionManager;
import org.operaton.bpm.engine.impl.el.VariableContextElResolver;
import org.operaton.bpm.engine.impl.el.VariableScopeElResolver;
import org.operaton.bpm.engine.impl.mock.MockElResolver;

public final class KoraExpressionManager extends JuelExpressionManager {

private final ELResolver koraELResolver;

public KoraExpressionManager(ELResolver koraELResolver) {
this.koraELResolver = koraELResolver;
}

@Override
protected ELResolver createElResolver() {
CompositeELResolver resolver = new CompositeELResolver();
resolver.add(koraELResolver);
resolver.add(new VariableScopeElResolver());
resolver.add(new VariableContextElResolver());
resolver.add(new MockElResolver());
resolver.add(new ArrayELResolver());
resolver.add(new ListELResolver());
resolver.add(new MapELResolver());
return resolver;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.koraframework.bpmn.operaton.engine;

import io.koraframework.application.graph.Lifecycle;
import io.koraframework.application.graph.Wrapped;
import io.koraframework.bpmn.operaton.engine.configurator.ProcessEngineConfigurator;
import io.koraframework.common.util.TimeUtils;
import org.jspecify.annotations.Nullable;
import org.operaton.bpm.engine.ProcessEngine;
import org.operaton.bpm.engine.ProcessEngineConfiguration;
import org.operaton.bpm.engine.ProcessEngines;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

public final class KoraProcessEngine implements Lifecycle, Wrapped<ProcessEngine> {

private static final Logger logger = LoggerFactory.getLogger(KoraProcessEngine.class);

private final ProcessEngineConfiguration engineConfiguration;
private final OperatonEngineBpmnConfig engineConfig;
private final Iterable<ProcessEngineConfigurator> engineConfigurators;

private volatile ProcessEngine processEngine;

public KoraProcessEngine(ProcessEngineConfiguration engineConfiguration,
OperatonEngineBpmnConfig engineConfig,
Iterable<ProcessEngineConfigurator> engineConfigurators) {
this.engineConfig = engineConfig;
this.engineConfiguration = engineConfiguration;
this.engineConfigurators = engineConfigurators;
}

@Override
public void init() {
try {
for (ProcessEngineConfigurator configurator : engineConfigurators) {
configurator.prepare(engineConfiguration);
}

if (engineConfig.parallelInitialization().enabled() && engineConfiguration instanceof KoraProcessEngineConfiguration) {
logger.info("Operaton BPMN Engine parallel initialization enabled");

logger.debug("Operaton BPMN Engine starting first stage...");
final long started = TimeUtils.started();

this.processEngine = engineConfiguration.buildProcessEngine();
ProcessEngines.registerProcessEngine(processEngine);
logger.info("Operaton BPMN Engine started first stage in {}", TimeUtils.tookForLogging(started));
} else {
logger.debug("Operaton BPMN Engine starting...");
final long started = TimeUtils.started();

this.processEngine = engineConfiguration.buildProcessEngine();
ProcessEngines.registerProcessEngine(processEngine);
logger.info("Operaton BPMN Engine started in {}", TimeUtils.tookForLogging(started));

logger.debug("Operaton BPMN Engine configuring...");
final long startedConfiguring = TimeUtils.started();

var configurators = new ArrayList<ProcessEngineConfigurator>();
for (var configurator : this.engineConfigurators) {
configurators.add(configurator);
}
var setups = new CompletableFuture<?>[configurators.size()];
for (var i = 0; i < configurators.size(); i++) {
var engineConfigurator = configurators.get(i);
var future = new CompletableFuture<@Nullable Void>();
Thread.ofVirtual().name("operaton-process-engine-config-" + i).start(() -> {
try {
engineConfigurator.setup(processEngine);
future.complete(null);
} catch (Throwable t) {
future.completeExceptionally(t);
}
});
setups[i] = future;
}
CompletableFuture.allOf(setups).join();
logger.info("Operaton BPMN Engine configured in {}", TimeUtils.tookForLogging(startedConfiguring));
}
} catch (Exception e) {
throw new RuntimeException("Operaton BPMN Engine failed to start, due to: " + e.getMessage(), e);
}
}

@Override
public void release() {
if (processEngine != null) {
logger.debug("Operaton BPMN Engine stopping...");
final long started = TimeUtils.started();

ProcessEngines.unregister(processEngine);
processEngine.close();

logger.info("Operaton BPMN Engine stopped in {}", TimeUtils.tookForLogging(started));
}
}

@Override
public ProcessEngine value() {
return processEngine;
}
}
Loading
Loading