Skip to content

backport steve/#1791#59

Merged
juherr merged 4 commits intomainfrom
features/steve-1791
Aug 28, 2025
Merged

backport steve/#1791#59
juherr merged 4 commits intomainfrom
features/steve-1791

Conversation

@juherr
Copy link
Copy Markdown
Owner

@juherr juherr commented Aug 27, 2025

Backport steve-community#1791

Summary by CodeRabbit

  • New Features

    • Schedule periods in charging profiles are now an ordered list with per-entry validation and clearer validation messages.
  • Bug Fixes

    • Only complete periods are saved and stored chronologically.
    • Missing or invalid profiles now produce explicit not-found / bad-request responses.
  • Refactor

    • UI row handling uses stable numeric indices for predictable add/remove behavior.
    • WebSocket logging moved to injected logger for more consistent runtime logs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 27, 2025

Walkthrough

Replaced map-based schedule periods with a validated List across DTO, mapper, JSP, JS, and repository; repository now sorts and batch-inserts periods and getDetails returns Optional. Converted WebSocketLogger to an injectable component and updated logging wiring; consolidated exception handling into nested SteveException types.

Changes

Cohort / File(s) Summary
DTO: ChargingProfile form model
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java
Replaced Map<String, SchedulePeriod> schedulePeriodMap with List<@NotNull @Valid SchedulePeriod> schedulePeriods; tightened field validations (@PositiveOrZero, @Digits, @Min/@Max); simplified isFromToAndProfileSettingCorrect(); removed numberPhases getter/setter in favor of field constraints.
Mapper: DB → Form mapping
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java
Populate ChargingProfileForm.schedulePeriods (List) instead of a Map; added helper to map period records; switched to local var and removed UUID/key-based mapping and unused imports.
View: JSP bindings
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp
Iterate form.schedulePeriods using status.index; update input path bindings to schedulePeriods[${status.index}]; added form:errors bindings for startPeriodInSeconds and powerLimit.
Frontend JS: dynamic rows
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js
Generate incremental numeric row IDs by inspecting last row (start at 0); change generated input names/ids to schedulePeriods[...]; use const and guard for empty table.
Repository: persistence & retrieval
steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java
getDetails(...) now returns Optional<ChargingProfile.Details>; insertPeriods consumes form.getSchedulePeriods() (List), filters empties, sorts by startPeriodInSeconds, maps to CHARGING_SCHEDULE_PERIOD records (start, powerLimit, numberPhases) and uses batchInsert; retrieval orders periods ascending.
Service / Controller: Optional → NotFound
steve-core/.../ChargePointServiceClient.java, steve-ui-jsp/.../ChargingProfilesController.java
Switched to Optional-based retrieval and now throw SteveException.NotFound("Charging Profile not found") when details are absent.
WebSocket logging & wiring
steve-ocpp-websocket/.../WebSocketLogger.java, .../AbstractWebSocketEndpoint.java, .../PingTask.java, .../Sender.java, .../Ocpp12WebSocketEndpoint.java, .../Ocpp15WebSocketEndpoint.java, .../Ocpp16WebSocketEndpoint.java, steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java
Converted WebSocketLogger from static utility to Spring @Component with instance methods; injected into Sender, PingTask, AbstractWebSocketEndpoint and OCPP endpoint subclasses (constructor signatures updated); replaced static logging calls with injected-instance calls; endpoints moved from pipeline-init to constructor-wired handlers.
Incoming pipeline handler type
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java
Changed handler type from AbstractCallHandler to Consumer<CommunicationContext> (usage via accept unchanged).
Pipeline Sender changes
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java
Added required WebSocketLogger dependency and switched to instance logging; catch now throws SteveException.InternalError.
API docs config
steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
Added static SpringDoc property springdoc.writer-with-order-by-keys = true; local var usage switched to var.
Exception consolidation
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java, steve-api/.../ApiControllerAdvice.java, deleted steve-api/.../BadRequestException.java, deleted steve-api/.../NotFoundException.java, various controllers/tests
Made SteveException abstract; protected constructors; added public static inner classes SteveException.InternalError, SteveException.NotFound, SteveException.BadRequest; replaced usages of removed NotFoundException/BadRequestException and many plain SteveException throws with these nested types across controllers, services, repositories, pipeline components, and tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant JSP as JSP Form (schedulePeriods[])
  participant JS as schedulePeriodsTable.js
  participant Controller as ChargingProfilesController
  participant Repo as ChargingProfileRepositoryImpl
  participant DB as Database

  User->>JSP: Open page (mapper supplies List)
  JSP->>JS: Add/Delete rows (numeric ids)
  JS-->>JSP: Update inputs schedulePeriods[i].*
  User->>Controller: Submit ChargingProfileForm
  Controller->>Repo: save(form)
  Repo->>Repo: filter, sort by startPeriodInSeconds
  Repo->>DB: batchInsert(ChargingSchedulePeriodRecords)
  DB-->>Repo: OK
  Repo-->>Controller: Persisted
  Controller-->>User: Success
Loading
sequenceDiagram
  autonumber
  participant Endpoint as AbstractWebSocketEndpoint
  participant Sender
  participant PingTask
  participant Logger as WebSocketLogger

  Endpoint->>Logger: injected at construction
  Sender->>Logger: injected at construction
  Endpoint->>PingTask: new PingTask(Logger, chargeBoxId, session)
  PingTask->>Logger: webSocketLogger.sendingPing(...)
  Sender->>Logger: webSocketLogger.sending(...)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Rebase steve#1291 #32 — touches ChargePointServiceClient and getDetails retrieval; related by Optional/orElseThrow and NotFound handling.
  • Move from jodatime #1 — modifies charging-profile mapping and form structure; related by Map→List transition in schedule periods.

Poem

I hopped from maps to neat indexed rows,
Each period numbered where the current flows.
We validate, sort, and batch with care,
Injected logs now whisper everywhere.
A rabbit’s cheer — small changes, big goes! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch features/steve-1791

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (1)

295-297: Wrong returning field; breaks jOOQ insert returning

The insertInto(CHARGING_PROFILE) statement returns CHARGING_SCHEDULE_PERIOD.CHARGING_PROFILE_PK, which is from a different table. This will fail at runtime.

Apply this diff:

-                        .returning(CHARGING_SCHEDULE_PERIOD.CHARGING_PROFILE_PK)
+                        .returning(CHARGING_PROFILE.CHARGING_PROFILE_PK)
🧹 Nitpick comments (5)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (1)

79-79: Differentiate list-level vs entry-level validation message

Using the same message “Schedule Periods cannot be empty” both on the field and on the @AssertTrue method can confuse users. Consider changing the AssertTrue message to e.g. “At least one schedule period must be fully specified.”

steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (1)

260-264: Enforce deterministic ordering of schedule periods (optional)

The current implementation retrieves schedule periods without any ORDER BY clause, and the UI mapper simply preserves whatever sequence the database returns. Since relational databases do not guarantee row order without an explicit sort, the list of periods shown in the details view may appear in arbitrary order.

Consider one of the following optional refactors to ensure periods are always displayed sorted by their start time:

• At the repository level, add an ORDER BY on START_PERIOD_IN_SECONDS to the jOOQ query:

