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) {