diff --git a/pom.xml b/pom.xml
index b86cd38..fda4114 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,7 @@
25
+ 0.8.13
@@ -213,6 +214,7 @@
org.jacoco
jacoco-maven-plugin
+ ${jacoco.version}
diff --git a/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java b/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java
index ce92113..03b5963 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/app/config/WebConfig.java
@@ -5,7 +5,6 @@ import java.util.Arrays;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
-import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
@@ -24,7 +23,7 @@ public class WebConfig implements WebMvcConfigurer {
private String allowedOrigins;
@Override
- public void addCorsMappings(@NonNull CorsRegistry registry) {
+ public void addCorsMappings(CorsRegistry registry) {
String[] origins = Arrays.stream(allowedOrigins.split(","))
.map(String::trim)
.filter(StringUtils::hasText)
diff --git a/src/main/java/it/cnr/isti/workflow/manager/assistant/BlockCatalogService.java b/src/main/java/it/cnr/isti/workflow/manager/assistant/BlockCatalogService.java
index 0d2e4b2..4d313a2 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/assistant/BlockCatalogService.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/assistant/BlockCatalogService.java
@@ -120,7 +120,7 @@ public class BlockCatalogService {
}
JsonNode typeNode = descriptor.schema().path("properties").path("type").path("enum");
if (typeNode instanceof ArrayNode enumValues && !enumValues.isEmpty()) {
- return enumValues.get(0).asText();
+ return enumValues.get(0).stringValueOpt().orElse(null);
}
return descriptor.configurationClass();
}
@@ -137,7 +137,7 @@ public class BlockCatalogService {
entry.getKey(),
extractFieldType(entry.getValue()),
requiredFields.contains(entry.getKey()),
- entry.getValue().path("x-ui-placeholder").asText(null),
+ textOrNull(entry.getValue().path("x-ui-placeholder")),
entry.getValue().path("x-ui-structural").asBoolean(false)))
.sorted(Comparator
.comparing(AssistantPromptFieldDescriptor::required).reversed()
@@ -162,7 +162,10 @@ public class BlockCatalogService {
}
java.util.LinkedHashSet result = new java.util.LinkedHashSet<>();
for (JsonNode node : requiredArray) {
- result.add(node.asText());
+ String value = node.stringValueOpt().orElse(null);
+ if (value != null) {
+ result.add(value);
+ }
}
return result;
}
@@ -172,16 +175,27 @@ public class BlockCatalogService {
return "unknown";
}
if (node.has("type")) {
- return node.get("type").asText();
+ return defaultIfNull(textOrNull(node.get("type")), "unknown");
}
if (node.has("$ref")) {
- String ref = node.get("$ref").asText();
+ String ref = textOrNull(node.get("$ref"));
+ if (ref == null) {
+ return "object";
+ }
int separator = ref.lastIndexOf('/');
return separator >= 0 ? ref.substring(separator + 1) : ref;
}
return "object";
}
+ private String textOrNull(JsonNode node) {
+ return node == null ? null : node.stringValueOpt().orElse(null);
+ }
+
+ private String defaultIfNull(String value, String fallback) {
+ return value == null ? fallback : value;
+ }
+
private List> iterable(java.util.Iterator> fields) {
java.util.ArrayList> entries = new java.util.ArrayList<>();
fields.forEachRemaining(entries::add);
diff --git a/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java b/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java
index 55bb699..0b1fe03 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java
@@ -455,7 +455,7 @@ public class FlowAssistantService {
JsonNode root = ObjectMapperHolder.mapper.readTree(extractJsonObject(rawResponse));
JsonNode planNode = root.has("plan") ? root.get("plan") : root;
AssistantFlowPlan plan = ObjectMapperHolder.mapper.treeToValue(planNode, AssistantFlowPlan.class);
- return new ParsedPlan(plan, root.path("rationale").asText(""));
+ return new ParsedPlan(plan, textOrEmpty(root.path("rationale")));
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY,
"Assistant returned an invalid plan payload: " + e.getMessage());
@@ -467,13 +467,13 @@ public class FlowAssistantService {
JsonNode root = ObjectMapperHolder.mapper.readTree(extractJsonObject(rawResponse));
JsonNode blockNode = root.has("block") ? root.get("block") : root;
AssistantConfiguredBlockDraft block = new AssistantConfiguredBlockDraft(
- blockNode.path("blockId").asText(null),
- blockNode.path("name").asText(null),
+ textOrNull(blockNode.path("blockId")),
+ textOrNull(blockNode.path("name")),
blockNode.path("config"));
if (block.blockId() == null || block.config().isMissingNode()) {
throw new IllegalArgumentException("Missing blockId or config");
}
- return new ParsedBlockDraft(block, root.path("rationale").asText(""));
+ return new ParsedBlockDraft(block, textOrEmpty(root.path("rationale")));
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY,
"Assistant returned an invalid block configuration payload: " + e.getMessage());
@@ -497,7 +497,7 @@ public class FlowAssistantService {
connections.add(ObjectMapperHolder.mapper.treeToValue(node, AssistantConnectionDraft.class));
}
}
- return new ParsedConnections(connections, root.path("rationale").asText(""));
+ return new ParsedConnections(connections, textOrEmpty(root.path("rationale")));
} catch (IllegalArgumentException e) {
if (isLikelyNoConnectionsText(rawResponse)) {
return new ParsedConnections(List.of(), rawResponse == null ? "" : rawResponse.trim());
@@ -685,6 +685,15 @@ public class FlowAssistantService {
return value == null || value.isBlank() ? fallback : value;
}
+ private String textOrEmpty(JsonNode node) {
+ String value = textOrNull(node);
+ return value == null ? "" : value;
+ }
+
+ private String textOrNull(JsonNode node) {
+ return node == null ? null : node.stringValueOpt().orElse(null);
+ }
+
private LLMProvider resolveInternalProvider() {
LLMProvider provider = llmProviders.get("internalOllamaLLMProvider");
if (provider != null) {
diff --git a/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java b/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java
index 89765ef..87a3aa0 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/auth/config/JwtAuthenticationFilter.java
@@ -5,7 +5,6 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -46,9 +45,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
"/retriever/");
@Override
- protected void doFilterInternal(@NonNull HttpServletRequest request,
- @NonNull HttpServletResponse response,
- @NonNull FilterChain filterChain) throws ServletException, IOException {
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaCatalogBundler.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaCatalogBundler.java
index 1b8dacf..58303d4 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaCatalogBundler.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaCatalogBundler.java
@@ -90,9 +90,10 @@ public class JsonSchemaCatalogBundler {
private void rewriteSharedRefs(JsonNode node, Set sharedNames) {
if (node instanceof ObjectNode objectNode) {
JsonNode refNode = objectNode.get("$ref");
- if (refNode != null && refNode.isTextual()) {
- String rewritten = rewriteSharedRef(refNode.asText(), sharedNames);
- if (!rewritten.equals(refNode.asText())) {
+ String ref = refNode == null ? null : refNode.stringValueOpt().orElse(null);
+ if (ref != null) {
+ String rewritten = rewriteSharedRef(ref, sharedNames);
+ if (!rewritten.equals(ref)) {
objectNode.put("$ref", rewritten);
}
}
diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java
index d17d432..03687aa 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java
@@ -30,8 +30,8 @@ import com.github.victools.jsonschema.generator.SchemaGenerator;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
import com.github.victools.jsonschema.generator.SchemaVersion;
-import com.github.victools.jsonschema.module.jackson.JacksonModule;
import com.github.victools.jsonschema.module.jackson.JacksonOption;
+import com.github.victools.jsonschema.module.jackson.JacksonSchemaModule;
import it.cnr.isti.workflow.manager.configurations.annotations.FieldRetriever;
import it.cnr.isti.workflow.manager.configurations.annotations.LongText;
@@ -59,7 +59,7 @@ public class JsonSchemaProducer {
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(
SchemaVersion.DRAFT_7, OptionPreset.PLAIN_JSON)
.with(Option.DEFINITIONS_FOR_ALL_OBJECTS)
- .with(new JacksonModule(JacksonOption.IGNORE_TYPE_INFO_TRANSFORM));
+ .with(new JacksonSchemaModule(JacksonOption.IGNORE_TYPE_INFO_TRANSFORM));
this.schemaGenerator = new SchemaGenerator(configBuilder.build());
}
@@ -220,8 +220,9 @@ public class JsonSchemaProducer {
Set currentRequired = new LinkedHashSet<>();
for (JsonNode node : requiredArray) {
- if (node.isTextual()) {
- currentRequired.add(node.asText());
+ String requiredValue = node.stringValueOpt().orElse(null);
+ if (requiredValue != null) {
+ currentRequired.add(requiredValue);
}
}
diff --git a/src/main/java/it/cnr/isti/workflow/manager/containers/Container.java b/src/main/java/it/cnr/isti/workflow/manager/containers/Container.java
index 5f6f62c..6d9ca3d 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/containers/Container.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/containers/Container.java
@@ -51,7 +51,7 @@ public class Container implements FlowNode {
@JsonIgnore
public T getType() {
if (type == null) {
- type = (T) ContainerTypes.get(typeName);
+ type = resolveContainerType(typeName);
}
return type;
}
@@ -67,7 +67,12 @@ public class Container implements FlowNode {
this.outputs = outputs;
this.typeName = type != null ? type.getName() : resolvedTypeName;
this.position = position;
- this.type = type != null ? type : (T) ContainerTypes.get(this.typeName);
+ this.type = type != null ? type : resolveContainerType(this.typeName);
+ }
+
+ @SuppressWarnings("unchecked")
+ private T resolveContainerType(String resolvedTypeName) {
+ return (T) ContainerTypes.get(resolvedTypeName);
}
@Override
diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/NodeExecutors.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/NodeExecutors.java
index b71bbd5..9b002d7 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/NodeExecutors.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/NodeExecutors.java
@@ -38,12 +38,12 @@ public final class NodeExecutors {
Map executionVariables, Map executionVariableDescriptors,
ExecutionEventLogger eventLogger) {
if (node instanceof Block> block) {
- return BlockExecutors.get(block.getType()).execute((Block) block, inputs, authorizations, executionVariables,
- executionVariableDescriptors, eventLogger);
+ return executeBlock(block, inputs, authorizations, executionVariables, executionVariableDescriptors,
+ eventLogger);
}
if (node instanceof Container> container) {
- return ContainerExecutors.get(container.getType()).execute((Container) container, inputs, authorizations,
- executionVariables, executionVariableDescriptors, eventLogger);
+ return executeContainer(container, inputs, authorizations, executionVariables, executionVariableDescriptors,
+ eventLogger);
}
throw new IllegalStateException("No executor found for node type " + node.getClass().getName());
}
@@ -52,8 +52,8 @@ public final class NodeExecutors {
Map executionVariables, Map executionVariableDescriptors,
LLMDescriptor simulatorDescriptor, ExecutionEventLogger eventLogger) {
if (node instanceof Block> block) {
- return BlockExecutors.get(block.getType()).simulate((Block) block, inputs, authorizations, executionVariables,
- executionVariableDescriptors, simulatorDescriptor, eventLogger);
+ return simulateBlock(block, inputs, authorizations, executionVariables, executionVariableDescriptors,
+ simulatorDescriptor, eventLogger);
}
throw new IllegalStateException("No simulation executor found for node type " + node.getClass().getName());
}
@@ -63,8 +63,8 @@ public final class NodeExecutors {
Map executionVariables, Map executionVariableDescriptors,
ExecutionEventLogger eventLogger) {
if (node instanceof Block> block) {
- return BlockExecutors.get(block.getType()).interact((Block) block, inputs, interaction, partialResults,
- authorizations, executionVariables, executionVariableDescriptors, eventLogger);
+ return interactBlock(block, inputs, interaction, partialResults, authorizations, executionVariables,
+ executionVariableDescriptors, eventLogger);
}
throw new IllegalStateException("No interactive executor found for node type " + node.getClass().getName());
}
@@ -73,8 +73,49 @@ public final class NodeExecutors {
Map authorizations, Map executionVariables,
Map executionVariableDescriptors, ExecutionEventLogger eventLogger) {
if (node instanceof Block> block) {
- BlockExecutors.get(block.getType()).cancel((Block) block, inputs, partialResults, authorizations,
- executionVariables, executionVariableDescriptors, eventLogger);
+ cancelBlock(block, inputs, partialResults, authorizations, executionVariables, executionVariableDescriptors,
+ eventLogger);
}
}
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static Map executeBlock(Block> block, List inputs, Map authorizations,
+ Map executionVariables, Map executionVariableDescriptors,
+ ExecutionEventLogger eventLogger) {
+ return BlockExecutors.get(block.getType()).execute((Block) block, inputs, authorizations, executionVariables,
+ executionVariableDescriptors, eventLogger);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static Map executeContainer(Container> container, List inputs,
+ Map authorizations, Map executionVariables,
+ Map executionVariableDescriptors, ExecutionEventLogger eventLogger) {
+ return ContainerExecutors.get(container.getType()).execute((Container) container, inputs, authorizations,
+ executionVariables, executionVariableDescriptors, eventLogger);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static Map simulateBlock(Block> block, List inputs, Map authorizations,
+ Map executionVariables, Map executionVariableDescriptors,
+ LLMDescriptor simulatorDescriptor, ExecutionEventLogger eventLogger) {
+ return BlockExecutors.get(block.getType()).simulate((Block) block, inputs, authorizations, executionVariables,
+ executionVariableDescriptors, simulatorDescriptor, eventLogger);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static InteractionResult interactBlock(Block> block, List inputs, Map interaction,
+ Map partialResults, Map authorizations,
+ Map executionVariables, Map executionVariableDescriptors,
+ ExecutionEventLogger eventLogger) {
+ return BlockExecutors.get(block.getType()).interact((Block) block, inputs, interaction, partialResults,
+ authorizations, executionVariables, executionVariableDescriptors, eventLogger);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static void cancelBlock(Block> block, List inputs, Map partialResults,
+ Map authorizations, Map executionVariables,
+ Map executionVariableDescriptors, ExecutionEventLogger eventLogger) {
+ BlockExecutors.get(block.getType()).cancel((Block) block, inputs, partialResults, authorizations,
+ executionVariables, executionVariableDescriptors, eventLogger);
+ }
}
diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/ConditionalExecutor.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/ConditionalExecutor.java
index 882dc0b..75923a5 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/ConditionalExecutor.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/ConditionalExecutor.java
@@ -7,9 +7,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.MapAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/SwitchExecutor.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/SwitchExecutor.java
index 6ea39f8..f773a99 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/SwitchExecutor.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/SwitchExecutor.java
@@ -8,9 +8,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.MapAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/LoopContainerExecutor.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/LoopContainerExecutor.java
index c720ef5..12a212e 100644
--- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/LoopContainerExecutor.java
+++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/containers/LoopContainerExecutor.java
@@ -9,9 +9,9 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.MapAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@@ -188,7 +188,7 @@ public class LoopContainerExecutor implements ContainerExecutor> STRING_OBJECT_MAP = new TypeReference<>() {
+ };
private final WebClient.Builder webClientBuilder;
private final String mcpBridgeUrl;
@@ -146,7 +149,7 @@ public class MCPAgentService {
}
Map serverConfiguration = server.configuration() == null
? Collections.emptyMap()
- : mapper.convertValue(server.configuration(), Map.class);
+ : mapper.convertValue(server.configuration(), STRING_OBJECT_MAP);
if (server.isCustomServer()) {
String customServerName = server.customServerName();
if (!StringUtils.hasText(customServerName)) {
@@ -324,7 +327,6 @@ public class MCPAgentService {
executionVariables == null ? Map.of() : executionVariables);
}
- @SuppressWarnings("unchecked")
private Map resolveTemplatedObject(Map values, Map templateValues,
Map executionVariables) {
LinkedHashMap resolved = new LinkedHashMap<>();
@@ -335,7 +337,6 @@ public class MCPAgentService {
return resolved;
}
- @SuppressWarnings("unchecked")
private Object resolveTemplatedValue(Object value, Map templateValues,
Map executionVariables) {
if (value instanceof String stringValue) {