--- a/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java
+++ b/steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java
@@ -260,3 +260,4 @@ public ChargingProfile.Details getDetails(int chargingProfilePk) {
         var periods = ctx.selectFrom(CHARGING_SCHEDULE_PERIOD)
-                .where(CHARGING_SCHEDULE_PERIOD.CHARGING_PROFILE_PK.eq(chargingProfilePk))
-                .fetch();
+                .where(CHARGING_SCHEDULE_PERIOD.CHARGING_PROFILE_PK.eq(chargingProfilePk))
+                .orderBy(CHARGING_SCHEDULE_PERIOD.START_PERIOD_IN_SECONDS.asc())
+                .fetch();

         return new ChargingProfile.Details(profile, periods);

• Or in the UI mapper, sort before mapping:

--- a/steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java
@@ -58,7 +58,11 @@ private static ChargingProfileForm.SchedulePeriod mapToFormPeriod(ChargingSchedul
     }

     public static ChargingProfileForm mapToForm(ChargingProfile.Details details) {
-        var periods = details.getPeriods();
+        var periods = details.getPeriods().stream()
+                .sorted(Comparator.comparingInt(ChargingSchedulePeriodRecord::getStartPeriodInSeconds))
+                .toList();

         var form = new ChargingProfileForm();
         form.setChargingProfilePk(profile.getChargingProfilePk());
         …  
         form.setSchedulePeriods(
-                periods.stream().map(ChargingProfileDetailsMapper::mapToFormPeriod).toList());
+                periods.stream()
+                       .map(ChargingProfileDetailsMapper::mapToFormPeriod)
+                       .toList());

Both approaches guarantee that the UI always displays schedule periods in ascending order of startPeriodInSeconds.

steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java (1)

59-62: Preserve UX by sorting periods by startPeriodInSeconds

Without sorting, rows may appear in arbitrary order. Sort before mapping.

Apply this diff:

-        form.setSchedulePeriods(periods.stream()
-                .map(ChargingProfileDetailsMapper::mapToFormPeriod)
-                .toList());
+        form.setSchedulePeriods(periods.stream()
+                .sorted(java.util.Comparator.comparingInt(ChargingSchedulePeriodRecord::getStartPeriodInSeconds))
+                .map(ChargingProfileDetailsMapper::mapToFormPeriod)
+                .toList());
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp (1)

77-83: Surface per-field validation errors to users

Add form:errors under each input so users see messages from @assertfalse validators.

Example:

-        <td><form:input path="schedulePeriods[${status.index}].startPeriodInSeconds"/></td>
+        <td>
+          <form:input path="schedulePeriods[${status.index}].startPeriodInSeconds"/>
+          <form:errors path="schedulePeriods[${status.index}].startPeriodInSeconds" cssClass="error"/>
+        </td>
-        <td><form:input path="schedulePeriods[${status.index}].powerLimit"/></td>
+        <td>
+          <form:input path="schedulePeriods[${status.index}].powerLimit"/>
+          <form:errors path="schedulePeriods[${status.index}].powerLimit" cssClass="error"/>
+        </td>
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (1)

7-18: Prefer const/template literals and consider reindexing after deletions

Minor cleanup and more robust row creation:

-  let last = $('#periodsTable tbody tr').last();
-  // If there is no row (because table is empty), start with id 0. Otherwise, increment last row's id.
-  let id = last.length === 0 ? 0 : parseInt(last[0].id, 10) + 1;
-  var startPeriodInSeconds = "<td><input id=\"schedulePeriods" + id + ".startPeriodInSeconds\" name=\"schedulePeriods[" + id + "].startPeriodInSeconds\" type=\"text\" value=\"\"/></td>";
-  var powerLimit = "<td><input id=\"schedulePeriods" + id + ".powerLimit\" name=\"schedulePeriods[" + id + "].powerLimit\" type=\"text\" value=\"\"/></td>";
-  var numberPhases = "<td><input id=\"schedulePeriods" + id + ".numberPhases\" name=\"schedulePeriods[" + id + "].numberPhases\" type=\"text\" value=\"\" placeholder=\"if empty, 3 will be assumed\"/></td>";
-  var deleteButton = "<td><input type=\"button\" class=\"removeRow\" value=\"Delete\"></td>";
-  var row = "<tr id=" + id + ">" + startPeriodInSeconds + powerLimit + numberPhases + deleteButton + "</tr>";
+  const last = $('#periodsTable tbody tr').last();
+  const id = last.length === 0 ? 0 : parseInt(last[0].id, 10) + 1;
+  const startPeriodInSeconds = `<td><input id="schedulePeriods${id}.startPeriodInSeconds" name="schedulePeriods[${id}].startPeriodInSeconds" type="text" value=""/></td>`;
+  const powerLimit = `<td><input id="schedulePeriods${id}.powerLimit" name="schedulePeriods[${id}].powerLimit" type="text" value=""/></td>`;
+  const numberPhases = `<td><input id="schedulePeriods${id}.numberPhases" name="schedulePeriods[${id}].numberPhases" type="text" value="" placeholder="if empty, 3 will be assumed"/></td>`;
+  const deleteButton = `<td><input type="button" class="removeRow" value="Delete"></td>`;
+  const row = `<tr id="${id}">${startPeriodInSeconds}${powerLimit}${numberPhases}${deleteButton}</tr>`;

Optional: after a deletion, reindex row ids and input names to keep indices dense, reducing gaps sent to the server.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ca35cd6 and 91d8de1.

📒 Files selected for processing (5)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java (2 hunks)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp (2 hunks)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (2)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java (1)
  • Getter (35-86)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java (1)
  • Getter (37-67)
🔇 Additional comments (3)
steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (2)

381-397: Batch insert pipeline looks good; consider guarding against unsorted/null inputs upstream

You already filter isNonEmpty and sort by startPeriodInSeconds before batchInsert; this is solid. No further issues if DTO fix (defaulting numberPhases) lands.


382-385: Manual verification required: confirm removal of schedulePeriodMap

A repository-wide search for the literal schedulePeriodMap yielded no matches. Please manually verify that:

  • All code, tests, documentation, and configuration no longer reference the old schedulePeriodMap field.
  • Any migration steps or data transformations that depended on the map have been fully adapted to the new schedulePeriods approach.
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java (1)

66-72: Helper method is clean and aligns with DTO defaults

Mapping is straightforward; setNumberPhases() will apply the default when null. LGTM.

Comment on lines 126 to 120
private Integer startPeriodInSeconds; // from the startSchedule

@NotNull(message = "Schedule period: Power Limit has to be set") private BigDecimal powerLimit;
private BigDecimal powerLimit;

private Integer numberPhases;

public Integer getNumberPhases() {
return Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
}

public void setNumberPhases(Integer numberPhases) {
this.numberPhases = Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
}

public boolean isNonEmpty() {
return (startPeriodInSeconds != null) && (powerLimit != null) && (numberPhases != null);
}

@AssertFalse(message = "Schedule period: Power Limit has to be set") public boolean isPowerLimitMissing() {
return (startPeriodInSeconds != null) && (powerLimit == null) && (numberPhases != null);
}

@AssertFalse(message = "Schedule period: Start Period has to be set") public boolean isStartPeriodInSecondsMissing() {
return (startPeriodInSeconds == null) && (powerLimit != null) && (numberPhases != null);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Default numberPhases via getter and relax validations to match UI contract

Align with OcppTagForm’s “default in getter” pattern and ensure periods with start+power but empty numberPhases pass validation and persist with default=3.

Apply this diff:

 class SchedulePeriod {
@@
-        private Integer numberPhases;
+        private Integer numberPhases;
@@
         public void setNumberPhases(Integer numberPhases) {
             this.numberPhases = Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
         }
+
+        // Ensure default of 3 if not provided (consistent with UI hint and repository usage)
+        public Integer getNumberPhases() {
+            return Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
+        }
@@
-        public boolean isNonEmpty() {
-            return (startPeriodInSeconds != null) && (powerLimit != null) && (numberPhases != null);
-        }
+        public boolean isNonEmpty() {
+            // Only require the essential fields; numberPhases defaults to 3 if omitted.
+            return (startPeriodInSeconds != null) && (powerLimit != null);
+        }
@@
-        @AssertFalse(message = "Schedule period: Power Limit has to be set") public boolean isPowerLimitMissing() {
-            return (startPeriodInSeconds != null) && (powerLimit == null) && (numberPhases != null);
-        }
+        @AssertFalse(message = "Schedule period: Power Limit has to be set") public boolean isPowerLimitMissing() {
+            return (startPeriodInSeconds != null) && (powerLimit == null);
+        }
@@
-        @AssertFalse(message = "Schedule period: Start Period has to be set") public boolean isStartPeriodInSecondsMissing() {
-            return (startPeriodInSeconds == null) && (powerLimit != null) && (numberPhases != null);
-        }
+        @AssertFalse(message = "Schedule period: Start Period has to be set") public boolean isStartPeriodInSecondsMissing() {
+            return (startPeriodInSeconds == null) && (powerLimit != null);
+        }

Optionally add constraints to catch bad inputs:

  • startPeriodInSeconds: @PositiveOrZero
  • powerLimit: @positive
  • numberPhases: @min(1) @max(3)

You’ll need to import jakarta.validation.constraints.Min/Max/Positive as needed.

📝 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
private Integer startPeriodInSeconds; // from the startSchedule
@NotNull(message = "Schedule period: Power Limit has to be set") private BigDecimal powerLimit;
private BigDecimal powerLimit;
private Integer numberPhases;
public Integer getNumberPhases() {
return Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
}
public void setNumberPhases(Integer numberPhases) {
this.numberPhases = Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
}
public boolean isNonEmpty() {
return (startPeriodInSeconds != null) && (powerLimit != null) && (numberPhases != null);
}
@AssertFalse(message = "Schedule period: Power Limit has to be set") public boolean isPowerLimitMissing() {
return (startPeriodInSeconds != null) && (powerLimit == null) && (numberPhases != null);
}
@AssertFalse(message = "Schedule period: Start Period has to be set") public boolean isStartPeriodInSecondsMissing() {
return (startPeriodInSeconds == null) && (powerLimit != null) && (numberPhases != null);
}
}
private Integer startPeriodInSeconds; // from the startSchedule
private BigDecimal powerLimit;
private Integer numberPhases;
public void setNumberPhases(Integer numberPhases) {
this.numberPhases = Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
}
// Ensure default of 3 if not provided (consistent with UI hint and repository usage)
public Integer getNumberPhases() {
return Objects.requireNonNullElse(numberPhases, defaultNumberPhases);
}
public boolean isNonEmpty() {
// Only require the essential fields; numberPhases defaults to 3 if omitted.
return (startPeriodInSeconds != null) && (powerLimit != null);
}
@AssertFalse(message = "Schedule period: Power Limit has to be set")
public boolean isPowerLimitMissing() {
return (startPeriodInSeconds != null) && (powerLimit == null);
}
@AssertFalse(message = "Schedule period: Start Period has to be set")
public boolean isStartPeriodInSecondsMissing() {
return (startPeriodInSeconds == null) && (powerLimit != null);
}
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java
around lines 126 to 147, change the handling of numberPhases to follow the
"default in getter" pattern: remove the requireNonNullElse in the setter so
numberPhases can be left null, implement getNumberPhases() to return
numberPhases != null ? numberPhases : 3 (default), and adjust the validation
methods so a period with startPeriodInSeconds and powerLimit set but
numberPhases null is considered valid (it will persist as default=3); optionally
add jakarta.validation annotations on fields (startPeriodInSeconds:
@PositiveOrZero, powerLimit: @Positive, numberPhases: @Min(1) @Max(3)) and
import Min, Max, Positive, PositiveOrZero as needed.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (1)

138-148: Per-period validations now match UI contract

isNonEmpty relies on start+power only; missing-field guards no longer depend on numberPhases. This resolves the earlier edge case noted in prior reviews.

🧹 Nitpick comments (3)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (3)

81-81: Differentiate validation messages for clarity

Field-level @notempty and the custom @AssertTrue share the same message, but enforce different things (list non-empty vs. at least one fully specified period). Suggest clearer, distinct messages.

Apply:

-    @NotEmpty(message = "Schedule Periods cannot be empty") @Valid private List<SchedulePeriod> schedulePeriods;
+    @NotEmpty(message = "Schedule Periods list must not be empty") @Valid private List<SchedulePeriod> schedulePeriods;

113-119: Make “non-empty period” error explicit

The predicate is good. Update the message to reflect that at least one period must have both Start Period and Power Limit.

-    @AssertTrue(message = "Schedule Periods cannot be empty") public boolean isSchedulePeriodsValid() {
+    @AssertTrue(message = "At least one Schedule Period must specify both Start Period and Power Limit") public boolean isSchedulePeriodsValid() {
         if (CollectionUtils.isEmpty(schedulePeriods)) {
             return false;
         }
         return schedulePeriods.stream().filter(Objects::nonNull).anyMatch(SchedulePeriod::isNonEmpty);
     }

128-133: Review domain rule for powerLimit: zero is allowed by OCPP spec

  • In steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (lines 128–133) the field is annotated with @Positive, which forbids zero.
  • OCPP 1.6 defines ChargingSchedulePeriod.limit as a required decimal (no explicit “> 0” constraint) (scribd.com) and its JSON‐schema examples include "limit": 0 (ocpp-spec.org).

Optional change if you need to represent “stop charging” with a zero limit:

-        @Positive private BigDecimal powerLimit;
+        @PositiveOrZero private BigDecimal powerLimit;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 91d8de1 and 7d424f9.

📒 Files selected for processing (4)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (3 hunks)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp (2 hunks)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java
🧰 Additional context used
🧬 Code graph analysis (1)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (2)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargingProfile.java (3)
  • Getter (36-49)
  • Getter (51-58)
  • Getter (60-65)
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (3)
  • startPeriodInSeconds (11-11)
  • powerLimit (12-12)
  • numberPhases (13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
🔇 Additional comments (3)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (3)

29-45: Imports: additions look correct

New imports align with the validation and collection utilities used below.


126-126: Good: centralized default

DEFAULT_NUMBER_PHASES constant is clear and fixes past ambiguity around implicit defaults.


135-137: No direct field accesses found; getter usage confirmed

All downstream mappings and repository/task implementations invoke getNumberPhases(), preserving the default value, and no schedulePeriodMap or getSchedulePeriodMap references remain.

@juherr juherr force-pushed the features/steve-1791 branch from 7d424f9 to 6f92063 Compare August 28, 2025 18:54
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (1)

2-4: Reindex rows after deletion to keep list indices contiguous (prevents null holes on bind).

Without reindexing, deleting middle rows yields non-contiguous indices (e.g., 0,1,3), which can post null entries and trip server-side processing. Reindex immediately after a delete.

Apply this diff:

-$('#periodsTable').on('click', '.removeRow', function() {
-    $(this).closest("tr").remove();
-});
+$('#periodsTable').on('click', '.removeRow', function(e) {
+    e.preventDefault();
+    $(this).closest("tr").remove();
+    reindexSchedulePeriods();
+});

Add this helper in the same file (outside the shown range):

function reindexSchedulePeriods() {
  $('#periodsTable tbody tr').each(function (idx) {
    const $tr = $(this).attr('id', String(idx));
    $tr.find('input').each(function () {
      const $in = $(this);
      const name = $in.attr('name');      // schedulePeriods[3].field
      if (name) $in.attr('name', name.replace(/schedulePeriods\[\d+]/, `schedulePeriods[${idx}]`));
      const id = $in.attr('id');          // schedulePeriods3.field
      if (id) $in.attr('id', id.replace(/schedulePeriods\d+\./, `schedulePeriods${idx}.`));
    });
  });
}
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (1)

100-116: Guard against uninitialized pipeline to avoid NPEs.

pipeline is nullable and used without a null check. If init(...) hasn’t run yet when a message arrives, this will NPE.

Apply:

         var context = new CommunicationContext(session, chargeBoxId);
         context.setIncomingString(incomingString);
 
-        pipeline.accept(context);
+        var p = this.pipeline;
+        if (p == null) {
+            session.close(CloseStatus.SERVER_ERROR.withReason("Pipeline not initialized"));
+            return;
+        }
+        p.accept(context);

Additionally, consider safe publication:

-    private @Nullable IncomingPipeline pipeline;
+    private volatile @Nullable IncomingPipeline pipeline;
🧹 Nitpick comments (6)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/WebSocketLogger.java (1)

34-75: Harden against potential null session and reduce log volume

  • A defensive session-id accessor avoids accidental NPEs if a null slips through.
  • Consider logging payloads at debug to prevent noisy logs in production.

Apply within-body changes:

-        log.info("[chargeBoxId={}, sessionId={}] Sending: {}", chargeBoxId, session.getId(), msg);
+        log.debug("[chargeBoxId={}, sessionId={}] Sending: {}", chargeBoxId, session != null ? session.getId() : "null", msg);

-        log.info("[chargeBoxId={}, sessionId={}] Received: {}", chargeBoxId, session.getId(), msg);
+        log.debug("[chargeBoxId={}, sessionId={}] Received: {}", chargeBoxId, session != null ? session.getId() : "null", msg);

Support code to add (outside the changed ranges, near class end) if you prefer a helper:

private static String sid(WebSocketSession s) { return s != null ? s.getId() : "null"; }

Then replace session.getId() calls with sid(session).

steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)

70-75: Provide a templated constructor for NotFound (parity with AlreadyExists).
Enables richer messages without manual String.format at call sites.

     public static class NotFound extends SteveException {
 
         public NotFound(String message) {
             super(message);
         }
+        public NotFound(String template, Object arg1) {
+            super(format(template, arg1));
+        }
     }
steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java (1)

416-418: Include the profile PK in the NotFound message.
Improves diagnostics and client logs.

-        var details = chargingProfileRepository
-                .getDetails(params.getChargingProfilePk())
-                .orElseThrow(() -> new SteveException.NotFound("Charging Profile not found"));
+        var details = chargingProfileRepository
+                .getDetails(params.getChargingProfilePk())
+                .orElseThrow(() -> new SteveException.NotFound(
+                        "Charging Profile with pk=%s not found", params.getChargingProfilePk()));
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (1)

11-13: Prefer numeric inputs and safer element ids.

  • Use type="number" with min/step to improve UX/validation.
  • Avoid dots in element ids (fragile in CSS/jQuery selectors). Keep name=... as-is for Spring binding.

Apply this diff:

-  const startPeriodInSeconds = `<td><input id="schedulePeriods${id}.startPeriodInSeconds" name="schedulePeriods[${id}].startPeriodInSeconds" type="text" value=""/></td>`;
-  const powerLimit = `<td><input id="schedulePeriods${id}.powerLimit" name="schedulePeriods[${id}].powerLimit" type="text" value=""/></td>`;
-  const numberPhases = `<td><input id="schedulePeriods${id}.numberPhases" name="schedulePeriods[${id}].numberPhases" type="text" value="" placeholder="if empty, 3 will be assumed"/></td>`;
+  const startPeriodInSeconds = `<td><input id="schedulePeriods_${id}_startPeriodInSeconds" name="schedulePeriods[${id}].startPeriodInSeconds" type="number" min="0" step="1" value=""/></td>`;
+  const powerLimit = `<td><input id="schedulePeriods_${id}_powerLimit" name="schedulePeriods[${id}].powerLimit" type="number" min="0" step="0.001" value=""/></td>`;
+  const numberPhases = `<td><input id="schedulePeriods_${id}_numberPhases" name="schedulePeriods[${id}].numberPhases" type="number" min="1" max="3" step="1" value="" placeholder="if empty, 3 will be assumed"/></td>`;
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (2)

118-121: Avoid duplicate attribute lookups in handlePongMessage.

Minor nit: cache chargeBoxId once for consistency and clarity.

-    private void handlePongMessage(WebSocketSession session) {
-        webSocketLogger.receivedPong(getChargeBoxId(session), session);
-        ocppServerRepository.updateChargeboxHeartbeat(getChargeBoxId(session), Instant.now());
-    }
+    private void handlePongMessage(WebSocketSession session) {
+        var chargeBoxId = getChargeBoxId(session);
+        webSocketLogger.receivedPong(chargeBoxId, session);
+        ocppServerRepository.updateChargeboxHeartbeat(chargeBoxId, Instant.now());
+    }

175-178: onError logging switch to instance logger — LGTM.

Consider ensuring logger failures never propagate from here, but current usage is fine.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7d424f9 and 6f92063.

📒 Files selected for processing (18)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/ChargingProfileRepository.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java (3 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (5 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/PingTask.java (1 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/WebSocketLogger.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (3 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (3 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (3 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java (2 hunks)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp (2 hunks)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/00-charging-profile.jsp
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargingProfileForm.java
🧰 Additional context used
🧬 Code graph analysis (4)
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-76)
steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-76)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (2)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (1)
  • RequiredArgsConstructor (55-216)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/data/CommunicationContext.java (2)
  • RequiredArgsConstructor (41-116)
  • RequiredArgsConstructor (82-115)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/WebSocketLogger.java (3)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (1)
  • Component (58-122)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (1)
  • Component (59-127)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (1)
  • Component (59-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (19)
steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2)

78-86: Local var usage is fine

Type inference here is clear (String and SecurityScheme). No issues.


72-73: springdoc.writer-with-order-by-keys is supported and won’t be overridden by application.properties
Project uses SpringDoc 2.8.10 (see pom.xml) and the official Springdoc properties list includes springdoc.writer-with-order-by-keys (github.com, springdoc.org). In Spring Boot’s property resolution, Java system properties (System.setProperty) have higher precedence than values in application.properties (dev.to).

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/WebSocketLogger.java (2)

31-31: Bean-ifying the logger is correct

Switch to @Component + instance methods aligns with DI across call sites. Good move.


69-75: Error logging change is good

Passing Throwable as the last arg is the correct SLF4J pattern; removing isErrorEnabled() is fine.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java (1)

74-76: No duplicate Sender bean

Sender is not annotated with @Component; only the sender bean defined in OcppConfiguration is registered.

steve-core/src/main/java/de/rwth/idsg/steve/repository/ChargingProfileRepository.java (1)

30-31: Document Optional<ChargingProfile.Details> contract in Javadoc
Add a Javadoc comment above Optional<ChargingProfile.Details> getDetails(int chargingProfilePk) in ChargingProfileRepository.java to state that it returns an empty Optional when the profile PK isn’t found. No changes are needed in the implementation or existing callers—they already unwrap the Optional appropriately.

steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)

70-75: Add NotFound: good addition aligned with Optional-based lookups.

steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java (1)

21-21: Import SteveException: OK.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (1)

39-43: Sender is already registered as a Spring bean. The sender(WebSocketLogger) factory method in steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java defines and returns a Sender bean, so no additional registration is needed.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (1)

65-77: Constructor and super call updated to inject WebSocketLogger: looks correct.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (2)

31-31: LGTM: Logger is now injected.

Constructor injection of WebSocketLogger and forwarding to super is correct and consistent with DI best practices.


65-77: All WebSocket endpoint constructors include WebSocketLogger
Verified that Ocpp12WebSocketEndpoint, Ocpp15WebSocketEndpoint, and Ocpp16WebSocketEndpoint each declare WebSocketLogger as their first constructor argument; no additional bean definitions require changes.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (2)

31-31: LGTM: Logger injection mirrors 1.5 endpoint.


64-76: DI consistency verified: All OCPP WebSocketEndpoint subclasses (Ocpp12, Ocpp15, Ocpp16) accept WebSocketLogger and forward it in their super(…) calls, and both Sender and PingTask are correctly constructor-injected with WebSocketLogger.

steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (2)

256-271: LGTM: Optional return and deterministic period order.

Returning Optional for getDetails and ordering periods by START_PERIOD_IN_SECONDS improves API clarity and reproducibility.


388-402: No changes required: validation already enforces non-null schedule periods and startPeriodInSeconds
The DTO applies @notempty to schedulePeriods (and @NotNull on each element) and @NotNull on SchedulePeriod.startPeriodInSeconds, so the stream and comparator cannot receive nulls.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (3)

58-58: Injecting WebSocketLogger instead of statics — good change.

Instance-based logging enables easier testing and per-endpoint configuration.


157-157: onClose logging switch to instance logger — LGTM.


133-133: All PingTask instantiations are using the 3-argument constructor. No remaining 2-arg usages found.

Comment on lines +416 to +418
var details = chargingProfileRepository
.getDetails(params.getChargingProfilePk())
.orElseThrow(() -> new SteveException.NotFound("Charging Profile not found"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify HTTP mapping for NotFound → 404.
If no @ControllerAdvice translates SteveException.NotFound to 404, UI calls will surface as 500.


🏁 Script executed:

#!/bin/bash
# Expect: an @ExceptionHandler for SteveException.NotFound (preferably in UI/web module).
rg -nP '@ExceptionHandler\(\s*SteveException\.NotFound' -C3 --type=java

# Alternative mapping via ResponseStatusExceptionResolver-style annotations:
rg -nP 'class\s+.*ControllerAdvice|@ResponseStatus' -C2 --type=java

Length of output: 5140


🏁 Script executed:

#!/bin/bash
# Locate SteveException source file
STEVE_FILE=$(rg -l 'class SteveException' --type=java)
echo "SteveException file: $STEVE_FILE"

# Print first 200 lines to inspect nested NotFound class
sed -n '1,200p' "$STEVE_FILE"

# Locate all NotFoundException class declarations to check inheritance
rg -nP 'class\s+NotFoundException' -n --type=java

Length of output: 3093


Map SteveException.NotFound to HTTP 404
SteveException.NotFound isn’t handled by any ControllerAdvice and falls through to the generic 500 handler. Add an @ExceptionHandler(SteveException.NotFound) with @ResponseStatus(HttpStatus.NOT_FOUND) in ApiControllerAdvice (and GlobalControllerAdvice for UI) or annotate the exception class itself.

🤖 Prompt for AI Agents
In
steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java
around lines 416-418, SteveException.NotFound is thrown but not mapped to HTTP
404 and falls through to a 500 response; add handling so clients receive 404.
Fix by either annotating SteveException.NotFound with
@ResponseStatus(HttpStatus.NOT_FOUND) on the exception class, or add an
@ExceptionHandler(SteveException.NotFound) method in ApiControllerAdvice (and
GlobalControllerAdvice for UI) annotated with
@ResponseStatus(HttpStatus.NOT_FOUND) that returns an appropriate error
body/DTO; ensure the handler accepts the exception parameter and returns the
standardized error response used by the project.

Comment on lines 42 to 52
private static final PingMessage PING_MESSAGE = new PingMessage(ByteBuffer.wrap("ping".getBytes(UTF_8)));

@Override
public void run() {
WebSocketLogger.sendingPing(chargeBoxId, session);
webSocketLogger.sendingPing(chargeBoxId, session);
try {
session.sendMessage(PING_MESSAGE);
} catch (IOException e) {
WebSocketLogger.pingError(chargeBoxId, session, e);
webSocketLogger.pingError(chargeBoxId, session, e);
// TODO: Do something about this
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid sharing a single PingMessage/ByteBuffer across threads.
PingMessage/ByteBuffer can have mutable position; use a duplicated read-only buffer per send.

-    private static final PingMessage PING_MESSAGE = new PingMessage(ByteBuffer.wrap("ping".getBytes(UTF_8)));
+    private static final ByteBuffer PING_PAYLOAD = ByteBuffer.wrap("ping".getBytes(UTF_8)).asReadOnlyBuffer();
@@
-        try {
-            session.sendMessage(PING_MESSAGE);
+        try {
+            session.sendMessage(new PingMessage(PING_PAYLOAD.duplicate()));
📝 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
private static final PingMessage PING_MESSAGE = new PingMessage(ByteBuffer.wrap("ping".getBytes(UTF_8)));
@Override
public void run() {
WebSocketLogger.sendingPing(chargeBoxId, session);
webSocketLogger.sendingPing(chargeBoxId, session);
try {
session.sendMessage(PING_MESSAGE);
} catch (IOException e) {
WebSocketLogger.pingError(chargeBoxId, session, e);
webSocketLogger.pingError(chargeBoxId, session, e);
// TODO: Do something about this
}
// Replace shared PingMessage with a reusable read-only payload buffer
private static final ByteBuffer PING_PAYLOAD =
ByteBuffer.wrap("ping".getBytes(UTF_8))
.asReadOnlyBuffer();
@Override
public void run() {
webSocketLogger.sendingPing(chargeBoxId, session);
try {
// Duplicate the read-only buffer for each send to avoid shared mutable state
session.sendMessage(new PingMessage(PING_PAYLOAD.duplicate()));
} catch (IOException e) {
webSocketLogger.pingError(chargeBoxId, session, e);
// TODO: Do something about this
}
}
🤖 Prompt for AI Agents
In steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/PingTask.java
around lines 42 to 52, don't reuse the static PING_MESSAGE (its ByteBuffer is
mutable and not thread-safe); instead keep a static byte[] or CharSequence for
the ping payload and create a fresh, read-only ByteBuffer for each send (e.g.
ByteBuffer.wrap(PING_BYTES).asReadOnlyBuffer() or
ByteBuffer.wrap(...).duplicate() before calling new PingMessage(...)) and call
session.sendMessage with that new PingMessage so each thread/send uses its own
buffer instance.

Comment on lines +59 to +61
form.setSchedulePeriods(periods.stream()
.map(ChargingProfileDetailsMapper::mapToFormPeriod)
.toList());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid Stream.toList() immutability if callers may mutate schedulePeriods.

Stream.toList() returns an unmodifiable List; if later code appends/edits periods on the form, this will throw. Prefer a mutable list.

Apply this diff:

-        form.setSchedulePeriods(periods.stream()
-                .map(ChargingProfileDetailsMapper::mapToFormPeriod)
-                .toList());
+        form.setSchedulePeriods(periods.stream()
+                .map(ChargingProfileDetailsMapper::mapToFormPeriod)
+                .collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new)));

If you prefer imports, add:

import java.util.ArrayList;
import java.util.stream.Collectors;
🤖 Prompt for AI Agents
In
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/mapper/ChargingProfileDetailsMapper.java
around lines 59-61, the code uses Stream.toList() which returns an unmodifiable
List; change the mapping to produce a mutable ArrayList (e.g. collect into a new
ArrayList via Collectors.toCollection(ArrayList::new) or wrap the result in new
ArrayList<>(...)) so callers can safely modify form.schedulePeriods, and add
imports for java.util.ArrayList and java.util.stream.Collectors if not already
present.

Comment on lines +129 to 133
var details = repository
.getDetails(chargingProfilePk)
.orElseThrow(() -> new SteveException.NotFound("Charging Profile not found"));
;
var form = ChargingProfileDetailsMapper.mapToForm(details);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Prefer informative message and drop stray semicolon

Include the PK in the NotFound message and remove the extra ; line.

-        var details = repository
-                .getDetails(chargingProfilePk)
-                .orElseThrow(() -> new SteveException.NotFound("Charging Profile not found"));
-        ;
+        var details = repository
+                .getDetails(chargingProfilePk)
+                .orElseThrow(() -> new SteveException.NotFound(
+                        String.format("Charging Profile %d not found", chargingProfilePk)));

Do we have a @ControllerAdvice mapping SteveException.NotFound to HTTP 404? If not, users will see 500. Search:


🏁 Script executed:

#!/bin/bash
rg -nP '@ExceptionHandler\(\s*SteveException\.NotFound' -C2

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Search for any @ControllerAdvice classes
rg -nP '@ControllerAdvice' -C2 .

# Search for ExceptionHandler methods targeting SteveException.NotFound
rg -nP '@ExceptionHandler\(.*SteveException\.NotFound' -C2 .

Length of output: 650


🏁 Script executed:

#!/bin/bash
# Check for any exception handlers in GlobalControllerAdvice
rg -nP '@ExceptionHandler' -C3 steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/GlobalControllerAdvice.java

Length of output: 438


Include PK in NotFound message, remove stray semicolon, and map to 404

  • Update code to include the chargingProfilePk in the exception message and drop the extra semicolon:

    -        var details = repository
    -                .getDetails(chargingProfilePk)
    -                .orElseThrow(() -> new SteveException.NotFound("Charging Profile not found"));
    -        ;
    +        var details = repository
    +                .getDetails(chargingProfilePk)
    +                .orElseThrow(() -> new SteveException.NotFound(
    +                        String.format("Charging Profile %d not found", chargingProfilePk)));
  • Currently SteveException.NotFound bubbles to the generic @ExceptionHandler(Exception.class) in GlobalControllerAdvice, resulting in HTTP 500. Add a specific handler for NotFound to return HTTP 404:

    @ExceptionHandler(SteveException.NotFound.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ModelAndView handleNotFound(HttpServletRequest req, SteveException.NotFound ex) {
        log.warn("Resource not found: {}", req.getRequestURL(), ex);
        return errorModel("not-found", ex.getMessage());
    }
🤖 Prompt for AI Agents
In
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/ChargingProfilesController.java
around lines 129 to 133, remove the stray semicolon, change the NotFound
exception to include the chargingProfilePk in the message (e.g. "Charging
Profile not found: " + chargingProfilePk) and then map details to form as
before; additionally add a specific exception handler in GlobalControllerAdvice
for SteveException.NotFound that returns HTTP 404 (annotate with
@ExceptionHandler(SteveException.NotFound.class) and
@ResponseStatus(HttpStatus.NOT_FOUND)), logs a warning with the request URL and
exception, and returns the "not-found" error model with ex.getMessage().

Comment on lines +7 to +10
// Find the last table row
const last = $('#periodsTable tbody tr').last();
// If there is no row (because table is empty), start with id 0. Otherwise, increment last row's id.
const id = last.length === 0 ? 0 : parseInt(last[0].id, 10) + 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use tbody length for next index; avoid NaN/DOM id coupling.

Relying on last row’s DOM id can break if ids drift; using current row count is simpler and robust, especially with reindexing.

Apply this diff:

-  // If there is no row (because table is empty), start with id 0. Otherwise, increment last row's id.
-  const id = last.length === 0 ? 0 : parseInt(last[0].id, 10) + 1;
+  // Next index = current row count (rows are reindexed on delete)
+  const id = $('#periodsTable tbody tr').length;
📝 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
// Find the last table row
const last = $('#periodsTable tbody tr').last();
// If there is no row (because table is empty), start with id 0. Otherwise, increment last row's id.
const id = last.length === 0 ? 0 : parseInt(last[0].id, 10) + 1;
// Find the last table row
const last = $('#periodsTable tbody tr').last();
// Next index = current row count (rows are reindexed on delete)
const id = $('#periodsTable tbody tr').length;
🤖 Prompt for AI Agents
In
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/snippets/schedulePeriodsTable.js
around lines 7 to 10, replace the logic that parses the last row's DOM id with
using the current tbody row count to compute the next index: compute const id =
$('#periodsTable tbody tr').length so an empty table yields 0 and each new row
uses the stable count-based index; remove reliance on last[0].id/parseInt to
avoid NaN and decouple from DOM id drift.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (1)

128-134: Consider guarding pipeline exceptions to avoid dropping sessions.

If pipeline.accept(context) throws, the handler thread will bubble up to the container. Wrap and close with 1011 or log as needed.

-        pipeline.accept(context);
+        try {
+            pipeline.accept(context);
+        } catch (RuntimeException e) {
+            webSocketLogger.transportError(chargeBoxId, session, e);
+            try {
+                session.close(CloseStatus.SERVER_ERROR.withReason("Unhandled error while processing message"));
+            } catch (Exception ignore) { }
+        }
steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java (1)

59-63: Bug: Instantiating abstract SteveException will not compile.

SteveException is now abstract; new SteveException(...) is invalid. This path represents an invariant breach and should be a 5xx. Use IllegalStateException (or introduce a dedicated internal error subtype) instead.

-        var body = reservationsService
-                .getReservation(id)
-                .orElseThrow(
-                        () -> new SteveException("Reservation not found after creation, this should never happen"));
+        var body = reservationsService
+                .getReservation(id)
+                .orElseThrow(() -> new IllegalStateException(
+                        "Reservation not found after creation, this should never happen. id=" + id));
♻️ Duplicate comments (1)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (1)

59-61: Resolved: close when chargeBoxId is missing on open.

The null/empty guard with 1008 (policy violation) addresses the earlier risk.

Also applies to: 145-151

🧹 Nitpick comments (10)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (1)

92-98: Make callback lists thread-safe or document single-writer semantics.

If registerConnectedCallback/registerDisconnectedCallback may be called while iterating on connect/disconnect, switch to CopyOnWriteArrayList to avoid CME.

-    private final List<Consumer<String>> connectedCallbackList;
-    private final List<Consumer<String>> disconnectedCallbackList;
+    private final List<Consumer<String>> connectedCallbackList;
+    private final List<Consumer<String>> disconnectedCallbackList;
@@
-        this.connectedCallbackList = new ArrayList<>();
+        this.connectedCallbackList = new java.util.concurrent.CopyOnWriteArrayList<>();
@@
-        this.disconnectedCallbackList = new ArrayList<>();
+        this.disconnectedCallbackList = new java.util.concurrent.CopyOnWriteArrayList<>();

Also applies to: 216-222

steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (3)

65-67: Delegate to super(template, …) instead of pre-formatting.

Using super(format(...)) bypasses the base “template + args” constructors and duplicates formatting logic. Call the formatting-aware super instead for consistency and to keep one formatting path.

-        public AlreadyExists(String template, Object arg1) {
-            super(format(template, arg1));
-        }
+        public AlreadyExists(String template, Object arg1) {
+            super(template, arg1);
+        }
@@
-        public NotFound(String template, Object arg1) {
-            super(format(template, arg1));
-        }
+        public NotFound(String template, Object arg1) {
+            super(template, arg1);
+        }
@@
-        public BadRequest(String template, Object arg1) {
-            super(format(template, arg1));
-        }
+        public BadRequest(String template, Object arg1) {
+            super(template, arg1);
+        }

Also applies to: 76-78, 86-88


70-89: Add cause-bearing overloads for NotFound/BadRequest.

Parity with the base class helps preserve stack traces and makes wrapping easier.

     public static class NotFound extends SteveException {
         public NotFound(String message) {
             super(message);
         }
+        public NotFound(String message, Throwable cause) {
+            super(message, cause);
+        }
         public NotFound(String template, Object arg1) {
             super(template, arg1);
         }
+        public NotFound(String template, Object arg1, Throwable cause) {
+            super(template, arg1, cause);
+        }
     }
 
     public static class BadRequest extends SteveException {
         public BadRequest(String message) {
             super(message);
         }
+        public BadRequest(String message, Throwable cause) {
+            super(message, cause);
+        }
         public BadRequest(String template, Object arg1) {
             super(template, arg1);
         }
+        public BadRequest(String template, Object arg1, Throwable cause) {
+            super(template, arg1, cause);
+        }
     }

63-69: Consider a message-only constructor for AlreadyExists.

To match NotFound/BadRequest and reduce friction at call sites.

     public static class AlreadyExists extends SteveException {
-
-        public AlreadyExists(String template, Object arg1) {
-            super(template, arg1);
-        }
+        public AlreadyExists(String template, Object arg1) {
+            super(template, arg1);
+        }
+        public AlreadyExists(String message) {
+            super(message);
+        }
     }
steve-api/src/main/java/de/rwth/idsg/steve/web/api/TasksRestController.java (1)

84-85: Use the template-aware ctor for NotFound (consistency).

Keeps exception messages consistent and avoids string concatenation.

-            throw new SteveException.NotFound("Task not found: " + taskId);
+            throw new SteveException.NotFound("Task not found: %d", taskId);
steve-api/src/main/java/de/rwth/idsg/steve/web/api/OcppTagsRestController.java (1)

189-189: Use template ctor instead of string concatenation.
Align with SteveException guidance and keep messages consistent.

-            throw new SteveException.NotFound("Could not find ocppTag with id " + ocppTagPk);
+            throw new SteveException.NotFound("Could not find ocppTag with id %s", ocppTagPk);
steve-api/src/main/java/de/rwth/idsg/steve/web/api/RemoteCommandsRestController.java (2)

146-147: Consider 409 (Conflict) for “active task/transaction” cases.
These are state conflicts rather than malformed requests. Optional follow-up: introduce SteveException.Conflict and map to 409 in ApiControllerAdvice, then update these throws.

Also applies to: 159-160, 221-222, 238-239


146-147: Prefer template ctor over String.format when using a single placeholder.
Minor consistency nit; reduces verbosity.

-            var errMsg = String.format("Active task found on ChargeBox %s!", params.getChargeBoxId());
-            throw new SteveException.BadRequest(errMsg);
+            throw new SteveException.BadRequest("Active task found on ChargeBox %s!", params.getChargeBoxId());

Also applies to: 183-183

steve-api/src/main/java/de/rwth/idsg/steve/web/api/ApiControllerAdvice.java (2)

77-80: Optional: add dedicated handlers for Conflict/Forbidden if introduced.
Prepares for more accurate HTTP semantics suggested in controllers.


94-100: Avoid leaking internal error messages on 500 responses.
Return a generic message for INTERNAL_SERVER_ERROR; log the details.

Example tweak:

-        return createResponse(url, HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage());
+        return createResponse(url, HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected server error");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6f92063 and e8bbf94.

📒 Files selected for processing (15)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/ApiControllerAdvice.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/OcppTagsRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/RemoteCommandsRestController.java (6 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java (1 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/TasksRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/TransactionsRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/BadRequestException.java (0 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/NotFoundException.java (0 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (6 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java (1 hunks)
💤 Files with no reviewable changes (2)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/BadRequestException.java
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/NotFoundException.java
🧰 Additional context used
🧬 Code graph analysis (5)
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-90)
steve-api/src/main/java/de/rwth/idsg/steve/web/api/TransactionsRestController.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-90)
steve-api/src/main/java/de/rwth/idsg/steve/web/api/RemoteCommandsRestController.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-90)
steve-api/src/main/java/de/rwth/idsg/steve/web/api/OcppTagsRestController.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-90)
steve-api/src/main/java/de/rwth/idsg/steve/web/api/TasksRestController.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)
  • SteveException (27-90)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build (ubuntu-24.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (14)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java (1)

49-55: Fail-fast path looks good.

Early return on deserialization error before switching on message type is correct and preserves existing behavior.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (2)

86-115: Dispatch table is consistent with OCPP 1.5.

Switch covers expected request types; default throws with a clear message.


57-79: Approve changes; DI wiring validated. WebSocketLogger is registered as a Spring @component, Sender is exposed via a @bean in OcppConfiguration matching its constructor, and there’s no leftover @PostConstruct pipeline initialization in the endpoints.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (1)

86-115: Dispatch mapping looks correct.

Covers standard v1.6 requests; default path is explicit.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (2)

85-110: Dispatch coverage OK.

Cases and transport settings look right for OCPP 1.2 JSON.


56-78: Approve constructor-based wiring. Verified that Ocpp12TypeStore is annotated @component and CentralSystemService12_SoapServer is annotated @service, so both beans are defined for injection.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (2)

71-98: Base-class constructor: solid centralization of pipeline wiring.

Creating IncomingPipeline here reduces duplication across endpoints; the event callbacks are set up cleanly.


212-214: No action needed; CHARGEBOX_ID_KEY is used consistently by both producer (OcppWebSocketHandshakeHandler.java:96) and consumer (AbstractWebSocketEndpoint.java:213).

steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)

21-21: LGTM: tests updated to SteveException.NotFound.

The switch to the new exception type aligns with the refactor; assertion for 404 remains correct.

Also applies to: 92-95

steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java (1)

72-75: Message formatting: prefer template-aware ctor for consistency.

Minor: Using the template-aware constructor keeps messaging uniform across the codebase (already done here). No action needed.

steve-api/src/main/java/de/rwth/idsg/steve/web/api/TransactionsRestController.java (1)

64-66: LGTM – controller advice correctly maps SteveException.BadRequest to HTTP 400 for unsupported CSV flag.

steve-api/src/main/java/de/rwth/idsg/steve/web/api/OcppTagsRestController.java (1)

21-21: LGTM: migrated to SteveException import.
Consistent with project-wide exception unification.

steve-api/src/main/java/de/rwth/idsg/steve/web/api/ApiControllerAdvice.java (2)

60-63: LGTM: moved NotFound handling to SteveException.NotFound.
Matches new exception model; 404 mapping intact.


77-80: LGTM: BadRequest now handled via SteveException.BadRequest.
Consistent 400 mapping maintained.

Comment on lines +166 to 168
.orElseThrow(() -> new SteveException.BadRequest(
String.format("ChargeBox %s not found!", params.getChargeBoxId())));
transactionParams.setChargePointSelectList(Collections.singletonList(chargePoint));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Return 404 (Not Found) for missing resources instead of 400 (Bad Request).
ChargeBox/Transaction absence is a lookup miss; map to NotFound so ApiControllerAdvice returns 404.

-                .orElseThrow(() -> new SteveException.BadRequest(
-                        String.format("ChargeBox %s not found!", params.getChargeBoxId())));
+                .orElseThrow(() -> new SteveException.NotFound("ChargeBox %s not found!", params.getChargeBoxId()));
@@
-                .orElseThrow(() -> new SteveException.BadRequest(
-                        String.format("ChargeBox %s not found!", params.getChargeBoxId())));
+                .orElseThrow(() -> new SteveException.NotFound("ChargeBox %s not found!", params.getChargeBoxId()));
@@
-                .orElseThrow(() -> new SteveException.BadRequest(String.format(
+                .orElseThrow(() -> new SteveException.NotFound(String.format(
                         "No active transaction found for connector %s at ChargeBox %s!",
                         params.getConnectorId(), params.getChargeBoxId())));
@@
-                .orElseThrow(
-                        () -> new SteveException.BadRequest(String.format("Transaction %s not found!", transactionId)))
+                .orElseThrow(
+                        () -> new SteveException.NotFound("Transaction %s not found!", transactionId))

Also applies to: 190-192, 198-201, 204-206, 227-229


🏁 Script executed:

#!/bin/bash
# Find BadRequest uses with "not found" to convert to NotFound
rg -nP --type=java -C2 'new\s+SteveException\.BadRequest\([^)]*not found' steve-api steve-core

Length of output: 830


🏁 Script executed:

#!/bin/bash
# List all occurrences of SteveException.BadRequest to identify "not found" cases
rg -n --type=java 'new\s+SteveException\.BadRequest' -C2 steve-api steve-core

Length of output: 8618


Map missing-resource lookups to HTTP 404 Not Found
Replace all .orElseThrow(() -> new SteveException.BadRequest(... "not found" ...)) in RemoteCommandsRestController with SteveException.NotFound, so ApiControllerAdvice returns 404 instead of 400.

Affected locations:

  • lines 166–168 & 189–192: ChargeBox lookup
  • lines 196–200: No active transaction lookup
  • lines 203–207: Transaction lookup
  • lines 225–229: ChargeBox lookup
@@
- .orElseThrow(() -> new SteveException.BadRequest(String.format("ChargeBox %s not found!", params.getChargeBoxId())));
+ .orElseThrow(() -> new SteveException.NotFound("ChargeBox %s not found!", params.getChargeBoxId())));

@@
- .orElseThrow(() -> new SteveException.BadRequest(String.format(
+ .orElseThrow(() -> new SteveException.NotFound(String.format(
       "No active transaction found for connector %s at ChargeBox %s!",
       params.getConnectorId(), params.getChargeBoxId())));

@@
- .orElseThrow(() -> new SteveException.BadRequest(String.format("Transaction %s not found!", transactionId)))
+ .orElseThrow(() -> new SteveException.NotFound("Transaction %s not found!", transactionId))

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 207 to 209
if (!ocppTag.contentEquals(params.getOcppTag())) {
throw new BadRequestException("The transaction was authorised with another OCPP Tag!");
throw new SteveException.BadRequest("The transaction was authorised with another OCPP Tag!");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Make OCPP tag comparison null-safe.
Avoid NPE if params.getOcppTag() is null.

-        if (!ocppTag.contentEquals(params.getOcppTag())) {
+        if (!java.util.Objects.equals(ocppTag, params.getOcppTag())) {

Additionally add:

+import java.util.Objects;
📝 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
if (!ocppTag.contentEquals(params.getOcppTag())) {
throw new BadRequestException("The transaction was authorised with another OCPP Tag!");
throw new SteveException.BadRequest("The transaction was authorised with another OCPP Tag!");
}
// Add alongside your other imports at the top of the file
import java.util.Objects;
// In RemoteCommandsRestController.java, around line 207
if (!Objects.equals(ocppTag, params.getOcppTag())) {
throw new SteveException.BadRequest("The transaction was authorised with another OCPP Tag!");
}
🤖 Prompt for AI Agents
In
steve-api/src/main/java/de/rwth/idsg/steve/web/api/RemoteCommandsRestController.java
around lines 207 to 209, replace the direct contentEquals call with a null-safe
comparison to avoid NPEs (e.g. use java.util.Objects.equals(ocppTag,
params.getOcppTag())); update the if to if (!Objects.equals(ocppTag,
params.getOcppTag())) { throw new SteveException.BadRequest("The transaction was
authorised with another OCPP Tag!"); } and add the import java.util.Objects at
the top of the file.

* @since 28.08.2014
*/
public class SteveException extends RuntimeException {
public abstract class SteveException extends RuntimeException {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Abstract base + protected ctors are a breaking change; verify no remaining direct instantiations.

Making SteveException abstract and restricting constructors to protected is good, but any new SteveException(...) will now fail. I already spotted one in ReservationsRestController (see separate comment). Please scan the codebase for others and update callers to a concrete subtype.

Also applies to: 31-57


🏁 Script executed:

#!/bin/bash
# Find direct instantiations of SteveException (will not compile now)
rg -nP -C2 '\bnew\s+SteveException\s*\('

# Also list external subclasses which may rely on removed public constructors
rg -nP -C2 '\bclass\s+\w+\s+extends\s+SteveException\b'

Length of output: 33177


Replace all direct new SteveException(…) calls with concrete subclasses
Direct instantiations of SteveException now fail due to its protected constructors. Update each occurrence across modules (UI, service, repository, OCPP websocket, core, API) to throw an appropriate subtype (e.g. SteveException.AlreadyExists, NotFound, BadRequest) or introduce a new subclass.

🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java around line
27, direct instantiations of SteveException across the codebase now fail because
constructors are protected; replace each "new SteveException(...)" usage with a
concrete subclass (e.g. SteveException.AlreadyExists, NotFound, BadRequest) that
matches the semantic error, or add a new specific subclass in SteveException if
none exists; update import/usages in UI, service, repository, OCPP websocket,
core, and API modules to throw the chosen concrete type and adjust any catch
blocks to handle the new subclass where necessary.

Comment on lines 57 to 79
public Ocpp16WebSocketEndpoint(
WebSocketLogger webSocketLogger,
DelegatingTaskScheduler asyncTaskScheduler,
OcppServerRepository ocppServerRepository,
FutureResponseContextStore futureResponseContextStore,
ApplicationEventPublisher applicationEventPublisher,
CentralSystemService16_SoapServer server,
Ocpp16TypeStore typeStore,
SessionContextStore sessionStore,
Sender sender,
@Qualifier("ocppObjectMapper") ObjectMapper ocppMapper,
Sender sender) {
CentralSystemService16_SoapServer ocpp16Server,
Ocpp16TypeStore ocpp16TypeStore) {
super(
webSocketLogger,
asyncTaskScheduler,
ocppServerRepository,
futureResponseContextStore,
applicationEventPublisher,
sessionStore);
var serializer = new Serializer(ocppMapper);
var deserializer = new Deserializer(ocppMapper, futureResponseContextStore, typeStore);
this.pipeline = new IncomingPipeline(serializer, deserializer, sender, new Ocpp16CallHandler(server));
}

@PostConstruct
public void init() {
super.init(pipeline);
sessionStore,
sender,
ocppMapper,
ocpp16TypeStore,
new Ocpp16CallHandler(ocpp16Server));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Matches new AbstractWebSocketEndpoint constructor; check DI consistency.

Wiring mirrors OCPP 1.5 changes; ensure the Ocpp16TypeStore bean and CentralSystemService16_SoapServer name match configuration.


🏁 Script executed:

#!/bin/bash
# Confirm ocpp16 beans exist
rg -nP 'class\s+Ocpp16TypeStore\b|@Bean\s*\(\)\s*Ocpp16TypeStore' -C2
rg -nP 'class\s+CentralSystemService16_SoapServer\b|@Bean.*CentralSystemService16_SoapServer' -C2

Length of output: 1486


Register CentralSystemService16_SoapServer as a Spring bean
CentralSystemService16_SoapServer (steve-ocpp-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/CentralSystemService16_SoapServer.java) isn’t annotated or defined as a @bean—add @component or declare it in a @configuration so DI can provide it to Ocpp16WebSocketEndpoint.

🤖 Prompt for AI Agents
In
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java
around lines 57 to 79, the constructor requires
CentralSystemService16_SoapServer but that class isn't currently a Spring bean;
register it so DI can inject it by either annotating the
CentralSystemService16_SoapServer class with @Component (or @Service) and ensure
its package is component-scanned, or create a @Configuration class with a @Bean
method that returns an instance of CentralSystemService16_SoapServer (wiring its
dependencies there); after registering the bean, recompile and verify the
application context injects CentralSystemService16_SoapServer into
Ocpp16WebSocketEndpoint without errors.

Comment on lines +43 to 44
private final Consumer<CommunicationContext> handler;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against handler exceptions; always attempt a response/log on failure.

If handler.accept(context) throws, the client receives no reply and the connection might be left in an inconsistent state. Wrap handler execution to avoid propagating, and still try to serialize/send whatever the handler may have set.

-            case OcppJsonCall ocppJsonCall -> {
-                handler.accept(context);
-                send(context);
-            }
+            case OcppJsonCall ocppJsonCall -> {
+                try {
+                    handler.accept(context);
+                } catch (RuntimeException e) {
+                    log.error("Incoming handler failed for {}", context.getChargeBoxId(), e);
+                    // Optionally, consider setting an internal error into the context here if your
+                    // Serializer expects an outgoing message/error.
+                }
+                send(context);
+            }

Also applies to: 57-60

🤖 Prompt for AI Agents
In
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java
around lines 43-44 (and similarly lines 57-60), the call to
handler.accept(context) is unguarded and may throw, leaving the client without a
reply and the connection in an inconsistent state; wrap the handler invocation
in a try-catch that catches Throwable/Exception, log the error with context
details, do not rethrow, and ensure execution continues to the
serialization/send path so you still attempt to serialize and send any response
the handler set (or send a sensible error/fallback response if none was set) and
perform any necessary cleanup/close in a finally or after the catch to keep
connection state consistent.

@juherr juherr force-pushed the features/steve-1791 branch from e8bbf94 to 6453add Compare August 28, 2025 20:13
@juherr juherr changed the title backport https://github.com/steve-community/steve/pull/1791 backport #steve/1791 Aug 28, 2025
@juherr juherr changed the title backport #steve/1791 backport steve/#1791 Aug 28, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
steve-api/src/main/java/de/rwth/idsg/steve/web/api/ApiControllerAdvice.java (1)

60-66: Swapping to SteveException.NotFound/BadRequest is correct; add explicit InternalError handler and avoid leaking 500 messages.
Currently, 500s echo exception.getMessage(); prefer a generic message and a dedicated handler for SteveException.InternalError.

Add an explicit InternalError handler (outside the shown ranges):

@ExceptionHandler(SteveException.InternalError.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiErrorResponse handleInternalError(HttpServletRequest req, SteveException.InternalError ex) {
    String url = req.getRequestURL().toString();
    log.error("Request: {} raised following exception.", url, ex);
    return createResponse(url, HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error");
}

Also, sanitize the catch‑all:

- return createResponse(url, HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage());
+ return createResponse(url, HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error");

Also applies to: 77-83

♻️ Duplicate comments (3)
steve/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java (1)

132-132: Confirm InternalError overload sets the cause.

Same constructor/cause concern as noted elsewhere; ensure stack traces are preserved.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (1)

145-148: Addressed: fail fast on missing chargeBoxId.
Matches prior review guidance; closes with 1008 and returns.

steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)

27-27: Abstract base + protected ctors: verify repo-wide breakages.

Making SteveException abstract with protected ctors is a breaking change; ensure no remaining direct instantiations and no usages of removed top-level exceptions persist.

Run:

#!/usr/bin/env bash
set -euo pipefail

echo "Direct instantiations of SteveException (should be 0):"
rg -nP -C2 '\bnew\s+SteveException\s*\('

echo
echo "Legacy top-level exceptions (should be 0):"
rg -nP -C2 '\b(new|catch\s*\()\s*(NotFoundException|BadRequestException)\b'

echo
echo "Subclasses of SteveException (review ctor calls after ctor visibility change):"
rg -nP -C2 '\bclass\s+\w+\s+extends\s+SteveException\b'
🧹 Nitpick comments (22)
steve-core/src/main/java/de/rwth/idsg/steve/service/UsersService.java (1)

50-52: Improve InternalError message with the created ID for actionable diagnostics

Include the created user ID and avoid “this should never happen” phrasing.

-                .orElseThrow(() ->
-                        new SteveException.InternalError("User not found after creation, this should never happen"));
+                .orElseThrow(() ->
+                        new SteveException.InternalError(String.format(
+                                "Inconsistent state: created user id=%d but getDetails() returned empty", id)));
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/InvocationContext.java (1)

38-43: Fail fast on null RequestType and enrich error message

Add a null check for requestType and include context (chargeBoxId, task) in the error to aid debugging. Also tighten the wording (“Action not found …”).

 var requestType = request().apply(task);
 
 var pair = typeStore().findActionResponse(requestType);
-        if (pair == null) {
-            throw new SteveException.InternalError("Action name is not found");
-        }
+        if (requestType == null) {
+            throw new SteveException.InternalError(String.format(
+                "RequestType mapper returned null (task=%s, chargeBoxId=%s)",
+                task == null ? "null" : task.getClass().getName(), chargeBoxId));
+        }
+        if (pair == null) {
+            throw new SteveException.InternalError(String.format(
+                "Action not found for requestType=%s (class=%s), chargeBoxId=%s",
+                requestType, requestType.getClass().getName(), chargeBoxId));
+        }
steve/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java (1)

277-277: Improve error message to include the enum value; consider null safety.

Include the problematic value to aid debugging; also verify form.getPeriodType() cannot be null before the switch.

-            default -> throw new SteveException.InternalError("Unknown enum type");
+            default -> throw new SteveException.InternalError("Unknown enum type: " + form.getPeriodType());
steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java (2)

191-191: Include the offending enum in the error for faster diagnostics.

Surface the actual heartbeat period value to aid debugging.

-            default -> throw new SteveException.InternalError("Unknown enum type");
+            default -> throw new SteveException.InternalError(
+                    "Unknown heartbeat period: %s", form.getHeartbeatPeriod());

337-338: Add chargeBoxPk to the delete failure message.

Helps correlate logs with inputs.

-                throw new SteveException.InternalError("Failed to delete the charge point", e);
+                throw new SteveException.InternalError(
+                        "Failed to delete the charge point with id '%d'", chargeBoxPk, e);
steve/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java (1)

90-92: Simplify removal with removeIf for clarity and performance.

Replacing stream+remove with removeIf avoids extra lookups and is idiomatic for ConcurrentHashMap views.

-        lookupTable.entrySet().stream().filter(filterPredicate).forEach(entry -> lookupTable.remove(entry.getKey()));
+        lookupTable.entrySet().removeIf(filterPredicate);
steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java (1)

382-383: Unknown period type: consider BadRequest (input) vs InternalError (state).

If period type originates from user input (web/API), BadRequest better reflects a client error. If it’s purely internal, InternalError is fine. Please confirm origin.

-            default -> throw new SteveException.InternalError("Unknown enum type");
+            default -> throw new SteveException.BadRequest("Unknown enum type: %s", form.getPeriodType());
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java (5)

81-82: Clarify error text for unknown message type.

"Unknown enum type" is vague. Include the numeric/type info to speed diagnostics.

-                default -> throw new SteveException.InternalError("Unknown enum type");
+                default -> throw new SteveException.InternalError(
+                        "Unknown OCPP message type: typeNr=%s", messageTypeNr);

84-86: Avoid embedding full incoming payload in exception; risk of PII/log bloat.

Consider truncating the payload or logging it at DEBUG separately, and keep the exception concise.

-            throw new SteveException.InternalError(
-                    "Deserialization of incoming string failed: %s", context.getIncomingString(), e);
+            throw new SteveException.InternalError("Deserialization of incoming string failed", e);

148-151: Same here: cap or omit raw message in exception.

Large/PII-rich payloads should not be embedded in exception text.

-            throw new SteveException.InternalError(
-                    "A result message was received as response to a not-sent call. The message was: %s",
-                    context.getIncomingString());
+            throw new SteveException.InternalError(
+                    "A result message was received as response to a not-sent call");

177-180: Same concern: avoid including full incoming payload in exception.

Prefer concise message; log payload separately at DEBUG if needed.

-            throw new SteveException.InternalError(
-                    "An error message was received as response to a not-sent call. The message was: %s",
-                    context.getIncomingString());
+            throw new SteveException.InternalError(
+                    "An error message was received as response to a not-sent call");

145-151: Consider logging session context for mismatched result/error.

Adding chargeBoxId/sessionId to logs (not exception) eases triage of out-of-order or orphaned responses.

Would you like a follow-up patch to add guarded DEBUG logs with session.getId() and a short payload preview?

steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java (3)

114-125: Include the unexpected enum value in the error message

Helps diagnosis when clients send an out-of-range value.

-            default -> throw new SteveException.InternalError("Unknown enum type");
+            default -> throw new SteveException.InternalError(
+                "Unknown enum value for 'expired': %s", form.getExpired());

250-252: Use ocppTagPk in update error message to avoid null/ambiguous idTag

The WHERE clause uses ocppTagPk; prefer that identifier in diagnostics.

-            throw new SteveException.InternalError(
-                    "Execution of updateOcppTag for idTag '%s' FAILED.", u.getIdTag(), e);
+            throw new SteveException.InternalError(
+                    "Execution of updateOcppTag for ocppTagPk %d FAILED.", u.getOcppTagPk(), e);

259-261: Fix message: refers to idTag but the parameter is ocppTagPk

Align the message with the method argument.

-            throw new SteveException.InternalError("Execution of deleteOcppTag for idTag FAILED.", e);
+            throw new SteveException.InternalError(
+                "Execution of deleteOcppTag for ocppTagPk %d FAILED.", ocppTagPk, e);
steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java (1)

61-63: Include the created id in the InternalError message

Improves traceability in logs.

-                .orElseThrow(() -> new SteveException.InternalError(
-                        "Reservation not found after creation, this should never happen"));
+                .orElseThrow(() -> new SteveException.InternalError(
+                        "Reservation %d not found after creation, this should never happen", id));
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (1)

57-63: Enrich error context with chargeBoxId and standardize logging

Add chargeBoxId to both the thrown message (CALL) and the log line (non-CALL).

-                var msg = "Failed to send outgoing " + call.getAction() + " CALL over WebSocket";
-                throw new SteveException.InternalError(msg, e);
+                var msg = String.format(
+                    "Failed to send outgoing %s CALL over WebSocket for chargeBoxId '%s'",
+                    call.getAction(), chargeBoxId);
+                throw new SteveException.InternalError(msg, e);
             } else {
-                log.error("Failed to send outgoing message over WebSocket", e);
+                log.error("Failed to send outgoing message over WebSocket for chargeBoxId {}",
+                          chargeBoxId, e);
             }
steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)

63-88: InternalError: constructor symmetry, serialVersionUID, and minor dedup.

  • Expose 2-arg+cause ctor publicly for parity.
  • Add serialVersionUID for nested type.
  • Prefer calling super(template, …) to avoid re-formatting here.
 public static class InternalError extends SteveException {
- 
-        public InternalError(String message) {
-            super(message);
-        }
- 
-        public InternalError(String message, Throwable cause) {
-            super(message, cause);
-        }
- 
-        public InternalError(String template, Object arg1) {
-            this(format(template, arg1));
-        }
- 
-        public InternalError(String template, Object arg1, Throwable cause) {
-            this(format(template, arg1), cause);
-        }
- 
-        public InternalError(String template, Object arg1, Object arg2) {
-            this(format(template, arg1, arg2));
-        }
- 
-        protected InternalError(String template, Object arg1, Object arg2, Throwable cause) {
-            this(format(template, arg1, arg2), cause);
-        }
+        private static final long serialVersionUID = 1L;
+
+        public InternalError(String message) { super(message); }
+        public InternalError(String message, Throwable cause) { super(message, cause); }
+        public InternalError(String template, Object arg1) { super(template, arg1); }
+        public InternalError(String template, Object arg1, Throwable cause) { super(template, arg1, cause); }
+        public InternalError(String template, Object arg1, Object arg2) { super(template, arg1, arg2); }
+        public InternalError(String template, Object arg1, Object arg2, Throwable cause) { super(template, arg1, arg2, cause); }
 }
steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (4)

267-267: Add supporting DB index for ordered fetch

Consider a composite index on CHARGING_SCHEDULE_PERIOD(CHARGING_PROFILE_PK, START_PERIOD_IN_SECONDS) to back the ORDER BY and frequent lookups.


309-309: Enrich error context on add()

Include identifying context (e.g., description/stackLevel) to aid diagnostics.

Apply:

-                throw new SteveException.InternalError("Failed to add the charging profile", e);
+                throw new SteveException.InternalError(
+                    String.format("Failed to add charging profile [desc=%s, stackLevel=%s]",
+                                  form.getDescription(), form.getStackLevel()),
+                    e
+                );

382-384: Use a non-internal error for business-rule violation + safe formatting

This is a user/action conflict, not an internal error. If a domain/validation/conflict SteveException exists, use it; otherwise at least avoid ambiguous formatting.

Apply safe formatting:

-            throw new SteveException.InternalError(
-                    "Cannot modify this charging profile, since the following stations are still using it: %s", r);
+            throw new SteveException.InternalError(
+                String.format("Cannot modify this charging profile; still used by stations: %s", r)
+            );

If available, prefer:

  • SteveException.Conflict / SteveException.Validation with same message.

388-399: Validate/normalize schedule periods before insert

Consider rejecting negative start periods and duplicate startPeriodInSeconds early to avoid DB errors and nondeterminism.

Example:

-        var r = form.getSchedulePeriods().stream()
+        var r = form.getSchedulePeriods().stream()
+                .filter(p -> p.getStartPeriodInSeconds() >= 0)
                 .sorted(Comparator.comparingInt(ChargingProfileForm.SchedulePeriod::getStartPeriodInSeconds))

If duplicates are possible, enforce uniqueness upstream or via a DB UNIQUE constraint on (CHARGING_PROFILE_PK, START_PERIOD_IN_SECONDS).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e8bbf94 and 6453add.

📒 Files selected for processing (38)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/ApiControllerAdvice.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/OcppTagsRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/RemoteCommandsRestController.java (6 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/TasksRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/TransactionsRestController.java (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/BadRequestException.java (0 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/NotFoundException.java (0 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/UsersService.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/ChangeConfigurationParams.java (1 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (6 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/InvocationContext.java (1 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (1 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (2 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java (5 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java (1 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (3 hunks)
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Serializer.java (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/TasksController.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (5 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/ReservationRepositoryImpl.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/SettingsRepositoryImpl.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java (1 hunks)
💤 Files with no reviewable changes (2)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/NotFoundException.java
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/exception/BadRequestException.java
🚧 Files skipped from review as they are similar to previous changes (7)
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/TransactionsRestController.java
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/RemoteCommandsRestController.java
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/TasksRestController.java
  • steve-api/src/main/java/de/rwth/idsg/steve/web/api/OcppTagsRestController.java
  • steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
🧰 Additional context used
🧬 Code graph analysis (1)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (1)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/data/CommunicationContext.java (2)
  • RequiredArgsConstructor (41-116)
  • RequiredArgsConstructor (82-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
🔇 Additional comments (38)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/InvocationContext.java (2)

42-42: Good move to InternalError

Throwing SteveException.InternalError here correctly classifies a server-side mapping/config error.


35-55: Ensure callers aren’t expecting the old exception type
No catch blocks for SteveException.InternalError or IllegalStateException were found—manually verify no upstream code assumes the previous exception.

steve/src/main/java/de/rwth/idsg/steve/repository/impl/GenericRepositoryImpl.java (1)

78-81: LGTM: InternalError on TZ mismatch is appropriate.

Consistent with the new exception hierarchy; message already includes both offsets.

steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/TasksController.java (1)

99-101: Unsupported task type as InternalError is fine; also guard null task.

Switch on a null task will NPE. Add a pre-check and return NotFound for missing tasks.

// Add before the switch:
if (task == null) {
    throw new SteveException.NotFound("Task not found: id=" + taskId);
}
steve/src/main/java/de/rwth/idsg/steve/service/MailServiceDefault.java (1)

64-65: LGTM: Wrap MessagingException as InternalError.

Message is generic (no secrets) and preserves the cause.

steve/src/main/java/de/rwth/idsg/steve/repository/impl/UserRepositoryImpl.java (5)

104-105: LGTM: Internal DB failure mapped to InternalError.

Correctly ensures transaction rollback and consistent error semantics.


119-120: LGTM: Update failure mapped to InternalError.

Consistent with repository-wide error handling.


132-133: LGTM: Delete failure mapped to InternalError.

Keeps behavior uniform across CRUD paths.


234-235: LGTM: No-returning record treated as InternalError.

Appropriate invariant check for an insert-returning flow.


238-239: LGTM: Insert failure mapped to InternalError.

Consistent with the new exception hierarchy.

steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java (1)

307-309: No change needed: cause is preserved by correct InternalError overload
Verified that InternalError(String template, Object arg1, Throwable cause) delegates to super(formattedMessage, cause), so the passed exception isn’t formatted into the message and the stack‐trace is retained.

steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (1)

244-245: LGTM: NotFound is the right classification here.

Switching to SteveException.NotFound for missing users aligns with the new exception taxonomy.

Also applies to: 259-260

steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1)

349-351: LGTM: Internal error on unexpected null id.

Appropriate safeguard; jOOQ should return a key, but this ensures a clear failure mode.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (1)

139-139: InternalError constructor with (String, Object, Throwable) confirmed; the cause will be attached correctly.

steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ocpp/ChangeConfigurationParams.java (1)

77-78: LGTM: InternalError on illegal KeyType is consistent with project taxonomy.

No further changes needed.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java (2)

159-160: LGTM: preserve cause for payload deserialization failures.

Exception wrapping is correct.


208-209: LGTM: cause propagation retained for error-message deserialization.

No changes needed.

steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java (1)

233-235: LGTM: InternalError preserves context and cause on add failure

Message includes idTag and propagates the original exception.

steve-api/src/main/java/de/rwth/idsg/steve/web/api/ReservationsRestController.java (1)

74-75: LGTM: 404 mapped to SteveException.NotFound

Consistent with the new exception model; aligns with controller advice.

steve-api/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (2)

364-367: LGTM: test uses InternalError to simulate 5xx path

Keeps the contract while aligning with the new exception type.


416-418: LGTM: delete 5xx simulation updated to InternalError

Consistent with controller error handling.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (2)

49-52: LGTM: switch to injected WebSocketLogger

Removes static coupling and eases testing.


39-43: DI wiring for Sender is correctly configured. OcppConfiguration declares a @bean for Sender(WebSocketLogger), and WebSocketLogger is annotated with @component.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Serializer.java (2)

67-67: Use of SteveException.InternalError on serialization failure looks correct.
Aligns with internal-failure semantics; upstream can uniformly treat it as 500-class.


84-85: Consistent InternalError on payload conversion failure.
Good replacement; matches project-wide migration to nested SteveException types.

steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java (7)

71-99: Constructor-based wiring of IncomingPipeline is solid.
Thread-safe reuse of ObjectMapper here is fine; event callbacks correctly registered.


124-129: Text handling + logging LGTM.
Empty payload guard and logging are appropriate.


136-140: PONG handling LGTM.
Heartbeat update on pong is appropriate.


180-181: Close logging LGTM.
Uses instance logger; consistent format.


200-201: Transport error logging LGTM.
Uniform error path via WebSocketLogger.


59-69: Approved: CHARGEBOX_ID_KEY is set in OcppWebSocketHandshakeHandler (line 96) and retrieved in AbstractWebSocketEndpoint (line 213), confirming consistent use.


150-159: LGTM: Ping scheduling wiring is correct.

steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java (1)

31-57: Base constructors/readability: LGTM.

Protected overloads + format helpers are clean and prevent ad‑hoc base instantiation.

steve/src/main/java/de/rwth/idsg/steve/repository/impl/ChargingProfileRepositoryImpl.java (5)

41-44: Imports for Comparator/Optional — OK

These additions match the new usages below.


301-301: Fix returning PK target — good catch

Returning CHARGING_PROFILE.CHARGING_PROFILE_PK here is correct for retrieving the profile PK.


256-270: Resolved: Optional return and DB ordering verified
ChargingProfileRepository interface declares Optional<ChargingProfile.Details> getDetails(...), the implementation returns Optional.empty() when absent and orders schedule periods ascending, and all callers (e.g. ChargingProfilesController, ChargePointServiceClient) handle the empty case via .orElseThrow(...).


359-361: Constructor overload is unambiguous; cause is preserved
The call InternalError(String, Object, Throwable) matches the three-arg constructor, which formats the message then delegates to super(message, cause). The exception’s cause is not dropped—no change needed.

Likely an incorrect or invalid review comment.


392-399: Stream.toList() is supported with Java 21
The compiler is configured with <release>${java.version}</release> and java.version is set to 21 in the parent POM, so the code is compiled under Java 21 (which includes Stream.toList() since Java 16). No change needed.

Likely an incorrect or invalid review comment.

Comment on lines +60 to +61
throw new SteveException.InternalError(
"ChargePointMaxProfile can only be set at Charge Point ConnectorId 0");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use BadRequest for spec-violating inputs instead of InternalError.

These are client/request validation errors; surfacing them as 400 aligns API semantics and avoids 500s.

-            throw new SteveException.InternalError(
+            throw new SteveException.BadRequest(
                     "ChargePointMaxProfile can only be set at Charge Point ConnectorId 0");
...
-            throw new SteveException.InternalError("TxProfile should only be set at Charge Point ConnectorId > 0");
+            throw new SteveException.BadRequest("TxProfile should only be set at Charge Point ConnectorId > 0");

Also applies to: 65-65

🤖 Prompt for AI Agents
In
steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java
around lines 60-61 (and the similar occurrence at line 65), replace the thrown
SteveException.InternalError for spec-violating inputs with the client-side 400
variant by throwing SteveException.BadRequest (or the project's BadRequest
variant) using the same descriptive message; ensure both locations use
BadRequest so these validation failures surface as HTTP 400 instead of 500.

Comment on lines +61 to +62
.orElseThrow(() ->
new SteveException.InternalError("User not found after update, this should never happen"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use NotFound (4xx) instead of InternalError (5xx) when user is missing after update

A missing user after update is a resource/state issue (invalid userPk or concurrent delete), not an internal failure.

-                .orElseThrow(() ->
-                        new SteveException.InternalError("User not found after update, this should never happen"));
+                .orElseThrow(() ->
+                        new SteveException.NotFound(String.format(
+                                "User with id %d not found for update", form.getUserPk())));
📝 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
.orElseThrow(() ->
new SteveException.InternalError("User not found after update, this should never happen"));
.orElseThrow(() ->
new SteveException.NotFound(String.format(
"User with id %d not found for update", form.getUserPk())));
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/service/UsersService.java around
lines 61-62, the code throws SteveException.InternalError when the user is
missing after an update; change this to throw the appropriate 4xx NotFound
variant (e.g., SteveException.NotFound) with a clear message like "User not
found after update" so the response reflects a client/resource error rather than
a server/internal error.

Comment on lines 90 to 95
public static class AlreadyExists extends SteveException {

public AlreadyExists(String template, Object arg1) {
super(format(template, arg1));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

AlreadyExists: add message/cause ctors and serialVersionUID; use super(template,…).

Only having (template, arg1) limits cause propagation and message-only use.

 public static class AlreadyExists extends SteveException {
-
-        public AlreadyExists(String template, Object arg1) {
-            super(format(template, arg1));
-        }
+        private static final long serialVersionUID = 1L;
+
+        public AlreadyExists(String message) { super(message); }
+        public AlreadyExists(String template, Object arg1) { super(template, arg1); }
+        public AlreadyExists(String message, Throwable cause) { super(message, cause); }
+        public AlreadyExists(String template, Object arg1, Throwable cause) { super(template, arg1, cause); }
 }
📝 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
public static class AlreadyExists extends SteveException {
public AlreadyExists(String template, Object arg1) {
super(format(template, arg1));
}
}
public static class AlreadyExists extends SteveException {
private static final long serialVersionUID = 1L;
public AlreadyExists(String message) {
super(message);
}
public AlreadyExists(String template, Object arg1) {
super(template, arg1);
}
public AlreadyExists(String message, Throwable cause) {
super(message, cause);
}
public AlreadyExists(String template, Object arg1, Throwable cause) {
super(template, arg1, cause);
}
}
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java around lines
90-95, the AlreadyExists subclass only has a single (template, arg1) ctor and
lacks serialVersionUID; add a private static final long serialVersionUID and
implement additional constructors: (String template) and (String template,
Throwable cause) (and keep the existing one but change it to delegate to
super(template, arg1) rather than pre-formatting), ensuring each ctor calls the
appropriate super(...) overload so message-only and message+cause use-cases and
cause propagation are supported.

Comment on lines +97 to +106
public static class NotFound extends SteveException {

public NotFound(String message) {
super(message);
}

public NotFound(String template, Object arg1) {
this(format(template, arg1));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

NotFound: add cause-enabled ctors + serialVersionUID; dedup to super(template,…).

Without cause ctors, stack traces may be lost; add them and keep formatting centralized.

-    public static class NotFound extends SteveException {
-
-        public NotFound(String message) {
-            super(message);
-        }
-
-        public NotFound(String template, Object arg1) {
-            this(format(template, arg1));
-        }
-    }
+    public static class NotFound extends SteveException {
+        private static final long serialVersionUID = 1L;
+
+        public NotFound(String message) { super(message); }
+        public NotFound(String message, Throwable cause) { super(message, cause); }
+        public NotFound(String template, Object arg1) { super(template, arg1); }
+        public NotFound(String template, Object arg1, Throwable cause) { super(template, arg1, cause); }
+    }
📝 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
public static class NotFound extends SteveException {
public NotFound(String message) {
super(message);
}
public NotFound(String template, Object arg1) {
this(format(template, arg1));
}
}
public static class NotFound extends SteveException {
private static final long serialVersionUID = 1L;
public NotFound(String message) {
super(message);
}
public NotFound(String message, Throwable cause) {
super(message, cause);
}
public NotFound(String template, Object arg1) {
super(template, arg1);
}
public NotFound(String template, Object arg1, Throwable cause) {
super(template, arg1, cause);
}
}
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java around lines
97-106, the NotFound subclass should add a serialVersionUID and cause-aware
constructors, and delegate formatting to the superclass to avoid duplication:
add a private static final long serialVersionUID, replace the template-based
ctor that calls this(format(...)) with a call to super(template, arg1) (or
super(template, args...) if varargs exist), and add overloads accepting (String
message, Throwable cause) and (String template, Throwable cause, Object... args)
that pass the cause up to super so stack traces are preserved. Ensure signatures
match SteveException's existing constructors and remove the local format call to
centralize formatting in the base class.

Comment on lines +108 to +116
public static class BadRequest extends SteveException {
public BadRequest(String message) {
super(message);
}

public BadRequest(String template, Object arg1) {
this(format(template, arg1));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

BadRequest: add cause-enabled ctors + serialVersionUID; dedup to super(template,…).

Mirror NotFound to preserve causal chains and consistency.

-    public static class BadRequest extends SteveException {
-        public BadRequest(String message) {
-            super(message);
-        }
-
-        public BadRequest(String template, Object arg1) {
-            this(format(template, arg1));
-        }
-    }
+    public static class BadRequest extends SteveException {
+        private static final long serialVersionUID = 1L;
+
+        public BadRequest(String message) { super(message); }
+        public BadRequest(String message, Throwable cause) { super(message, cause); }
+        public BadRequest(String template, Object arg1) { super(template, arg1); }
+        public BadRequest(String template, Object arg1, Throwable cause) { super(template, arg1, cause); }
+    }
📝 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
public static class BadRequest extends SteveException {
public BadRequest(String message) {
super(message);
}
public BadRequest(String template, Object arg1) {
this(format(template, arg1));
}
}
public static class BadRequest extends SteveException {
private static final long serialVersionUID = 1L;
public BadRequest(String message) {
super(message);
}
public BadRequest(String message, Throwable cause) {
super(message, cause);
}
public BadRequest(String template, Object arg1) {
super(template, arg1);
}
public BadRequest(String template, Object arg1, Throwable cause) {
super(template, arg1, cause);
}
}
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/SteveException.java around lines
108 to 116, the BadRequest subclass is missing a serialVersionUID, lacks
constructors that accept a cause, and its templated constructor delegates to
this(format(...)) instead of calling the corresponding super(template, ...);
update BadRequest to match the NotFound pattern by adding a private static final
long serialVersionUID, adding constructors public BadRequest(String message,
Throwable cause) and public BadRequest(String template, Object arg1, Throwable
cause) (and a varargs templated overload if NotFound has one), and change the
existing templated ctor to delegate directly to super(template, arg1) (i.e.,
call the superclass template-formatting constructor) so causal chains are
preserved and code is consistent.

Comment on lines +124 to 125
throw new SteveException.InternalError("Result not found for chargeBoxId '" + chargeBoxId + "'");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prefer NotFound over InternalError when result is absent.

Missing entry for a user-supplied chargeBoxId is not an internal failure.

-            throw new SteveException.InternalError("Result not found for chargeBoxId '" + chargeBoxId + "'");
+            throw new SteveException.NotFound("Result not found for chargeBoxId '" + chargeBoxId + "'");
📝 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
throw new SteveException.InternalError("Result not found for chargeBoxId '" + chargeBoxId + "'");
}
throw new SteveException.NotFound("Result not found for chargeBoxId '" + chargeBoxId + "'");
}
🤖 Prompt for AI Agents
In
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/TasksController.java
around lines 124 to 125, the code throws SteveException.InternalError when a
result for a user-supplied chargeBoxId is missing; change this to throw
SteveException.NotFound with an appropriate message (e.g., "Result not found for
chargeBoxId '...'" ) so the absence of a resource is represented as a 404-level
error rather than an internal server error; ensure any callers or exception
handlers already map NotFound to the correct HTTP 404 response.

Comment on lines 100 to 103
} catch (DataAccessException e) {
throw new SteveException("Failed to insert the address");
throw new SteveException.InternalError("Failed to insert the address");
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve root cause on insert failure and guard against null RETURNING row

Wrap and propagate the original DataAccessException; also handle a potential null from fetchOne() to avoid NPEs.

-        } catch (DataAccessException e) {
-            throw new SteveException.InternalError("Failed to insert the address");
-        }
+        } catch (DataAccessException e) {
+            throw new SteveException.InternalError("Failed to insert the address", e);
+        }

Optionally, make the RETURNING handling explicit:

-                    .returning(ADDRESS.ADDRESS_PK)
-                    .fetchOne()
-                    .getAddressPk();
+                    .returning(ADDRESS.ADDRESS_PK)
+                    .fetchOne();
+            if (rec == null) {
+                throw new SteveException.InternalError("Failed to insert the address: no RETURNING row");
+            }
+            return rec.getAddressPk();
📝 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
} catch (DataAccessException e) {
throw new SteveException("Failed to insert the address");
throw new SteveException.InternalError("Failed to insert the address");
}
}
} catch (DataAccessException e) {
throw new SteveException.InternalError("Failed to insert the address", e);
}
}
🤖 Prompt for AI Agents
In
steve/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java
around lines 100 to 103, the catch currently swallows the DataAccessException
and assumes fetchOne() returned a non-null row; change it to preserve the
original exception as the cause when throwing SteveException.InternalError (e.g.
pass 'e' into the new exception) and add a null-check for the result of
fetchOne() so you handle a null RETURNING row explicitly (throw a clear
SteveException or return an Optional/appropriate error) instead of risking an
NPE.

Comment on lines 115 to 117
if (count != 1) {
throw new SteveException("Failed to update the address");
throw new SteveException.InternalError("Failed to update the address");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Differentiate “not found” vs “unexpected row count” on update

If count == 0, the PK doesn’t exist (404). If count > 1, that’s an internal integrity issue (500).

-        if (count != 1) {
-            throw new SteveException.InternalError("Failed to update the address");
-        }
+        if (count == 0) {
+            throw new SteveException.NotFound("Address with id %d not found", ad.getAddressPk());
+        } else if (count != 1) {
+            throw new SteveException.InternalError(
+                "Updated %d rows for address id %d, expected 1", count, ad.getAddressPk());
+        }
📝 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
if (count != 1) {
throw new SteveException("Failed to update the address");
throw new SteveException.InternalError("Failed to update the address");
}
if (count == 0) {
throw new SteveException.NotFound(
"Address with id %d not found", ad.getAddressPk());
} else if (count != 1) {
throw new SteveException.InternalError(
"Updated %d rows for address id %d, expected 1",
count, ad.getAddressPk());
}
🤖 Prompt for AI Agents
In
steve/src/main/java/de/rwth/idsg/steve/repository/impl/AddressRepositoryImpl.java
around lines 115-117, differentiate the update result: if count == 0, throw a
404-style exception (e.g. SteveException.NotFound with a message like "Address
not found for id ..."); if count > 1, throw the existing internal error
(SteveException.InternalError) with a clear message about the unexpected row
count (e.g. "Unexpected row count >1 when updating address"). Ensure the
exception types and messages make the distinction explicit and include the
address id when available.

CommunicationTask r = lookupTable.get(taskId);
if (r == null) {
throw new SteveException("There is no task with taskId '%s'", taskId);
throw new SteveException.InternalError("There is no task with taskId '%s'", taskId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use NotFound for missing task rather than InternalError.

A non-existent taskId is a “not found” outcome, not an internal failure. Prefer SteveException.NotFound for clearer semantics and consistent API mapping.

-            throw new SteveException.InternalError("There is no task with taskId '%s'", taskId);
+            throw new SteveException.NotFound("There is no task with taskId '%s'", taskId);
📝 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
throw new SteveException.InternalError("There is no task with taskId '%s'", taskId);
throw new SteveException.NotFound("There is no task with taskId '%s'", taskId);
🤖 Prompt for AI Agents
In steve/src/main/java/de/rwth/idsg/steve/repository/impl/TaskStoreImpl.java
around line 67, the code throws SteveException.InternalError for a missing
taskId; change this to throw SteveException.NotFound with the same message and
taskId so the API semantics correctly signal a missing resource, and add or
adjust the import if necessary to reference SteveException.NotFound.


if (transaction == null) {
throw new SteveException("There is no transaction with id '%s'", transactionPk);
throw new SteveException.InternalError("There is no transaction with id '%s'", transactionPk);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Return NotFound for absent transaction.

Absence of a transaction id should be classified as NotFound to align with repository semantics and potential 404 mapping at higher layers.

-            throw new SteveException.InternalError("There is no transaction with id '%s'", transactionPk);
+            throw new SteveException.NotFound("There is no transaction with id '%s'", transactionPk);
📝 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
throw new SteveException.InternalError("There is no transaction with id '%s'", transactionPk);
@@ steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java:147
throw new SteveException.NotFound("There is no transaction with id '%s'", transactionPk);
🤖 Prompt for AI Agents
In
steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl
around line 147, the code throws a SteveException.InternalError when a
transaction id is absent; change this to throw the appropriate
SteveException.NotFound (or the project's NotFound variant) with the same
formatted message including transactionPk so the absence is classified as
NotFound and correctly maps to a 404 at higher layers.

@juherr juherr merged commit ac76750 into main Aug 28, 2025
50 checks passed
@juherr juherr deleted the features/steve-1791 branch August 28, 2025 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant