explicit parameters, changed array nodes in xls files from * to []
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
package com.customer.work;
|
package com.customer.work;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
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;
|
||||||
import jakarta.servlet.DispatcherType;
|
import jakarta.servlet.DispatcherType;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -18,10 +19,7 @@ import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
|||||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
|
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
|
||||||
|
|
||||||
import com.flowable.autoconfigure.security.FlowableHttpSecurityCustomizer;
|
import java.util.stream.Collectors;
|
||||||
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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnProperty(prefix = "application.security", name = "type", havingValue = "basic", matchIfMissing = true)
|
@ConditionalOnProperty(prefix = "application.security", name = "type", havingValue = "basic", matchIfMissing = true)
|
||||||
@@ -32,7 +30,7 @@ public class SecurityHttpBasicConfiguration {
|
|||||||
@Order(10)
|
@Order(10)
|
||||||
public SecurityFilterChain basicDefaultSecurity(HttpSecurity http, ObjectProvider<FlowableHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
|
public SecurityFilterChain basicDefaultSecurity(HttpSecurity http, ObjectProvider<FlowableHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
|
||||||
for (FlowableHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream()
|
for (FlowableHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream()
|
||||||
.collect(Collectors.toList())) {
|
.toList()) {
|
||||||
customizer.customize(http);
|
customizer.customize(http);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.customer.work;
|
package com.customer.work;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.CacheControl;
|
import org.springframework.http.CacheControl;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
public class StaticResourceConfiguration implements WebMvcConfigurer {
|
public class StaticResourceConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ public class TaskProcessStarter {
|
|||||||
private RuntimeService runtimeService;
|
private RuntimeService runtimeService;
|
||||||
|
|
||||||
public void start(String processKey, DelegateTask task) {
|
public void start(String processKey, DelegateTask task) {
|
||||||
// String processKey = (String) task.getVariable("subProcessKey"); // key from variable
|
|
||||||
|
|
||||||
Map<String, Object> vars = new HashMap<>(task.getVariables()); // carry over existing vars
|
Map<String, Object> vars = new HashMap<>(task.getVariables()); // carry over existing vars
|
||||||
vars.put("originTaskId", task.getId());
|
vars.put("originTaskId", task.getId());
|
||||||
vars.put("originTaskName", task.getName());
|
vars.put("originTaskName", task.getName());
|
||||||
|
|
||||||
runtimeService.startProcessInstanceByKey(processKey, vars);
|
runtimeService.startProcessInstanceByKey(processKey, vars);
|
||||||
// runtimeService.startProcessInstanceById(processKey, vars);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
|||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import org.flowable.cmmn.api.CmmnRuntimeService;
|
import org.flowable.cmmn.api.CmmnRuntimeService;
|
||||||
|
import org.flowable.cmmn.api.runtime.CaseInstance;
|
||||||
|
import org.flowable.cmmn.api.runtime.PlanItemInstance;
|
||||||
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
|
import org.flowable.cmmn.engine.CmmnEngineConfiguration;
|
||||||
import org.flowable.cmmn.engine.impl.persistence.entity.CaseInstanceEntity;
|
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.FlowableEngineEntityEvent;
|
||||||
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
|
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
|
||||||
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
|
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
|
||||||
import org.flowable.engine.ProcessEngineConfiguration;
|
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.RuntimeService;
|
||||||
import org.flowable.engine.delegate.DelegateExecution;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
|
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
|
||||||
|
|||||||
@@ -4,332 +4,218 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.*;
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an Excel workbook (created with Excel or LibreOffice) to test data rows.
|
||||||
|
* Sheet layout:
|
||||||
|
* - The 1st row of every sheet contains the path of each variable.
|
||||||
|
* Object fields are separated by ".", array elements are addressed with "[index]",
|
||||||
|
* e.g. "root.param[0]" or "root.items[1].name".
|
||||||
|
* - The 2nd row contains the type of each variable:
|
||||||
|
* String, Boolean, Integer, Long, Double, Date
|
||||||
|
* - All other rows contain the values. Empty cells are skipped.
|
||||||
|
* Special String values: __NULL, __N_A, __EMPTY, __BLANK
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class FlowableExcelMapper {
|
public class FlowableExcelMapper {
|
||||||
|
|
||||||
protected static ObjectMapper objectMapper = new ObjectMapper();
|
protected final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
protected static JavaTimeModule javaTimeModule = new JavaTimeModule();
|
protected final FlowableExcelParser flowableExcelParser;
|
||||||
protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
|
|
||||||
|
|
||||||
public FlowableExcelMapper() {
|
protected static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
protected static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
|
public FlowableExcelMapper(FlowableExcelParser flowableExcelParser) {
|
||||||
|
this.flowableExcelParser = flowableExcelParser;
|
||||||
// Enable ObjectMapper for handling Instant as string
|
// Enable ObjectMapper for handling Instant as string
|
||||||
objectMapper.registerModule(javaTimeModule);
|
objectMapper.registerModule(new JavaTimeModule());
|
||||||
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// excelPath defines the location of the Excel file
|
// Book as JSON: one ArrayNode per sheet, one node per data row
|
||||||
// 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) {
|
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();
|
ArrayNode bookNode = objectMapper.createArrayNode();
|
||||||
for (ArrayList<ArrayList<String>> sheet : book) {
|
for (List<Map<String, Object>> sheet : excelBookResourceToObj(resourcePath)) {
|
||||||
bookNode.add(sheetToJsonNode(sheet));
|
ArrayNode sheetNode = objectMapper.createArrayNode();
|
||||||
|
for (Map<String, Object> row : sheet) {
|
||||||
|
sheetNode.add(objectMapper.valueToTree(row));
|
||||||
|
}
|
||||||
|
bookNode.add(sheetNode);
|
||||||
}
|
}
|
||||||
return bookNode;
|
return bookNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ArrayNode sheetToJsonNode(ArrayList<ArrayList<String>> rows) {
|
// Book as plain Java objects: one list per sheet, one map per data row
|
||||||
ArrayList<String> paths = new ArrayList<>();
|
public List<List<Map<String, Object>>> excelBookResourceToObj(String resourcePath) {
|
||||||
ArrayList<String> types = new ArrayList<>();
|
List<List<Map<String, Object>>> book = new ArrayList<>();
|
||||||
ArrayNode arrayNode = objectMapper.createArrayNode();
|
for (List<List<String>> sheet : flowableExcelParser.parseExcelBookFromResource(resourcePath)) {
|
||||||
for (int rowNum = 0; rowNum < rows.size(); rowNum++) {
|
book.add(sheetToRows(sheet));
|
||||||
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 book;
|
||||||
return arrayNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JsonNode rowToJsonNode(ArrayList<String> types, ArrayList<String> paths, ArrayList<String> row, JsonNode jsonNode) {
|
protected List<Map<String, Object>> sheetToRows(List<List<String>> rows) {
|
||||||
|
List<Map<String, Object>> dataRows = new ArrayList<>();
|
||||||
|
if (rows.size() < 2) {
|
||||||
|
// A sheet needs at least the path row and the type row to contain data
|
||||||
|
return dataRows;
|
||||||
|
}
|
||||||
|
List<String> paths = rows.get(0);
|
||||||
|
List<String> types = rows.get(1);
|
||||||
|
for (int rowNum = 2; rowNum < rows.size(); rowNum++) {
|
||||||
|
dataRows.add(rowToMap(paths, types, rows.get(rowNum)));
|
||||||
|
}
|
||||||
|
return dataRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Object> rowToMap(List<String> paths, List<String> types, List<String> row) {
|
||||||
|
Map<String, Object> rowMap = new LinkedHashMap<>();
|
||||||
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
|
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
|
||||||
String cellValue = row.get(cellNum);
|
String cellValue = row.get(cellNum);
|
||||||
// Empty cells are not added to node
|
// Empty cells are not added
|
||||||
if (cellValue != null && !cellValue.isEmpty()) {
|
if (cellValue != null && !cellValue.isEmpty()) {
|
||||||
String type = types.get(cellNum);
|
Object content = parseCellValue(types.get(cellNum), cellValue);
|
||||||
Object content;
|
setValue(rowMap, parsePath(paths.get(cellNum)), content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rowMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object parseCellValue(String type, String cellValue) {
|
||||||
switch (cellValue) {
|
switch (cellValue) {
|
||||||
case "__NULL": content = null; break;
|
case "__NULL": return null;
|
||||||
case "__N_A": content = "__N_A"; break;
|
case "__N_A": return "__N_A";
|
||||||
default:
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "String":
|
case "String":
|
||||||
switch (cellValue) {
|
switch (cellValue) {
|
||||||
case "__EMPTY": content = ""; break;
|
case "__EMPTY": return "";
|
||||||
case "__BLANK": content = " "; break;
|
case "__BLANK": return " ";
|
||||||
default: content = cellValue;
|
default: return cellValue;
|
||||||
}
|
}
|
||||||
break;
|
case "Boolean": return Boolean.parseBoolean(cellValue);
|
||||||
case "Boolean": content = Boolean.parseBoolean(cellValue); break;
|
case "Integer": return Integer.parseInt(cellValue);
|
||||||
case "Integer": content = Integer.parseInt(cellValue); break;
|
case "Long": return Long.parseLong(cellValue);
|
||||||
case "Double": content = Double.parseDouble(cellValue); break;
|
case "Double": return Double.parseDouble(cellValue);
|
||||||
case "Long": content = Long.parseLong(cellValue); break;
|
case "Date": return parseDate(cellValue);
|
||||||
case "Date":
|
default:
|
||||||
// Excel considers dates as local dates, but we want them as UTC
|
throw new IllegalArgumentException("Variable type not supported: " + type);
|
||||||
Instant instant;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Instant parseDate(String cellValue) {
|
||||||
|
// Excel and LibreOffice consider dates as local dates, the tests treat them as UTC
|
||||||
try {
|
try {
|
||||||
LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
return LocalDateTime.parse(cellValue, DATE_TIME_FORMAT).toInstant(ZoneOffset.UTC);
|
||||||
instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
|
|
||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
return LocalDate.parse(cellValue, DATE_FORMAT).atStartOfDay(ZoneOffset.UTC).toInstant();
|
||||||
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) {
|
* Splits a path like "root.items[1].name" into the tokens "root", "items", 1, "name".
|
||||||
parent = objectMapper.createObjectNode();
|
* Field names are Strings, array indexes are Integers.
|
||||||
|
*/
|
||||||
|
protected List<Object> parsePath(String path) {
|
||||||
|
List<Object> tokens = new ArrayList<>();
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < path.length()) {
|
||||||
|
char c = path.charAt(pos);
|
||||||
|
if (c == '[') {
|
||||||
|
int end = path.indexOf(']', pos);
|
||||||
|
if (end < 0) {
|
||||||
|
throw new IllegalArgumentException("Missing ']' in path: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
tokens.add(Integer.parseInt(path.substring(pos + 1, end)));
|
||||||
instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
|
} catch (NumberFormatException e) {
|
||||||
} catch (DateTimeParseException e) {
|
throw new IllegalArgumentException("Invalid array index in path: " + path, e);
|
||||||
LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
|
||||||
instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
|
|
||||||
}
|
}
|
||||||
content = instant.plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds());
|
pos = end + 1;
|
||||||
break;
|
// An index may be followed by ".field", another "[index]" or the end of the path
|
||||||
default:
|
if (pos < path.length() && path.charAt(pos) == '.') {
|
||||||
throw new RuntimeException("Variable type not supported: " + type);
|
pos++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (c == '.') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
int end = pos;
|
||||||
|
while (end < path.length() && path.charAt(end) != '.' && path.charAt(end) != '[') {
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
if (end == pos) {
|
||||||
|
throw new IllegalArgumentException("Empty field name in path: " + path);
|
||||||
|
}
|
||||||
|
tokens.add(path.substring(pos, end));
|
||||||
|
pos = end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node = addObj(node, paths.get(cellNum), content);
|
if (tokens.isEmpty() || !(tokens.getFirst() instanceof String)) {
|
||||||
|
throw new IllegalArgumentException("Path must start with a field name: " + path);
|
||||||
}
|
}
|
||||||
}
|
return tokens;
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value in the nested map/list structure, creating intermediate
|
||||||
|
* containers as needed. Missing array elements are padded with empty
|
||||||
|
* maps because Flowable cannot store null list elements as variables.
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected Object addObj(Object parent, String path, Object value) {
|
protected void setValue(Map<String, Object> rowMap, List<Object> tokens, Object value) {
|
||||||
if (parent == null) {
|
Object current = rowMap;
|
||||||
parent = new LinkedHashMap<String, Object>();
|
for (int i = 0; i < tokens.size(); i++) {
|
||||||
|
Object token = tokens.get(i);
|
||||||
|
boolean last = i == tokens.size() - 1;
|
||||||
|
if (token instanceof String key) {
|
||||||
|
if (!(current instanceof Map)) {
|
||||||
|
throw new IllegalArgumentException("Conflicting paths: '" + key + "' in " + tokens + " addresses an array as an object");
|
||||||
}
|
}
|
||||||
|
Map<String, Object> map = (Map<String, Object>) current;
|
||||||
int delimiterPosition = indexOfFirstDelimiter(path, ".*");
|
if (last) {
|
||||||
if (delimiterPosition <= 0) {
|
map.put(key, value);
|
||||||
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 {
|
} else {
|
||||||
throw new RuntimeException("Parent type not supported: " + parent.getClass().getName());
|
Object child = map.get(key);
|
||||||
|
if (child == null) {
|
||||||
|
child = newContainer(tokens.get(i + 1));
|
||||||
|
map.put(key, child);
|
||||||
}
|
}
|
||||||
return parent;
|
current = child;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
node = null;
|
int index = (Integer) token;
|
||||||
|
if (!(current instanceof List)) {
|
||||||
|
throw new IllegalArgumentException("Conflicting paths: index " + index + " in " + tokens + " addresses an object as an array");
|
||||||
}
|
}
|
||||||
if (node != null) {
|
List<Object> list = (List<Object>) current;
|
||||||
// Take the existing ArrayNode as child
|
while (list.size() <= index) {
|
||||||
child = node;
|
list.add(new LinkedHashMap<String, Object>());
|
||||||
}
|
}
|
||||||
while (((ArrayList<Object>) parent).size() <= index) {
|
if (last) {
|
||||||
// Create empty entries to parent ArrayNode
|
list.set(index, value);
|
||||||
((ArrayList<Object>) parent).add(new LinkedHashMap<String, Object> ());
|
|
||||||
}
|
|
||||||
((ArrayList<Object>) parent).set(index, addObj(child, newPath, value));
|
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Parent type not supported: " + parent.getClass().getName());
|
current = list.get(index);
|
||||||
}
|
}
|
||||||
return parent;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object newContainer(Object nextToken) {
|
||||||
|
return nextToken instanceof Integer ? new ArrayList<>() : new LinkedHashMap<String, Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,46 +4,38 @@ import org.apache.poi.ss.usermodel.*;
|
|||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.io.InputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an .xlsx workbook (created with Excel or LibreOffice) from the classpath
|
||||||
|
* and returns all cells as Strings: book > sheets > rows > cells.
|
||||||
|
* Restrictions per sheet:
|
||||||
|
* - The column count is defined by the first row: it starts with the 1st cell
|
||||||
|
* and ends at the 1st empty cell
|
||||||
|
* - Parsing stops at the 1st empty row
|
||||||
|
* - Formulas in cells are evaluated
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class FlowableExcelParser {
|
public class FlowableExcelParser {
|
||||||
|
|
||||||
// excelPath defines the location of the Excel file
|
public List<List<List<String>>> parseExcelBookFromResource(String excelResourcePath) {
|
||||||
// Return all cells in all sheets with the following restrictions:
|
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(excelResourcePath);
|
||||||
// - Max column count per sheet is defined in the first row
|
if (inputStream == null) {
|
||||||
// - Max column count starts with the 1st cell and ends with the 1st empty cell
|
throw new IllegalArgumentException("Resource not found: " + excelResourcePath);
|
||||||
// - Parsing the sheet stops after the 1st empty row
|
}
|
||||||
// All sheets are added to an ArrayList
|
try (inputStream; Workbook workBook = new XSSFWorkbook(inputStream)) {
|
||||||
// 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);
|
return parseBook(workBook);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new UncheckedIOException("Cannot read Excel resource: " + excelResourcePath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getResourcePath(String resourcePath) {
|
protected List<List<List<String>>> parseBook(Workbook workBook) {
|
||||||
ClassLoader classLoader = FlowableExcelParser.class.getClassLoader();
|
List<List<List<String>>> book = new ArrayList<>();
|
||||||
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();
|
FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
|
||||||
for (Sheet workSheet : workBook) {
|
for (Sheet workSheet : workBook) {
|
||||||
book.add(parseSheet(workSheet, formulaEvaluator));
|
book.add(parseSheet(workSheet, formulaEvaluator));
|
||||||
@@ -51,88 +43,64 @@ public class FlowableExcelParser {
|
|||||||
return book;
|
return book;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<ArrayList<String>> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) {
|
protected List<List<String>> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) {
|
||||||
ArrayList<ArrayList<String>> sheet = new ArrayList<>();
|
List<List<String>> sheet = new ArrayList<>();
|
||||||
// Count columns of first row
|
|
||||||
int maxCols = getMaxCols(workSheet.getRow(0));
|
int maxCols = getMaxCols(workSheet.getRow(0));
|
||||||
|
if (maxCols == 0) {
|
||||||
|
// Sheet without a header row is empty
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
DataFormatter dataFormatter = new DataFormatter();
|
DataFormatter dataFormatter = new DataFormatter();
|
||||||
int firstRow = workSheet.getFirstRowNum();
|
|
||||||
int lastRow = workSheet.getLastRowNum();
|
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 (Row workRow : workSheet) sometimes skips empty rows, then the first empty row as an exit criterion wouldn't work
|
||||||
for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
|
for (int rowIndex = 0; rowIndex <= lastRow; rowIndex++) {
|
||||||
ArrayList<String> row = parseRow(workSheet.getRow(rowIndex), maxCols, formulaEvaluator, dataFormatter);
|
List<String> row = parseRow(workSheet.getRow(rowIndex), maxCols, formulaEvaluator, dataFormatter);
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
// Take all rows until the first empty row
|
// Take all rows until the first empty row
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
sheet.add(row);
|
|
||||||
}
|
}
|
||||||
|
sheet.add(row);
|
||||||
}
|
}
|
||||||
return sheet;
|
return sheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<String> parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator, DataFormatter dataFormatter) {
|
protected List<String> parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator, DataFormatter dataFormatter) {
|
||||||
if (workRow == null) {
|
if (workRow == null) {
|
||||||
// Row is empty
|
// Row is empty
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
ArrayList<String> row = new ArrayList<>();
|
List<String> row = new ArrayList<>();
|
||||||
boolean allCellsNull = true;
|
boolean allCellsEmpty = true;
|
||||||
for (int colIndex = 0; colIndex < maxCols; colIndex++) {
|
for (int colIndex = 0; colIndex < maxCols; colIndex++) {
|
||||||
Cell workCell = workRow.getCell(colIndex);
|
Cell workCell = workRow.getCell(colIndex);
|
||||||
formulaEvaluator.evaluate(workCell);
|
|
||||||
String content = dataFormatter.formatCellValue(workCell, formulaEvaluator);
|
String content = dataFormatter.formatCellValue(workCell, formulaEvaluator);
|
||||||
if (content != null && !content.isEmpty()) {
|
if (content != null && !content.isEmpty()) {
|
||||||
allCellsNull = false;
|
allCellsEmpty = false;
|
||||||
}
|
}
|
||||||
row.add(content);
|
row.add(content);
|
||||||
}
|
}
|
||||||
if (allCellsNull) {
|
if (allCellsEmpty) {
|
||||||
// Row is empty
|
// Row is empty
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private int getMaxCols(Row workRow) {
|
protected int getMaxCols(Row workRow) {
|
||||||
|
if (workRow == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
int maxCol = workRow.getLastCellNum();
|
int maxCol = workRow.getLastCellNum();
|
||||||
for (int colIndex = 0; colIndex < maxCol; colIndex++) {
|
for (int colIndex = 0; colIndex < maxCol; colIndex++) {
|
||||||
Cell workCell = workRow.getCell(colIndex);
|
Cell workCell = workRow.getCell(colIndex);
|
||||||
if (workCell == null || workCell.getCellType() == CellType.BLANK || workCell.getCellType() == CellType._NONE) {
|
// Count cells until the first empty cell; cells holding an empty string
|
||||||
// Count cells until the first empty cell
|
// (formatting leftovers from Excel/LibreOffice) also count as empty
|
||||||
|
if (workCell == null || workCell.getCellType() == CellType.BLANK || workCell.getCellType() == CellType._NONE
|
||||||
|
|| (workCell.getCellType() == CellType.STRING && workCell.getStringCellValue().isEmpty())) {
|
||||||
return colIndex;
|
return colIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return maxCol;
|
return Math.max(maxCol, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.flowable.core.spring.security.SecurityUtils;
|
|||||||
import com.flowable.platform.service.task.CompleteFormRepresentation;
|
import com.flowable.platform.service.task.CompleteFormRepresentation;
|
||||||
import com.flowable.platform.service.task.PlatformTaskService;
|
import com.flowable.platform.service.task.PlatformTaskService;
|
||||||
import jakarta.mail.Address;
|
import jakarta.mail.Address;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.flowable.bpmn.model.*;
|
import org.flowable.bpmn.model.*;
|
||||||
import org.flowable.bpmn.model.Process;
|
import org.flowable.bpmn.model.Process;
|
||||||
@@ -37,8 +36,8 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.FileOutputStream;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -59,9 +58,13 @@ public class FlowableModelTestUtils {
|
|||||||
protected final AuditService auditService;
|
protected final AuditService auditService;
|
||||||
|
|
||||||
|
|
||||||
public static String TENANT_ID = null;
|
public static final String TENANT_ID = null;
|
||||||
public static String ROOT_PROCESS_ID = "ROOT_PROCESS_ID";
|
public static final String ROOT_PROCESS_ID = "ROOT_PROCESS_ID";
|
||||||
public static String TEST_PROCESS_ID = "TEST_PROCESS_ID";
|
public static final String TEST_PROCESS_ID = "TEST_PROCESS_ID";
|
||||||
|
|
||||||
|
public static final String ROOT_PARAM = "__ROOT";
|
||||||
|
public static final String IN_PARAM = "__IN";
|
||||||
|
public static final String OUT_PARAM = "__OUT";
|
||||||
|
|
||||||
|
|
||||||
public FlowableModelTestUtils(ProcessEngine processEngine,
|
public FlowableModelTestUtils(ProcessEngine processEngine,
|
||||||
@@ -84,7 +87,7 @@ public class FlowableModelTestUtils {
|
|||||||
this.auditService = auditService;
|
this.auditService = auditService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkAndAndAssertAuditRecord(Map<String, Object> map, int auditNumber, String hint, String message, String category, String type) {
|
public void checkAndAssertAuditRecord(Map<String, Object> map, int auditNumber, String hint, String message, String category, String type) {
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
Object check = map.get("audit");
|
Object check = map.get("audit");
|
||||||
if (check instanceof Integer && (Integer) check > 0) {
|
if (check instanceof Integer && (Integer) check > 0) {
|
||||||
@@ -94,10 +97,9 @@ public class FlowableModelTestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void assertAuditRecord(int auditNumber, String hint, String message, String category, String type) {
|
protected void assertAuditRecord(int auditNumber, String hint, String message, String category, String type) {
|
||||||
|
if (auditNumber <= 0) return;
|
||||||
List<AuditInstance> auditTrail = getAuditTrail();
|
List<AuditInstance> auditTrail = getAuditTrail();
|
||||||
int auditTrailSize = auditTrail.size();
|
Assertions.assertThat(auditTrail.size()).as("%s: invalid auditNumber %s", hint, auditNumber).isGreaterThanOrEqualTo(auditNumber);
|
||||||
Assertions.assertThat(auditTrailSize).as(hint + ": invalid auditNumber " + auditTrailSize).isGreaterThanOrEqualTo(auditNumber);
|
|
||||||
if (auditTrailSize == 0) return;
|
|
||||||
|
|
||||||
AuditInstance auditInstance = auditTrail.get(auditNumber - 1);
|
AuditInstance auditInstance = auditTrail.get(auditNumber - 1);
|
||||||
Assertions.assertThat(auditInstance.getPayload().get("message")).as(hint).isEqualTo(message);
|
Assertions.assertThat(auditInstance.getPayload().get("message")).as(hint).isEqualTo(message);
|
||||||
@@ -115,33 +117,14 @@ public class FlowableModelTestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void assertEmail(int emailNumber, String hint, String subject, String receivers) {
|
protected void assertEmail(int emailNumber, String hint, String subject, String receivers) {
|
||||||
|
if (emailNumber <= 0) return;
|
||||||
List<EmailDto> emails = getMailList();
|
List<EmailDto> emails = getMailList();
|
||||||
int emailListSize = emails.size();
|
Assertions.assertThat(emails.size()).as("%s: invalid email number %s", hint, emailNumber).isGreaterThanOrEqualTo(emailNumber);
|
||||||
Assertions.assertThat(emailListSize).as(hint + ": invalid email number " + emailListSize).isGreaterThanOrEqualTo(emailNumber);
|
|
||||||
if (emailNumber == 0) return;
|
|
||||||
|
|
||||||
EmailDto email = emails.get(emailNumber - 1);
|
EmailDto email = emails.get(emailNumber - 1);
|
||||||
String emailSubject = email.getSubject();
|
Assertions.assertThat(email.getSubject()).as("%s: invalid subject %s", hint, email.getSubject()).endsWith(subject);
|
||||||
Assertions.assertThat(emailSubject).as(hint + ": invalid subject " + emailSubject).endsWith(subject);
|
Assertions.assertThat(email.getReceiverList()).as("%s: invalid email receivers", hint)
|
||||||
|
.containsExactlyInAnyOrder(receivers.split("[,\\s]+"));
|
||||||
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) {
|
public Map<String, Object> getHistoryCasePayload(String caseInstanceId) {
|
||||||
@@ -176,13 +159,13 @@ public class FlowableModelTestUtils {
|
|||||||
|
|
||||||
public Task getOpenTask(String taskKey) {
|
public Task getOpenTask(String taskKey) {
|
||||||
Task task = taskService.createTaskQuery().taskDefinitionKey(taskKey).singleResult();
|
Task task = taskService.createTaskQuery().taskDefinitionKey(taskKey).singleResult();
|
||||||
Assertions.assertThat(task).as("No open task with key {} found", taskKey).isNotNull();
|
Assertions.assertThat(task).as("No open task with key %s found", taskKey).isNotNull();
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task getOpenTaskByName(String taskName) {
|
public Task getOpenTaskByName(String taskName) {
|
||||||
Task task = taskService.createTaskQuery().taskName(taskName).singleResult();
|
Task task = taskService.createTaskQuery().taskName(taskName).singleResult();
|
||||||
Assertions.assertThat(task).as("No open task with name {} found", taskName).isNotNull();
|
Assertions.assertThat(task).as("No open task with name %s found", taskName).isNotNull();
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,9 +264,8 @@ public class FlowableModelTestUtils {
|
|||||||
|
|
||||||
public Stream<Arguments> getObjArgumentsFromExcel(String path) {
|
public Stream<Arguments> getObjArgumentsFromExcel(String path) {
|
||||||
ArrayList<Arguments> argumentList = new ArrayList<>();
|
ArrayList<Arguments> argumentList = new ArrayList<>();
|
||||||
ArrayList<ArrayList<Object>> book = flowableExcelMapper.excelBookResourceToObj(path);
|
for (List<Map<String, Object>> sheet : flowableExcelMapper.excelBookResourceToObj(path)) {
|
||||||
for (ArrayList<Object> sheet : book) {
|
for (Map<String, Object> row : sheet) {
|
||||||
for (Object row : sheet) {
|
|
||||||
argumentList.add(Arguments.of(path, row));
|
argumentList.add(Arguments.of(path, row));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,11 +286,11 @@ public class FlowableModelTestUtils {
|
|||||||
}
|
}
|
||||||
JsonNode inParam = row.get("in");
|
JsonNode inParam = row.get("in");
|
||||||
if (inParam != null) {
|
if (inParam != null) {
|
||||||
vars.set("__IN", inParam);
|
vars.set(IN_PARAM, inParam);
|
||||||
}
|
}
|
||||||
JsonNode outParam = row.get("out");
|
JsonNode outParam = row.get("out");
|
||||||
if (outParam != null) {
|
if (outParam != null) {
|
||||||
vars.set("__OUT", outParam);
|
vars.set(OUT_PARAM, outParam);
|
||||||
}
|
}
|
||||||
JsonNode idParam = row.get("id");
|
JsonNode idParam = row.get("id");
|
||||||
JsonNode test = row.get("test");
|
JsonNode test = row.get("test");
|
||||||
@@ -324,6 +306,7 @@ public class FlowableModelTestUtils {
|
|||||||
|
|
||||||
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
|
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
|
||||||
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
|
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
|
||||||
|
Assertions.assertThat(idParam).as("Column 'id' missing in row of %s", path).isNotNull();
|
||||||
ObjectNode processIds = createRootTestProcessInstance(idParam.asText(), jsonUtils.convertJsonNodeToMap(vars));
|
ObjectNode processIds = createRootTestProcessInstance(idParam.asText(), jsonUtils.convertJsonNodeToMap(vars));
|
||||||
for (int i = 0; i < timerCount; i++) {
|
for (int i = 0; i < timerCount; i++) {
|
||||||
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
|
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
|
||||||
@@ -338,16 +321,13 @@ public class FlowableModelTestUtils {
|
|||||||
result.set("root", root);
|
result.set("root", root);
|
||||||
}
|
}
|
||||||
if (auditRecordCount != null) {
|
if (auditRecordCount != null) {
|
||||||
// List<AuditInstance> auditTrail = getAuditTrail(processIds.get(ROOT_PROCESS_ID).asText());
|
|
||||||
List<AuditInstance> auditTrail = getAuditTrail();
|
List<AuditInstance> auditTrail = getAuditTrail();
|
||||||
int auditTrailSize = auditTrail.size();
|
Assertions.assertThat(auditTrail.size()).as("Invalid audit trail size: %s", auditTrail.size()).isEqualTo(auditRecordCount);
|
||||||
Assertions.assertThat(auditTrailSize).as("Invalid audit trail size: {}", auditTrailSize).isEqualTo(auditRecordCount);
|
|
||||||
result.set("auditTrail", jsonUtils.convertListToJsonNode(Collections.singletonList(auditTrail)));
|
result.set("auditTrail", jsonUtils.convertListToJsonNode(Collections.singletonList(auditTrail)));
|
||||||
}
|
}
|
||||||
if (emailCount != null) {
|
if (emailCount != null) {
|
||||||
List<EmailDto> emailList = getMailList();
|
List<EmailDto> emailList = getMailList();
|
||||||
int emailListSize = emailList.size();
|
Assertions.assertThat(emailList.size()).as("Invalid number of emails: %s", emailList.size()).isEqualTo(emailCount);
|
||||||
Assertions.assertThat(emailListSize).as("Invalid number of emails: {}", emailListSize).isEqualTo(emailCount);
|
|
||||||
result.set("emails", jsonUtils.convertListToJsonNode(Collections.singletonList(emailList)));
|
result.set("emails", jsonUtils.convertListToJsonNode(Collections.singletonList(emailList)));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -357,17 +337,15 @@ public class FlowableModelTestUtils {
|
|||||||
Map<String, Object> vars = new LinkedHashMap<>();
|
Map<String, Object> vars = new LinkedHashMap<>();
|
||||||
Map<String, Object> rootParam = (Map<String, Object>) row.get("root");
|
Map<String, Object> rootParam = (Map<String, Object>) row.get("root");
|
||||||
if (rootParam != null) {
|
if (rootParam != null) {
|
||||||
for (Map.Entry<String, Object> entry : rootParam.entrySet()) {
|
vars.putAll(rootParam);
|
||||||
vars.put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Map<String, Object> inParam = (Map<String, Object>) row.get("in");
|
Map<String, Object> inParam = (Map<String, Object>) row.get("in");
|
||||||
if (inParam != null) {
|
if (inParam != null) {
|
||||||
vars.put("__IN", inParam);
|
vars.put(IN_PARAM, inParam);
|
||||||
}
|
}
|
||||||
Map<String, Object> outParam = (Map<String, Object>) row.get("out");
|
Map<String, Object> outParam = (Map<String, Object>) row.get("out");
|
||||||
if (outParam != null) {
|
if (outParam != null) {
|
||||||
vars.put("__OUT", outParam);
|
vars.put(OUT_PARAM, outParam);
|
||||||
}
|
}
|
||||||
String idParam = (String) row.get("id");
|
String idParam = (String) row.get("id");
|
||||||
Map<String, Object> test =(Map<String, Object>) row.get("test");
|
Map<String, Object> test =(Map<String, Object>) row.get("test");
|
||||||
@@ -383,6 +361,7 @@ public class FlowableModelTestUtils {
|
|||||||
|
|
||||||
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
|
logger.info("TestExcelRow file={} id={} rootParam={} inParam={} outParam={} timer={} test={} audit={} email={} ",
|
||||||
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
|
path, idParam, rootParam, inParam, outParam, timerCount, test, auditRecordCount, emailCount);
|
||||||
|
Assertions.assertThat(idParam).as("Column 'id' missing in row of %s", path).isNotNull();
|
||||||
ObjectNode processIds = createRootTestProcessInstance(idParam, vars);
|
ObjectNode processIds = createRootTestProcessInstance(idParam, vars);
|
||||||
for (int i = 0; i < timerCount; i++) {
|
for (int i = 0; i < timerCount; i++) {
|
||||||
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
|
executeTimer(processIds.get(TEST_PROCESS_ID).asText());
|
||||||
@@ -397,16 +376,13 @@ public class FlowableModelTestUtils {
|
|||||||
result.put("root", rootProcessPayload);
|
result.put("root", rootProcessPayload);
|
||||||
}
|
}
|
||||||
if (auditRecordCount != null) {
|
if (auditRecordCount != null) {
|
||||||
// List<AuditInstance> auditTrail = getAuditTrail(processIds.get(ROOT_PROCESS_ID).asText());
|
|
||||||
List<AuditInstance> auditTrail = getAuditTrail();
|
List<AuditInstance> auditTrail = getAuditTrail();
|
||||||
int auditTrailSize = auditTrail.size();
|
Assertions.assertThat(auditTrail.size()).as("Invalid audit trail size: %s", auditTrail.size()).isEqualTo(auditRecordCount);
|
||||||
Assertions.assertThat(auditTrailSize).as("Invalid audit trail size: {}", auditTrailSize).isEqualTo(auditRecordCount);
|
|
||||||
result.put("audit", auditRecordCount);
|
result.put("audit", auditRecordCount);
|
||||||
}
|
}
|
||||||
if (emailCount != null) {
|
if (emailCount != null) {
|
||||||
List<EmailDto> emailList = getMailList();
|
List<EmailDto> emailList = getMailList();
|
||||||
int emailListSize = emailList.size();
|
Assertions.assertThat(emailList.size()).as("Invalid number of emails: %s", emailList.size()).isEqualTo(emailCount);
|
||||||
Assertions.assertThat(emailListSize).as("Invalid number of emails: {}", emailListSize).isEqualTo(emailCount);
|
|
||||||
result.put("email", emailCount);
|
result.put("email", emailCount);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -418,11 +394,13 @@ public class FlowableModelTestUtils {
|
|||||||
.defaultHeaders(h -> h.setBasicAuth(username, password))
|
.defaultHeaders(h -> h.setBasicAuth(username, password))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
File file = new File(Paths.get("src/test/resources/test-auto-deploy-apps", appModelKey + ".zip").toString());
|
File file = Paths.get("src", "test", "resources", "test-auto-deploy-apps", appModelKey + ".zip").toFile();
|
||||||
restClient.get()
|
restClient.get()
|
||||||
.uri("/design-api/workspaces/" + workspaceKey + "/apps/" + appModelKey + "/export?excludeChildReferences=false")
|
.uri("/design-api/workspaces/" + workspaceKey + "/apps/" + appModelKey + "/export?excludeChildReferences=false")
|
||||||
.exchange((req, resp) -> {
|
.exchange((req, resp) -> {
|
||||||
StreamUtils.copy(resp.getBody(), new FileOutputStream(file, false));
|
try (FileOutputStream outputStream = new FileOutputStream(file, false)) {
|
||||||
|
StreamUtils.copy(resp.getBody(), outputStream);
|
||||||
|
}
|
||||||
return file;
|
return file;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -436,12 +414,49 @@ public class FlowableModelTestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ObjectNode loadObjectNodeFromResources(String path) {
|
public ObjectNode loadObjectNodeFromResources(String path) {
|
||||||
if (path.isEmpty()) return jsonUtils.getEmptyObjectNode();
|
if (path == null || path.isEmpty()) return jsonUtils.getEmptyObjectNode();
|
||||||
else return jsonUtils.loadObjectNodeFromFile(Paths.get("src","test", "resources", path).toString());
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) {
|
||||||
|
Assertions.assertThat(inputStream).as("Resource not found: %s", path).isNotNull();
|
||||||
|
JsonNode jsonNode = jsonUtils.convertJsonStringToJsonNode(new String(inputStream.readAllBytes(), StandardCharsets.UTF_8));
|
||||||
|
Assertions.assertThat(jsonNode).as("Resource %s is not a JSON object", path).isInstanceOf(ObjectNode.class);
|
||||||
|
return (ObjectNode) jsonNode;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException("Cannot read resource: " + path, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> loadMapFromResources(String path) {
|
/**
|
||||||
return jsonUtils.convertJsonNodeToMap(loadObjectNodeFromResources(path));
|
* Loads process start variables from a JSON resource. All parameters must be
|
||||||
|
* declared explicitly: root process variables inside "__ROOT", call activity
|
||||||
|
* in/out mappings inside "__IN" and "__OUT". Any other top-level field fails.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> loadTestVarsFromResources(String path) {
|
||||||
|
ObjectNode node = loadObjectNodeFromResources(path);
|
||||||
|
Map<String, Object> vars = new LinkedHashMap<>();
|
||||||
|
for (Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); fields.hasNext(); ) {
|
||||||
|
Map.Entry<String, JsonNode> field = fields.next();
|
||||||
|
String key = field.getKey();
|
||||||
|
switch (key) {
|
||||||
|
case ROOT_PARAM:
|
||||||
|
Assertions.assertThat(field.getValue().isObject())
|
||||||
|
.as("%s in %s must be a JSON object", ROOT_PARAM, path).isTrue();
|
||||||
|
for (Iterator<String> rootKeys = field.getValue().fieldNames(); rootKeys.hasNext(); ) {
|
||||||
|
String rootKey = rootKeys.next();
|
||||||
|
Assertions.assertThat(rootKey.startsWith("__"))
|
||||||
|
.as("Root parameter '%s' in %s must not start with '__'", rootKey, path).isFalse();
|
||||||
|
}
|
||||||
|
vars.putAll(jsonUtils.convertJsonNodeToMap(field.getValue()));
|
||||||
|
break;
|
||||||
|
case IN_PARAM:
|
||||||
|
case OUT_PARAM:
|
||||||
|
vars.put(key, jsonUtils.convertJsonNodeToMap(field.getValue()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Implicit parameter '" + key + "' in " + path
|
||||||
|
+ ": parameters must be declared explicitly inside " + ROOT_PARAM + ", " + IN_PARAM + " or " + OUT_PARAM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeTimer(String processId) {
|
public void executeTimer(String processId) {
|
||||||
@@ -450,22 +465,6 @@ public class FlowableModelTestUtils {
|
|||||||
managementService.moveTimerToExecutableJob(timerJobEntity.getId());
|
managementService.moveTimerToExecutableJob(timerJobEntity.getId());
|
||||||
managementService.executeJob(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) {
|
public void setTestAuthenticatedUser(String userId, String tenantId, String... groupKeys) {
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
this.resetTestAuthenticatedUser();
|
this.resetTestAuthenticatedUser();
|
||||||
@@ -585,21 +584,21 @@ public class FlowableModelTestUtils {
|
|||||||
CallActivity callActivity = new CallActivity();
|
CallActivity callActivity = new CallActivity();
|
||||||
callActivity.setId(testKey);
|
callActivity.setId(testKey);
|
||||||
callActivity.setCalledElement(testKey);
|
callActivity.setCalledElement(testKey);
|
||||||
JsonNode inNode = variables.get("__IN");
|
JsonNode inNode = variables.get(IN_PARAM);
|
||||||
if (inNode instanceof ObjectNode) {
|
if (inNode instanceof ObjectNode) {
|
||||||
Map<String, Object> inMap = jsonUtils.convertObjectNodeToMap((ObjectNode) inNode);
|
Map<String, Object> inMap = jsonUtils.convertObjectNodeToMap((ObjectNode) inNode);
|
||||||
ArrayList<IOParameter> inParameters = new ArrayList<>();
|
ArrayList<IOParameter> inParameters = new ArrayList<>();
|
||||||
for (Map.Entry<String, Object> entry : inMap.entrySet()) {
|
for (Map.Entry<String, Object> entry : inMap.entrySet()) {
|
||||||
IOParameter ioParameter = new IOParameter();
|
IOParameter ioParameter = new IOParameter();
|
||||||
ioParameter.setSourceExpression("${__IN." + entry.getKey() + "}");
|
ioParameter.setSourceExpression("${" + IN_PARAM + "." + entry.getKey() + "}");
|
||||||
ioParameter.setTarget(entry.getKey());
|
ioParameter.setTarget(entry.getKey());
|
||||||
inParameters.add(ioParameter);
|
inParameters.add(ioParameter);
|
||||||
}
|
}
|
||||||
callActivity.setInParameters(inParameters);
|
callActivity.setInParameters(inParameters);
|
||||||
}
|
}
|
||||||
JsonNode outNode = variables.get("__OUT");
|
JsonNode outNode = variables.get(OUT_PARAM);
|
||||||
if (outNode instanceof ObjectNode) {
|
if (outNode instanceof ObjectNode) {
|
||||||
Map<String, String> outMap = (Map) jsonUtils.convertJsonNodeToMap(variables).get("__OUT");
|
Map<String, String> outMap = (Map) jsonUtils.convertJsonNodeToMap(variables).get(OUT_PARAM);
|
||||||
ArrayList<IOParameter> outParameters = new ArrayList<>();
|
ArrayList<IOParameter> outParameters = new ArrayList<>();
|
||||||
for (Map.Entry<String, String> entry : outMap.entrySet()) {
|
for (Map.Entry<String, String> entry : outMap.entrySet()) {
|
||||||
IOParameter ioParameter = new IOParameter();
|
IOParameter ioParameter = new IOParameter();
|
||||||
|
|||||||
@@ -24,10 +24,16 @@ public class TestMailServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
if (greenMail != null) {
|
||||||
greenMail.stop();
|
greenMail.stop();
|
||||||
|
greenMail = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MimeMessage[] getMessages() {
|
public MimeMessage[] getMessages() {
|
||||||
|
if (greenMail == null) {
|
||||||
|
throw new IllegalStateException("TestMailServer is not started, is the TestMailServerExtension registered?");
|
||||||
|
}
|
||||||
return greenMail.getReceivedMessages();
|
return greenMail.getReceivedMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import org.junit.jupiter.params.provider.Arguments;
|
|||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -53,7 +52,6 @@ public class ModelTest {
|
|||||||
Assertions.assertThat(rootProcessPayload.get("dataEntry")).isEqualTo("my root text");
|
Assertions.assertThat(rootProcessPayload.get("dataEntry")).isEqualTo("my root text");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Stream<Arguments> p001TestData() {
|
private Stream<Arguments> p001TestData() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of("admin", "model/test/P001/initiator.json")
|
Arguments.of("admin", "model/test/P001/initiator.json")
|
||||||
@@ -64,14 +62,20 @@ public class ModelTest {
|
|||||||
public void p001Test(String initiator, String path) {
|
public void p001Test(String initiator, String path) {
|
||||||
flowableModelTest.setTestAuthenticatedUser(initiator, null);
|
flowableModelTest.setTestAuthenticatedUser(initiator, null);
|
||||||
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P001",
|
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P001",
|
||||||
flowableModelTest.loadMapFromResources(path));
|
flowableModelTest.loadTestVarsFromResources(path));
|
||||||
flowableModelTest.completeOpenTask("TST_P001_T001", flowableModelTest.emptyNode(), "complete");
|
flowableModelTest.completeOpenTask("TST_P001_T001", flowableModelTest.emptyNode(), "complete");
|
||||||
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
|
||||||
Assertions.assertThat(rootProcessPayload.get("rootResult")).isEqualTo(null);
|
Assertions.assertThat(rootProcessPayload.get("rootResult")).isEqualTo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@Test
|
||||||
|
public void implicitParameterTest() {
|
||||||
|
Assertions.assertThatThrownBy(() -> flowableModelTest.loadTestVarsFromResources("model/test/implicit.json"))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("Implicit parameter 'param'");
|
||||||
|
}
|
||||||
|
|
||||||
private Stream<Arguments> p002TestData() {
|
private Stream<Arguments> p002TestData() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of("model/test/P002/boolean1.json", true, false),
|
Arguments.of("model/test/P002/boolean1.json", true, false),
|
||||||
@@ -88,7 +92,7 @@ public class ModelTest {
|
|||||||
@MethodSource("p002TestData")
|
@MethodSource("p002TestData")
|
||||||
public void p002Test(String path, Object result, Object out) {
|
public void p002Test(String path, Object result, Object out) {
|
||||||
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P002",
|
ObjectNode processIds = flowableModelTest.createRootTestProcessInstance("TST_P002",
|
||||||
flowableModelTest.loadMapFromResources(path));
|
flowableModelTest.loadTestVarsFromResources(path));
|
||||||
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
Map<String, Object> rootProcessPayload = flowableModelTest.getHistProcessPayload(processIds.get(FlowableModelTestUtils.ROOT_PROCESS_ID).asText());
|
||||||
|
|
||||||
List<EmailDto> mail = flowableModelTest.getMailList();
|
List<EmailDto> mail = flowableModelTest.getMailList();
|
||||||
@@ -104,7 +108,7 @@ public class ModelTest {
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("p002ExcelTestData")
|
@MethodSource("p002ExcelTestData")
|
||||||
public void p002ExcelTest(String path, JsonNode argument) {
|
public void p002ExcelTest(String path, JsonNode argument) {
|
||||||
ObjectNode result = flowableModelTest.testJsonExcelRow(path, argument);
|
flowableModelTest.testJsonExcelRow(path, argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<Arguments> p002ExcelTestData2() {
|
private Stream<Arguments> p002ExcelTestData2() {
|
||||||
@@ -116,7 +120,7 @@ public class ModelTest {
|
|||||||
Map<String, Object> result = flowableModelTest.testObjExcelRow(path, argument);
|
Map<String, Object> result = flowableModelTest.testObjExcelRow(path, argument);
|
||||||
flowableModelTest.checkAndAssertEmail(result, 1,
|
flowableModelTest.checkAndAssertEmail(result, 1,
|
||||||
"Email", "Test", "test@flowable.com");
|
"Email", "Test", "test@flowable.com");
|
||||||
flowableModelTest.checkAndAndAssertAuditRecord(result, 1,
|
flowableModelTest.checkAndAssertAuditRecord(result, 1,
|
||||||
"Audit record", "TST_P002 Audit trail entry", "system", null);
|
"Audit record", "TST_P002 Audit trail entry", "system", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": true,
|
"__ROOT": {
|
||||||
|
"param": true
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": false
|
"param": false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": false,
|
"__ROOT": {
|
||||||
|
"param": false
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": true
|
"param": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": "2025-11-12",
|
"__ROOT": {
|
||||||
|
"param": "2025-11-12"
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": "2025-11-11"
|
"param": "2025-11-11"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": 123.456,
|
"__ROOT": {
|
||||||
|
"param": 123.456
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": 456.789
|
"param": 456.789
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": 123,
|
"__ROOT": {
|
||||||
|
"param": 123
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": 456
|
"param": 456
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": "hello root",
|
"__ROOT": {
|
||||||
|
"param": "hello root"
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": "hello"
|
"param": "hello"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": "123",
|
"__ROOT": {
|
||||||
|
"param": "123"
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": "456"
|
"param": "456"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"param": "123.456,",
|
"__ROOT": {
|
||||||
|
"param": "123.456,"
|
||||||
|
},
|
||||||
"__IN": {
|
"__IN": {
|
||||||
"param": "456.789"
|
"param": "456.789"
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"param": true,
|
||||||
|
"__IN": {
|
||||||
|
"param": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user