Initial commit

This commit is contained in:
Andreas Isler
2026-05-22 14:59:58 +02:00
commit 2f9c0a5c9e
130 changed files with 13126 additions and 0 deletions
BIN
View File
Binary file not shown.
+102
View File
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.customer</groupId>
<artifactId>customer-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>customer-work</artifactId>
<name>customer-work</name>
<description>customer-work</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<dependencies>
<!-- Flowable Work -->
<!-- ================= -->
<dependency>
<groupId>com.flowable.work</groupId>
<artifactId>flowable-work-frontend</artifactId>
</dependency>
<dependency>
<groupId>com.flowable.platform</groupId>
<artifactId>flowable-platform-default-models</artifactId>
</dependency>
<dependency>
<groupId>com.flowable.platform</groupId>
<artifactId>flowable-spring-boot-starter-platform-rest</artifactId>
</dependency>
<dependency>
<groupId>com.flowable.platform</groupId>
<artifactId>flowable-tenant-setup</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Flowable Inspect -->
<!-- ======= -->
<dependency>
<groupId>com.flowable.inspect</groupId>
<artifactId>flowable-spring-boot-starter-inspect-rest</artifactId>
</dependency>
<!-- Testing -->
<!-- ======= -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.wnameless.json</groupId>
<artifactId>json-flattener</artifactId>
<version>0.16.6</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
BIN
View File
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,62 @@
package com.customer.work;
import java.util.stream.Collectors;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
import com.flowable.autoconfigure.security.FlowableHttpSecurityCustomizer;
import com.flowable.autoconfigure.security.servlet.PlatformPathRequest;
import com.flowable.core.spring.security.web.authentication.AjaxAuthenticationFailureHandler;
import com.flowable.core.spring.security.web.authentication.AjaxAuthenticationSuccessHandler;
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "application.security", name = "type", havingValue = "basic", matchIfMissing = true)
@EnableWebSecurity
public class SecurityHttpBasicConfiguration {
@Bean
@Order(10)
public SecurityFilterChain basicDefaultSecurity(HttpSecurity http, ObjectProvider<FlowableHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
for (FlowableHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream()
.collect(Collectors.toList())) {
customizer.customize(http);
}
http
.logout(logout -> logout.logoutUrl("/auth/logout").logoutSuccessUrl("/"));
// Non authenticated exception handling. The formLogin and httpBasic configure the exceptionHandling
// We have to initialize the exception handling with a default authentication entry point in order to return 401 each time and not have a
// forward due to the formLogin or the http basic popup due to the httpBasic
http
.exceptionHandling(exceptionHandling -> exceptionHandling
.defaultAuthenticationEntryPointFor((request, response, authException) -> {}, new DispatcherTypeRequestMatcher(DispatcherType.ERROR))
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), AnyRequestMatcher.INSTANCE))
.formLogin(formLogin -> formLogin
.loginProcessingUrl("/auth/login")
.successHandler(new AjaxAuthenticationSuccessHandler())
.failureHandler(new AjaxAuthenticationFailureHandler())
)
.authorizeHttpRequests(configurer -> configurer
.requestMatchers(PlatformPathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
@@ -0,0 +1,65 @@
package com.customer.work;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class StaticResourceConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.setOrder(10)
.addResourceHandler("/ext/*.js", "/*/ext/*.js")
.addResourceLocations("classpath:/static/ext/", "classpath:/public/ext/")
.setCacheControl(CacheControl.noCache());
registry.setOrder(20)
.addResourceHandler("/ext/*.css", "/*/ext/*.css")
.addResourceLocations("classpath:/static/ext/", "classpath:/public/ext/")
.setCacheControl(CacheControl.noCache());
registry.setOrder(30)
.addResourceHandler("/*.js")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(40)
.addResourceHandler("/*.css")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(50)
.addResourceHandler("/*.woff")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(60)
.addResourceHandler("/*.woff2")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(70)
.addResourceHandler("/*.svg")
.addResourceLocations("classpath:/public/", "classpath:/public/twemoji/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(80)
.addResourceHandler("/*.map")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(90)
.addResourceHandler("/*.png")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.setOrder(100)
.addResourceHandler("/*.ico")
.addResourceLocations("classpath:/public/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
}
}
@@ -0,0 +1,13 @@
package com.customer.work;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WorkApplication {
public static void main(String[] args) {
SpringApplication.run(WorkApplication.class, args);
}
}
@@ -0,0 +1,107 @@
package com.customer.work.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.github.wnameless.json.flattener.JsonFlattener;
import com.github.wnameless.json.unflattener.JsonUnflattener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Component
public class JsonUtils {
protected static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);
protected final ObjectMapper objectMapper;
protected final JavaTimeModule javaTimeModule;
public JsonUtils(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.javaTimeModule = new JavaTimeModule();
// Enable ObjectMapper for handling Instant as string
this.objectMapper.registerModule(javaTimeModule);
this.objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
public JsonNode convertListToJsonNode(List<Object> list) {
return objectMapper.valueToTree(list);
}
public JsonNode convertMapToJsonNode(Map<String, Object> map) {
return objectMapper.valueToTree(map);
}
public Map<String, Object> convertJsonNodeToMap(JsonNode jsonNode) {
return objectMapper.convertValue(jsonNode, new TypeReference<Map<String, Object>>() {});
}
public Map<String, Object> flatten(Object payload) {
try {
return JsonFlattener.flattenAsMap(objectMapper.writeValueAsString(payload));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public Map<String, Object> unflatten(Map<String, Object> flatVars) {
try {
return objectMapper.readValue(JsonUnflattener.unflatten(flatVars), Map.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public JsonNode convertJsonStringToJsonNode(String jsonString) {
try {
return objectMapper.readTree(jsonString);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public Map<String, Object> convertObjectNodeToMap(ObjectNode objectNode) {
return objectMapper.convertValue(objectNode, new TypeReference<Map<String, Object>>() {});
}
public ObjectNode getEmptyObjectNode() {
return objectMapper.createObjectNode();
}
public ObjectNode loadObjectNodeFromFile(String path) {
try {
return (ObjectNode) this.objectMapper.readTree(new File(path));
} catch (IOException e) {
LOGGER.debug(e.getMessage());
return null;
}
}
public ArrayNode convertObjectToArrayNode(Object object) {
if (object == null) {
LOGGER.debug("{}: Argument is null", this.getClass().getName());
return null;
}
String obyTypeName = object.getClass().getName();
if (obyTypeName.equals("java.util.Collections$EmptyList") || obyTypeName.equals("java.util.ArrayList")) {
return objectMapper.convertValue(object, ArrayNode.class);
} else if (obyTypeName.equals("com.fasterxml.jackson.databind.node.ArrayNode")) {
return (ArrayNode) object;
} else {
LOGGER.debug("{}: {} is not implemented}", this.getClass().getName(), obyTypeName);
return null;
}
}
}
@@ -0,0 +1,418 @@
package com.customer.work.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.flowable.cmmn.api.CmmnRuntimeService;
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.api.runtime.PlanItemInstance;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.variable.api.delegate.VariableScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* General-purpose Flowable variable utility bean.
* Usable in BPMN process and CMMN case backend expressions without any scope parameter:
* ${varUtils.get('order.customer.name')}
* ${varUtils.track('order.customer.name,order.total,status')}
* Root variables are supported.
* How the scope is resolved without a parameter
* -----------------------------------------------
* The bean registers itself as a Flowable event listener on both engines.
* Before a service task expression is evaluated, Flowable fires events on the
* same thread that allow us to capture the current variable scope:
* BPMN — PROCESS_STARTED fires a FlowableEngineEntityEvent.
* The execution is retrieved via event.getEntity().
* CMMN — CASE_STARTED fires a FlowableEngineEntityEvent.; Instead, when a plan
* The case instance is retrieved via event.getEntity().
*/
@Component("varUtils")
public class VarUtils implements FlowableEventListener, SmartInitializingSingleton {
private static final Logger LOGGER = LoggerFactory.getLogger(VarUtils.class);
private static final ThreadLocal<VariableScope> SCOPE = new ThreadLocal<>();
private static final ObjectMapper MAPPER;
static {
MAPPER = new ObjectMapper();
MAPPER.registerModule(new JavaTimeModule());
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
@Autowired
private ProcessEngineConfiguration processEngineConfiguration;
@Autowired
private CmmnEngineConfiguration cmmnEngineConfiguration;
@Autowired
private RuntimeService runtimeService;
@Autowired
private CmmnRuntimeService cmmnRuntimeService;
// -------------------------------------------------------------------------
// Listener registration — runs after all beans are ready
// -------------------------------------------------------------------------
@Override
public void afterSingletonsInstantiated() {
if (processEngineConfiguration != null) {
processEngineConfiguration.getEventDispatcher().addEventListener(this);
LOGGER.debug("varUtils registered on BPMN event dispatcher");
}
if (cmmnEngineConfiguration != null) {
cmmnEngineConfiguration.getEventDispatcher().addEventListener(this);
LOGGER.debug("varUtils registered on CMMN event dispatcher");
}
}
// -------------------------------------------------------------------------
// FlowableEventListener — bind / unbind the scope ThreadLocal
// -------------------------------------------------------------------------
@Override
public void onEvent(FlowableEvent event) {
if (event instanceof FlowableEngineEntityEvent entityEvent) {
String typeName = event.getType().name();
Object entity = entityEvent.getEntity();
// BPMN execution
if (entity instanceof ExecutionEntity execution) {
switch (typeName) {
case "PROCESS_STARTED"
-> SCOPE.set(execution);
case "PROCESS_CANCELLED",
"PROCESS_COMPLETED",
"PROCESS_COMPLETED_WITH_ERROR_END_EVENT",
"PROCESS_COMPLETED_WITH_ESCALATION_END_EVENT",
"PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT"
-> SCOPE.remove();
}
return;
}
// CMMN case instance itself
if (entity instanceof CaseInstanceEntity caseInstance) {
if ("CASE_STARTED".equals(typeName))
SCOPE.set(caseInstance);
else if ("CASE_ENDED".equals(typeName))
SCOPE.remove();
}
}
}
@Override public boolean isFailOnException() { return false; }
@Override public boolean isFireOnTransactionLifecycleEvent() { return false; }
@Override public String getOnTransaction() { return null; }
// -------------------------------------------------------------------------
// Public API — called from Flowable expressions
// -------------------------------------------------------------------------
/**
* Checks which of the given comma-separated variable paths changed since the
* last call and returns a JSON array describing each change.
* ${varUtils.trackVars('root.snapshot', 'root.myString, status')}
* {@code snapshotPath} is a dot-notation path (supports the {@code root.} prefix)
* pointing to where the previous-values snapshot is stored and updated.
* Each subsequent call compares against the previous snapshot and updates it.
* Example return value:
* [{"path":"root.myString","oldValue":"a","newValue":"b"}]
*/
public ArrayNode trackVars(String snapshotPath, String variablePathsCsv) {
// Check parameters
if (snapshotPath == null || snapshotPath.isBlank() || variablePathsCsv == null || variablePathsCsv.isBlank()) {
LOGGER.debug("{}.trackVars: empty parameters", getClass().getName());
return MAPPER.createArrayNode();
}
// Get current scope
VariableScope currentScope = getCurrentScope();
if (currentScope == null) {
LOGGER.debug("{}.trackVars: currentScope not found", getClass().getName());
return MAPPER.createArrayNode();
}
// Create paths list
List<String> paths = Arrays.stream(variablePathsCsv.split(","))
.map(String::trim).filter(s -> !s.isEmpty()).toList();
Map<String, JsonNode> oldSnapshot = loadSnapshot(currentScope, snapshotPath);
Map<String, JsonNode> newSnapshot = new HashMap<>();
ArrayNode changes = MAPPER.createArrayNode();
// Loop paths list
for (String path : paths) {
JsonNode newValue = toJson(readVariableFromPath(currentScope, path));
newSnapshot.put(path, newValue);
JsonNode oldValue = oldSnapshot.getOrDefault(path, MAPPER.nullNode());
if (!oldValue.equals(newValue)) {
ObjectNode change = MAPPER.createObjectNode();
change.put("path", path);
change.set("oldValue", oldValue);
change.set("newValue", newValue);
changes.add(change);
}
}
saveSnapshot(currentScope, snapshotPath, newSnapshot);
return changes;
}
// -------------------------------------------------------------------------
// Path resolution
// -------------------------------------------------------------------------
private Object readVariableFromPath(VariableScope scope, String path) {
String[] segments = path.split("\\.", -1);
if (segments.length < 1) return null;
int startIndex = 0;
if ("root".equals(segments[0])) {
if (segments.length < 2) return null;
startIndex = 1;
scope = getRootScope(scope);
}
if (scope == null) return null;
Object currentValue = scope.getVariable(segments[startIndex]);
for (int i = startIndex + 1; i < segments.length; i++) {
if (currentValue == null) return null;
currentValue = getNestedVariable(currentValue, segments[i]);
}
return currentValue;
}
private void writeVariableToPath(VariableScope scope, String path, Object value) {
String[] segments = path.split("\\.", -1);
if (segments.length < 1) return;
VariableScope targetScope;
String[] varSegments;
if ("root".equals(segments[0])) {
if (segments.length < 2) return;
targetScope = getRootScope(scope);
varSegments = Arrays.copyOfRange(segments, 1, segments.length);
} else {
targetScope = scope;
varSegments = segments;
}
if (targetScope == null) return;
if (varSegments.length == 1) {
targetScope.setVariable(varSegments[0], value);
return;
}
// Nested path: read the top-level variable, navigate to the parent node,
// mutate it in-place, then write the top-level variable back.
String topVar = varSegments[0];
Object topValue = targetScope.getVariable(topVar);
Object parent = topValue;
for (int i = 1; i < varSegments.length - 1; i++) {
if (parent == null) {
LOGGER.warn("varUtils.writeVariableToPath: null at '{}' in path '{}'", varSegments[i - 1], path);
return;
}
parent = getNestedVariable(parent, varSegments[i]);
}
if (!setNestedValue(parent, varSegments[varSegments.length - 1], value, path)) return;
targetScope.setVariable(topVar, topValue);
}
@SuppressWarnings("unchecked")
private boolean setNestedValue(Object parent, String key, Object value, String path) {
if (parent instanceof Map map) {
map.put(key, value);
return true;
}
if (parent instanceof ObjectNode on) {
on.set(key, toJson(value));
return true;
}
LOGGER.warn("varUtils.writeVariableToPath: cannot set '{}' on {} in path '{}'",
key, parent == null ? "null" : parent.getClass().getName(), path);
return false;
}
// -------------------------------------------------------------------------
// Scope resolution
// -------------------------------------------------------------------------
private VariableScope getCurrentScope() {
VariableScope scope = SCOPE.get();
if (scope == null) {
LOGGER.error("{}.getCurrentScope called without an active Flowable scope", getClass().getName());
}
return scope;
}
private VariableScope getRootScope(VariableScope scope) {
if (scope instanceof DelegateExecution ex) return findBpmnRootScope(ex.getProcessInstanceId());
if (scope instanceof CaseInstance ci) return findCmmnRootScope(ci.getId());
return null;
}
private VariableScope findBpmnRootScope(String processInstanceId) {
if (processInstanceId == null || runtimeService == null) return null;
try {
Execution piExec = runtimeService.createExecutionQuery()
.executionId(processInstanceId).singleResult();
if (piExec != null && piExec.getSuperExecutionId() != null) {
Execution superExec = runtimeService.createExecutionQuery()
.executionId(piExec.getSuperExecutionId()).singleResult();
if (superExec != null)
return findBpmnRootScope(superExec.getProcessInstanceId());
}
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
if (pi != null && pi.getCallbackType() != null && pi.getCallbackId() != null
&& cmmnRuntimeService != null) {
PlanItemInstance planItem = cmmnRuntimeService.createPlanItemInstanceQuery()
.planItemInstanceId(pi.getCallbackId()).singleResult();
if (planItem != null)
return findCmmnRootScope(planItem.getCaseInstanceId());
}
return (ExecutionEntity) piExec; // root: ExecutionEntity implements VariableScope
} catch (Exception e) {
LOGGER.debug("{}.findBpmnRootScope: BPMN root scope climb failed: {}", getClass().getName(), e.getMessage());
return null;
}
}
private VariableScope findCmmnRootScope(String caseInstanceId) {
if (caseInstanceId == null || cmmnRuntimeService == null) return null;
try {
CaseInstance ci = cmmnRuntimeService.createCaseInstanceQuery()
.caseInstanceId(caseInstanceId).singleResult();
if (ci == null) return null;
if (ci.getParentId() != null)
return findCmmnRootScope(ci.getParentId());
if (ci.getCallbackType() != null && ci.getCallbackId() != null
&& runtimeService != null) {
Execution callbackExec = runtimeService.createExecutionQuery()
.executionId(ci.getCallbackId()).singleResult();
if (callbackExec != null)
return findBpmnRootScope(callbackExec.getProcessInstanceId());
}
return (CaseInstanceEntity) ci; // root: CaseInstanceEntity implements VariableScope
} catch (Exception e) {
LOGGER.debug("{}.findCmmnRootScope: CMMN root scope climb failed: {}", getClass().getName(), e.getMessage());
return null;
}
}
private static Object getNestedVariable(Object obj, String segment) {
if (obj instanceof List<?> list) {
try {
int i = Integer.parseInt(segment);
return i >= 0 && i < list.size() ? list.get(i) : null;
} catch (NumberFormatException ignored) {}
}
if (obj instanceof Object[] arr) {
try {
int i = Integer.parseInt(segment);
return i >= 0 && i < arr.length ? arr[i] : null;
} catch (NumberFormatException ignored) {}
}
if (obj instanceof Map<?, ?> map) return map.get(segment);
if (obj instanceof JsonNode jn) {
JsonNode node = jn.get(segment);
if (node == null || node.isNull()) return null;
if (node.isTextual()) return node.asText();
if (node.isBoolean()) return node.asBoolean();
if (node.isLong()) return node.asLong();
if (node.isInt()) return node.asInt();
if (node.isDouble()) return node.asDouble();
return node;
}
String cap = Character.toUpperCase(segment.charAt(0)) + segment.substring(1);
try {
return obj.getClass().getMethod("get" + cap).invoke(obj);
} catch (Exception ignored) {}
try {
return obj.getClass().getMethod("is" + cap).invoke(obj);
} catch (Exception ignored) {}
try {
java.lang.reflect.Field f = findField(obj.getClass(), segment);
if (f != null) { f.setAccessible(true); return f.get(obj); }
} catch (Exception ignored) {}
LOGGER.warn("varUtils: cannot resolve '{}' on {}", segment, obj.getClass().getName());
return null;
}
private static java.lang.reflect.Field findField(Class<?> c, String name) {
while (c != null && c != Object.class) {
try { return c.getDeclaredField(name); }
catch (NoSuchFieldException e) { c = c.getSuperclass(); }
}
return null;
}
// -------------------------------------------------------------------------
// Snapshot persistence
// -------------------------------------------------------------------------
private Map<String, JsonNode> loadSnapshot(VariableScope scope, String path) {
Object raw = readVariableFromPath(scope, path);
if (raw == null) return new HashMap<>();
try {
String json = raw instanceof String s ? s : MAPPER.writeValueAsString(raw);
Map<String, Object> flat = MAPPER.readValue(json,
MAPPER.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class));
Map<String, JsonNode> result = new HashMap<>();
flat.forEach((k, v) -> result.put(k, toJson(v)));
return result;
} catch (Exception e) {
LOGGER.warn("varUtils: could not load snapshot at '{}': {}", path, e.getMessage());
return new HashMap<>();
}
}
private void saveSnapshot(VariableScope scope, String path, Map<String, JsonNode> snapshot) {
try {
writeVariableToPath(scope, path, MAPPER.writeValueAsString(snapshot));
} catch (Exception e) {
LOGGER.error("varUtils: could not save snapshot at '{}'", path, e);
}
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
private static JsonNode toJson(Object value) {
if (value == null) return MAPPER.nullNode();
if (value instanceof JsonNode jn) return jn;
return MAPPER.valueToTree(value);
}
}
@@ -0,0 +1,38 @@
server.port=8105
# Enable all endpoints over HTTP
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=when_authorized
flowable.frontend.title=flowable-work
#spring.datasource.url=jdbc:h2:~/flowable-work-db/db;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1
#spring.datasource.username=flowable
#spring.datasource.password=flowable
#Comment out and configure database
spring.datasource.url=jdbc:postgresql://localhost:5435/flowable
spring.datasource.username=flowable
spring.datasource.password=flowable
# Local Elasticsearch config
spring.elasticsearch.uris=http://localhost:9203
# spring.data.elasticsearch.repositories.enabled=true
# spring.data.elasticsearch.cluster-nodes=localhost:9300
# spring.data.elasticsearch.cluster-name=elasticsearch
flowable.indexing.index-name-prefix=flowable-work-
#Disable ElasticSearch Indexing
#flowable.indexing.enabled=false
# Enable Flowable Inspect
flowable.inspect.enabled=true
# Forms will update even if an old process/case/task definition will be used
flowable.platform.enable-latest-form-definition-lookup=true
# Server URL for REST calls
baseUrl=http://localhost:8105
@@ -0,0 +1,48 @@
[
{
"key": "all",
"labelKey": "contacts.filter.all",
"defaultLabel": "All",
"parameters": {}
},
{
"key": "internal",
"labelKey": "contacts.filter.internal",
"defaultLabel": "Internal",
"parameters": {
"must": {
"type": "default"
}
}
},
{
"key": "external",
"labelKey": "contacts.filter.external",
"defaultLabel": "External",
"parameters": {
"must" : {
"type": "external"
}
}
},
{
"key": "active",
"labelKey": "contacts.filter.active",
"defaultLabel": "Active",
"parameters": {
"must" : {
"state": "ACTIVE"
}
}
},
{
"key": "inactive",
"labelKey": "contacts.filter.inactive",
"defaultLabel": "Inactive",
"parameters": {
"must" : {
"state": "INACTIVE"
}
}
}
]
@@ -0,0 +1,21 @@
{
"name": "Flowable",
"groups": [
{ "key": "flowableUser", "name": "Flowable User" },
{ "key": "flowableAdministrator", "name": "Flowable Administrator" }
],
"users": [
{
"firstName": "Flowable",
"lastName": "Admin",
"login": "admin",
"email": "test@demo.flowable.io",
"language": "en",
"theme": "flowable",
"userDefinitionKey": "user-admin"
}
]
}
@@ -0,0 +1,67 @@
[
{
"key": "user-default",
"name": "Default user",
"description": "Creates a new, non-specific user where the member groups can be freely chosen.",
"initialState": "ACTIVE",
"initialSubState": "ACTIVE",
"forms": {
"init": "F01_userInitFormDefault",
"view": "F02_userViewFormDefault",
"edit": "F03_userEditFormDefault"
},
"memberGroups": [
"flowableUser"
],
"lookupGroups":[
"flowableUser"
],
"actionPermissions": {
"create": [ "flowableAdministrator" ],
"edit": [ "flowableAdministrator" ],
"deactivate": [ "flowableAdministrator" ],
"activate": [ "flowableAdministrator" ]
},
"contactFilters": [ "all" ],
"allowedFeatures": [ "contacts", "bubbles", "markdownInput", "replyToMessage", "forwardMessage", "reactToMessage", "fileUpload", "work", "createWork",
"personalAccessTokens",
"tasks", "documents", "changeOwnPassword", "changeOwnTheme", "editOwnAvatar"]
},
{
"key": "user-admin",
"name": "Administration User",
"description": "Creates a new, administration user.",
"initialUserSubType": "admin",
"initialState": "ACTIVE",
"initialSubState": "ACTIVE",
"forms": {
"init": "F01_userInitFormDefault",
"view": "F02_userViewFormDefault",
"edit": "F03_userEditFormDefault"
},
"memberGroups": [
"flowableUser",
"flowableAdministrator"
],
"lookupGroups":[
"flowableUser"
],
"actionPermissions": {
"create": [ "flowableAdministrator"],
"edit": [ "flowableAdministrator" ],
"deactivate": [ "flowableAdministrator" ],
"activate": [ "flowableAdministrator" ]
},
"initialVariables": {
"adminUser": true,
"description": "Admin"
},
"contactFilters": [ "all", "internal", "external", "inactive"],
"allowedFeatures": [ "contacts", "createUser", "reports",
"actuators", "user-mgmt", "search-api", "workobject-api", "templateManagement",
"markdownInput", "replyToMessage", "forwardMessage", "reactToMessage", "fileUpload", "work", "createWork", "tasks", "documents",
"impersonateUser",
"personalAccessTokens",
"changeOwnPassword", "changeOwnTheme", "editOwnAvatar", "themeManagement"]
}
]
@@ -0,0 +1,13 @@
package com.customer.work;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WorkApplicationTests {
@Test
void contextLoads() {
}
}
@@ -0,0 +1,7 @@
package com.customer.work.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestConfiguration {
}
@@ -0,0 +1,49 @@
package com.customer.work.model;
import java.util.List;
public class EmailDto {
private String subject;
private List<String> receiverList;
private String content;
private Object contentRaw;
public EmailDto(String subject, List<String> receiverList, String content, Object contentRaw) {
this.subject = subject;
this.receiverList = receiverList;
this.content = content;
this.contentRaw = contentRaw;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public List<String> getReceiverList() {
return receiverList;
}
public void setReceiverList(List<String> receiverList) {
this.receiverList = receiverList;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Object getContentRaw() {
return contentRaw;
}
public void setContentRaw(Object contentRaw) {
this.contentRaw = contentRaw;
}
}
@@ -0,0 +1,335 @@
package com.customer.work.model;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.stereotype.Component;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class FlowableExcelMapper {
protected static ObjectMapper objectMapper = new ObjectMapper();
protected static JavaTimeModule javaTimeModule = new JavaTimeModule();
protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
public FlowableExcelMapper() {
// Enable ObjectMapper for handling Instant as string
objectMapper.registerModule(javaTimeModule);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
// excelPath defines the location of the Excel file
// FlowableExcelParser is used to create an ArrayList from the Excel file
// The 1st row of every sheet contains the path of the variables to be created in the JsonNode
// Delimiter is "." for an ObjectNode and "*" for an ArrayNode
// The 2nd row contains tht type of the variable. Supported types are:
// - String
// - Boolean
// - Integer
// - Double
// - Date
// All sheets are added to an ArrayNode
// All rows are added to an ArrayNode in the sheets ArrayNode
// All cell values are added to an ObjectNode in the rows ArrayNode
public ArrayNode excelBookResourceToJsonNode(String resourcePath) {
ArrayList<ArrayList<ArrayList<String>>> book = flowableExcelParser.parseExcelBookFromResource(resourcePath);
return bookToJsonNode(book);
}
protected ArrayNode bookToJsonNode(ArrayList<ArrayList<ArrayList<String>>> book) {
ArrayNode bookNode = objectMapper.createArrayNode();
for (ArrayList<ArrayList<String>> sheet : book) {
bookNode.add(sheetToJsonNode(sheet));
}
return bookNode;
}
protected ArrayNode sheetToJsonNode(ArrayList<ArrayList<String>> rows) {
ArrayList<String> paths = new ArrayList<>();
ArrayList<String> types = new ArrayList<>();
ArrayNode arrayNode = objectMapper.createArrayNode();
for (int rowNum = 0; rowNum < rows.size(); rowNum++) {
if (rowNum == 0) {
// The 1st row contains the paths
paths.addAll(rows.get(rowNum));
} else if (rowNum == 1) {
// The 2nd row contains the types of the variables
types.addAll(rows.get(rowNum));
} else {
// All other rows contain the values of the variables
arrayNode.add(rowToJsonNode(types, paths, rows.get(rowNum), null));
}
}
return arrayNode;
}
protected JsonNode rowToJsonNode(ArrayList<String> types, ArrayList<String> paths, ArrayList<String> row, JsonNode jsonNode) {
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
String cellValue = row.get(cellNum);
// Empty cells are not added to node
if (cellValue != null && !cellValue.isEmpty()) {
String type = types.get(cellNum);
Object content;
switch (cellValue) {
case "__NULL": content = null; break;
case "__N_A": content = "__N_A"; break;
default:
switch (type) {
case "String":
switch (cellValue) {
case "__EMPTY": content = ""; break;
case "__BLANK": content = " "; break;
default: content = cellValue;
}
break;
case "Boolean": content = Boolean.parseBoolean(cellValue); break;
case "Integer": content = Integer.parseInt(cellValue); break;
case "Double": content = Double.parseDouble(cellValue); break;
case "Long": content = Long.parseLong(cellValue); break;
case "Date":
// Excel considers dates as local dates, but we want them as UTC
Instant instant;
try {
LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
} catch (DateTimeParseException e) {
LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
}
content = instant.plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds());
break;
default:
throw new RuntimeException("Variable type not supported: " + type);
}
}
jsonNode = addNode(jsonNode, paths.get(cellNum), content);
}
}
return jsonNode;
}
protected JsonNode addNode(JsonNode parent, String path, Object value) {
if (parent == null) {
parent = objectMapper.createObjectNode();
}
int delimiterPosition = indexOfFirstDelimiter(path, ".*");
if (delimiterPosition <= 0) {
if (parent instanceof ObjectNode) {
// Key of a value in an ObjectNode
((ObjectNode) parent).set(path, objectMapper.valueToTree(value));
} else {
// Index of a value in an ArrayNode
int index = Integer.parseInt(path);
while (parent.size() <= index) {
// Create empty entries to parent ArrayNode
((ArrayNode) parent).add(objectMapper.createObjectNode());
}
((ArrayNode) parent).set(index, objectMapper.valueToTree(value));
}
return parent;
}
String key = path.substring(0, delimiterPosition);
String newPath = path.substring(delimiterPosition + 1);
String delimiter = path.substring(delimiterPosition, delimiterPosition + 1);
JsonNode child = null;
if (delimiter.equals(".")) {
child = objectMapper.createObjectNode();
} else if (delimiter.equals("*")) {
child = objectMapper.createArrayNode();
}
JsonNode node;
if (parent instanceof ObjectNode) {
// Key of a JsonNode in an ObjectNode
node = parent.get(key);
if (node != null) {
// Take the existing ObjectNode as child
child = node;
}
((ObjectNode) parent).set(key, addNode(child, newPath, value));
} else {
// Index of a JsonNode in an ArrayNode
int index = Integer.parseInt(key);
node = parent.get(index);
if (node != null) {
// Take the existing ArrayNode as child
child = node;
}
while (parent.size() <= index) {
// Create empty entries to parent ArrayNode
((ArrayNode) parent).add(objectMapper.createObjectNode());
}
((ArrayNode) parent).set(index, addNode(child, newPath, value));
}
return parent;
}
protected int indexOfFirstDelimiter(String str, String delimiters) {
for (int i = 0; i < str.length(); i++) {
for (char c : delimiters.toCharArray()) {
if (str.charAt(i) == c) {
return i;
}
}
}
return -1;
}
public ArrayList<ArrayList<Object>> excelBookResourceToObj(String resourcePath) {
ArrayList<ArrayList<ArrayList<String>>> book = flowableExcelParser.parseExcelBookFromResource(resourcePath);
return bookToObj(book);
}
protected ArrayList<ArrayList<Object>> bookToObj(ArrayList<ArrayList<ArrayList<String>>> book) {
ArrayList<ArrayList<Object>> bookNode = new ArrayList<>();
for (ArrayList<ArrayList<String>> sheet : book) {
bookNode.add(sheetToObj(sheet));
}
return bookNode;
}
protected ArrayList<Object> sheetToObj(ArrayList<ArrayList<String>> rows) {
ArrayList<String> paths = new ArrayList<>();
ArrayList<String> types = new ArrayList<>();
ArrayList<Object> arrayNode = new ArrayList<>();
for (int rowNum = 0; rowNum < rows.size(); rowNum++) {
if (rowNum == 0) {
// The 1st row contains the paths
paths.addAll(rows.get(rowNum));
} else if (rowNum == 1) {
// The 2nd row contains the types of the variables
types.addAll(rows.get(rowNum));
} else {
// All other rows contain the values of the variables
arrayNode.add(rowToObj(types, paths, rows.get(rowNum), null));
}
}
return arrayNode;
}
protected Object rowToObj(ArrayList<String> types, ArrayList<String> paths, ArrayList<String> row, Object node) {
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
String cellValue = row.get(cellNum);
// Empty cells are not added to node
if (cellValue != null && !cellValue.isEmpty()) {
String type = types.get(cellNum);
Object content;
switch (cellValue) {
case "__NULL": content = null; break;
case "__N_A": content = "__N_A"; break;
default:
switch (type) {
case "String":
switch (cellValue) {
case "__EMPTY": content = ""; break;
case "__BLANK": content = " "; break;
default: content = cellValue;
}
break;
case "Boolean": content = Boolean.parseBoolean(cellValue); break;
case "Integer": content = Integer.parseInt(cellValue); break;
case "Double": content = Double.parseDouble(cellValue); break;
case "Long": content = Long.parseLong(cellValue); break;
case "Date":
// Excel considers dates as local dates, but we want them as UTC
Instant instant;
try {
LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
} catch (DateTimeParseException e) {
LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
}
content = instant.plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds());
break;
default:
throw new RuntimeException("Variable type not supported: " + type);
}
}
node = addObj(node, paths.get(cellNum), content);
}
}
return node;
}
@SuppressWarnings("unchecked")
protected Object addObj(Object parent, String path, Object value) {
if (parent == null) {
parent = new LinkedHashMap<String, Object>();
}
int delimiterPosition = indexOfFirstDelimiter(path, ".*");
if (delimiterPosition <= 0) {
if (parent instanceof LinkedHashMap) {
// Key of a value in an ObjectNode
((Map<String, Object>) parent).put(path, value);
} else if (parent instanceof ArrayList) {
// Index of a value in an ArrayNode
int index = Integer.parseInt(path);
while (((ArrayList<Object>) parent).size() <= index) {
// Create empty entries to parent ArrayNode
((ArrayList<Object>) parent).add(new LinkedHashMap<String, Object> ());
}
((ArrayList<Object>) parent).set(index, value);
} else {
throw new RuntimeException("Parent type not supported: " + parent.getClass().getName());
}
return parent;
}
String key = path.substring(0, delimiterPosition);
String newPath = path.substring(delimiterPosition + 1);
String delimiter = path.substring(delimiterPosition, delimiterPosition + 1);
Object child = null;
if (delimiter.equals(".")) {
child = new LinkedHashMap<String, Object>();
} else if (delimiter.equals("*")) {
child = new ArrayList<>();
}
Object node;
if (parent instanceof LinkedHashMap) {
// Key of a JsonNode in an ObjectNode
node = ((Map<String, Object>) parent).get(key);
if (node != null) {
// Take the existing ObjectNode as child
child = node;
}
((Map<String, Object>) parent).put(key, addObj(child, newPath, value));
} else if (parent instanceof ArrayList) {
// Index of a JsonNode in an ArrayNode
int index = Integer.parseInt(key);
if (((ArrayList<Object>) parent).size() > index) {
node = ((ArrayList<Object>) parent).get(index);
} else {
node = null;
}
if (node != null) {
// Take the existing ArrayNode as child
child = node;
}
while (((ArrayList<Object>) parent).size() <= index) {
// Create empty entries to parent ArrayNode
((ArrayList<Object>) parent).add(new LinkedHashMap<String, Object> ());
}
((ArrayList<Object>) parent).set(index, addObj(child, newPath, value));
} else {
throw new RuntimeException("Parent type not supported: " + parent.getClass().getName());
}
return parent;
}
}
@@ -0,0 +1,138 @@
package com.customer.work.model;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@Component
public class FlowableExcelParser {
// excelPath defines the location of the Excel file
// Return all cells in all sheets with the following restrictions:
// - Max column count per sheet is defined in the first row
// - Max column count starts with the 1st cell and ends with the 1st empty cell
// - Parsing the sheet stops after the 1st empty row
// All sheets are added to an ArrayList
// All rows are added to an ArrayList in the sheets ArrayList
// All cell values are added to an ArrayList in the rows ArrayList
// Formulas in cells are evaluated
// All values are returned as String
public ArrayList<ArrayList<ArrayList<String>>> parseExcelBookFromResource(String excelResourcePath) {
try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(excelResourcePath))) {
Workbook workBook = new XSSFWorkbook(fileInputStream);
return parseBook(workBook);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String getResourcePath(String resourcePath) {
ClassLoader classLoader = FlowableExcelParser.class.getClassLoader();
URL resourceUrl = classLoader.getResource(resourcePath);
if (resourceUrl == null) {
throw new RuntimeException("Resource not found: " + resourcePath);
}
return resourceUrl.getPath();
}
private ArrayList<ArrayList<ArrayList<String>>> parseBook(Workbook workBook ) {
ArrayList<ArrayList<ArrayList<String>>> book = new ArrayList<>();
FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
for (Sheet workSheet : workBook) {
book.add(parseSheet(workSheet, formulaEvaluator));
}
return book;
}
private ArrayList<ArrayList<String>> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) {
ArrayList<ArrayList<String>> sheet = new ArrayList<>();
// Count columns of first row
int maxCols = getMaxCols(workSheet.getRow(0));
DataFormatter dataFormatter = new DataFormatter();
int firstRow = workSheet.getFirstRowNum();
int lastRow = workSheet.getLastRowNum();
// For (Row workRow : workSheet) sometimes ignores empty rows, then the first empty row as an exit criterion wouldn't work
for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
ArrayList<String> row = parseRow(workSheet.getRow(rowIndex), maxCols, formulaEvaluator, dataFormatter);
if (row == null) {
// Take all rows until the first empty row
break;
} else {
sheet.add(row);
}
}
return sheet;
}
private ArrayList<String> parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator, DataFormatter dataFormatter) {
if (workRow == null) {
// Row is empty
return null;
}
ArrayList<String> row = new ArrayList<>();
boolean allCellsNull = true;
for (int colIndex = 0; colIndex < maxCols; colIndex++) {
Cell workCell = workRow.getCell(colIndex);
formulaEvaluator.evaluate(workCell);
String content = dataFormatter.formatCellValue(workCell, formulaEvaluator);
if (content != null && !content.isEmpty()) {
allCellsNull = false;
}
row.add(content);
}
if (allCellsNull) {
// Row is empty
return null;
} else {
return row;
}
}
private int getMaxCols(Row workRow) {
int maxCol = workRow.getLastCellNum();
for (int colIndex = 0; colIndex < maxCol; colIndex++) {
Cell workCell = workRow.getCell(colIndex);
if (workCell == null || workCell.getCellType() == CellType.BLANK || workCell.getCellType() == CellType._NONE) {
// Count cells until the first empty cell
return colIndex;
}
}
return maxCol;
}
@Deprecated
public ArrayList<LinkedHashMap<String, Object>> parseExcelFromResource(String resourcePath) {
try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(resourcePath))) {
Workbook workBook = new XSSFWorkbook(fileInputStream);
FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
ArrayList<ArrayList<String>> sheet = parseSheet(workBook.getSheetAt(0), formulaEvaluator);
ArrayList<String> colHeaders = new ArrayList<>();
ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>();
for (int rowIndex = 0; rowIndex < sheet.size(); rowIndex++) {
ArrayList<String> row = sheet.get(rowIndex);
if (rowIndex == 0) {
// Header row
for (Object cell : row) {
colHeaders.add(String.valueOf(cell));
}
continue;
}
LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>();
for (int colIndex = 0; colIndex < row.size(); colIndex++) {
rowMap.put(colHeaders.get(colIndex), row.get(colIndex));
}
rowList.add(rowMap);
}
return rowList;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,110 @@
package com.customer.work.model;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class FlowableJsonParser {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final JavaTimeModule javaTimeModule = new JavaTimeModule();
public static final String BOOLEAN = "Boolean";
public static final String STRING = "String";
public static final String INTEGER = "Integer";
public static final String DOUBLE = "Double";
public static final String LONG = "Long";
public static final String INSTANT = "Instant";
public static final String JSON_OBJECT_NODE = "JsonObjectNode";
public static final String JSON_ARRAY_NODE = "JsonArrayNode";
public static final String MAP = "Map";
public static final String ARRAY_LIST = "ArrayList";
public FlowableJsonParser() {
objectMapper.registerModule(javaTimeModule);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
public Map<String, Object> parseMap(Object map) throws JsonProcessingException, ClassCastException {
return (Map<String, Object>) parseObject(map);
}
public Object parseObject(Object object) throws JsonProcessingException, ClassCastException {
if (object instanceof Map) {
Map<String, Object> resultMap = new LinkedHashMap<>(Map.of());
for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) object).entrySet()) {
String key = mapEntry.getKey();
resultMap.put(key, parseMapEntry(key, mapEntry.getValue()));
}
return resultMap;
}
if (object instanceof ArrayList) {
ArrayList<Object> resultList = new ArrayList<>();
for (Object arrayEntry : (ArrayList<Object>) object) {
resultList.add(parseArrayEntry(arrayEntry));
}
return resultList;
}
return null;
}
private Object parseMapEntry(String key, Object value) throws JsonProcessingException {
if (value instanceof Map) {
Map<String, Object> valueMap = (Map<String, Object>) value;
if (valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE") && valueMap.size() == 2) {
// if Map in Map, check if inner Map has explicit type (__TYPE and __VALUE)
return convertObjectExplicit(valueMap.get("__VALUE"), (String) valueMap.get("__TYPE"));
}
if (Arrays.asList("__IN", "__OUT").contains(key)) {
// convert __IN and __OUT maps as Map
return parseObject(value);
}
// convert Map to JsonObjectNode (default in models)
return convertObjectExplicit(value, JSON_OBJECT_NODE);
}
if (value instanceof ArrayList) {
// convert ArrayList to JsonArrayNode (default in models)
return convertObjectExplicit(value, JSON_ARRAY_NODE);
}
// take json supported type (Boolean, String, Integer, Double)
return value;
}
private Object parseArrayEntry(Object value) throws JsonProcessingException {
if (value instanceof Map) {
// convert Map to JsonObjectNode (default in models)
return convertObjectExplicit(value, JSON_OBJECT_NODE);
}
if (value instanceof ArrayList) {
// convert ArrayList to JsonArrayNode (default in models)
return convertObjectExplicit(value, JSON_ARRAY_NODE);
}
// take json supported type (Boolean, String, Integer, Double)
return value;
}
private Object convertObjectExplicit(Object value, String explicitType) throws JsonProcessingException {
switch (explicitType) {
case BOOLEAN: return Boolean.parseBoolean(value.toString());
case STRING: return value.toString();
case INTEGER: return Integer.parseInt(value.toString());
case DOUBLE: return Double.parseDouble(value.toString());
case LONG: return Long.parseLong(value.toString());
case INSTANT: return Instant.parse(value.toString());
case MAP: case ARRAY_LIST: return parseObject(value);
case JSON_OBJECT_NODE: case JSON_ARRAY_NODE: return objectMapper.convertValue(parseObject(value), JsonNode.class);
default: return value;
}
}
}
@@ -0,0 +1,52 @@
package com.customer.work.model;
import com.flowable.action.engine.test.ActionExtension;
import com.flowable.app.engine.test.FlowableAppExtension;
import com.flowable.dataobject.engine.test.DataObjectExtension;
import com.flowable.form.spring.impl.test.FlowableFormSpringExtension;
import com.flowable.idm.engine.test.PlatformIdmExtension;
import com.flowable.platform.tenant.test.TenantSetupExtension;
import com.flowable.policy.engine.test.PolicyExtension;
import com.flowable.serviceregistry.engine.test.ServiceRegistryExtension;
import com.flowable.template.engine.test.TemplateExtension;
import org.flowable.cmmn.spring.impl.test.FlowableCmmnSpringExtension;
import org.flowable.dmn.spring.impl.test.FlowableDmnSpringExtension;
import org.flowable.spring.impl.test.FlowableSpringExtension;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ExtendWith(SpringExtension.class)
@ExtendWith(ActionExtension.class)
@ExtendWith(DataObjectExtension.class)
//@ExtendWith(EngageExtension.class)
@ExtendWith(FlowableAppExtension.class)
@ExtendWith(FlowableSpringExtension.class)
@ExtendWith(FlowableCmmnSpringExtension.class)
@ExtendWith(FlowableFormSpringExtension.class)
@ExtendWith(FlowableDmnSpringExtension.class)
@ExtendWith(PlatformIdmExtension.class)
@ExtendWith(PolicyExtension.class)
@ExtendWith(ServiceRegistryExtension.class)
@ExtendWith(TemplateExtension.class)
@ExtendWith(TenantSetupExtension.class)
@ExtendWith(TestMailServerExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Transactional
@SpringBootTest
public @interface FlowableModelTest {
/*
@AliasFor(annotation = SpringBootTest.class, attribute = "webEnvironment")
SpringBootTest.WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;
*/
}
@@ -0,0 +1,656 @@
package com.customer.work.model;
import com.customer.work.service.JsonUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.flowable.audit.api.AuditService;
import com.flowable.audit.api.runtime.AuditInstance;
import com.flowable.core.spring.security.SecurityUtils;
import com.flowable.platform.service.task.CompleteFormRepresentation;
import com.flowable.platform.service.task.PlatformTaskService;
import jakarta.mail.Address;
import org.apache.commons.lang3.tuple.Pair;
import org.assertj.core.api.Assertions;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.engine.CmmnEngine;
import org.flowable.common.engine.api.identity.AuthenticationContext;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ManagementService;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.job.service.impl.persistence.entity.TimerJobEntity;
import org.flowable.spring.security.SpringSecurityAuthenticationContext;
import org.flowable.task.api.Task;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestClient;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@Component
public class FlowableModelTestUtils {
protected final ProcessEngine processEngine;
protected final CmmnEngine cmmnEngine;
protected final TaskService taskService;
protected final PlatformTaskService platformTaskService;
protected final ManagementService managementService;
protected final JsonUtils jsonUtils;
protected final FlowableExcelMapper flowableExcelMapper;
protected static final Logger logger = LoggerFactory.getLogger(FlowableModelTestUtils.class);
protected final TestMailServer testMailServer;
protected final AuditService auditService;
public static String TENANT_ID = null;
public static String ROOT_PROCESS_ID = "ROOT_PROCESS_ID";
public static String TEST_PROCESS_ID = "TEST_PROCESS_ID";
public FlowableModelTestUtils(ProcessEngine processEngine,
CmmnEngine cmmnEngine,
TaskService taskService,
PlatformTaskService platformTaskService,
ManagementService managementService,
JsonUtils jsonUtils,
FlowableExcelMapper flowableExcelMapper,
TestMailServer testMailServer,
AuditService auditService) {
this.processEngine = processEngine;
this.cmmnEngine = cmmnEngine;
this.taskService = taskService;
this.platformTaskService = platformTaskService;
this.managementService = managementService;
this.jsonUtils = jsonUtils;
this.flowableExcelMapper = flowableExcelMapper;
this.testMailServer = testMailServer;
this.auditService = auditService;
}
public void checkAndAndAssertAuditRecord(Map<String, Object> map, int auditNumber, String hint, String message, String category, String type) {
if (map != null) {
Object check = map.get("audit");
if (check instanceof Integer && (Integer) check > 0) {
assertAuditRecord(auditNumber, hint, message, category, type);
}
}
}
protected void assertAuditRecord(int auditNumber, String hint, String message, String category, String type) {
List<AuditInstance> auditTrail = getAuditTrail();
int auditTrailSize = auditTrail.size();
Assertions.assertThat(auditTrailSize).as(hint + ": invalid auditNumber " + auditTrailSize).isGreaterThanOrEqualTo(auditNumber);
if (auditTrailSize == 0) return;
AuditInstance auditInstance = auditTrail.get(auditNumber - 1);
Assertions.assertThat(auditInstance.getPayload().get("message")).as(hint).isEqualTo(message);
Assertions.assertThat(auditInstance.getPayload().get("category")).as(hint).isEqualTo(category);
Assertions.assertThat(auditInstance.getType()).as(hint).isEqualTo(type);
}
public void checkAndAssertEmail(Map<String, Object> map, int emailNumber, String hint, String subject, String receivers) {
if (map != null) {
Object check = map.get("email");
if (check instanceof Integer && (Integer) check > 0) {
assertEmail(emailNumber, hint, subject, receivers);
}
}
}
protected void assertEmail(int emailNumber, String hint, String subject, String receivers) {
List<EmailDto> emails = getMailList();
int emailListSize = emails.size();
Assertions.assertThat(emailListSize).as(hint + ": invalid email number " + emailListSize).isGreaterThanOrEqualTo(emailNumber);
if (emailNumber == 0) return;
EmailDto email = emails.get(emailNumber - 1);
String emailSubject = email.getSubject();
Assertions.assertThat(emailSubject).as(hint + ": invalid subject " + emailSubject).endsWith(subject);
List<Pair<String, Boolean>> receiverCheckList = new ArrayList<>();
for (String receiver : receivers.split("[,\\s]+")) {
receiverCheckList.add(Pair.of(receiver, false));
}
List<String> emailReceiverList = email.getReceiverList();
Assertions.assertThat(emailReceiverList.size()).as(hint + ": invalid number of receivers " + emailReceiverList).isEqualTo(receiverCheckList.size());
for (String emailReceiver : emailReceiverList) {
for (int i = 0; i < receiverCheckList.size(); i++) {
Pair<String, Boolean> checkReceiver = receiverCheckList.get(i);
if (checkReceiver.getLeft().equals(emailReceiver) && !checkReceiver.getRight()) {
receiverCheckList.set(i, Pair.of(emailReceiver, true));
break;
} else if (i == receiverCheckList.size() - 1) {
Assertions.fail(hint + ": invalid email receiver " + emailReceiverList);
}
}
}
}
public Map<String, Object> getHistoryCasePayload(String caseInstanceId) {
List<HistoricVariableInstance> historicVariableInstanceList = cmmnEngine.getCmmnHistoryService()
.createHistoricVariableInstanceQuery()
.caseInstanceId(caseInstanceId)
.list();
return convertHistVariableListToMap(historicVariableInstanceList);
}
public Map<String, Object> getRuntimeCasePayload(String caseId) {
return cmmnEngine.getCmmnRuntimeService().getVariables(caseId);
}
public CaseInstance startCaseInstance(String key, Map<String, Object> variables) {
return cmmnEngine.getCmmnRuntimeService()
.createCaseInstanceBuilder()
.caseDefinitionKey(key)
.tenantId(TENANT_ID) // Must not be "default"
.variables(variables)
.start();
}
public ProcessInstance startProcessInstance(String key, Map<String, Object> variables) {
return processEngine.getRuntimeService()
.createProcessInstanceBuilder()
.processDefinitionKey(key)
.tenantId(TENANT_ID) // Must not be "default"
.variables(variables)
.start();
}
public Task getOpenTask(String taskKey) {
Task task = taskService.createTaskQuery().taskDefinitionKey(taskKey).singleResult();
Assertions.assertThat(task).as("No open task with key {} found", taskKey).isNotNull();
return task;
}
public Task getOpenTaskByName(String taskName) {
Task task = taskService.createTaskQuery().taskName(taskName).singleResult();
Assertions.assertThat(task).as("No open task with name {} found", taskName).isNotNull();
return task;
}
public void claimOpenTask(String taskKey, String userId) {
setTestAuthenticatedUser(userId, TENANT_ID);
taskService.claim(getOpenTask(taskKey).getId(), userId);
}
public List<AuditInstance> getAuditTrail() {
return auditService.createAuditInstanceQuery()
//.tenantId(TENANT_ID) TODO: Must not be null, should work with default tenant
.list();
}
public List<EmailDto> getMailList() {
return Arrays.stream(testMailServer.getMessages()).map(mimeMessage -> {
try {
return new EmailDto(mimeMessage.getSubject(), Arrays.stream(mimeMessage.getAllRecipients())
.map(Address::toString).collect(Collectors.toList()), mimeMessage.getContent().toString(), mimeMessage.getContent());
} catch (Exception e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
}
public boolean isSubset(JsonNode root, JsonNode test) {
// If test is null, it is always a subset of root
if (test == null || test.isNull()) {
return true;
}
// If test is a value node, compare values
if (test.isValueNode()) {
if (test.isTextual() && "__N_A".equals(test.asText())) {
return false;
}
if (root == null) {
return false;
}
return root.isValueNode() && root.asText().equals(test.asText());
}
// If test is an array node, check if all elements of test exist in root
if(test.isArray()){
for (int i = 0; i < test.size(); i++) {
JsonNode testElement = test.get(i);
JsonNode rootElement = null;
if (root != null) {
rootElement = root.get(i);
}
if (testElement.isTextual() && "__N_A".equals(testElement.asText())) {
if (rootElement != null && !rootElement.isNull()) {
return false;
}
continue;
}
if (!isSubset(rootElement, testElement)) {
return false;
}
}
return true;
}
// If test is an object node, check if all fields in test exist in root
if (test.isObject()){
for (Iterator<String> iterator = test.fieldNames(); iterator.hasNext(); ) {
String fieldName = iterator.next();
JsonNode testValue = test.get(fieldName);
JsonNode rootValue = null;
if (root != null) {
rootValue = root.get(fieldName);
}
if (testValue.isTextual() && "__N_A".equals(testValue.asText())) {
if (root != null && root.has(fieldName)) {
return false;
}
continue;
}
if (!isSubset(rootValue, testValue)){
return false;
}
}
return true;
}
// If none of the above matches, return false
return false;
}
public Stream<Arguments> getJsonArgumentsFromExcel(String path) {
ArrayList<Arguments> argumentList = new ArrayList<>();
JsonNode book = flowableExcelMapper.excelBookResourceToJsonNode(path);
for (JsonNode sheet : book) {
for (JsonNode row : sheet) {
argumentList.add(Arguments.of(path, row));
}
}
return argumentList.stream();
}
public Stream<Arguments> getObjArgumentsFromExcel(String path) {
ArrayList<Arguments> argumentList = new ArrayList<>();
ArrayList<ArrayList<Object>> book = flowableExcelMapper.excelBookResourceToObj(path);
for (ArrayList<Object> sheet : book) {
for (Object row : sheet) {
argumentList.add(Arguments.of(path, row));
}
}
return argumentList.stream();
}
public ObjectNode testJsonExcelRow(String path, JsonNode row) {
ObjectNode vars = jsonUtils.getEmptyObjectNode();
JsonNode rootParam = row.get("root");
if (rootParam != null) {
Iterator<Map.Entry<String, JsonNode>> rootVars = rootParam.fields();
while (rootVars.hasNext()) {
Map.Entry<String, JsonNode> rootVar = rootVars.next();
String key = rootVar.getKey();
JsonNode value = rootVar.getValue();
vars.set(key, value);
}
}
JsonNode inParam = row.get("in");
if (inParam != null) {
vars.set("__IN", inParam);
}
JsonNode outParam = row.get("out");
if (outParam != null) {
vars.set("__OUT", outParam);
}
JsonNode idParam = row.get("id");
JsonNode test = row.get("test");
int timerCount = 0;
JsonNode timerNode = row.get("timer");
if (timerNode != null) timerCount = timerNode.asInt();
Integer auditRecordCount = null;
JsonNode auditNode = row.get("audit");
if (auditNode != null) auditRecordCount = auditNode.asInt();
Integer emailCount = null;
JsonNode emailNode = row.get("email");
if (emailNode != null) emailCount = emailNode.asInt();
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
ObjectNode processIds = createRootTestProcessInstance(idParam.asText(), jsonUtils.convertJsonNodeToMap(vars));
for (int i = 0; i < timerCount; i++) {
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
}
ObjectNode result = jsonUtils.getEmptyObjectNode();
Map<String, Object> rootProcessPayload = getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
JsonNode root = jsonUtils.convertMapToJsonNode(rootProcessPayload);
if (root != null) {
Assertions.assertThat(isSubset(root, test))
.withFailMessage(System.lineSeparator() + "test :" + test + System.lineSeparator() + "root :" + root).isTrue();
result.set("root", root);
}
if (auditRecordCount != null) {
// List<AuditInstance> auditTrail = getAuditTrail(processIds.get(ROOT_PROCESS_ID).asText());
List<AuditInstance> auditTrail = getAuditTrail();
int auditTrailSize = auditTrail.size();
Assertions.assertThat(auditTrailSize).as("Invalid audit trail size: {}", auditTrailSize).isEqualTo(auditRecordCount);
result.set("auditTrail", jsonUtils.convertListToJsonNode(Collections.singletonList(auditTrail)));
}
if (emailCount != null) {
List<EmailDto> emailList = getMailList();
int emailListSize = emailList.size();
Assertions.assertThat(emailListSize).as("Invalid number of emails: {}", emailListSize).isEqualTo(emailCount);
result.set("emails", jsonUtils.convertListToJsonNode(Collections.singletonList(emailList)));
}
return result;
}
public Map<String, Object> testObjExcelRow(String path, Map<String, Object> row) {
Map<String, Object> vars = new LinkedHashMap<>();
Map<String, Object> rootParam = (Map<String, Object>) row.get("root");
if (rootParam != null) {
for (Map.Entry<String, Object> entry : rootParam.entrySet()) {
vars.put(entry.getKey(), entry.getValue());
}
}
Map<String, Object> inParam = (Map<String, Object>) row.get("in");
if (inParam != null) {
vars.put("__IN", inParam);
}
Map<String, Object> outParam = (Map<String, Object>) row.get("out");
if (outParam != null) {
vars.put("__OUT", outParam);
}
String idParam = (String) row.get("id");
Map<String, Object> test =(Map<String, Object>) row.get("test");
int timerCount = 0;
Object timerNode = row.get("timer");
if (timerNode != null) timerCount = (Integer) timerNode;
Integer auditRecordCount = null;
Object auditNode = row.get("audit");
if (auditNode != null) auditRecordCount = (Integer) auditNode;
Integer emailCount = null;
Object emailNode = row.get("email");
if (emailNode != null) emailCount = (Integer) emailNode;
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
ObjectNode processIds = createRootTestProcessInstance(idParam, vars);
for (int i = 0; i < timerCount; i++) {
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
}
Map<String, Object> result = new LinkedHashMap<>();
Map<String, Object> rootProcessPayload = getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
JsonNode root = jsonUtils.convertMapToJsonNode(rootProcessPayload);
if (root != null) {
Assertions.assertThat(isSubset(root, jsonUtils.convertMapToJsonNode(test)))
.withFailMessage(System.lineSeparator() + "test :" + test + System.lineSeparator() + "root :" + root).isTrue();
result.put("root", rootProcessPayload);
}
if (auditRecordCount != null) {
// List<AuditInstance> auditTrail = getAuditTrail(processIds.get(ROOT_PROCESS_ID).asText());
List<AuditInstance> auditTrail = getAuditTrail();
int auditTrailSize = auditTrail.size();
Assertions.assertThat(auditTrailSize).as("Invalid audit trail size: {}", auditTrailSize).isEqualTo(auditRecordCount);
result.put("audit", auditRecordCount);
}
if (emailCount != null) {
List<EmailDto> emailList = getMailList();
int emailListSize = emailList.size();
Assertions.assertThat(emailListSize).as("Invalid number of emails: {}", emailListSize).isEqualTo(emailCount);
result.put("email", emailCount);
}
return result;
}
public void exportApp(String rootUrl, String appModelKey, String username, String password) {
RestClient restClient = RestClient.builder()
.baseUrl(rootUrl)
.defaultHeaders(h -> h.setBasicAuth(username, password))
.build();
ResponseEntity<JsonNode> responseEntity = restClient.post()
.uri("/app/authentication?j_username=" + username + "&j_password=" + password + "&spring_security_remember_me=true&submit=Login")
.retrieve()
.toEntity(JsonNode.class);
String designCookieRaw = responseEntity.getHeaders().get("Set-Cookie").stream().collect(Collectors.joining(";"));
String flowableDesignRememberMeTokenValue = Arrays.stream(designCookieRaw.split(";")).filter(part -> part.contains("FLOWABLE_DESIGN_REMEMBER_ME")).findAny().get();
String csrfToken = Arrays.stream(designCookieRaw.split(";")).filter(part -> part.contains("FLOWABLE_DESIGN_CSRF_TOKEN")).findAny().get();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Cookie", flowableDesignRememberMeTokenValue + ";" + csrfToken);
ArrayNode apps = (ArrayNode) restClient.get()
.uri("/app/models?filter=apps&modelType=3&sort=modifiedDesc")
.headers(h -> h.addAll(httpHeaders))
.retrieve()
.body(JsonNode.class)
.path("data");
String appModelId = StreamSupport.stream(apps.spliterator(), false).filter(app -> app.path("key").asText().equals(appModelKey)).map(app -> app.path("id").asText()).findAny().get();
File file = new File(Paths.get("src/test/resources/test-auto-deploy-apps", appModelKey + ".zip").toString());
restClient.get()
.uri("/app/app-definitions/" + appModelId + "/export-bar?includeChildReferences=true")
.header("Cookie", flowableDesignRememberMeTokenValue + ";" + csrfToken)
.exchange((req, resp) -> {
StreamUtils.copy(resp.getBody(), new FileOutputStream(file, false));
return file;
});
}
public ObjectNode emptyNode() {
return jsonUtils.getEmptyObjectNode();
}
public Map<String, Object> emptyMap() {
return new LinkedHashMap<>();
}
public ObjectNode loadObjectNodeFromResources(String path) {
if (path.isEmpty()) return jsonUtils.getEmptyObjectNode();
else return jsonUtils.loadObjectNodeFromFile(Paths.get("src","test", "resources", path).toString());
}
public Map<String, Object> loadMapFromResources(String path) {
return jsonUtils.convertJsonNodeToMap(loadObjectNodeFromResources(path));
}
public void executeTimer(String processId) {
TimerJobEntity timerJobEntity = (TimerJobEntity) managementService.createTimerJobQuery().processInstanceId(processId).singleResult();
Assertions.assertThat(timerJobEntity).withFailMessage("No active timer").isNotNull();
managementService.moveTimerToExecutableJob(timerJobEntity.getId());
managementService.executeJob(timerJobEntity.getId());
}
public void setTestAuthenticatedUser(String userId, String tenantId) {
if (userId == null) {
this.resetTestAuthenticatedUser();
} else {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if (StringUtils.isNotBlank(tenantId)) {
grantedAuthorities.add(SecurityUtils.createTenantAuthority(tenantId));
}
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(userId, "", grantedAuthorities));
AuthenticationContext authenticationContext = Authentication.getAuthenticationContext();
if (!(authenticationContext instanceof SpringSecurityAuthenticationContext)) {
Authentication.setAuthenticatedUserId(userId);
}
}
}
public void setTestAuthenticatedUser(String userId, String tenantId, String... groupKeys) {
if (userId == null) {
this.resetTestAuthenticatedUser();
} else {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if (StringUtils.isNotBlank(tenantId)) {
grantedAuthorities.add(SecurityUtils.createTenantAuthority(tenantId));
}
for (String groupKey : groupKeys) {
grantedAuthorities.add(SecurityUtils.createGroupAuthority(groupKey));
}
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(userId, "", grantedAuthorities));
AuthenticationContext authenticationContext = Authentication.getAuthenticationContext();
if (!(authenticationContext instanceof SpringSecurityAuthenticationContext)) {
Authentication.setAuthenticatedUserId(userId);
}
}
}
public void resetTestAuthenticatedUser() {
SecurityContextHolder.getContext().setAuthentication(null);
AuthenticationContext authenticationContext = Authentication.getAuthenticationContext();
if (!(authenticationContext instanceof SpringSecurityAuthenticationContext)) {
Authentication.setAuthenticatedUserId(null);
}
}
public void completeTaskWithFlatVars(String taskId, Map<String, Object> variables, String outcome) {
Map<String, Object> taskVariables = platformTaskService.getTaskVariables(taskId);
Map<String, Object> taskVariablesFlat = jsonUtils.flatten(taskVariables);
taskVariablesFlat.putAll(variables);
Map<String, Object> completionVars = jsonUtils.unflatten(taskVariablesFlat);
completeTask(taskId, completionVars, outcome);
}
public void completeTask(String taskId, Map<String, Object> completionVariables, String outcome) {
CompleteFormRepresentation form = new CompleteFormRepresentation();
for (String key : completionVariables.keySet()) {
form.setValues(key, completionVariables.get(key));
}
form.setOutcome(outcome);
platformTaskService.completeTaskForm(taskId, form);
}
public void completeOpenTask(String taskKey, ObjectNode vars, String outcome) {
Task task = getOpenTask(taskKey);
Map<String, Object> flatVars = jsonUtils.convertJsonNodeToMap(vars);
completeTaskWithFlatVars(task.getId(), flatVars, outcome);
}
public Map<String, Object> getHistProcessPayload(String processInstanceId) {
List<HistoricVariableInstance> historicVariableInstanceList = processEngine.getHistoryService()
.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
return convertHistVariableListToMap(historicVariableInstanceList);
}
private HashMap<String, Object> convertHistVariableListToMap(List<HistoricVariableInstance> historicVariableInstanceList) {
HashMap<String, Object> variableMap = new HashMap<>();
for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) {
variableMap.put(historicVariableInstance.getVariableName(), historicVariableInstance.getValue());
}
return variableMap;
}
public ObjectNode createRootTestProcessInstance(String testKey, Map<String, Object> variables) {
// Create root process with test process as call activity
String wrapperProcessKey = testKey + "_T";
BpmnModel bpmnModel = createWrapperTestProcessModel(testKey, (ObjectNode) jsonUtils.convertMapToJsonNode(variables));
processEngine.getProcessEngineConfiguration()
.getRepositoryService()
.createDeployment()
.addBpmnModel(wrapperProcessKey + ".bpmn20.xml", bpmnModel)
.tenantId(TENANT_ID) // Must not be "default"
.deploy();
// Start root process
ProcessInstance rootProcessInstance = startProcessInstance(wrapperProcessKey, variables);
// Store the process IDs for using them in assertions
ProcessInstance runtimeTestProcessInstance = processEngine.getRuntimeService()
.createProcessInstanceQuery()
.superProcessInstanceId(rootProcessInstance.getId())
.singleResult();
String testProcessId;
if (runtimeTestProcessInstance != null) {
testProcessId = runtimeTestProcessInstance.getId();
} else {
HistoricProcessInstance historicTestProcessInstance = processEngine.getHistoryService()
.createHistoricProcessInstanceQuery()
.superProcessInstanceId(rootProcessInstance.getId())
.singleResult();
// if history level is none, testProcessId is not available
testProcessId = historicTestProcessInstance != null ? historicTestProcessInstance.getId() : null;
}
ObjectNode rootNode = jsonUtils.getEmptyObjectNode();
rootNode.put(ROOT_PROCESS_ID, rootProcessInstance.getProcessInstanceId());
rootNode.put(TEST_PROCESS_ID, testProcessId);
return rootNode;
}
protected BpmnModel createWrapperTestProcessModel(String testKey, ObjectNode variables) {
BpmnModel bpmnModel = new BpmnModel();
Process process = new Process();
process.setId(testKey + "_T");
process.setName(testKey + "_T");
StartEvent startEvent = new StartEvent();
startEvent.setId(testKey + "_Start");
startEvent.setName(testKey + "_Start");
process.addFlowElement(startEvent);
CallActivity callActivity = new CallActivity();
callActivity.setId(testKey);
callActivity.setCalledElement(testKey);
JsonNode inNode = variables.get("__IN");
if (inNode instanceof ObjectNode) {
Map<String, Object> inMap = jsonUtils.convertObjectNodeToMap((ObjectNode) inNode);
ArrayList<IOParameter> inParameters = new ArrayList<>();
for (Map.Entry<String, Object> entry : inMap.entrySet()) {
IOParameter ioParameter = new IOParameter();
ioParameter.setSourceExpression("${__IN." + entry.getKey() + "}");
ioParameter.setTarget(entry.getKey());
inParameters.add(ioParameter);
}
callActivity.setInParameters(inParameters);
}
JsonNode outNode = variables.get("__OUT");
if (outNode instanceof ObjectNode) {
Map<String, String> outMap = (Map) jsonUtils.convertJsonNodeToMap(variables).get("__OUT");
ArrayList<IOParameter> outParameters = new ArrayList<>();
for (Map.Entry<String, String> entry : outMap.entrySet()) {
IOParameter ioParameter = new IOParameter();
ioParameter.setSource(entry.getKey());
ioParameter.setTarget(entry.getValue());
outParameters.add(ioParameter);
}
callActivity.setOutParameters(outParameters);
}
process.addFlowElement(callActivity);
EndEvent endEvent = new EndEvent();
endEvent.setId(testKey + "_End");
endEvent.setName(testKey + "_End");
process.addFlowElement(endEvent);
SequenceFlow start = new SequenceFlow(testKey + "_Start", testKey);
start.setId("Start_Sequence_Flow");
process.addFlowElement(start);
SequenceFlow end = new SequenceFlow(testKey, testKey + "_End");
end.setId("End_Sequence_Flow");
process.addFlowElement(end);
bpmnModel.addProcess(process);
return bpmnModel;
}
}
@@ -0,0 +1,33 @@
package com.customer.work.model;
import com.flowable.spring.boot.properties.FlowableMailProperties;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetup;
import jakarta.mail.internet.MimeMessage;
import org.springframework.stereotype.Component;
import static com.icegreen.greenmail.util.ServerSetup.PROTOCOL_SMTP;
@Component
public class TestMailServer {
private GreenMail greenMail;
private final FlowableMailProperties flowableMailProperties;
public TestMailServer(FlowableMailProperties flowableMailProperties) {
this.flowableMailProperties = flowableMailProperties;
}
public void setup() {
greenMail = new GreenMail(new ServerSetup(flowableMailProperties.getPort(), flowableMailProperties.getHost(), PROTOCOL_SMTP));
greenMail.start();
}
public void stop() {
greenMail.stop();
}
public MimeMessage[] getMessages() {
return greenMail.getReceivedMessages();
}
}
@@ -0,0 +1,22 @@
package com.customer.work.model;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
public class TestMailServerExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
getTestMailServer(context).setup();
}
@Override
public void afterEach(ExtensionContext context) {
getTestMailServer(context).stop();
}
protected TestMailServer getTestMailServer(ExtensionContext context) {
return SpringExtension.getApplicationContext(context).getBean(TestMailServer.class);
}
}
@@ -0,0 +1,122 @@
package com.customer.work.model.test;
import com.customer.work.model.EmailDto;
import com.customer.work.model.FlowableModelTest;
import com.customer.work.model.FlowableModelTestUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.assertj.core.api.Assertions;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@FlowableModelTest
public class ModelTest {
@Autowired
protected FlowableModelTestUtils flowableModelTest;
@Test
@Disabled
public void exportApp() {
flowableModelTest.exportApp("http://localhost:8093", "TST_APP", "admin", "test");
}
@Test
public void c001Test() {
flowableModelTest.setTestAuthenticatedUser("admin", null);
CaseInstance caseInstance = flowableModelTest.startCaseInstance("TST_C001", Map.of());
String caseInstanceId = caseInstance.getId();
flowableModelTest.completeOpenTask("TST_P001_T001", flowableModelTest.loadObjectNodeFromResources("model/test/C001/T001.json"), "COMPLETE");
Assertions.assertThat(flowableModelTest.getRuntimeCasePayload(caseInstanceId).get("testText")).isEqualTo("my test text");
flowableModelTest.completeOpenTask("TST_C001_T002", flowableModelTest.emptyNode(), "COMPLETE");
Assertions.assertThat(flowableModelTest.getHistoryCasePayload(caseInstanceId).get("testText")).isEqualTo("my test text");
}
@Test
public void p005HappyPathTest() {
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P005", flowableModelTest.emptyMap());
flowableModelTest.claimOpenTask("TST_P005_T001", "admin");
flowableModelTest.completeOpenTask("TST_P005_T001", flowableModelTest.loadObjectNodeFromResources("model/test/P005/T001.json"), null);
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
Assertions.assertThat(rootProcessPayload.get("dataEntry")).isEqualTo("my root text");
}
@NotNull
private Stream<Arguments> p001TestData() {
return Stream.of(
Arguments.of("admin", "model/test/P001/initiator.json")
);
}
@ParameterizedTest
@MethodSource("p001TestData")
public void p001Test(String initiator, String path) {
flowableModelTest.setTestAuthenticatedUser(initiator, null);
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P001",
flowableModelTest.loadMapFromResources(path));
flowableModelTest.completeOpenTask("TST_P001_T001", flowableModelTest.emptyNode(), "complete");
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
Assertions.assertThat(rootProcessPayload.get("rootResult")).isEqualTo(null);
}
@NotNull
private Stream<Arguments> p002TestData() {
return Stream.of(
Arguments.of("model/test/P002/boolean1.json", true, false),
Arguments.of("model/test/P002/boolean2.json", false, true),
Arguments.of("model/test/P002/string1.json", "hello root", "hello"),
Arguments.of("model/test/P002/string2.json", "123", "456"),
Arguments.of("model/test/P002/string3.json", "123.456,", "456.789"),
Arguments.of("model/test/P002/int.json", 123, 456),
Arguments.of("model/test/P002/double.json", 123.456, 456.789),
Arguments.of("model/test/P002/date.json", "2025-11-12", "2025-11-11")
);
}
@ParameterizedTest
@MethodSource("p002TestData")
public void p002Test(String path, Object result, Object out) {
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P002",
flowableModelTest.loadMapFromResources(path));
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
List<EmailDto> mail = flowableModelTest.getMailList();
Assertions.assertThat(mail.size()).isEqualTo(0);
Assertions.assertThat(rootProcessPayload.get("result")).isEqualTo(result);
Assertions.assertThat(rootProcessPayload.get("out")).isEqualTo(out);
}
private Stream<Arguments> p002ExcelTestData() {
return flowableModelTest.getJsonArgumentsFromExcel("model/test/P002/p002Test.xlsx");
}
@ParameterizedTest
@MethodSource("p002ExcelTestData")
public void p002ExcelTest(String path, JsonNode argument) {
ObjectNode result = flowableModelTest.testJsonExcelRow(path, argument);
}
private Stream<Arguments> p002ExcelTestData2() {
return flowableModelTest.getObjArgumentsFromExcel("model/test/P002/p002Test.xlsx");
}
@ParameterizedTest
@MethodSource("p002ExcelTestData2")
public void p002ExcelTest2(String path, Map<String, Object> argument) {
Map<String, Object> result = flowableModelTest.testObjExcelRow(path, argument);
flowableModelTest.checkAndAssertEmail(result, 1,
"Email", "Test", "test@flowable.com");
flowableModelTest.checkAndAndAssertAuditRecord(result, 1,
"Audit record", "TST_P002 Audit trail entry", "system", null);
}
}
@@ -0,0 +1,24 @@
spring.datasource.url=jdbc:h2:mem:flowable-work-db;DB_CLOSE_DELAY=1000
spring.datasource.username=admin
spring.datasource.password=test
# To make our life easier in tests we are disabling the async executor and elasticsearch
flowable.async-executor-activate=false
flowable.async-history-executor-activate=false
flowable.indexing.enabled=false
management.health.elasticsearch.enabled=false
management.metrics.export.elastic.enabled=false
# Set debug level in tests
logging.level.com.flowable=INFO
logging.level.com.flowable.local.work.model.FlowableModelTestUtils=INFO
# Disable the timeout process in the tests
flowable.external-system.wechat.timeout.process-definition-key=
# Path to test-auto-deploy-apps
flowable.app.resource-location=classpath*:/test-auto-deploy-apps/
# Email
flowable.mail.server.host=localhost
flowable.mail.server.port=3025
@@ -0,0 +1,5 @@
{
"root": {
"testText": "my test text"
}
}
@@ -0,0 +1,5 @@
{
"__IN": {
"initiator": "admin"
}
}
@@ -0,0 +1,9 @@
{
"param": true,
"__IN": {
"param": false
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": false,
"__IN": {
"param": true
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "2025-11-12",
"__IN": {
"param": "2025-11-11"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": 123.456,
"__IN": {
"param": 456.789
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": 123,
"__IN": {
"param": 456
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "hello root",
"__IN": {
"param": "hello"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "123",
"__IN": {
"param": "456"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "123.456,",
"__IN": {
"param": "456.789"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,5 @@
{
"root": {
"dataEntry": "my root text"
}
}
@@ -0,0 +1,38 @@
server.port=8105
# Enable all endpoints over HTTP
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=when_authorized
flowable.frontend.title=flowable-work
#spring.datasource.url=jdbc:h2:~/flowable-work-db/db;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1
#spring.datasource.username=flowable
#spring.datasource.password=flowable
#Comment out and configure database
spring.datasource.url=jdbc:postgresql://localhost:5435/flowable
spring.datasource.username=flowable
spring.datasource.password=flowable
# Local Elasticsearch config
spring.elasticsearch.uris=http://localhost:9203
# spring.data.elasticsearch.repositories.enabled=true
# spring.data.elasticsearch.cluster-nodes=localhost:9300
# spring.data.elasticsearch.cluster-name=elasticsearch
flowable.indexing.index-name-prefix=flowable-work-
#Disable ElasticSearch Indexing
#flowable.indexing.enabled=false
# Enable Flowable Inspect
flowable.inspect.enabled=true
# Forms will update even if an old process/case/task definition will be used
flowable.platform.enable-latest-form-definition-lookup=true
# Server URL for REST calls
baseUrl=http://localhost:8105
@@ -0,0 +1,48 @@
[
{
"key": "all",
"labelKey": "contacts.filter.all",
"defaultLabel": "All",
"parameters": {}
},
{
"key": "internal",
"labelKey": "contacts.filter.internal",
"defaultLabel": "Internal",
"parameters": {
"must": {
"type": "default"
}
}
},
{
"key": "external",
"labelKey": "contacts.filter.external",
"defaultLabel": "External",
"parameters": {
"must" : {
"type": "external"
}
}
},
{
"key": "active",
"labelKey": "contacts.filter.active",
"defaultLabel": "Active",
"parameters": {
"must" : {
"state": "ACTIVE"
}
}
},
{
"key": "inactive",
"labelKey": "contacts.filter.inactive",
"defaultLabel": "Inactive",
"parameters": {
"must" : {
"state": "INACTIVE"
}
}
}
]
@@ -0,0 +1,21 @@
{
"name": "Flowable",
"groups": [
{ "key": "flowableUser", "name": "Flowable User" },
{ "key": "flowableAdministrator", "name": "Flowable Administrator" }
],
"users": [
{
"firstName": "Flowable",
"lastName": "Admin",
"login": "admin",
"email": "test@demo.flowable.io",
"language": "en",
"theme": "flowable",
"userDefinitionKey": "user-admin"
}
]
}
@@ -0,0 +1,67 @@
[
{
"key": "user-default",
"name": "Default user",
"description": "Creates a new, non-specific user where the member groups can be freely chosen.",
"initialState": "ACTIVE",
"initialSubState": "ACTIVE",
"forms": {
"init": "F01_userInitFormDefault",
"view": "F02_userViewFormDefault",
"edit": "F03_userEditFormDefault"
},
"memberGroups": [
"flowableUser"
],
"lookupGroups":[
"flowableUser"
],
"actionPermissions": {
"create": [ "flowableAdministrator" ],
"edit": [ "flowableAdministrator" ],
"deactivate": [ "flowableAdministrator" ],
"activate": [ "flowableAdministrator" ]
},
"contactFilters": [ "all" ],
"allowedFeatures": [ "contacts", "bubbles", "markdownInput", "replyToMessage", "forwardMessage", "reactToMessage", "fileUpload", "work", "createWork",
"personalAccessTokens",
"tasks", "documents", "changeOwnPassword", "changeOwnTheme", "editOwnAvatar"]
},
{
"key": "user-admin",
"name": "Administration User",
"description": "Creates a new, administration user.",
"initialUserSubType": "admin",
"initialState": "ACTIVE",
"initialSubState": "ACTIVE",
"forms": {
"init": "F01_userInitFormDefault",
"view": "F02_userViewFormDefault",
"edit": "F03_userEditFormDefault"
},
"memberGroups": [
"flowableUser",
"flowableAdministrator"
],
"lookupGroups":[
"flowableUser"
],
"actionPermissions": {
"create": [ "flowableAdministrator"],
"edit": [ "flowableAdministrator" ],
"deactivate": [ "flowableAdministrator" ],
"activate": [ "flowableAdministrator" ]
},
"initialVariables": {
"adminUser": true,
"description": "Admin"
},
"contactFilters": [ "all", "internal", "external", "inactive"],
"allowedFeatures": [ "contacts", "createUser", "reports",
"actuators", "user-mgmt", "search-api", "workobject-api", "templateManagement",
"markdownInput", "replyToMessage", "forwardMessage", "reactToMessage", "fileUpload", "work", "createWork", "tasks", "documents",
"impersonateUser",
"personalAccessTokens",
"changeOwnPassword", "changeOwnTheme", "editOwnAvatar", "themeManagement"]
}
]
@@ -0,0 +1,3 @@
com/customer/work/WorkApplication.class
com/customer/work/StaticResourceConfiguration.class
com/customer/work/SecurityHttpBasicConfiguration.class
@@ -0,0 +1,3 @@
/Users/andi/prj/flowable/2025.2/customer-work/src/main/java/com/customer/work/SecurityHttpBasicConfiguration.java
/Users/andi/prj/flowable/2025.2/customer-work/src/main/java/com/customer/work/StaticResourceConfiguration.java
/Users/andi/prj/flowable/2025.2/customer-work/src/main/java/com/customer/work/WorkApplication.java
@@ -0,0 +1 @@
com/customer/work/WorkApplicationTests.class
@@ -0,0 +1 @@
/Users/andi/prj/flowable/2025.2/customer-work/src/test/java/com/customer/work/WorkApplicationTests.java
File diff suppressed because one or more lines are too long
@@ -0,0 +1,292 @@
-------------------------------------------------------------------------------
Test set: com.customer.work.WorkApplicationTests
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 7.754 s <<< FAILURE! -- in com.customer.work.WorkApplicationTests
com.customer.work.WorkApplicationTests.contextLoads -- Time elapsed: 0.013 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@40e41f88 testClass = com.customer.work.WorkApplicationTests, locations = [], classes = [com.customer.work.WorkApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.web.server.context.SpringBootTestRandomPortContextCustomizer@62e70ea3, org.springframework.boot.test.context.PropertyMappingContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@25ddbbbb, org.springframework.boot.test.http.client.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@226642a5, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@625e134e, org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@5dbe30be, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@dfa5ba73], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:195)
at org.springframework.test.context.cache.DefaultContextCache.put(DefaultContextCache.java:214)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:160)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:128)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:210)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:186)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1716)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:632)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0: Error creating bean with name 'basicDefaultSecurity' defined in class path resource [com/customer/work/SecurityHttpBasicConfiguration.class]: Unsatisfied dependency expressed through method 'basicDefaultSecurity' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: Error creating bean with name 'flowableUserDetailsService' defined in class path resource [com/flowable/autoconfigure/security/FlowablePlatformSecurityAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableUserDetailsService' parameter 0: Error creating bean with name 'platformIdentityService' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'platformIdentityService' parameter 0: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:872)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:827)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:493)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1446)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1218)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1184)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1121)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:994)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:756)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$2(SpringBootContextLoader.java:156)
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1465)
at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:605)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:156)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:115)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:247)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.lambda$loadContext$0(DefaultCacheAwareContextLoaderDelegate.java:167)
... 21 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'basicDefaultSecurity' defined in class path resource [com/customer/work/SecurityHttpBasicConfiguration.class]: Unsatisfied dependency expressed through method 'basicDefaultSecurity' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: Error creating bean with name 'flowableUserDetailsService' defined in class path resource [com/flowable/autoconfigure/security/FlowablePlatformSecurityAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableUserDetailsService' parameter 0: Error creating bean with name 'platformIdentityService' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'platformIdentityService' parameter 0: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:2008)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1971)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeanCollection(DefaultListableBeanFactory.java:1863)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1833)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1711)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:864)
... 48 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: Error creating bean with name 'flowableUserDetailsService' defined in class path resource [com/flowable/autoconfigure/security/FlowablePlatformSecurityAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableUserDetailsService' parameter 0: Error creating bean with name 'platformIdentityService' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'platformIdentityService' parameter 0: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:351)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1762)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 65 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: Error creating bean with name 'flowableUserDetailsService' defined in class path resource [com/flowable/autoconfigure/security/FlowablePlatformSecurityAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableUserDetailsService' parameter 0: Error creating bean with name 'platformIdentityService' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'platformIdentityService' parameter 0: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:183)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:72)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:152)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
... 77 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'flowableUserDetailsService' defined in class path resource [com/flowable/autoconfigure/security/FlowablePlatformSecurityAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableUserDetailsService' parameter 0: Error creating bean with name 'platformIdentityService' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'platformIdentityService' parameter 0: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1305)
at org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer.configure(InitializeUserDetailsBeanManagerConfigurer.java:94)
at org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer.configure(InitializeUserDetailsBeanManagerConfigurer.java:63)
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.configure(AbstractConfiguredSecurityBuilder.java:386)
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:336)
at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38)
at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.getAuthenticationManager(AuthenticationConfiguration.java:121)
at org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.authenticationManager(HttpSecurityConfiguration.java:152)
at org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity(HttpSecurityConfiguration.java:119)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:155)
... 80 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'platformIdentityService' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'platformIdentityService' parameter 0: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1762)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 100 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'coreIdmEngine' defined in class path resource [com/flowable/spring/boot/idm/IdmEngineServicesAutoConfiguration$AlreadyInitializedAppEngineConfiguration.class]: Unsatisfied dependency expressed through method 'coreIdmEngine' parameter 0: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:1225)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1704)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 114 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.flowable.spring.boot.app.AppEngineServicesAutoConfiguration': Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:610)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1762)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 128 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appIndexingConfigurer' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appIndexingConfigurer' parameter 0: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:2015)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1971)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1792)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1711)
at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.resolveStream(DefaultListableBeanFactory.java:2685)
at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.orderedStream(DefaultListableBeanFactory.java:2679)
at com.flowable.spring.boot.BaseEngineConfigurationWithConfigurers.setEngineConfigurers(BaseEngineConfigurationWithConfigurers.java:32)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:832)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:493)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1446)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
... 147 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appEngineIndexingConfigurator' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'appEngineIndexingConfigurator' parameter 0: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:1225)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1704)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 170 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'indexManager' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'indexManager' parameter 0: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:1225)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1704)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 184 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'flowableElasticsearchClient' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Unsatisfied dependency expressed through method 'flowableElasticsearchClient' parameter 1: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1194)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1762)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 198 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'elasticsearchCompatibility' defined in class path resource [com/flowable/autoconfigure/indexing/IndexingAutoConfiguration.class]: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1817)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:603)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:1225)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1704)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 212 more
Caused by: org.flowable.common.engine.api.FlowableException: Could not retrieve Elasticsearch version information. One possible reason could be that ES is not accessible. See root cause
at com.flowable.indexing.ElasticsearchCompatibilityImpl.afterPropertiesSet(ElasticsearchCompatibilityImpl.java:134)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1864)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1813)
... 223 more
Caused by: java.net.ConnectException: Connect to http://localhost:9203 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused
at co.elastic.clients.transport.rest5_client.low_level.Rest5Client.extractAndWrapCause(Rest5Client.java:945)
at co.elastic.clients.transport.rest5_client.low_level.Rest5Client.performRequest(Rest5Client.java:308)
at co.elastic.clients.transport.rest5_client.low_level.Rest5Client.performRequest(Rest5Client.java:293)
at com.flowable.indexing.ElasticsearchCompatibilityImpl.afterPropertiesSet(ElasticsearchCompatibilityImpl.java:56)
... 225 more
Caused by: org.apache.hc.client5.http.HttpHostConnectException: Connect to http://localhost:9203 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:639)
at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:1046)
at org.apache.hc.core5.reactor.InternalConnectChannel.onIOEvent(InternalConnectChannel.java:70)
at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:176)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:125)
at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:92)
at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
at java.base/java.lang.Thread.run(Thread.java:1474)
@@ -0,0 +1,24 @@
spring.datasource.url=jdbc:h2:mem:flowable-work-db;DB_CLOSE_DELAY=1000
spring.datasource.username=admin
spring.datasource.password=test
# To make our life easier in tests we are disabling the async executor and elasticsearch
flowable.async-executor-activate=false
flowable.async-history-executor-activate=false
flowable.indexing.enabled=false
management.health.elasticsearch.enabled=false
management.metrics.export.elastic.enabled=false
# Set debug level in tests
logging.level.com.flowable=INFO
logging.level.com.flowable.local.work.model.FlowableModelTestUtils=INFO
# Disable the timeout process in the tests
flowable.external-system.wechat.timeout.process-definition-key=
# Path to test-auto-deploy-apps
flowable.app.resource-location=classpath*:/test-auto-deploy-apps/
# Email
flowable.mail.server.host=localhost
flowable.mail.server.port=3025
@@ -0,0 +1,5 @@
{
"root": {
"testText": "my test text"
}
}
@@ -0,0 +1,5 @@
{
"__IN": {
"initiator": "admin"
}
}
@@ -0,0 +1,9 @@
{
"param": true,
"__IN": {
"param": false
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": false,
"__IN": {
"param": true
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "2025-11-12",
"__IN": {
"param": "2025-11-11"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": 123.456,
"__IN": {
"param": 456.789
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": 123,
"__IN": {
"param": 456
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "hello root",
"__IN": {
"param": "hello"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "123",
"__IN": {
"param": "456"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,9 @@
{
"param": "123.456,",
"__IN": {
"param": "456.789"
},
"__OUT": {
"result": "out"
}
}
@@ -0,0 +1,5 @@
{
"root": {
"dataEntry": "my root text"
}
}