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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.monitor.commons;

import lombok.Getter;

import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Getter
public class CaseResultInfos {
private final UUID caseResultUuid;

private final UUID executionUuid;

private final UUID reportUuid;

private final UUID resultUuid;

private final String stepType;

private final String status;

public CaseResultInfos(UUID caseResultUuid, UUID executionUuid, UUID reportUuid, UUID resultUuid, String stepType, String status) {
this.caseResultUuid = caseResultUuid;
this.executionUuid = executionUuid;
this.reportUuid = reportUuid;
this.resultUuid = resultUuid;
this.stepType = stepType;
this.status = status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ public class ProcessExecutionStep {
private UUID reportId;
private Instant startedAt;
private Instant completedAt;
private UUID resultCaseId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,16 @@ public ResponseEntity<Void> deleteExecution(@PathVariable UUID executionId) {
ResponseEntity.ok().build() :
ResponseEntity.notFound().build();
}

@PostMapping("/execute/security-analysis-using-servers")
@Operation(summary = "Execute a security analysis process using existing servers (network-modification-server and security-analysis-server)")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The security analysis execution has been started")})
public ResponseEntity<UUID> executeSecurityAnalysisUsingServers(
@RequestParam UUID caseUuid,
@RequestBody SecurityAnalysisConfig securityAnalysisConfig,
@RequestHeader(HEADER_USER_ID) String userId) {
UUID executionId = monitorService.executeProcessUsingServers(caseUuid, userId, securityAnalysisConfig);
return ResponseEntity.ok(executionId);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,7 @@ public class ProcessExecutionStepEntity {

@Column
private Instant completedAt;

@Column
private UUID resultCaseId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static ProcessExecutionStep toDto(ProcessExecutionStepEntity entity) {
.reportId(entity.getReportId())
.startedAt(entity.getStartedAt())
.completedAt(entity.getCompletedAt())
.resultCaseId(entity.getResultCaseId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.monitor.server.services;

import org.gridsuite.monitor.commons.CaseResultInfos;
import org.gridsuite.monitor.commons.ProcessConfig;
import org.gridsuite.monitor.commons.ProcessExecutionStep;
import org.gridsuite.monitor.commons.ProcessRunMessage;
import org.gridsuite.monitor.commons.ProcessStatus;
import org.gridsuite.monitor.commons.ResultType;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.commons.StepStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Configuration
public class ConsumerServiceUsingServers {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerServiceUsingServers.class);

private final NetworkModificationRestService networkModificationRestService;
private final SecurityAnalysisRestService securityAnalysisRestService;
private final MonitorService monitorService;

private record ProcessExecutionContext(
UUID applyModificationsStepId,
UUID securityAnalysisStepId,
UUID caseUuid,
List<String> contingencies,
UUID parametersUuid
) { }

private final Map<UUID, ProcessExecutionContext> processExecutionContexts = new ConcurrentHashMap<>();

@Autowired
ConsumerServiceUsingServers(NetworkModificationRestService networkModificationRestService,
SecurityAnalysisRestService securityAnalysisRestService,
MonitorService monitorService) {
this.networkModificationRestService = networkModificationRestService;
this.securityAnalysisRestService = securityAnalysisRestService;
this.monitorService = monitorService;
}

@Bean
public <T extends ProcessConfig> Consumer<Message<ProcessRunMessage<T>>> consumeRunUsingServers() {
// consume message to launch process
//
return message -> {
ProcessRunMessage<T> processRunMessage = message.getPayload();
UUID caseUuid = processRunMessage.caseUuid();
UUID executionId = processRunMessage.executionId();

ProcessConfig processConfig = processRunMessage.config();
List<UUID> modificationUuids = ((SecurityAnalysisConfig) processConfig).modificationUuids();

UUID applyModificationsStepId = UUID.randomUUID();
UUID securityAnalysisStepId = UUID.randomUUID();

List<String> contingencies = null;
UUID parametersUuid = null;
switch (processConfig.processType()) {

Check warning on line 78 in monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this "switch" statement by "if" statements to increase readability.

See more on https://sonarcloud.io/project/issues?id=org.gridsuite%3Amonitor-core&issues=AZyQTJDkxpdnehsRS7ZQ&open=AZyQTJDkxpdnehsRS7ZQ&pullRequest=47
case SECURITY_ANALYSIS -> {
contingencies = ((SecurityAnalysisConfig) processConfig).contingencies();
parametersUuid = ((SecurityAnalysisConfig) processConfig).parametersUuid();
}
}
Comment on lines +76 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast for unsupported process types.

Only SECURITY_ANALYSIS is handled; other types leave contingencies/parametersUuid null but still schedule security analysis and call the SA server. Add a default branch that marks the execution failed and returns.

✅ Add an explicit default branch
             List<String> contingencies = null;
             UUID parametersUuid = null;
             switch (processConfig.processType()) {
                 case SECURITY_ANALYSIS -> {
-                    contingencies = ((SecurityAnalysisConfig) processConfig).contingencies();
-                    parametersUuid = ((SecurityAnalysisConfig) processConfig).parametersUuid();
+                    SecurityAnalysisConfig saConfig = (SecurityAnalysisConfig) processConfig;
+                    contingencies = saConfig.contingencies();
+                    parametersUuid = saConfig.parametersUuid();
                 }
+                default -> {
+                    LOGGER.error("Unsupported process type {} for executionId: {}", processConfig.processType(), executionId);
+                    monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
+                    return;
+                }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<String> contingencies = null;
UUID parametersUuid = null;
switch (processConfig.processType()) {
case SECURITY_ANALYSIS -> {
contingencies = ((SecurityAnalysisConfig) processConfig).contingencies();
parametersUuid = ((SecurityAnalysisConfig) processConfig).parametersUuid();
}
}
List<String> contingencies = null;
UUID parametersUuid = null;
switch (processConfig.processType()) {
case SECURITY_ANALYSIS -> {
SecurityAnalysisConfig saConfig = (SecurityAnalysisConfig) processConfig;
contingencies = saConfig.contingencies();
parametersUuid = saConfig.parametersUuid();
}
default -> {
LOGGER.error("Unsupported process type {} for executionId: {}", processConfig.processType(), executionId);
monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
return;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java`
around lines 76 - 83, The switch over processConfig.processType() only handles
SECURITY_ANALYSIS and leaves contingencies and parametersUuid null for other
types; add an explicit default branch in that switch (inside
ConsumerServiceUsingServers -> the block where contingencies and parametersUuid
are set) that marks the execution as failed and returns early instead of
continuing to schedule a security analysis, e.g. call the failure-path used
elsewhere in this class (or set the appropriate execution status/emit error and
return) so unsupported process types cannot proceed with null
contingencies/parametersUuid.


processExecutionContexts.put(executionId, new ProcessExecutionContext(
applyModificationsStepId,
securityAnalysisStepId,
caseUuid,
contingencies,
parametersUuid
));

monitorService.updateExecutionStatus(executionId, ProcessStatus.RUNNING, null, Instant.now(), null);
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(applyModificationsStepId, "APPLY_MODIFICATIONS", 0, StepStatus.RUNNING, null, null, null, Instant.now(), null, null));
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(securityAnalysisStepId, "SECURITY_ANALYSIS", 1, StepStatus.SCHEDULED, null, null, null, null, null, null));

// call network-modification-server to apply modifications
networkModificationRestService.applyModifications(caseUuid, executionId, modificationUuids);
Comment on lines +99 to +100
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle REST client failures to avoid stuck RUNNING executions.

Lines 99-100 and 132 call external services without exception handling. If either call throws, execution state can remain inconsistent and context may leak.

🧯 Proposed fix
-            networkModificationRestService.applyModifications(caseUuid, executionId, modificationUuids);
+            try {
+                networkModificationRestService.applyModifications(caseUuid, executionId, modificationUuids);
+            } catch (RuntimeException ex) {
+                LOGGER.error("applyModifications failed for executionId: {}", executionId, ex);
+                monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
+                processExecutionContexts.remove(executionId);
+            }
...
             if (stepStatus == StepStatus.COMPLETED) {
                 // call security-analysis-server to run security analysis
-                securityAnalysisRestService.runSecurityAnalysis(caseResultUuid, executionId, processExecutionContext.contingencies(), processExecutionContext.parametersUuid());
+                try {
+                    securityAnalysisRestService.runSecurityAnalysis(caseResultUuid, executionId, processExecutionContext.contingencies(), processExecutionContext.parametersUuid());
+                } catch (RuntimeException ex) {
+                    LOGGER.error("runSecurityAnalysis failed for executionId: {}", executionId, ex);
+                    monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
+                    processExecutionContexts.remove(executionId);
+                }
             } else {
                 processExecutionContexts.remove(executionId);
             }

Also applies to: 130-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java`
around lines 99 - 100, Wrap external REST calls (notably
networkModificationRestService.applyModifications(caseUuid, executionId,
modificationUuids) and the other REST call in the same method) in try/catch to
prevent uncaught exceptions from leaving executions stuck in RUNNING; on failure
catch Exception, log the error with context
(caseUuid/executionId/modificationUuids), transition the execution to a terminal
failure state via the existing execution state updater (e.g.,
executionRepository or executionService method used elsewhere in this class),
persist that state change, and ensure any execution context cleanup/rollback
runs in a finally block so no context leaks remain.

};
Comment on lines +85 to +101
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard against duplicate run messages to avoid double modifications.

Message delivery is typically at-least-once; this code generates new step IDs and calls applyModifications on every delivery, which can duplicate steps and apply changes twice. Add a deduplication guard (and keep the external call idempotent by executionId).

🛡️ Proposed deduplication guard
-            processExecutionContexts.put(executionId, new ProcessExecutionContext(
-                applyModificationsStepId,
-                securityAnalysisStepId,
-                caseUuid,
-                contingencies,
-                parametersUuid
-            ));
+            ProcessExecutionContext newContext = new ProcessExecutionContext(
+                applyModificationsStepId,
+                securityAnalysisStepId,
+                caseUuid,
+                contingencies,
+                parametersUuid
+            );
+            ProcessExecutionContext existing = processExecutionContexts.putIfAbsent(executionId, newContext);
+            if (existing != null) {
+                LOGGER.warn("Duplicate run message ignored for executionId: {}", executionId);
+                return;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
processExecutionContexts.put(executionId, new ProcessExecutionContext(
applyModificationsStepId,
securityAnalysisStepId,
caseUuid,
contingencies,
parametersUuid
));
monitorService.updateExecutionStatus(executionId, ProcessStatus.RUNNING, null, Instant.now(), null);
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(applyModificationsStepId, "APPLY_MODIFICATIONS", 0, StepStatus.RUNNING, null, null, null, Instant.now(), null));
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(securityAnalysisStepId, "SECURITY_ANALYSIS", 1, StepStatus.SCHEDULED, null, null, null, null, null));
// call network-modification-server to apply modifications
networkModificationRestService.applyModifications(caseUuid, executionId, modificationUuids);
};
ProcessExecutionContext newContext = new ProcessExecutionContext(
applyModificationsStepId,
securityAnalysisStepId,
caseUuid,
contingencies,
parametersUuid
);
ProcessExecutionContext existing = processExecutionContexts.putIfAbsent(executionId, newContext);
if (existing != null) {
LOGGER.warn("Duplicate run message ignored for executionId: {}", executionId);
return;
}
monitorService.updateExecutionStatus(executionId, ProcessStatus.RUNNING, null, Instant.now(), null);
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(applyModificationsStepId, "APPLY_MODIFICATIONS", 0, StepStatus.RUNNING, null, null, null, Instant.now(), null));
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(securityAnalysisStepId, "SECURITY_ANALYSIS", 1, StepStatus.SCHEDULED, null, null, null, null, null));
// call network-modification-server to apply modifications
networkModificationRestService.applyModifications(caseUuid, executionId, modificationUuids);
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java`
around lines 85 - 101, The code currently creates new ProcessExecutionContext
entries and always calls networkModificationRestService.applyModifications for
every incoming message, which can cause duplicate modifications; add a
deduplication guard that checks whether this executionId already exists or is
past the start state before creating a new ProcessExecutionContext and updating
step statuses (e.g., check processExecutionContexts.containsKey(executionId) or
consult monitorService for an existing ProcessStatus for executionId and return
early if already RUNNING/COMPLETED), and ensure the external call
networkModificationRestService.applyModifications is invoked only when the guard
allows (also verify applyModifications treats executionId idempotently on the
network-modification-server side).

}

@Bean
public Consumer<Message<CaseResultInfos>> consumeNetworkModifications() {
// consume message received from network-modification-server
//
return message -> {
CaseResultInfos caseResultInfos = message.getPayload();
UUID caseResultUuid = caseResultInfos.getCaseResultUuid();
UUID executionId = caseResultInfos.getExecutionUuid();
String stepType = caseResultInfos.getStepType();
UUID reportUuid = caseResultInfos.getReportUuid();

StepStatus stepStatus = StepStatus.valueOf(caseResultInfos.getStatus());

ProcessExecutionContext processExecutionContext = processExecutionContexts.get(executionId);
if (processExecutionContext == null) {
LOGGER.error("Process execution context not found for executionId: {}", executionId);
return;
}

monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(processExecutionContext.applyModificationsStepId(), stepType, 0, stepStatus, null, null, reportUuid, null, Instant.now(), caseResultUuid));
monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(processExecutionContext.securityAnalysisStepId(), "SECURITY_ANALYSIS", 1,
stepStatus == StepStatus.COMPLETED ? StepStatus.RUNNING : StepStatus.SKIPPED,
null, null, null, Instant.now(), null, null));

if (stepStatus == StepStatus.COMPLETED) {
// call security-analysis-server to run security analysis
securityAnalysisRestService.runSecurityAnalysis(caseResultUuid, executionId, processExecutionContext.contingencies(), processExecutionContext.parametersUuid());
} else {
processExecutionContexts.remove(executionId);
}
Comment on lines +130 to +135
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update execution status when network modifications fail.

On non-COMPLETED statuses you skip security analysis and remove the context, but the execution remains RUNNING. Mark the execution as FAILED (or CANCELLED) and set the end time.

🧭 Propagate failure to the execution status
             if (stepStatus == StepStatus.COMPLETED) {
                 // call security-analysis-server to run security analysis
                 securityAnalysisRestService.runSecurityAnalysis(caseResultUuid, executionId, processExecutionContext.contingencies(), processExecutionContext.parametersUuid());
             } else {
+                monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
                 processExecutionContexts.remove(executionId);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java`
around lines 129 - 134, In ConsumerServiceUsingServers, when handling
non-COMPLETED stepStatus (the else branch that currently only does
processExecutionContexts.remove(executionId)), update the corresponding
execution record to a terminal state (FAILED or CANCELLED) and set its end time
before removing the context; locate the execution by executionId (or via any
Execution/ExecutionRepository/ExecutionService used elsewhere in this class) and
call its update/persist method so the execution status and endTimestamp are
saved, then remove the processExecutionContexts entry and skip calling
securityAnalysisRestService.runSecurityAnalysis.

};
}

@Bean
public Consumer<Message<CaseResultInfos>> consumeSecurityAnalysis() {
// consume message received from security-analysis-server
//
return message -> {
CaseResultInfos caseResultInfos = message.getPayload();
UUID executionId = caseResultInfos.getExecutionUuid();
String stepType = caseResultInfos.getStepType();
UUID reportUuid = caseResultInfos.getReportUuid();
UUID resultUuid = caseResultInfos.getResultUuid();
StepStatus stepStatus = StepStatus.valueOf(caseResultInfos.getStatus());
UUID caseResultUuid = caseResultInfos.getCaseResultUuid();

Comment on lines +149 to +151
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the file and examine the current state around lines 148-149
find . -type f -name "ConsumerServiceUsingServers.java" -exec wc -l {} \;

Repository: gridsuite/monitor-core

Length of output: 171


🏁 Script executed:

# Check the file structure and content around the specified lines
find . -type f -name "ConsumerServiceUsingServers.java" -exec sed -n '140,160p' {} +

Repository: gridsuite/monitor-core

Length of output: 1332


🏁 Script executed:

# Find CaseResultInfos class definition
fd 'CaseResultInfos.java' -x cat -n {}

Repository: gridsuite/monitor-core

Length of output: 1386


🏁 Script executed:

# Search for all StepStatus.valueOf usages in the codebase
rg -n "StepStatus\.valueOf" -g '*.java' -B2 -A2

Repository: gridsuite/monitor-core

Length of output: 1740


🏁 Script executed:

# Find consumeNetworkModifications method
rg -n "consumeNetworkModifications" -g '*.java' -B2 -A10

Repository: gridsuite/monitor-core

Length of output: 2030


🏁 Script executed:

# Check StepStatus enum definition to see valid values
fd 'StepStatus.java' -x cat -n {}

Repository: gridsuite/monitor-core

Length of output: 663


🏁 Script executed:

# Check the method name for line 148
sed -n '130,150p' ./monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java

Repository: gridsuite/monitor-core

Length of output: 1133


🏁 Script executed:

# Search for any other StepStatus usages or related parsing
rg -n "StepStatus|getStatus" ./monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java -B1 -A1

Repository: gridsuite/monitor-core

Length of output: 2294


🏁 Script executed:

# Check if there's any validation or documentation about status values from external servers
rg -n "status|Status" ./monitor-server/src/main/java/org/gridsuite/monitor/commons/CaseResultInfos.java -B2 -A2

Repository: gridsuite/monitor-core

Length of output: 187


Harden status parsing from external servers in two locations.

StepStatus.valueOf will throw if the server sends an unexpected or case-mismatched status, causing the consumer to crash and potentially retry indefinitely. Unsafe parsing exists at:

  • Line 114 in consumeNetworkModifications()
  • Line 148 in consumeSecurityAnalysis()

Use safe parsing with error handling and fallback in both methods.

🛡️ Safer status parsing pattern
-            StepStatus stepStatus = StepStatus.valueOf(caseResultInfos.getStatus());
+            StepStatus stepStatus;
+            try {
+                stepStatus = StepStatus.valueOf(caseResultInfos.getStatus());
+            } catch (IllegalArgumentException ex) {
+                LOGGER.error("Unsupported step status '{}' for executionId: {}", caseResultInfos.getStatus(), executionId, ex);
+                monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
+                processExecutionContexts.remove(executionId);
+                return;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
StepStatus stepStatus = StepStatus.valueOf(caseResultInfos.getStatus());
StepStatus stepStatus;
try {
stepStatus = StepStatus.valueOf(caseResultInfos.getStatus());
} catch (IllegalArgumentException ex) {
LOGGER.error("Unsupported step status '{}' for executionId: {}", caseResultInfos.getStatus(), executionId, ex);
monitorService.updateExecutionStatus(executionId, ProcessStatus.FAILED, null, null, Instant.now());
processExecutionContexts.remove(executionId);
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/services/ConsumerServiceUsingServers.java`
around lines 148 - 149, The call StepStatus.valueOf(caseResultInfos.getStatus())
can throw for unexpected server values; in both consumeNetworkModifications()
and consumeSecurityAnalysis() replace direct valueOf usage with safe parsing:
catch IllegalArgumentException/NullPointerException around the valueOf call (or
use StepStatus.fromString helper) and fall back to a default safe status (e.g.,
StepStatus.UNKNOWN or StepStatus.FAILED) while logging the raw status and
context; ensure the surrounding logic uses the fallback status so consumers
don't crash or loop indefinitely and add a unit-friendly message to logs
identifying the method (consumeNetworkModifications / consumeSecurityAnalysis)
and the offending status string.

ProcessExecutionContext processExecutionContext = processExecutionContexts.get(executionId);
if (processExecutionContext == null) {
LOGGER.error("Process execution context not found for executionId: {}", executionId);
return;
}

monitorService.updateStepStatus(executionId,
new ProcessExecutionStep(processExecutionContext.securityAnalysisStepId(), stepType, 1, stepStatus, resultUuid, ResultType.SECURITY_ANALYSIS, reportUuid, null, Instant.now(), caseResultUuid));
monitorService.updateExecutionStatus(executionId,
stepStatus == StepStatus.COMPLETED ? ProcessStatus.COMPLETED : ProcessStatus.FAILED,
null, null, Instant.now());

processExecutionContexts.remove(executionId);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,16 @@ private void updateStep(ProcessExecutionEntity execution, ProcessExecutionStepEn
existingStep.setStatus(stepEntity.getStatus());
existingStep.setStepType(stepEntity.getStepType());
existingStep.setStepOrder(stepEntity.getStepOrder());
existingStep.setStartedAt(stepEntity.getStartedAt());
existingStep.setCompletedAt(stepEntity.getCompletedAt());
if (stepEntity.getStartedAt() != null) {
existingStep.setStartedAt(stepEntity.getStartedAt());
}
if (stepEntity.getCompletedAt() != null) {
existingStep.setCompletedAt(stepEntity.getCompletedAt());
}
existingStep.setResultId(stepEntity.getResultId());
existingStep.setResultType(stepEntity.getResultType());
existingStep.setReportId(stepEntity.getReportId());
existingStep.setResultCaseId(stepEntity.getResultCaseId());
},
() -> steps.add(stepEntity));
}
Expand Down Expand Up @@ -146,6 +151,7 @@ private ProcessExecutionStepEntity toStepEntity(ProcessExecutionStep processExec
.reportId(processExecutionStep.getReportId())
.startedAt(processExecutionStep.getStartedAt())
.completedAt(processExecutionStep.getCompletedAt())
.resultCaseId(processExecutionStep.getResultCaseId())
.build();
}

Expand Down Expand Up @@ -239,4 +245,25 @@ public boolean deleteExecution(UUID executionId) {
}
return false;
}

@Transactional
public UUID executeProcessUsingServers(UUID caseUuid, String userId, ProcessConfig processConfig) {
ProcessExecutionEntity execution = ProcessExecutionEntity.builder()
.type(processConfig.processType().name())
.caseUuid(caseUuid)
.status(ProcessStatus.SCHEDULED)
.scheduledAt(Instant.now())
.userId(userId)
.executionEnvName("using-servers-env")
.build();
executionRepository.save(execution);

String bindingName = switch (processConfig.processType()) {
case SECURITY_ANALYSIS -> "publishRunSecurityAnalysisUsingServers-out-0";
};
notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId(), null, bindingName);

return execution.getId();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.gridsuite.monitor.server.services;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Service
public class NetworkModificationRestService {
private static final String NETWORK_MODIFICATION_SERVER_API_VERSION = "v1";
private static final String DELIMITER = "/";

private final RestTemplate networkModificationServerRest;
private final String networkModificationServerBaseUri;

public NetworkModificationRestService(@Value("${gridsuite.services.network-modification-server.base-uri:http://network-modification-server/}") String networkModificationServerBaseUri,
RestTemplateBuilder restTemplateBuilder) {
this.networkModificationServerRest = restTemplateBuilder.build();
this.networkModificationServerBaseUri = networkModificationServerBaseUri;
}

public void applyModifications(UUID caseUuid, UUID executionId, List<UUID> modificationUuids) {
var uriComponentsBuilder = UriComponentsBuilder.fromPath(DELIMITER + NETWORK_MODIFICATION_SERVER_API_VERSION + DELIMITER + "cases/{caseUuid}/network-composite-modifications");
var path = uriComponentsBuilder
.queryParam("executionUuid", executionId)
.queryParam("uuids", modificationUuids)
.buildAndExpand(caseUuid)
.toUriString();

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity<>(null, headers);

Check warning on line 50 in monitor-server/src/main/java/org/gridsuite/monitor/server/services/NetworkModificationRestService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=org.gridsuite%3Amonitor-core&issues=AZyQTJAsxpdnehsRS7ZP&open=AZyQTJAsxpdnehsRS7ZP&pullRequest=47

networkModificationServerRest.exchange(networkModificationServerBaseUri + path, HttpMethod.POST, httpEntity, Void.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public void sendProcessRunMessage(UUID caseUuid, ProcessConfig processConfig, UU
String bindingName = switch (processConfig.processType()) {
case SECURITY_ANALYSIS -> "publishRunSecurityAnalysis-out-0";
};
sendProcessRunMessage(caseUuid, processConfig, executionId, bindingName, debugFileLocation);
}

public void sendProcessRunMessage(UUID caseUuid, ProcessConfig processConfig, UUID executionId, String bindingName, String debugFileLocation) {
ProcessRunMessage<?> message = new ProcessRunMessage<>(executionId, caseUuid, processConfig, debugFileLocation);
publisher.send(bindingName, message);
}
Comment on lines +33 to 36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all call sites of the 5-arg sendProcessRunMessage overload
rg -n --type java 'sendProcessRunMessage\s*\(\s*[^,]+\s*,\s*[^,]+\s*,\s*[^,]+\s*,\s*[^,]+\s*,\s*[^,]+\s*\)' -A 2 -B 2

Repository: gridsuite/monitor-core

Length of output: 2037


🏁 Script executed:

#!/bin/bash
# Get directory structure to understand package layout
git ls-files "monitor-server/src/main/java/org/gridsuite/monitor/server/**" | grep -E "(NotificationService|MonitorService)" | head -20

Repository: gridsuite/monitor-core

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Read the full NotificationService.java to understand the context
wc -l monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java

Repository: gridsuite/monitor-core

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Read NotificationService.java around the method definition
cat -n monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java | head -37

Repository: gridsuite/monitor-core

Length of output: 1817


🏁 Script executed:

#!/bin/bash
# Read MonitorService.java around the call site to see full context
sed -n '255,270p' monitor-server/src/main/java/org/gridsuite/monitor/server/services/MonitorService.java | cat -n

Repository: gridsuite/monitor-core

Length of output: 680


🏁 Script executed:

#!/bin/bash
# Check if there are other call sites to this method
rg 'sendProcessRunMessage' monitor-server/src/main/java/org/gridsuite/monitor/server/services/ -B 1 -A 1

Repository: gridsuite/monitor-core

Length of output: 2266


Fix parameter order mismatch in MonitorService call to the 5-parameter overload.

The call at MonitorService.java:264 passes arguments in the wrong positions for the two adjacent String parameters:

notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId(), null, bindingName);

But the method signature expects:

public void sendProcessRunMessage(..., String bindingName, String debugFileLocation)

This swaps the parameters: null is passed to bindingName (causing publisher.send(null, ...)) and bindingName is passed to debugFileLocation. Correct the argument order:

Fix parameter order
-notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId(), null, bindingName);
+notificationService.sendProcessRunMessage(caseUuid, processConfig, execution.getId(), bindingName, null);

Alternatively, to avoid this fragility in the future, consider whether this call should use the 4-parameter overload instead and let the routing logic remain centralized in NotificationService.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@monitor-server/src/main/java/org/gridsuite/monitor/server/services/NotificationService.java`
around lines 33 - 36, The MonitorService call to
NotificationService.sendProcessRunMessage passes the two String arguments in the
wrong order; update the call site in MonitorService where
notificationService.sendProcessRunMessage(caseUuid, processConfig,
execution.getId(), null, bindingName) is invoked to either swap the last two
args to notificationService.sendProcessRunMessage(caseUuid, processConfig,
execution.getId(), bindingName, null) so bindingName is used for publisher.send,
or replace the call with the 4-parameter overload
(notificationService.sendProcessRunMessage(caseUuid, processConfig,
execution.getId(), bindingName)) to centralize routing logic; ensure you modify
the call that references execution.getId() and bindingName accordingly.

Expand Down
Loading