Skip to content

backport of steve-community/steve#1822#120

Merged
juherr merged 1 commit intomainfrom
backport/steve-1822
Sep 29, 2025
Merged

backport of steve-community/steve#1822#120
juherr merged 1 commit intomainfrom
backport/steve-1822

Conversation

@juherr
Copy link
Owner

@juherr juherr commented Sep 22, 2025

Backport of steve-community#1822

Summary by CodeRabbit

  • New Features

    • App migrated to Spring Boot with a new entrypoint; final artifact is a runnable WAR.
  • Changes

    • Configuration consolidated to application.yml (with dev/test variants); routing, WebSocket/OCPP, SSL and security now driven by central properties.
    • Logging unified to a profile-aware logback-spring setup (console + rolling file).
    • Packaging, Dockerfile and CI startup examples updated for WAR-based deployment and Spring Boot packaging.
  • Tests

    • Tests converted to Spring-managed contexts with per-test setup/cleanup.

@coderabbitai
Copy link

coderabbitai bot commented Sep 22, 2025

Walkthrough

Replace custom bootstrap and file-based configuration with a Spring Boot application: introduce SteveProperties bound to YAML, switch packaging to WAR and Spring Boot starters, remove legacy boot/Jetty/property loaders, refactor CXF SOAP/WebSocket wiring and validators, migrate JOOQ/Flyway wiring, and convert many tests to SpringBootTest/DI. (49 words)

Changes

Cohort / File(s) Summary
Build & Packaging
pom.xml, steve/pom.xml, steve-*/pom.xml, .github/workflows/main.yml, Dockerfile
Add Spring Boot parent/starter dependencies, consolidate BOMs, change packaging to war, update build plugins (spring-boot-maven-plugin), CI and Docker to run target/steve.war.
Configuration & docs
steve/src/main/resources/application*.yml, steve/src/main/resources/config/main.properties (removed), AGENTS.md, steve/README.md
Add application.yml and env YAMLs, remove legacy main.properties, update docs and run examples to reference YAML and WAR.
Config model & loaders
Removed: <...>/SteveConfiguration.java, .../PropertyConfig.java, .../utils/PropertiesFileLoader.java, .../utils/SteveConfigurationReader.java
Added: steve-core/.../config/SteveProperties.java
Remove legacy file-based SteveConfiguration and loaders; introduce @ConfigurationProperties SteveProperties; delete placeholder configurer and property loaders.
Application bootstrap & server
steve/src/main/java/.../SteveApplication.java (added), steve/src/main/java/.../Application.java (removed), steve/src/main/java/.../JettyServer.java (removed), steve/src/main/resources/logback-spring.xml (added), steve/src/main/java/.../config/WebConfig.java (updated)
Replace custom Application/Jetty bootstrap with Spring Boot entrypoint and logback-spring; adapt WebConfig to use SteveProperties.
CXF SOAP & TLS client
steve-ocpp-transport-soap/.../OcppSoapConfiguration.java, .../ocpp/soap/ClientProvider.java, .../ocpp/soap/MediatorInInterceptor.java
Migrate to CXF Spring Boot style: publish EndpointImpl beans, create TLS params from ServerProperties.Ssl, and route requests using EndpointImpl lists.
WebSocket transport
steve-ocpp-transport-websocket/.../OcppWebSocketConfiguration.java, .../ocpp/ws/OcppWebSocketHandshakeHandler.java
Derive pathInfix from SteveProperties, pass into handshake handler, remove hard-coded path constant, update URL parsing.
Validation & factory
steve-core/.../web/validation/*, steve-api/.../ChargePointsRestControllerTest.java
Replace @value field wiring with constructor injection using SteveProperties; add per-element validation (TYPE_USE); remove custom SpringConstraintValidatorFactory; tests register LocalValidatorFactoryBean.
JOOQ, Flyway & DB wiring
steve-jooq/.../JooqConfiguration.java, steve-jooq/.../FlywayMigrationRunner.java (removed), steve-jooq/pom.xml
Use SteveProperties in JooqConfiguration, remove FlywayMigrationRunner, adopt Spring Boot-managed jOOQ/Flyway and starter dependencies.
Core services & security
steve-core/.../service/*.java, steve/src/main/java/.../config/SecurityConfiguration.java, steve/src/main/java/.../config/BeanConfiguration.java
Replace SteveConfiguration/@value usage with SteveProperties, inject BCryptPasswordEncoder, update security filter chains and path matchers to use properties.
Tests → SpringBootTest & DI
steve/src/test/java/... (many files)
Convert many tests to @SpringBootTest with test profile; inject ServerProperties/SteveProperties/DSLContext; replace static config readers with DI-backed helpers and URI-based clients.
UI & utils
steve-ui-jsp/src/main/java/.../utils/LogFileRetriever.java, .../web/controller/AboutSettingsController.java, .../web/dto/EndpointInfo.java
Make LogFileRetriever a Spring @component; controllers/DTOs switch to SteveProperties; modernize local typing.
Repository & DTO tweaks
steve-jooq/.../OcppServerRepositoryImpl.java, steve-core/.../repository/dto/TransactionStopEventActor.java, steve-core/.../web/dto/ChargePointBatchInsertForm.java
Refactor connector PK handling to query-based flows; add Lombok-generated getter/ctor for enum; change list validation to element-level annotations.
Removed legacy glue
multiple files (e.g., Application, JettyServer, PropertiesFileLoader, SteveConfigurationReader, SpringConstraintValidatorFactory, FlywayMigrationRunner)
Delete legacy bootstrap, property loaders, custom validator factory, and other obsolete glue as part of migration to Spring Boot.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Operator
  participant SteveApp as SteveApplication
  participant Spring as Spring Boot
  participant Props as SteveProperties
  participant Jooq as JooqConfiguration
  participant CXF as OcppSoapConfiguration
  participant WS as OcppWebSocketConfiguration

  Operator->>SteveApp: java -jar target/steve.war
  SteveApp->>Spring: SpringApplication.run(...)
  Spring->>Props: bind application.yml -> steve.*
  Spring->>Jooq: create DataSource & DSLContext (use SteveProperties)
  Spring->>CXF: publish EndpointImpl beans (addresses from SteveProperties)
  Spring->>WS: register WebSocket handlers (pathInfix from SteveProperties)
  Spring-->>Operator: application started
Loading
sequenceDiagram
  autonumber
  participant Client as OCPP Client
  participant Router as Router Endpoint
  participant Mediator as MediatorInInterceptor
  participant Map as Endpoint Lookup (EndpointImpl list)
  participant Service as Target OCPP Service

  Client->>Router: SOAP request to routerEndpointPath
  Router->>Mediator: inbound message
  Mediator->>Map: resolve target by namespace/action
  Map-->>Mediator: EndpointImpl (target)
  Mediator->>Service: invoke matched OCPP service
  Service-->>Client: SOAP response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Poem

🐇 I hopped through YAML, swapped props for beans,
The jar became a war and boot awoke the scenes.
Endpoints now sprout, websockets find their route,
Tests wake with DI, old glue tiptoes out.
A carrot for the patch — the burrow's code delights.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.06% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “backport of steve-community#1822” only indicates that this PR is a backport of another pull request and does not describe the substantive changes introduced, such as migrating to Spring Boot configuration, replacing SteveConfiguration with SteveProperties, rewriting the application entry point, and converting packaging to a WAR. Because it uses a PR reference rather than summarizing what was changed, it remains too generic for quick understanding. Please rename the pull request to clearly summarize its primary change—for example, “Migrate SteVe to Spring Boot configuration and entry point,” focusing on the core migration and packaging updates rather than the backport reference.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch backport/steve-1822

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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 (12)
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (1)

91-103: Only the first appender is processed; multi-appender support is broken.

getActiveLogFilePaths() uses a single if (hasNext()) and returns at most one path, making rollTheDice() effectively unreachable. Iterate all appenders.

Apply:

-        var logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
-        var appenderIterator = logger.iteratorForAppenders();
-
-        var fileNameList = new ArrayList<Path>();
-        if (appenderIterator.hasNext()) {
-            var appender = appenderIterator.next();
-            var fileName = extractFileName(appender);
-            if (fileName != null) {
-                fileNameList.add(Paths.get(fileName));
-            }
-        }
-        return fileNameList;
+        var logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+        var appenderIterator = logger.iteratorForAppenders();
+
+        var fileNameList = new ArrayList<Path>();
+        while (appenderIterator.hasNext()) {
+            var appender = appenderIterator.next();
+            var fileName = extractFileName(appender);
+            if (fileName != null) {
+                fileNameList.add(Paths.get(fileName));
+            }
+        }
+        return fileNameList;
steve-jooq/pom.xml (1)

183-191: Ensure jOOQ codegen uses the same version as runtime

steve-jooq/pom.xml — plugin dependencies use ${jooq.version} (lines 182–191); runtime dependency org.jooq:jooq is declared without a version (lines 31–33) and no <jooq.version> property is defined in the repository. Add/define <jooq.version> in the parent POM (pom.xml) and set it to the Spring Boot-managed jOOQ version, or make the plugin dependencies use that same runtime version.

pom.xml (1)

37-48: Build break: ${lombok.version} is referenced but not defined.

annotationProcessorPaths requires an explicit version. Define the property and use it consistently.

Apply:

@@
   <properties>
     <java.version>21</java.version>
@@
-    <cxf.version>4.1.3</cxf.version>
+    <cxf.version>4.1.3</cxf.version>
+    <lombok.version>1.18.34</lombok.version>
@@
       <annotationProcessorPaths>
         <path>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
-          <version>${lombok.version}</version>
+          <version>${lombok.version}</version>
         </path>

Optionally also pin the dependency for coherence:

   <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
+    <version>${lombok.version}</version>
     <scope>provided</scope>
   </dependency>

Also applies to: 191-196

steve-api/pom.xml (1)

23-25: Mark jakarta.servlet-api as provided.

Servlet API should be container-provided; avoid bundling it into artifacts.

Apply:

     <dependency>
       <groupId>jakarta.servlet</groupId>
       <artifactId>jakarta.servlet-api</artifactId>
+      <scope>provided</scope>
     </dependency>
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)

79-83: Fix stray Checkstyle text in Javadoc and wrap the long line.

The embedded “Check failure…” text will re-trigger line-length violations.

-    /**
-     * https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configurationCheck failure[checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Check failure: [checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Line is longer than 120 characters (found 121).build and run tests / checkstyleView detailsCode has alerts. Press enter to view.
-     *
-     * Otherwise, defaults come from {@link WebSocketConstants}
-     */
+    /**
+     * See Spring docs:
+     * https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configuration
+     * Otherwise, defaults come from {@link WebSocketConstants}.
+     */
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (1)

76-82: Be robust to trailing slashes and URL-encoding when extracting chargeBoxId.

Current logic returns empty for subtle path variants or includes a trailing “/”. Decode and trim.

- var chargeBoxId = getLastBitFromUrl(pathInfix, request.getURI().getPath());
+ var chargeBoxId = getLastBitFromUrl(pathInfix, request.getURI().getPath());
+ if (chargeBoxId.endsWith("/")) {
+   chargeBoxId = chargeBoxId.substring(0, chargeBoxId.length() - 1);
+ }
+ chargeBoxId = java.net.URLDecoder.decode(chargeBoxId, java.nio.charset.StandardCharsets.UTF_8);
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)

55-73: Fix servlet/filter URL pattern normalization (avoid broken mappings).
Risk: wrong/duplicate wildcards (e.g., "/steve/" + "") and DispatcherServlet mapped to an exact path ("/steve/") instead of a path pattern ("/steve/*"). Normalize once and reuse for both servlet and filter.

Apply this diff and helper:

-        var web = servletContext.addServlet("spring-dispatcher", new DispatcherServlet(springContext));
-        web.setLoadOnStartup(1);
-        web.addMapping(steveProperties.getPaths().getRootMapping());
+        var web = servletContext.addServlet("spring-dispatcher", new DispatcherServlet(springContext));
+        web.setLoadOnStartup(1);
+        var rootPattern = normalizeMapping(steveProperties.getPaths().getRootMapping());
+        web.addMapping(rootPattern);

-        var cxf = servletContext.addServlet("cxf", new CXFServlet());
-        cxf.setLoadOnStartup(1);
-        cxf.addMapping(steveProperties.getPaths().getSoapMapping() + "/*");
+        var cxf = servletContext.addServlet("cxf", new CXFServlet());
+        cxf.setLoadOnStartup(1);
+        var soapPattern = normalizeMapping(steveProperties.getPaths().getSoapMapping());
+        cxf.addMapping(soapPattern);

-                .addMappingForUrlPatterns(
+                .addMappingForUrlPatterns(
                         EnumSet.allOf(DispatcherType.class),
                         true,
-                        steveProperties.getPaths().getRootMapping() + "*");
+                        rootPattern);

Add this helper inside the class:

private static String normalizeMapping(String path) {
    if (path == null || path.isEmpty() || "/".equals(path)) return "/";
    if (path.endsWith("/*")) return path;
    return path.endsWith("/") ? path + "*" : path + "/*";
}
steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (1)

76-82: Unify duplicate PasswordEncoder fields.
Both passwordEncoder and encoder are injected; keep one to avoid confusion and DI surprises.

Apply this diff:

-    private final PasswordEncoder passwordEncoder;
+    private final PasswordEncoder passwordEncoder;
     private final ObjectMapper mapper;
     private final WebUserRepository webUserRepository;
     private final SecurityContextHolderStrategy securityContextHolderStrategy = getContextHolderStrategy();
-    private final PasswordEncoder encoder;

And replace usages:

-        webUserRepository.changePassword(username, encoder.encode(newPassword));
+        webUserRepository.changePassword(username, passwordEncoder.encode(newPassword));
-        webUserRepository.changePassword(form.getWebUserPk(), encoder.encode(form.getPassword()));
+        webUserRepository.changePassword(form.getWebUserPk(), passwordEncoder.encode(form.getPassword()));
-            newPassword = encoder.encode(form.getApiPassword());
+            newPassword = passwordEncoder.encode(form.getApiPassword());
-            encPw = encoder.encode(rawPassword);
+            encPw = passwordEncoder.encode(rawPassword);
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (1)

44-51: Null-safe validation for list value (avoid NPE).

Bean Validation convention: null value is considered valid; current loop NPEs on null lists.

File: steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (lines 44-51)

     @Override
     public boolean isValid(List<String> value, ConstraintValidatorContext context) {
-        for (var s : value) {
+        if (value == null) {
+            return true;
+        }
+        for (var s : value) {
             if (!validator.isValid(s, context)) {
                 return false;
             }
         }
         return true;
     }

Do we want to treat null elements inside the list as invalid? If yes, add if (s == null) return false; inside the loop.

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

114-117: Ensure jOOQ participates in Spring-managed transactions

Wrap the DataSource with TransactionAwareDataSourceProxy so @transactional boundaries are honored; otherwise jOOQ may operate on non-transactional connections.

Apply:

@@
-                .set(new DataSourceConnectionProvider(
-                        // new org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy()
-                        dataSource))
+                .set(new DataSourceConnectionProvider(
+                        new org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy(dataSource)))
steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (1)

101-105: Path variable authorization likely broken without MVC matchers

Using WebExpressionAuthorizationManager with "#name" requires MvcRequestMatcher so path variables are exposed. With simple String matchers, "#name" is null, denying legit requests.

Apply:

@@
-    public SecurityFilterChain securityFilterChain(HttpSecurity http, SteveProperties steveProperties)
+    public SecurityFilterChain securityFilterChain(
+            HttpSecurity http,
+            SteveProperties steveProperties,
+            org.springframework.web.servlet.handler.HandlerMappingIntrospector introspector)
             throws Exception {
@@
-                        .requestMatchers(prefix + "/webusers/password/{name}")
-                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
-                        .requestMatchers(prefix + "/webusers/apipassword/{name}")
-                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
+                        .requestMatchers(new org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher.Builder(introspector)
+                                .pattern(prefix + "/webusers/password/{name}")
+                                .build())
+                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
+                        .requestMatchers(new org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher.Builder(introspector)
+                                .pattern(prefix + "/webusers/apipassword/{name}")
+                                .build())
+                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

586-592: Fix assertion: sampledValues cannot be empty; prevents IndexOutOfBounds.

assertThat(sampledValues).isEmpty() contradicts sampledValues.get(0). Replace with a non‑empty assertion.

-            var sampledValues = meterValue.getSampledValue();
-            assertThat(sampledValues).isEmpty();
+            var sampledValues = meterValue.getSampledValue();
+            assertThat(sampledValues).isNotEmpty();
🧹 Nitpick comments (41)
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (2)

91-92: Optional: guard against non‑Logback backends to avoid ClassCastException.

If SLF4J binding changes, the cast to ch.qos.logback.classic.Logger will fail. Guard and degrade gracefully.

-        var logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
-        var appenderIterator = logger.iteratorForAppenders();
+        var root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+        if (!(root instanceof Logger logger)) {
+            log.debug("Root logger is not Logback; cannot resolve file appenders.");
+            return List.of();
+        }
+        var appenderIterator = logger.iteratorForAppenders();

102-102: Optional: return an unmodifiable snapshot.

Prevents accidental mutation of internal state.

-        return fileNameList;
+        return List.copyOf(fileNameList);
steve/src/main/resources/logback-spring.xml (2)

3-5: Prefer system property user.home for portable LOG_DIR default.

${USER_HOME} isn’t standard; ${HOME} may be missing on Windows. Use ${user.home} to avoid platform surprises.

Apply:

-    <springProperty scope="context" name="LOG_DIR" source="logging.file.path" defaultValue="${USER_HOME:-${HOME:-.}}/logs"/>
+    <springProperty scope="context" name="LOG_DIR" source="logging.file.path" defaultValue="${user.home:-.}/logs"/>

13-27: Consider size-and-time rolling to cap single-day file growth.

Current policy limits history and total size but not per‑day file size. If traffic spikes, a single daily file can explode. Size‑and‑time policy mitigates this.

Example:

-    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <file>${LOG_DIR}/steve.log</file>
-      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
         <fileNamePattern>${LOG_DIR}/steve-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+        <maxFileSize>100MB</maxFileSize>
         <maxHistory>30</maxHistory>
         <totalSizeCap>3GB</totalSizeCap>
       </rollingPolicy>
steve-jooq/pom.xml (1)

26-29: De-duplicate jOOQ runtime: prefer Boot starter or plain jOOQ, not both.

spring-boot-starter-jooq already provides org.jooq:jooq; keeping the explicit jooq dependency can cause version drift.

     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jooq</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.jooq</groupId>
-      <artifactId>jooq</artifactId>
-    </dependency>

Also applies to: 31-33

steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)

31-31: Constructor path exercised with null; add a SteveProperties-backed test.

To guard the new configuration path, add a test constructing the validator via SteveProperties to ensure regex parity with the legacy constructor.

steve-api/pom.xml (1)

33-35: Avoid redundant test libs with spring-boot-starter-test.

The starter already brings JUnit Jupiter, Mockito, AssertJ, and JsonPath (transitively). Keeping duplicates risks version skew.

Apply:

   <dependency>
-    <groupId>org.springframework.boot</groupId>
-    <artifactId>spring-boot-starter-test</artifactId>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
   </dependency>
-  <dependency>
-    <groupId>com.jayway.jsonpath</groupId>
-    <artifactId>json-path</artifactId>
-    <scope>test</scope>
-  </dependency>
-  <dependency>
-    <groupId>org.junit.jupiter</groupId>
-    <artifactId>junit-jupiter-engine</artifactId>
-    <scope>test</scope>
-  </dependency>
-  <dependency>
-    <groupId>org.junit.jupiter</groupId>
-    <artifactId>junit-jupiter-params</artifactId>
-    <scope>test</scope>
-  </dependency>
-  <dependency>
-    <groupId>org.mockito</groupId>
-    <artifactId>mockito-junit-jupiter</artifactId>
-    <scope>test</scope>
-  </dependency>
-  <dependency>
-    <groupId>org.assertj</groupId>
-    <artifactId>assertj-core</artifactId>
-    <scope>test</scope>
-  </dependency>

If any are intentionally newer/different, keep only those and exclude from the starter.

Also applies to: 43-61

steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)

44-44: Avoid redundant client creation in beforeRepeat.

Reuse the ThreadLocal client instead of creating a second proxy.

Apply:

-                var boot = getForOcpp16(soapPath)
+                var boot = client.get()
                         .bootNotification(
                                 new BootNotificationRequest()

Also applies to: 57-57, 60-61

steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (2)

40-53: Move logging out of static initializer; keep only system setup here.

Logback may not be fully initialized this early. Set timezone/provider in static block, but log on ApplicationStartedEvent.

 static {
   // Apache CXF
   LogUtils.setLoggerClass(Slf4jLogger.class);
   // For Hibernate validator
   System.setProperty("org.jboss.logging.provider", "slf4j");
   var zoneId = ZoneOffset.UTC;
   TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
-  log.info(
-          "Date/time zone of the application is set to {}. Current date/time: {}",
-          zoneId,
-          ZonedDateTime.now(zoneId));
 }
+
+@EventListener(org.springframework.boot.context.event.ApplicationStartedEvent.class)
+public void logStartup() {
+  var zoneId = ZoneOffset.UTC;
+  log.info("Date/time zone set to {}. Now: {}", zoneId, ZonedDateTime.now(zoneId));
+}

28-31: UTC default TZ: verify DB/session expectations.

Global TZ change is intentional; ensure DB/JDBC and cron/scheduler semantics expect UTC to avoid subtle timestamp shifts.

steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2)

91-97: Internet check on bean creation may slow startup and couples to network.

Consider lazy init or scheduling the check post-startup; default to Dummy when offline and let a background task swap to GithubReleaseCheckService.

-@Bean
-public ReleaseCheckService releaseCheckService(SteveProperties steveProperties) {
-  if (InternetChecker.isInternetAvailable(steveProperties.getSteveVersion())) {
-    return new GithubReleaseCheckService(steveProperties);
-  } else {
-    return new DummyReleaseCheckService();
-  }
-}
+@Bean
+public ReleaseCheckService releaseCheckService(SteveProperties steveProperties) {
+  return new DummyReleaseCheckService(); // fast, offline-safe default
+}
+// Option: publish an event that swaps the impl after a successful async reachability check.

103-113: ObjectMapper tweaks: good; consider centralizing.

Config here mirrors the @primary bean below. Prefer configuring only the @primary mapper and letting Spring reuse it to avoid drift.

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

120-123: Use isEmpty() and remove map entry atomically.

Small polish and intent clarity.

-                if (endpointDeque.size() == 0) {
-                    lookupTable.remove(chargeBoxId);
-                }
+                if (endpointDeque.isEmpty()) {
+                    lookupTable.remove(chargeBoxId);
+                }

166-168: getACopy returns live Deques.

ImmutableMap.copyOf wraps the map, not the Deque values. Expose unmodifiable copies to prevent external mutation.

-    public Map<String, Deque<SessionContext>> getACopy() {
-        return ImmutableMap.copyOf(lookupTable);
-    }
+    public Map<String, Deque<SessionContext>> getACopy() {
+        return lookupTable.entrySet().stream()
+                .collect(ImmutableMap.toImmutableMap(
+                        Map.Entry::getKey,
+                        e -> new ArrayDeque<>(e.getValue()))); // copy each deque
+    }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)

91-99: Use the same client instance within the loop to reduce connection overhead.

You already create httpClient; reuse getForOcpp16(soapPath) via threadLocalClient for all calls in toRepeat as well to avoid extra stubs per repeat.

Also applies to: 101-109

steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (2)

64-66: Normalize slashes when building pathInfix to avoid handshake mismatches.

Different property values (with/without leading/trailing “/”) can break getLastBitFromUrl().

- var pathInfix = steveProperties.getPaths().getWebsocketMapping()
-         + steveProperties.getPaths().getRouterEndpointPath() + "/";
+ var mapping = steveProperties.getPaths().getWebsocketMapping();
+ var router = steveProperties.getPaths().getRouterEndpointPath();
+ var pathInfix = "/" + strip(mapping) + "/" + strip(router) + "/";
+ // helper:
+ // private static String strip(String s){ return s.replaceAll("^/+|/+$",""); }

74-76: Don’t hardcode allowed origins.

Make it configurable (and default to “*” only in dev).

- registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
-         .setHandshakeHandler(handshakeHandler)
-         .setAllowedOrigins("*");
+ registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
+         .setHandshakeHandler(handshakeHandler)
+         .setAllowedOrigins(steveProperties.getWeb().getAllowedOrigins().toArray(String[]::new));
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (1)

110-116: Nit: typo in local name ‘requestedProcotol’.

Rename to requestedProtocol for clarity (no behavior change).

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

94-101: Minor: use isBlank() for header value check.
Slightly clearer and trims whitespace.

-        var encodedApiPassword = headerVal == null || headerVal.isEmpty() ? null : passwordEncoder.encode(headerVal);
+        var encodedApiPassword = (headerVal == null || headerVal.isBlank()) ? null : passwordEncoder.encode(headerVal);
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (2)

70-86: Initialize Mockito mocks to satisfy NullAway.
Explicitly open mocks in setUp so static analysis recognizes initialization before use.

@@
     @BeforeEach
-    public void setUp() {
+    public void setUp() {
+        org.mockito.MockitoAnnotations.openMocks(this);
         var ctx = new AnnotationConfigApplicationContext();
         ctx.register(ValidationConfig.class);
         ctx.register(SteveProperties.class);
         ctx.refresh();
 
         var validator = ctx.getBean(Validator.class);
 
         mockMvc = buildMockMvc(MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
                 .setValidator(validator));
     }

60-68: Optional: return LocalValidatorFactoryBean as Validator bean directly.
Slight simplification; avoids hunting by type and is idiomatic.

 @Configuration
 static class ValidationConfig {
-    @Bean
-    public ValidatorFactory validator(ApplicationContext applicationContext) {
-        var factory = new LocalValidatorFactoryBean();
-        factory.setApplicationContext(applicationContext);
-        return factory;
-    }
+    @Bean
+    public LocalValidatorFactoryBean validator(ApplicationContext applicationContext) {
+        var factory = new LocalValidatorFactoryBean();
+        factory.setApplicationContext(applicationContext);
+        return factory;
+    }
 }
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2)

41-44: @Autowired on single constructor is unnecessary.

Spring will inject the only constructor automatically. Dropping the annotation reduces noise.

-    @Autowired
-    public ChargeBoxIdValidator(SteveProperties steveProperties) {
+    public ChargeBoxIdValidator(SteveProperties steveProperties) {

46-47: Consider guarding against invalid regex in config.

If the admin supplies a malformed regex, Pattern.compile throws at context creation. If you prefer a clearer error, catch PatternSyntaxException and wrap with the property key for quicker diagnosis.

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

26-33: Add a Javadoc summary line to satisfy the linter.

Current Javadoc lacks the required first summary sentence.

 /**
+ * Central application configuration properties bound under the "steve" prefix.
  * @author Sevket Goekay <sevketgokay@gmail.com>
  * @since 19.08.2014
  */
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (2)

62-70: Avoid hard-coding MySQL driver properties that can break environments

Forcing useSSL=true and characterEncoding may fail against non-SSL dev DBs or conflict with URL parameters. Prefer configuring via the JDBC URL or a property.

Example adjustment:

@@
-        hc.addDataSourceProperty(PropertyKey.characterEncoding.getKeyName(), "utf8");
         hc.addDataSourceProperty(PropertyKey.connectionTimeZone.getKeyName(), dbTimeZoneId);
-        hc.addDataSourceProperty(PropertyKey.useSSL.getKeyName(), true);
+        // Prefer configuring encoding/SSL via JDBC URL (e.g., sslMode=REQUIRED/PREFERRED, useUnicode=true&characterEncoding=utf8mb4)

45-52: Expose pool sizing from properties

Consider sourcing maxPoolSize/minIdle/connectionTimeout from configuration to avoid hidden defaults in different environments.

steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (2)

120-137: Make the exception assertion deterministic

Currently the lambda contains multiple calls; assert on the specific call that is expected to throw to avoid false positives.

-        assertThatExceptionOfType(WebServiceException.class).isThrownBy(() -> {
-            var boot = client.bootNotification(
+        assertThatExceptionOfType(WebServiceException.class).isThrownBy(() ->
+            client.bootNotification(
                     new ocpp.cs._2015._10.BootNotificationRequest()
                             .withChargePointVendor(getRandomString())
                             .withChargePointModel(getRandomString()),
-                    getRandomString());
-            assertThat(boot).isNotNull();
-            assertThat(boot.getStatus()).isEqualTo(ocpp.cs._2015._10.RegistrationStatus.REJECTED);
-
-            var auth = client.authorize(
-                    new ocpp.cs._2015._10.AuthorizeRequest().withIdTag(getRandomString()), getRandomString());
-            assertThat(auth).isNotNull();
-            assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2015._10.AuthorizationStatus.INVALID);
-        });
+                    getRandomString())
+        );

68-73: Handle null server.address in getSoapPath — default to localhost or set it for tests

Helpers.getSoapPath concatenates serverProperties.getAddress() directly; application.yml leaves server.address as a placeholder (@server.host@) and no application-test.* was found, so serverProperties.getAddress() can be null and produce URLs like http://null:port/... Either set server.address for the test profile or default the host to "localhost" in Helpers.getSoapPath.

Locations:

  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java : getSoapPath
  • steve/src/main/resources/application.yml : server.address placeholder
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java : calls getSoapPath(...)

Suggested minimal change:

- return prefix + serverProperties.getAddress() + ":" + serverProperties.getPort()
+ var host = serverProperties.getAddress() != null ? serverProperties.getAddress() : "localhost";
+ return prefix + host + ":" + serverProperties.getPort()
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (3)

67-74: Hard‑coded schema name is brittle

SCHEMA_TO_TRUNCATE is fixed. If test schema changes via properties, prepare() will fail. Prefer deriving from ctx.meta()/DefaultCatalog or passing the schema via constructor/config.

-    private static final String SCHEMA_TO_TRUNCATE = "stevedb_test_2aa6a783d47d";
+    private final String schemaToTruncate;
@@
-    public void prepare() {
+    public void prepare() {
         runOperation(ctx -> {
-            truncateTables(ctx);
+            truncateTables(ctx, schemaToTruncate);
             insertChargeBox(ctx);
             insertOcppIdTag(ctx);
         });
     }

And overload truncateTables to accept the schema name.


158-160: Group prepare operations in a single transaction

truncate + inserts should be atomic to keep test DB consistent on failures.

-    private void runOperation(Consumer<DSLContext> consumer) {
-        consumer.accept(dslContext);
-    }
+    private void runOperation(Consumer<DSLContext> consumer) {
+        dslContext.transaction(cfg -> consumer.accept(DSL.using(cfg)));
+    }

162-189: Skip list relies on generated table constants

If codegen settings change (e.g., view/table renames), skipping by constants may break. Consider using table.getType().isView() where supported, or an allowlist derived from metadata.

steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (2)

73-89: Use Spring’s static resource matcher to future‑proof assets

Prefer PathRequest.toStaticResources() to avoid maintaining explicit /static/** etc.

-                        .requestMatchers(
-                                "/", // we have RootRedirectController to redirect "/" to "/manager"
-                                "/static/**",
-                                steveProperties.getPaths().getSoapMapping() + "/**",
-                                steveProperties.getPaths().getWebsocketMapping()
-                                        + steveProperties.getPaths().getRouterEndpointPath() + "/**")
+                        .requestMatchers("/")
+                        .permitAll()
+                        .requestMatchers(org.springframework.boot.autoconfigure.security.servlet.PathRequest.toStaticResources().atCommonLocations())
                         .permitAll()

Retain SOAP/WS permits as separate matchers.


59-61: BCrypt strength configurability

Default strength 10 is fine; consider making it configurable if login volume or hardware varies.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (2)

613-618: Remove unused helper or use truncation API.

nowWithoutMillis() is unused. Either remove it or use truncatedTo(ChronoUnit.SECONDS) if kept.

-    /**
-     * https://github.com/steve-community/steve/issues/1371
-     */
-    private static OffsetDateTime nowWithoutMillis() {
-        return OffsetDateTime.now().with(ChronoField.MILLI_OF_SECOND, 0);
-    }
+    // (Optional) If needed later:
+    // private static OffsetDateTime nowWithoutMillis() {
+    //     return OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS);
+    // }

74-99: Consider RANDOM_PORT to avoid CI port contention.

@SpringBootTest(webEnvironment = DEFINED_PORT) can flake if the port is busy. If feasible, switch to RANDOM_PORT and derive the URL from @LocalServerPort (or adapt getSoapPath accordingly).

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2)

103-115: Avoid direct @bean method call; inject LoggingFeature.

Calling loggingFeature() inside the config relies on proxying and can create multiple instances if proxyBeanMethods=false. Inject it instead.

-    private EndpointImpl createDefaultEndpoint(
-            Bus bus,
-            MessageIdInterceptor messageIdInterceptor,
-            MessageHeaderInterceptor messageHeaderInterceptor,
-            Object serviceBean,
-            String address) {
+    private EndpointImpl createDefaultEndpoint(
+            Bus bus,
+            MessageIdInterceptor messageIdInterceptor,
+            MessageHeaderInterceptor messageHeaderInterceptor,
+            LoggingFeature loggingFeature,
+            Object serviceBean,
+            String address) {
         return createEndpoint(
                 bus,
                 serviceBean,
                 address,
                 List.of(messageIdInterceptor, messageHeaderInterceptor),
-                List.of(loggingFeature()));
+                List.of(loggingFeature));
     }

And pass the bean to callers:

-    public EndpointImpl ocpp16Endpoint(Bus bus, MessageIdInterceptor messageIdInterceptor,
-            MessageHeaderInterceptor messageHeaderInterceptor,
-            ocpp.cs._2015._10.CentralSystemService ocpp16Server) {
-        return createDefaultEndpoint(bus, messageIdInterceptor, messageHeaderInterceptor, ocpp16Server, "/CentralSystemServiceOCPP16");
+    public EndpointImpl ocpp16Endpoint(Bus bus, MessageIdInterceptor messageIdInterceptor,
+            MessageHeaderInterceptor messageHeaderInterceptor,
+            LoggingFeature loggingFeature,
+            ocpp.cs._2015._10.CentralSystemService ocpp16Server) {
+        return createDefaultEndpoint(bus, messageIdInterceptor, messageHeaderInterceptor, loggingFeature,
+                ocpp16Server, "/CentralSystemServiceOCPP16");
     }

(Apply similarly to ocpp12/ocpp15.)


88-101: Reduce hard dependency on ocpp12 for router.

Router uses an ocpp12 service instance as a dummy just to publish an endpoint. Consider a trivial no-op service bean to decouple.

steve/pom.xml (2)

65-90: Rely on Boot BOM; trim duplicate/managed deps.

Many artifacts are already pulled by Spring Boot starters (Jackson, SLF4J, Spring Security config, Hikari). Keeping explicit entries risks version drift, especially with Jetty where direct Jetty modules may conflict with spring-boot-starter-jetty.

  • Prefer: spring-boot-starter-web (+ jetty starter), spring-boot-starter-websocket, spring-boot-starter-security, spring-boot-starter-jdbc.
  • Remove or justify: direct Jetty modules, jackson-*, slf4j-api, jcl-over-slf4j, spring-security-config, HikariCP, byte-buddy, junit engines (all provided by starters).
  • Keep: mysql-connector-j, CXF transport (test), any Jetty extras not covered by starters (only if required).

Also applies to: 109-128, 129-159, 170-190


1-1: Address CI failure: license headers.

license-maven-plugin:check failed. Run mvn license:format (or project’s wrapper task) and commit updated headers.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (2)

25-25: Avoid leaking CXF impl types in the interceptor API

Depending on org.apache.cxf.jaxws.EndpointImpl in the public ctor couples this class to a specific CXF/JAX‑WS impl. Prefer taking Collection<Server> or an already-built Map<String, Server> and build the lookup in configuration.

Example alternative signature:

- public MediatorInInterceptor(List<EndpointImpl> endpoints) {
+ public MediatorInInterceptor(Map<String, Server> serversByNamespace) {
     super(Phase.POST_STREAM);
     super.addBefore(StaxInInterceptor.class.getName());
-    actualServers = initServerLookupMap(endpoints);
+    this.actualServers = Map.copyOf(serversByNamespace);
 }

85-94: No routing target ⇒ silent drop

When targetServer is null we silently abort the chain. At minimum log; ideally return a SOAP Fault.

Apply:

- if (targetServer != null) {
-   targetServer.getDestination().getMessageObserver().onMessage(message);
- }
+ if (targetServer != null) {
+   targetServer.getDestination().getMessageObserver().onMessage(message);
+ } else {
+   log.warn("No server mapped for namespace '{}'", schemaNamespace);
+ }

If you want to hard‑fail, consider throwing a CXF Fault with a client error instead of aborting silently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8afb501 and 03a2285.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (59)
  • AGENTS.md (1 hunks)
  • pom.xml (4 hunks)
  • steve-api/pom.xml (1 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (2 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (6 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (1 hunks)
  • steve/pom.xml (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
💤 Files with no reviewable changes (11)
  • steve-ocpp-transport-websocket/pom.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
  • steve-ocpp/pom.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
  • steve/src/main/resources/config/main.properties
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/main/resources/application.yml
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • AGENTS.md
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/README.md
  • steve/src/main/resources/logback-spring.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/pom.xml
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve-jooq/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • AGENTS.md
  • pom.xml
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-jooq/pom.xml
  • AGENTS.md
📚 Learning: 2025-09-15T15:31:15.967Z
Learnt from: juherr
PR: juherr/evolve#91
File: steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java:125-129
Timestamp: 2025-09-15T15:31:15.967Z
Learning: In the steve-core OCPP project, JAXB bindings are customized in ocpp_binding.xml to map xs:dateTime XML types directly to java.time.OffsetDateTime using the JavaDateTimeConverter adapter, eliminating the need for XMLGregorianCalendar conversions that would normally be required in standard JAXB bindings.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
🧬 Code graph analysis (5)
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)
  • Configuration (39-121)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)
  • Slf4j (39-74)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (55-226)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java (1)
  • RequiredArgsConstructor (56-213)
steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1)
  • RequiredArgsConstructor (313-317)
🪛 GitHub Actions: analyze and review code
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-1-x/pom.xml

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-transport-soap/pom.xml

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java

[error] 1-1: license-maven-plugin:check failed. Some files do not have the expected license header. Run 'license:format' to update them.

🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 2-2: syntax error: found character '@' that cannot start any token

(syntax)

🪛 GitHub Actions: build and run tests
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java

[warning] 76-76: NullAway: initializer may not guarantee @nonnull field chargePointsService; ensure proper initialization.

steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java

[warning] 27-27: MissingSummary: A summary line is required on public/protected Javadocs.


[warning] 63-63: NullAway: initializer method does not guarantee @nonnull field schemaSource is initialized along all control-flow paths.

pom.xml

[warning] 1-1: Build warning: Maven project has deprecated property usage in effective model (generic deprecation warnings).

🪛 markdownlint-cli2 (0.18.1)
steve/README.md

77-77: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


78-78: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


79-79: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)


80-80: Unordered list style
Expected: asterisk; Actual: dash

(MD004, ul-style)

⏰ 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, mysql:8.0)
  • 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-24.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
  • 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, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • 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)

Comment on lines 76 to 92
private static @Nullable TLSClientParameters create(Ssl ssl) throws GeneralSecurityException, IOException {
if (!ssl.isEnabled()) {
return null;
}
var tlsClientParams = new TLSClientParameters();

var socketFactory = SslContextBuilder.builder()
.keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
.usingTLS()
.usingDefaultAlgorithm()
.usingKeyManagerPasswordFromKeyStore()
.buildMergedWithSystem()
.getSocketFactory();
tlsClientParams.setSSLSocketFactory(socketFactory);

return tlsClientParams;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Null‑safe SSL setup and trust store support.
Avoid NPE when SSL is unset and honor optional trust store for server cert validation.

-    private static @Nullable TLSClientParameters create(Ssl ssl) throws GeneralSecurityException, IOException {
-        if (!ssl.isEnabled()) {
-            return null;
-        }
+    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
+            throws GeneralSecurityException, IOException {
+        if (ssl == null || !ssl.isEnabled()) {
+            return null;
+        }
         var tlsClientParams = new TLSClientParameters();
 
-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder.keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword());
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        var socketFactory = builder
                 .usingTLS()
                 .usingDefaultAlgorithm()
                 .usingKeyManagerPasswordFromKeyStore()
                 .buildMergedWithSystem()
                 .getSocketFactory();
         tlsClientParams.setSSLSocketFactory(socketFactory);
 
         return tlsClientParams;
     }

Please confirm the Spring Boot version. If using a variant where Ssl has getEnabled() instead of isEnabled(), adjust accordingly.


🏁 Script executed:

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

echo "=== locate ClientProvider.java ==="
rg -n --hidden --no-ignore-vcs 'ClientProvider\.java' -S || true
FILE=$(rg -n --hidden --no-ignore-vcs 'ClientProvider\.java' -S --files | head -n1 || true)
if [ -n "$FILE" ] && [ -f "$FILE" ]; then
  echo "FOUND: $FILE"
  echo "---- file start (first 220 lines) ----"
  sed -n '1,220p' "$FILE"
  echo "---- file end ----"
else
  echo "ClientProvider.java not found in repository."
fi

echo
echo "=== search for Ssl type definitions ==="
rg -n --hidden --no-ignore-vcs 'class\s+Ssl\b|interface\s+Ssl\b|record\s+Ssl\b' -S || true

echo
echo "=== search for usages of 'Ssl' ==="
rg -n --hidden --no-ignore-vcs '\bSsl\b' -S || true

echo
echo "=== locate pom/build files and Spring Boot references ==="
rg -n --hidden --no-ignore-vcs '<artifactId>spring-boot|spring-boot-starter|spring-boot.version|spring.boot.version|springBootVersion' -S --glob '!**/target/**' || true
rg -n --hidden --no-ignore-vcs 'org.springframework.boot' -S --glob '!**/target/**' || true

if [ -f pom.xml ]; then
  echo
  echo "---- top-level pom.xml (first 200 lines) ----"
  sed -n '1,200p' pom.xml || true
else
  echo
  echo "No top-level pom.xml; listing pom.xml files (paths):"
  rg -n --hidden --no-ignore-vcs '^<project' -S --glob '!**/target/**' -g 'pom.xml' || true
fi

echo
echo "=== done ==="

Length of output: 7655


Null‑safe SSL setup and optional trust‑store support.

Make create(...) accept a nullable Ssl, return null when ssl is null or disabled, and only add keyStore/trustStore to the builder when configured. Ssl exposes isEnabled() (and static isEnabled(Ssl) since 3.1.0) and has trust-store getters — not getEnabled(). (docs.spring.io)

Location: steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (create method, ~lines 76–92). Spring Boot parent in pom.xml is 3.5.6.

-    private static @Nullable TLSClientParameters create(Ssl ssl) throws GeneralSecurityException, IOException {
-        if (!ssl.isEnabled()) {
-            return null;
-        }
+    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
+            throws GeneralSecurityException, IOException {
+        if (ssl == null || !ssl.isEnabled()) {
+            return null;
+        }
         var tlsClientParams = new TLSClientParameters();
 
-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder.keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword());
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        var socketFactory = builder
                 .usingTLS()
                 .usingDefaultAlgorithm()
                 .usingKeyManagerPasswordFromKeyStore()
                 .buildMergedWithSystem()
                 .getSocketFactory();
         tlsClientParams.setSSLSocketFactory(socketFactory);
 
         return tlsClientParams;
     }
🤖 Prompt for AI Agents
In
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
around lines 76–92, make create(...) null-safe and support optional trust-store:
change the signature to accept a nullable Ssl, return null immediately if ssl is
null or !ssl.isEnabled(), and only call the SslContextBuilder
keyStore/trustStore methods when the corresponding getters are non-null (e.g.
call keyStoreFromFile(...) only when ssl.getKeyStore() != null and include key
manager password only if ssl.getKeyStorePassword() is present; likewise add a
trustStoreFromFile(...) only when ssl.getTrustStore() != null and its password
when present). Keep the rest of the TLSClientParameters setup the same and
ensure imports/annotations reflect the nullable parameter.

Comment on lines +49 to 53
public MediatorInInterceptor(List<EndpointImpl> endpoints) {
super(Phase.POST_STREAM);
super.addBefore(StaxInInterceptor.class.getName());
actualServers = initServerLookupMap(bus, config);
actualServers = initServerLookupMap(endpoints);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Validate constructor input early

Null endpoints will NPE later. Add a defensive check.

Apply:

 public MediatorInInterceptor(List<EndpointImpl> endpoints) {
   super(Phase.POST_STREAM);
   super.addBefore(StaxInInterceptor.class.getName());
-  actualServers = initServerLookupMap(endpoints);
+  java.util.Objects.requireNonNull(endpoints, "endpoints must not be null");
+  actualServers = initServerLookupMap(endpoints);
 }

If you keep the current signature, also document that endpoints must be already published.


🏁 Script executed:

#!/bin/bash
# Locate the MediatorInInterceptor source file and snippet around class declaration and constructor
rg -nP --type=java "class\s+MediatorInInterceptor" -C3 .

# Show constructor and initServerLookupMap usage
rg -n "initServerLookupMap" -C3 --type java steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java

# Search for calls to the constructor
rg -n "new MediatorInInterceptor" -C3 --type java .

# Search for occurrences of endpoints parameter in this file
rg -n "endpoints" -C3 --type java steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java

Length of output: 3547


Validate constructor input early

Null endpoints will NPE in initServerLookupMap when iterating; add a defensive check.

File: steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (lines 49–53)

Apply:

 public MediatorInInterceptor(List<EndpointImpl> endpoints) {
   super(Phase.POST_STREAM);
   super.addBefore(StaxInInterceptor.class.getName());
-  actualServers = initServerLookupMap(endpoints);
+  java.util.Objects.requireNonNull(endpoints, "endpoints must not be null");
+  actualServers = initServerLookupMap(endpoints);
 }

If you keep the current signature, also document that endpoints must be already published.

📝 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 MediatorInInterceptor(List<EndpointImpl> endpoints) {
super(Phase.POST_STREAM);
super.addBefore(StaxInInterceptor.class.getName());
actualServers = initServerLookupMap(bus, config);
actualServers = initServerLookupMap(endpoints);
}
public MediatorInInterceptor(List<EndpointImpl> endpoints) {
super(Phase.POST_STREAM);
super.addBefore(StaxInInterceptor.class.getName());
java.util.Objects.requireNonNull(endpoints, "endpoints must not be null");
actualServers = initServerLookupMap(endpoints);
}
🤖 Prompt for AI Agents
In
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
around lines 49–53, the constructor accepts a List<EndpointImpl> endpoints but
does not validate it and will NPE inside initServerLookupMap when endpoints is
null; add an early defensive check that throws a clear IllegalArgumentException
(or NullPointerException with a descriptive message) if endpoints is null, then
proceed to call initServerLookupMap; alternatively, if you want to keep the
current signature without a runtime check, add JavaDoc to the constructor
stating that endpoints must be non-null and already published before calling
this constructor.

Comment on lines +62 to 69
var bis = new BufferedInputStream(is);
bis.mark(bis.available());
message.setContent(InputStream.class, bis);

String encoding = (String) message.get(Message.ENCODING);
XMLStreamReader reader = StaxUtils.createXMLStreamReader(bis, encoding);
DepthXMLStreamReader xmlReader = new DepthXMLStreamReader(reader);
var encoding = (String) message.get(Message.ENCODING);
var reader = StaxUtils.createXMLStreamReader(bis, encoding);
var xmlReader = new DepthXMLStreamReader(reader);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fragile mark/reset: available() can be 0 → reset() may fail and drop the request

Using bis.mark(bis.available()) is unreliable for network streams. If reset() fails, the message may be partially consumed while we still abort the chain.

Use a fixed, generous read limit and handle missing content:

- var is = message.getContent(InputStream.class);
- var bis = new BufferedInputStream(is);
- bis.mark(bis.available());
+ var is = message.getContent(InputStream.class);
+ if (is == null) {
+   log.warn("No InputStream on message; cannot route");
+   return;
+ }
+ var bis = new BufferedInputStream(is);
+ // mark enough bytes to parse envelope + first body element
+ bis.mark(64 * 1024);

Optionally, wrap the scan/reset in a try/finally and on reset failure log and return without aborting.

📝 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
var bis = new BufferedInputStream(is);
bis.mark(bis.available());
message.setContent(InputStream.class, bis);
String encoding = (String) message.get(Message.ENCODING);
XMLStreamReader reader = StaxUtils.createXMLStreamReader(bis, encoding);
DepthXMLStreamReader xmlReader = new DepthXMLStreamReader(reader);
var encoding = (String) message.get(Message.ENCODING);
var reader = StaxUtils.createXMLStreamReader(bis, encoding);
var xmlReader = new DepthXMLStreamReader(reader);
var is = message.getContent(InputStream.class);
if (is == null) {
log.warn("No InputStream on message; cannot route");
return;
}
var bis = new BufferedInputStream(is);
// mark enough bytes to parse envelope + first body element
bis.mark(64 * 1024);
message.setContent(InputStream.class, bis);
var encoding = (String) message.get(Message.ENCODING);
var reader = StaxUtils.createXMLStreamReader(bis, encoding);
var xmlReader = new DepthXMLStreamReader(reader);
🤖 Prompt for AI Agents
In
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
around lines 62 to 69, the code uses bis.mark(bis.available()) which is fragile
for network streams and can be zero causing reset() to fail and drop requests;
replace the use of available() with a fixed generous read limit (e.g. 64KB or
larger depending on expected payloads) or fully buffer the stream into a byte
array/ByteArrayOutputStream before creating the XML reader, wrap the
mark/scan/reset in a try/finally, catch IllegalStateException/IOException from
reset(), log a clear warning including context, and on reset failure return
without aborting the interceptor chain (or reattach the best-effort buffered
content) so the request is not lost.

Comment on lines 49 to 59
public static String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
var prefix = "http://";
if (serverProperties.getSsl().isEnabled()) {
prefix = "https://";
port = config.getJetty().getHttpsPort();
} else {
throw new RuntimeException();
}

return prefix + config.getJetty().getServerHost() + ":" + port
+ config.getPaths().getContextPath()
+ config.getPaths().getSoapMapping()
+ config.getPaths().getRouterEndpointPath();
return prefix + serverProperties.getAddress() + ":" + serverProperties.getPort()
+ serverProperties.getServlet().getContextPath()
+ steveProperties.getPaths().getSoapMapping()
+ steveProperties.getPaths().getRouterEndpointPath() + "/";
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Null/format pitfalls building SOAP URL (NPEs, “null” host/port, IPv6).

  • serverProperties.getSsl() may be null → NPE.
  • address/port can be null → "null:null".
  • IPv6 literals require [brackets] in URIs.
  • servlet/contextPath may be null.

Harden path building:

-    public static String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
-        var prefix = "http://";
-        if (serverProperties.getSsl().isEnabled()) {
-            prefix = "https://";
-        }
-
-        return prefix + serverProperties.getAddress() + ":" + serverProperties.getPort()
-                + serverProperties.getServlet().getContextPath()
-                + steveProperties.getPaths().getSoapMapping()
-                + steveProperties.getPaths().getRouterEndpointPath() + "/";
-    }
+    public static String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
+        var ssl = serverProperties.getSsl();
+        var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "https://" : "http://";
+
+        var addr = serverProperties.getAddress();
+        var host = (addr != null) ? addr.getHostAddress() : "localhost";
+        if (host.indexOf(':') >= 0) { // IPv6
+            host = "[" + host + "]";
+        }
+
+        Integer port = serverProperties.getPort();
+        int p = (port != null) ? port : 8080;
+
+        var servlet = serverProperties.getServlet();
+        var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
+
+        return scheme + host + ":" + p
+                + ctx
+                + steveProperties.getPaths().getSoapMapping()
+                + steveProperties.getPaths().getRouterEndpointPath() + "/";
+    }

Alternatively, use UriComponentsBuilder to avoid manual escaping.

📝 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 String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
var prefix = "http://";
if (serverProperties.getSsl().isEnabled()) {
prefix = "https://";
port = config.getJetty().getHttpsPort();
} else {
throw new RuntimeException();
}
return prefix + config.getJetty().getServerHost() + ":" + port
+ config.getPaths().getContextPath()
+ config.getPaths().getSoapMapping()
+ config.getPaths().getRouterEndpointPath();
return prefix + serverProperties.getAddress() + ":" + serverProperties.getPort()
+ serverProperties.getServlet().getContextPath()
+ steveProperties.getPaths().getSoapMapping()
+ steveProperties.getPaths().getRouterEndpointPath() + "/";
}
public static String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
var ssl = serverProperties.getSsl();
var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "https://" : "http://";
var addr = serverProperties.getAddress();
var host = (addr != null) ? addr.getHostAddress() : "localhost";
if (host.indexOf(':') >= 0) { // IPv6
host = "[" + host + "]";
}
Integer port = serverProperties.getPort();
int p = (port != null) ? port : 8080;
var servlet = serverProperties.getServlet();
var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
return scheme + host + ":" + p
ctx
steveProperties.getPaths().getSoapMapping()
steveProperties.getPaths().getRouterEndpointPath() + "/";
}
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java around lines 49 to
59, the current manual concatenation can throw NPEs and produce invalid URIs
(ssl may be null, address/port may be null, IPv6 literals need brackets, servlet
contextPath may be null); fix by validating and defaulting values before
building the URL: guard serverProperties.getSsl() for null and choose "https"
only if non-null and enabled, treat null address/port by throwing a clear
IllegalArgumentException or using sensible defaults, wrap IPv6 addresses in
square brackets when they contain ':'; ensure servlet contextPath and other path
segments are non-null and normalized (leading slash, no double slashes, ensure
final slash), and preferably replace the manual concat with a
UriComponentsBuilder (or java.net.URI/URL builder) to assemble scheme, host,
port and path safely to avoid manual escaping and formatting mistakes.

@juherr juherr force-pushed the backport/steve-1822 branch from 03a2285 to 8a5f43b Compare September 23, 2025 06:34
Copy link

@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 (1)
steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

585-596: Fix assertion: sampledValues cannot be empty if accessed at index 0.

This would throw IndexOutOfBoundsException.

Apply this diff:

-            assertThat(sampledValues).isEmpty();
+            assertThat(sampledValues).isNotEmpty();
             var thisValueFound = false;
             // and check, if it can be found in the DB
             for (var values : details.getValues()) {
                 if (values.getValue().equals(sampledValues.get(0).getValue())) {
                     thisValueFound = true;
                     break;
                 }
             }
🧹 Nitpick comments (16)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)

57-57: Normalize servlet/filter/CXF mappings to avoid malformed patterns

Guard against root/soap mappings that already include a trailing wildcard or lack a leading slash. This prevents cases like "/app//" or "/app*" and ensures "/" vs "/*" are applied correctly.

Apply:

-        web.addMapping(steveProperties.getPaths().getRootMapping());
+        web.addMapping(normalizeDispatcherMapping(steveProperties.getPaths().getRootMapping()));
...
-        cxf.addMapping(steveProperties.getPaths().getSoapMapping() + "/*");
+        cxf.addMapping(normalizeFilterMapping(steveProperties.getPaths().getSoapMapping()));
...
-                        steveProperties.getPaths().getRootMapping() + "*");
+                        normalizeFilterMapping(steveProperties.getPaths().getRootMapping()));

Add these helpers in the class (outside the changed ranges):

private static String normalizeDispatcherMapping(String mapping) {
    if (mapping == null || mapping.isBlank() || "/".equals(mapping) || "/*".equals(mapping)) {
        return "/";
    }
    String m = mapping.startsWith("/") ? mapping : "/" + mapping;
    if (m.endsWith("/*")) return m;
    if (m.endsWith("/")) return m + "*";
    return m + "/*";
}

private static String normalizeFilterMapping(String mapping) {
    String dm = normalizeDispatcherMapping(mapping);
    return "/".equals(dm) ? "/*" : dm;
}

Also applies to: 62-62, 72-72

steve-jooq/pom.xml (1)

26-29: Avoid duplicate jOOQ inclusion with the Boot starter

spring-boot-starter-jooq already brings jOOQ; keeping both is redundant.

Apply:

     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jooq</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.jooq</groupId>
-      <artifactId>jooq</artifactId>
-    </dependency>
+    <!-- jOOQ is provided via the Spring Boot starter -->

Also applies to: 30-33

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

60-68: Return Validator from the bean for clarity and direct use

The bean is consumed as Validator. Returning Validator avoids ambiguity.

-        public ValidatorFactory validator(ApplicationContext applicationContext) {
+        public Validator validator(ApplicationContext applicationContext) {
             var factory = new LocalValidatorFactoryBean();
             factory.setApplicationContext(applicationContext);
             return factory;
         }

75-86: Close the ad-hoc ApplicationContext to avoid resource leaks in tests

Register a shutdown hook so the context is closed after the test JVM ends.

         ctx.register(ValidationConfig.class);
         ctx.register(SteveProperties.class);
         ctx.refresh();
+        ctx.registerShutdownHook();
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (1)

125-132: Typo: requestedProcotol -> requestedProtocol

-        for (var requestedProcotol : requestedProtocols) {
+        for (var requestedProtocol : requestedProtocols) {
-            for (var item : endpoints) {
-                if (item.getVersion().getValue().equals(requestedProcotol)) {
+            for (var item : endpoints) {
+                if (item.getVersion().getValue().equals(requestedProtocol)) {
                     return item;
                 }
             }
         }
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (1)

147-153: Guard getSize with the same per-ID lock

Reading ArrayDeque#size() without the stripe lock can race with writers. Lock for the queried chargeBoxId.

     @Override
     public int getSize(String chargeBoxId) {
-        var endpointDeque = lookupTable.get(chargeBoxId);
-        if (endpointDeque == null) {
-            return 0;
-        }
-        return endpointDeque.size();
+        var l = locks.get(chargeBoxId);
+        l.lock();
+        try {
+            var endpointDeque = lookupTable.get(chargeBoxId);
+            return endpointDeque == null ? 0 : endpointDeque.size();
+        } finally {
+            l.unlock();
+        }
     }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)

91-99: Don’t reuse the same HttpUriRequest across threads

HttpUriRequest isn’t thread-safe; build a new request per iteration.

-        var req = RequestBuilder.post(soapPath)
-                .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
-                .setEntity(new StringEntity(body, contentType))
-                .build();
+        // build request per-iteration in toRepeat()

And inside toRepeat():

             @Override
             public void toRepeat() {
+                var req = RequestBuilder.post(soapPath)
+                        .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
+                        .setEntity(new StringEntity(body, contentType))
+                        .build();
                 var mvr = threadLocalClient
                         .get()
                         .meterValues(
                                 new MeterValuesRequest()
                                         .withConnectorId(connectorId)
                                         .withTransactionId(transactionId)
                                         .withMeterValue(new MeterValue()
                                                 .withTimestamp(stopDateTime)
                                                 .withSampledValue(new SampledValue()
                                                         .withMeasurand(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER)
                                                         .withValue("555")
                                                         .withUnit(UnitOfMeasure.WH))),
                                 chargeBoxId);
                 assertThat(mvr).isNotNull();

Also applies to: 112-140

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (2)

99-109: Guard against null schemaSource to avoid invalid render mapping.

If schema is set but schemaSource is null, you’ll configure a MappedSchema with input=null. Require both to be non-null before mapping.

-        var dbSchema = steveProperties.getJooq().getSchema();
-        if (dbSchema != null && !dbSchema.equals(steveProperties.getJooq().getSchemaSource())) {
+        var target = steveProperties.getJooq().getSchema();
+        var source = steveProperties.getJooq().getSchemaSource();
+        if (source != null && target != null && !target.equals(source)) {
             // ...
             settings = settings.withRenderMapping(new RenderMapping()
                     .withSchemata(new MappedSchema()
-                            .withInput(steveProperties.getJooq().getSchemaSource())
-                            .withOutput(steveProperties.getJooq().getSchema())));
+                            .withInput(source)
+                            .withOutput(target)));
         }

63-70: Avoid forcing MySQL SSL unconditionally; prefer sslMode and make it configurable.

Hard-coding useSSL=true can break non-SSL dev DBs; MySQL 8 recommends sslMode instead.

-        hc.addDataSourceProperty(PropertyKey.useSSL.getKeyName(), true);
+        // Let configuration decide SSL. Prefer sslMode (e.g., PREFERRED/REQUIRED) when available.
+        // Example: expose via steve.datasource.sslMode or rely on spring.datasource.* properties.
+        // hc.addDataSourceProperty("sslMode", "PREFERRED");
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (1)

49-51: Consider RANDOM_PORT to avoid port conflicts in CI.

WebEnvironment.DEFINED_PORT relies on server.port and can collide across tests/builds.

-@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

If you keep DEFINED_PORT, ensure the port is unique and substituted during tests.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (3)

74-76: Consider RANDOM_PORT to avoid port conflicts in CI.

Using DEFINED_PORT can fail on shared runners; prefer RANDOM_PORT + @LocalServerPort.

If acceptable, I can provide a patch switching to RANDOM_PORT and adapting getSoapPath via @LocalServerPort.


131-132: Javadoc link likely unresolved.

{@link OcppWebSocketUpgrader} isn’t imported; use fully qualified name or import the type to avoid broken Javadoc links.


613-618: Remove unused helper and ChronoField import

nowWithoutMillis() (steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java:616–617) is declared but not referenced elsewhere; delete the method and the ChronoField import at line 54.

steve/pom.xml (3)

176-179: Scope spring-security-test to test.

Avoid bundling it into the WAR.

Apply this diff:

     <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-test</artifactId>
+      <scope>test</scope>
     </dependency>

181-184: Jetty 12 alignment: use ee10 websocket client artifact.

With Boot 3.5.x (Jetty 12), prefer ee10 coordinates.

Apply this diff:

-    <dependency>
-      <groupId>org.eclipse.jetty.websocket</groupId>
-      <artifactId>jetty-websocket-jetty-client</artifactId>
-      <scope>test</scope>
-    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.ee10.websocket</groupId>
+      <artifactId>jetty-ee10-websocket-jetty-client</artifactId>
+      <scope>test</scope>
+    </dependency>

129-150: Jetty deps duplication with starter-jetty.

Some core Jetty artifacts (jetty-server, ee10-webapp, annotations) are likely already pulled by spring-boot-starter-jetty; keep only extras (rewrite, websocket) to reduce surface.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03a2285 and 8a5f43b.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (61)
  • .github/workflows/main.yml (1 hunks)
  • AGENTS.md (2 hunks)
  • Dockerfile (1 hunks)
  • pom.xml (5 hunks)
  • steve-api/pom.xml (1 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (2 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (6 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
💤 Files with no reviewable changes (11)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
  • steve-ocpp-transport-websocket/pom.xml
  • steve-ocpp/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
  • steve/src/main/resources/config/main.properties
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/main.yml
🚧 Files skipped from review as they are similar to previous changes (20)
  • steve-core/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve-api/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve/README.md
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve-ui-jsp/pom.xml
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-ocpp-1-x/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • AGENTS.md
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-jooq/pom.xml
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • pom.xml
  • steve-jooq/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • Dockerfile
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/src/main/resources/logback-spring.xml
  • pom.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/main/resources/application.yml
📚 Learning: 2025-09-15T15:31:15.967Z
Learnt from: juherr
PR: juherr/evolve#91
File: steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java:125-129
Timestamp: 2025-09-15T15:31:15.967Z
Learning: In the steve-core OCPP project, JAXB bindings are customized in ocpp_binding.xml to map xs:dateTime XML types directly to java.time.OffsetDateTime using the JavaDateTimeConverter adapter, eliminating the need for XMLGregorianCalendar conversions that would normally be required in standard JAXB bindings.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
🧬 Code graph analysis (4)
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)
  • Configuration (39-121)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java (1)
  • RequiredArgsConstructor (56-213)
steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1)
  • RequiredArgsConstructor (313-317)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)
  • Slf4j (39-74)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (55-226)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 2-2: syntax error: found character '@' that cannot start any token

(syntax)

⏰ 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). (19)
  • GitHub Check: license-check
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • 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-22.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, 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, 21, mariadb:10.3)
🔇 Additional comments (62)
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2)

80-80: jsonPath is declared and initialized in StressTest — no guard required.
StressTest declares "protected String jsonPath" and sets it via jsonPath = getWsPath(serverProperties, steveProperties) before constructing OcppJsonChargePoint (see StressTest.java:60,80); OcppJsonChargePoint has matching constructors.

Likely an incorrect or invalid review comment.


64-64: Switch to no-arg instantiation is safe — no remaining call sites or docs reference a String path.
Repo search found no Java usages passing arguments to new StressTestJsonOCPP16 and no markdown/docs mentioning the old ctor.

steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2)

43-47: Constructor-injected SteveProperties: LGTM

Clean switch to constructor injection. No concerns.


72-72: Confirm security filter coverage of SOAP endpoints

Currently only root mapping is filtered. If SOAP endpoints must pass through Spring Security (even for basic auth/IP filters), add the SOAP mapping as well.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (3)

49-53: Defensive null-check for endpoints

Prevent NPE if endpoints is null.

Apply:

   public MediatorInInterceptor(List<EndpointImpl> endpoints) {
     super(Phase.POST_STREAM);
     super.addBefore(StaxInInterceptor.class.getName());
-    actualServers = initServerLookupMap(endpoints);
+    java.util.Objects.requireNonNull(endpoints, "endpoints must not be null");
+    actualServers = initServerLookupMap(endpoints);
   }

88-94: Abort only when forwarded (avoid dropping unmatched requests)

If no target server is found, consider not aborting the chain.

Suggested change:

-        if (targetServer != null) {
-            targetServer.getDestination().getMessageObserver().onMessage(message);
-        }
-
-        // Now the response has been put in the message, abort the chain
-        message.getInterceptorChain().abort();
+        if (targetServer != null) {
+            targetServer.getDestination().getMessageObserver().onMessage(message);
+            // Now the response has been put in the message, abort the chain
+            message.getInterceptorChain().abort();
+        }

62-69: Avoid fragile mark/reset; handle missing InputStream

available() can be 0 causing reset() to fail; also guard when InputStream is absent.

Apply:

-            var is = message.getContent(InputStream.class);
-            var bis = new BufferedInputStream(is);
-            bis.mark(bis.available());
+            var is = message.getContent(InputStream.class);
+            if (is == null) {
+                log.warn("No InputStream on message; cannot route");
+                return;
+            }
+            var bis = new BufferedInputStream(is);
+            bis.mark(64 * 1024);
             message.setContent(InputStream.class, bis);
 
-            var encoding = (String) message.get(Message.ENCODING);
-            var reader = StaxUtils.createXMLStreamReader(bis, encoding);
-            var xmlReader = new DepthXMLStreamReader(reader);
+            var encoding = (String) message.get(Message.ENCODING);
+            var reader = StaxUtils.createXMLStreamReader(bis, encoding);
+            var xmlReader = new DepthXMLStreamReader(reader);
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (1)

22-24: LGTM – clearer, safer validation

Constructor injection + precompiled Pattern and strict whitespace checks look good.

Also applies to: 39-48, 52-52, 56-61, 63-74

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (1)

76-92: Null-safe SSL and optional trust/trustless configs; avoid NPE when SSL unset

serverProperties.getSsl() can be null; current code will NPE. Also, only call keystore/truststore methods when configured. Support trust-store only setups.

Apply:

-    private static @Nullable TLSClientParameters create(Ssl ssl) throws GeneralSecurityException, IOException {
-        if (!ssl.isEnabled()) {
-            return null;
-        }
+    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
+            throws GeneralSecurityException, IOException {
+        if (ssl == null || !ssl.isEnabled()) {
+            return null;
+        }
         var tlsClientParams = new TLSClientParameters();
 
-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
-                .usingTLS()
-                .usingDefaultAlgorithm()
-                .usingKeyManagerPasswordFromKeyStore()
-                .buildMergedWithSystem()
-                .getSocketFactory();
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder.keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword());
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        builder = builder.usingTLS().usingDefaultAlgorithm();
+        if (ssl.getKeyStore() != null) {
+            builder = builder.usingKeyManagerPasswordFromKeyStore();
+        }
+        var socketFactory = builder.buildMergedWithSystem().getSocketFactory();
         tlsClientParams.setSSLSocketFactory(socketFactory);
 
         return tlsClientParams;
     }
steve-ocpp-transport-soap/pom.xml (3)

18-19: LGTM: Clean Spring Boot integration.

The migration from CXF-specific dependencies to Spring Boot starter follows Spring Boot best practices. The spring-boot-starter-web provides the necessary web layer support for SOAP endpoints.


22-24: LGTM: Jakarta EE migration aligned with Spring Boot 3.

Spring Boot 3.5 includes dependency updates including Jakarta EE components, so the migration from javax.servlet to jakarta.servlet-api is consistent with the Spring Boot 3.x ecosystem.


25-29: LGTM: CXF Spring Boot starter integration.

The comment references the official CXF Spring Boot documentation, and the cxf-spring-boot-starter-jaxws is the correct starter for JAX-WS SOAP services in Spring Boot applications.

steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (8)

30-31: LGTM: Direct BCrypt encoder configuration.

Removing the dependency on SteveConfiguration and directly returning BCryptPasswordEncoder simplifies the configuration and aligns with Spring Boot's convention-over-configuration approach.


39-39: LGTM: Jakarta imports for Spring Boot 3 compatibility.

The import change from javax.servlet to jakarta.servlet aligns with the Jakarta EE migration in Spring Boot 3.x.


59-61: LGTM: Simplified password encoder configuration.

The method now directly returns a BCryptPasswordEncoder instance instead of relying on external configuration, which is cleaner and more explicit.


64-66: LGTM: SteveProperties integration for security configuration.

The method signature change from SteveConfiguration to SteveProperties is consistent with the broader migration pattern across the codebase.


73-96: LGTM: Comprehensive security configuration with SteveProperties.

The security configuration properly uses SteveProperties for path resolution and includes comprehensive dispatcher type matchers and request matchers. The addition of dispatcher type matchers for FORWARD, INCLUDE, and ERROR is a best practice for Spring Security configuration.


139-141: LGTM: Explicit permitAll for noAccess endpoints.

Adding explicit permitAll() for noAccess endpoints ensures that access denied redirects work correctly, preventing potential redirect loops.


147-158: LGTM: Properties-driven CSRF and authentication configuration.

The CSRF configuration now uses SteveProperties for SOAP mapping, and the form login configuration includes proper URLs derived from the properties. The request cache disable is a reasonable optimization for this use case.


164-167: LGTM: API key filter chain with SteveProperties integration.

The method signature properly injects SteveProperties and ApiAuthenticationManager, with the security matcher correctly deriving the API path from properties.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (7)

22-22: LGTM: SteveProperties migration in test.

The import change from SteveConfiguration to SteveProperties aligns with the broader configuration migration.


30-38: LGTM: Spring Boot test annotations.

The test annotations follow Spring Boot testing conventions with proper profile activation and defined port configuration for integration testing.


55-57: LGTM: Spring Boot test class declaration.

The class is properly configured as a Spring Boot test with the correct naming convention.


61-68: LGTM: Spring dependency injection.

The autowired fields for ServerProperties, SteveProperties, and DSLContext follow Spring testing best practices.


72-81: LGTM: Proper test lifecycle management.

The @beforeeach and @AfterEach methods properly manage database state for each test, ensuring test isolation.


83-85: LGTM: Test method with proper WebSocket path resolution.

The test method uses the helper function getWsPath with the injected properties, which is consistent with the SteveProperties migration.


121-121: LGTM: Instance method with database preparer integration.

The sendStartTx method is correctly converted to an instance method to access the databasePreparer field, and the assertion logic properly validates the expected authorization status.

Also applies to: 130-130, 132-133

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (6)

30-31: LGTM: Jakarta EE imports for Spring Boot 3.

The import changes from javax.xml.ws to jakarta.xml.ws align with the Jakarta EE migration in Spring Boot 3.x.


49-52: LGTM: MessageIdInterceptor as Spring bean.

Exposing MessageIdInterceptor as a Spring bean enables proper dependency injection and lifecycle management.


54-62: LGTM: Endpoint beans with dependency injection.

The endpoint methods properly inject dependencies and delegate to the helper method for consistent endpoint creation. The path constants are appropriately defined for each OCPP version.

Also applies to: 64-72, 74-82


87-101: LGTM: Router endpoint configuration.

The router endpoint properly integrates SteveProperties for path configuration and creates a mediator with the list of EndpointImpl instances. The TODO comment is helpful for future refactoring.


103-115: LGTM: Clean endpoint creation helper.

The createDefaultEndpoint method provides consistent configuration for all OCPP endpoints with proper interceptor and feature injection.


117-128: LGTM: Generic endpoint creation method.

The createEndpoint method follows the builder pattern for endpoint configuration and ensures proper publication of the endpoint.

pom.xml (6)

5-10: LGTM: Spring Boot parent integration.

Spring Boot 3.5.6 includes updates to Spring Security 6.5.0 and other framework components, making this a recent and stable version choice. The Spring Boot parent POM provides comprehensive dependency management and plugin configurations.


44-44: LGTM: Centralized CXF version management.

Moving from plugin.cxf.version to cxf.version provides cleaner property naming and aligns with the consolidated CXF BOM approach.


47-47: LGTM: JAXB plugins version property.

The jaxb-plugins.version property follows Maven property naming conventions and enables consistent version management across modules.


98-103: LGTM: Simplified CXF dependency management.

Replacing multiple CXF BOMs with a single cxf-bom import reduces complexity and leverages CXF's own dependency management. This approach aligns with Spring Boot's philosophy of simplified dependency management.


178-179: LGTM: Explicit compiler plugin groupId.

Adding the explicit groupId for maven-compiler-plugin follows Maven best practices and improves build reproducibility.


220-221: LGTM: Dynamic license header configuration.

Using Maven properties in the license header makes the configuration more maintainable and consistent across the project.

Dockerfile (2)

82-82: LGTM: WAR artifact deployment.

The change from steve.jar to steve.war aligns with the Spring Boot WAR packaging changes in the project.


90-90: LGTM: WAR execution command.

The CMD update to run steve.war is consistent with the packaging change and maintains the same JVM options.

steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2)

21-21: LGTM: SteveProperties import migration.

The import change aligns with the broader migration from SteveConfiguration to SteveProperties across the codebase.


42-53: LGTM: Constructor updated for SteveProperties integration.

The constructor properly uses SteveProperties for path resolution, maintaining the same functionality while aligning with the new configuration approach.

steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (4)

22-22: LGTM: SteveProperties import migration.

The import change is consistent with the broader configuration migration pattern across the codebase.


45-49: LGTM: Constructor with SteveProperties integration.

The constructor properly injects SteveProperties and extracts the auto-registration configuration, replacing the @value annotation approach with a more structured configuration access pattern.


52-63: LGTM: Improved error handling with proper lock management.

The use of local variable inference (var) is appropriate here, and the finally block ensures proper lock release even in exceptional cases. This is a reliability improvement over the previous implementation.


67-76: LGTM: Enhanced error handling for registration status conversion.

The try-catch block around RegistrationStatus.fromValue() with proper logging is a good defensive programming practice, handling cases where database values might be corrupted or invalid.

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

125-126: LGTM: local var usage improves readability in tests

No issues with these refactors.

Also applies to: 145-151, 169-175

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

61-64: LGTM: properties-driven strategy selection

Switching to SteveProperties is consistent with the migration and removes string parsing.

steve/src/main/resources/logback-spring.xml (1)

35-42: TRACE/DEBUG for Spring Security/Web are globally enabled — scope them to dev/test

These global loggers override the dev/test INFO settings and will flood prod logs and risk leaking sensitive details.

Apply:

-    <logger name="org.springframework.security" level="TRACE"/>
-    <logger name="org.springframework.web" level="DEBUG"/>

And move them into the dev,test block (adjusting the existing entries):

     <springProfile name="dev,test">
         <logger name="org.apache.cxf" level="INFO"/>
         <logger name="org.eclipse.jetty" level="INFO"/>
         <logger name="com.zaxxer.hikari" level="INFO"/>
         <logger name="org.springframework" level="INFO"/>
-        <logger name="org.springframework.security" level="INFO"/>
-        <logger name="org.springframework.web.servlet" level="INFO"/>
+        <logger name="org.springframework.security" level="TRACE"/>
+        <logger name="org.springframework.web" level="DEBUG"/>
     </springProfile>

Optionally add a conservative prod override:

<springProfile name="prod">
    <logger name="org.springframework.security" level="INFO"/>
    <logger name="org.springframework.web" level="INFO"/>
</springProfile>

Also applies to: 44-45

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

136-146: URL parsing is too permissive — normalize, segment-bound, and URL-decode the ID

Current logic may return trailing segments (e.g., "id/extra"), accept partial matches, or fail when pathInfix is empty. This can break valid handshakes or admit malformed IDs.

-    public static String getLastBitFromUrl(String pathInfix, String input) {
-        if (Strings.isNullOrEmpty(input)) {
-            return "";
-        }
-
-        var index = input.indexOf(pathInfix);
-        if (index == -1) {
-            return "";
-        }
-        return input.substring(index + pathInfix.length());
-    }
+    public static String getLastBitFromUrl(String pathInfix, String input) {
+        if (Strings.isNullOrEmpty(pathInfix) || Strings.isNullOrEmpty(input)) {
+            return "";
+        }
+        // Ensure we match a segment-delimited infix: "/infix/"
+        String normInfix = "/" + pathInfix.replaceAll("^/+|/+$", "") + "/";
+        int index = input.indexOf(normInfix);
+        if (index < 0) {
+            return "";
+        }
+        String tail = input.substring(index + normInfix.length());
+        int slash = tail.indexOf('/');
+        String id = (slash >= 0) ? tail.substring(0, slash) : tail;
+        return java.net.URLDecoder.decode(id, java.nio.charset.StandardCharsets.UTF_8);
+    }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)

55-57: soapPath is declared and visible; no action required
Declared as protected String soapPath in StressTest and assigned in StressTest.attack() before attackInternal() runs.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)

50-50: LGTM: switched to shared soapPath from base StressTest.

The migration to use soapPath via getForOcpp16(soapPath) looks correct and removes prior constructor coupling.

Please confirm soapPath always ends with a slash (Helpers currently appends one), as CXF clients can be sensitive to path normalization.

Also applies to: 65-71, 73-81, 90-92

steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (2)

49-59: Harden SOAP URL building (nulls, IPv6, InetAddress.toString()).

getSsl() can be null; getAddress() is InetAddress and toString() yields name/addr; IPv6 needs brackets; contextPath may be null.

-    public static String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
-        var prefix = "http://";
-        if (serverProperties.getSsl().isEnabled()) {
-            prefix = "https://";
-        }
-
-        return prefix + serverProperties.getAddress() + ":" + serverProperties.getPort()
-                + serverProperties.getServlet().getContextPath()
-                + steveProperties.getPaths().getSoapMapping()
-                + steveProperties.getPaths().getRouterEndpointPath() + "/";
-    }
+    public static String getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties) {
+        var ssl = serverProperties.getSsl();
+        var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "https://" : "http://";
+
+        var addr = serverProperties.getAddress();
+        var host = (addr != null) ? addr.getHostAddress() : "localhost";
+        if (host.indexOf(':') >= 0) { // IPv6 literal
+            host = "[" + host + "]";
+        }
+        var port = serverProperties.getPort() != null ? serverProperties.getPort() : 8080;
+        var servlet = serverProperties.getServlet();
+        var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
+
+        return scheme + host + ":" + port
+                + ctx
+                + steveProperties.getPaths().getSoapMapping()
+                + steveProperties.getPaths().getRouterEndpointPath() + "/";
+    }

61-71: Apply the same hardening for WS URL (wss vs ws, nulls, IPv6).

-    public static String getWsPath(ServerProperties serverProperties, SteveProperties steveProperties) {
-        var prefix = "ws://";
-        if (serverProperties.getSsl().isEnabled()) {
-            prefix = "wss://";
-        }
-
-        return prefix + serverProperties.getAddress() + ":" + serverProperties.getPort()
-                + serverProperties.getServlet().getContextPath()
-                + steveProperties.getPaths().getWebsocketMapping()
-                + steveProperties.getPaths().getRouterEndpointPath() + "/";
-    }
+    public static String getWsPath(ServerProperties serverProperties, SteveProperties steveProperties) {
+        var ssl = serverProperties.getSsl();
+        var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "wss://" : "ws://";
+
+        var addr = serverProperties.getAddress();
+        var host = (addr != null) ? addr.getHostAddress() : "localhost";
+        if (host.indexOf(':') >= 0) {
+            host = "[" + host + "]";
+        }
+        var port = serverProperties.getPort() != null ? serverProperties.getPort() : 8080;
+        var servlet = serverProperties.getServlet();
+        var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
+
+        return scheme + host + ":" + port
+                + ctx
+                + steveProperties.getPaths().getWebsocketMapping()
+                + steveProperties.getPaths().getRouterEndpointPath() + "/";
+    }
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (1)

68-73: LGTM: path derivation via injected properties.

Resolving the SOAP endpoint with getSoapPath(serverProperties, steveProperties) in @BeforeEach aligns with the new config model.

steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)

72-80: LGTM: instance-based preparer using injected DSLContext.

Moving from static to instance methods improves test isolation and DI alignment.


162-175: No change required — schema constant matches CI/test DB.
SCHEMA_TO_TRUNCATE ("stevedb_test_2aa6a783d47d") matches DB_SCHEMA in .github/workflows/main.yml (CI creates that DB), so the hard-coded name is consistent.

steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

89-96: Close the Spring context and reverse teardown order to avoid leaks.

Use close() and clean DB before shutting the context; stop() may not release resources and closing first can invalidate the DataSource used by databasePreparer.

-            try {
-                if (app != null) {
-                    app.stop();
-                }
-            } finally {
-                if (databasePreparer != null) {
-                    databasePreparer.cleanUp();
-                }
-            }
+            try {
+                if (databasePreparer != null) {
+                    databasePreparer.cleanUp();
+                }
+            } finally {
+                if (app != null) {
+                    app.close();
+                }
+            }
steve/src/main/resources/application.yml (1)

2-2: Resource filtering is already enabled — no change required

steve/pom.xml configures true and includes application.yml; Maven's resources plugin supports @...@ delimiters by default, so the @token@ placeholders in steve/src/main/resources/application.yml are filtered at build time. SteveProperties is annotated with @ConfigurationProperties/@configuration and is bound at runtime/tests (tests set spring.profiles.active and CI passes -D... properties). Locations: steve/pom.xml, steve/src/main/resources/application.yml, steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

82-99: Spring Boot test wiring LGTM.

Autowiring ServerProperties/SteveProperties + per‑test DB setup is clean.

steve/pom.xml (1)

248-271: Replace unpack-with-dependency-plugin with WAR overlay.

Unpacking into ${project.build.directory}/${project.build.finalName} is fragile; prefer maven-war-plugin overlays to merge steve-ui-jsp.

Apply this diff:

-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-steve-ui-jsp-webapp</id>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <phase>process-resources</phase>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>de.rwth.idsg</groupId>
-                  <artifactId>steve-ui-jsp</artifactId>
-                  <version>${project.version}</version>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-                  <includes>WEB-INF/**,static/**</includes>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <overlays>
+            <overlay>
+              <groupId>de.rwth.idsg</groupId>
+              <artifactId>steve-ui-jsp</artifactId>
+              <type>war</type>
+            </overlay>
+          </overlays>
+        </configuration>
+      </plugin>

Note: Keep spring-boot-maven-plugin; it will repackage the WAR produced by maven-war-plugin.

goekay added a commit to steve-community/steve that referenced this pull request Sep 23, 2025
applied some of the relevant suggestions by AI review
Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (2)
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (1)

87-96: Abort chain only on successful forwarding
Return early when no server mapping exists; invoke abort() only after onMessage.

-        if (targetServer == null) {
-            log.warn("No server mapped for namespace '{}'", schemaNamespace);
-        } else {
-            targetServer.getDestination().getMessageObserver().onMessage(message);
-        }
-
-        // Now the response has been put in the message, abort the chain
-        message.getInterceptorChain().abort();
+        if (targetServer == null) {
+            log.warn("No server mapped for namespace '{}'", schemaNamespace);
+            return;  // allow the chain to continue on routing failure
+        }
+        targetServer.getDestination().getMessageObserver().onMessage(message);
+        // Now the response has been put in the message, abort the chain
+        message.getInterceptorChain().abort();
steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (1)

81-81: Potential duplicate PasswordEncoder field.

There are two PasswordEncoder fields: passwordEncoder (line 77) and encoder (line 81). This appears to be a duplicate field that could cause confusion.

#!/bin/bash
# Description: Verify usage of both PasswordEncoder fields to determine if one is redundant

# Search for usage of both passwordEncoder fields
rg -nP --type=java -C3 '\b(passwordEncoder|encoder)\.' steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java

Apply this diff to consolidate to a single PasswordEncoder field:

 private final SteveProperties steveProperties;
-private final PasswordEncoder passwordEncoder;
 private final ObjectMapper mapper;
 private final WebUserRepository webUserRepository;
 private final SecurityContextHolderStrategy securityContextHolderStrategy = getContextHolderStrategy();
-private final PasswordEncoder encoder;
+private final PasswordEncoder passwordEncoder;

Then update usages of encoder to use passwordEncoder consistently throughout the class.

🧹 Nitpick comments (12)
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (1)

76-76: Use raw path to avoid premature decoding.

Prefer getRawPath(); the parser can then decode reliably.

-        var chargeBoxId = getLastBitFromUrl(pathInfix, request.getURI().getPath());
+        var chargeBoxId = getLastBitFromUrl(pathInfix, request.getURI().getRawPath());
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (1)

104-111: Harden server map construction (skip unpublished endpoints, detect duplicates, return unmodifiable map).
Prevents NPEs when endpoints aren’t published and avoids accidental mutation.

Apply:

-    private static Map<String, Server> initServerLookupMap(List<EndpointImpl> endpoints) {
-        var actualServers = new HashMap<String, Server>();
-        for (var endpoint : endpoints) {
-            var server = endpoint.getServer();
-            var serverNamespace =
-                    server.getEndpoint().getEndpointInfo().getName().getNamespaceURI();
-            actualServers.put(serverNamespace, server);
-        }
-        return actualServers;
-    }
+    private static Map<String, Server> initServerLookupMap(List<EndpointImpl> endpoints) {
+        var map = new HashMap<String, Server>(endpoints.size());
+        for (var endpoint : endpoints) {
+            var server = endpoint.getServer();
+            if (server == null) {
+                log.warn("Skipping unpublished endpoint {}", endpoint);
+                continue;
+            }
+            var ns = server.getEndpoint().getEndpointInfo().getName().getNamespaceURI();
+            var prev = map.put(ns, server);
+            if (prev != null && prev != server) {
+                log.warn("Duplicate namespace mapping for '{}'; overriding previous server {}", ns, prev);
+            }
+        }
+        return java.util.Collections.unmodifiableMap(map);
+    }
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (2)

99-99: Minor: prefer Path.of over Paths.get

Modern JDKs favor Path.of(...). This also lets you drop the unused Paths import.

-                fileNameList.add(Paths.get(fileName));
+                fileNameList.add(Path.of(fileName));

And remove:

-import java.nio.file.Paths;

91-103: Direct ROOT appenders sufficient; no wrapper appenders detected
Search of logging configs found no AsyncAppender or SiftingAppender. Current iteration over ROOT’s appenders retrieves all configured log files. If wrapper appenders are added in the future, introduce a recursive collectFileAppenderPaths(Appender, List) helper to traverse AppenderAttachable and extract nested FileAppender paths.

steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (2)

183-186: Remove stray semicolon in lambda for cleanliness.

Tiny nit; the standalone ; is unnecessary.

-                .satisfies(c -> {
-                    ;
+                .satisfies(c -> {
                     var ue = (UpgradeException) c;

59-61: Consider RANDOM_PORT to avoid port collision in parallel runs.

If your build enables JUnit parallelism, DEFINED_PORT may clash across test classes. RANDOM_PORT with @LocalServerPort is safer.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3)

37-51: Avoid injecting one ConstraintValidator into another; extract shared logic or use container element constraints

ChargeBoxIdValidator has a lifecycle (initialize) and may be stateful per-annotation. Injecting and reusing it here risks incorrect state and threading concerns. Prefer:

  • Extract the core check into a stateless utility/service both validators call, or
  • Remove this validator and use Bean Validation 2.0 container element constraints: annotate as List<@ChargeBoxId String> and combine with @NotEmpty when needed.

This produces per‑element errors and lets Hibernate Validator manage lifecycle properly.


49-54: Consider reporting element index in violations for better UX

Currently, failures attach to the list as a whole. If you keep this validator, customize the ConstraintValidatorContext to include the offending index, or (preferably) rely on container element constraints which handle index/path automatically.


46-48: Companion constraints enforce non-emptiness; optional simplify null/empty check

The @ChargeBoxId validator is paired with @NotBlank on ChargePointForm.chargeBoxId and @NotEmpty on ChargePointBatchInsertForm.idList, so the early return for null/empty is safe.

-        if (CollectionUtils.isEmpty(value)) {
-            return true; // null or empty is valid, because it is another constraint's responsibility
-        }
+        if (value == null || value.isEmpty()) {
+            return true; // null or empty is valid; use @NotEmpty/@Size to enforce non-empty if needed
+        }
steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (1)

94-95: Review password encoding consistency across the service.

Line 95 uses passwordEncoder.encode() while line 99 uses passwordEncoder.encode(), but other methods in the class use encoder.encode(). Once the duplicate field issue is resolved, ensure all password encoding uses the same field consistently.

The earlier script will help verify the usage patterns. After consolidating to a single PasswordEncoder field, ensure all encoding operations use the same reference.

steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1)

39-39: Document JooqAutoConfiguration exclusion
Add a comment at SteveApplication.java:39 explaining why the default jOOQ auto-configuration is disabled in favor of your custom JooqConfiguration bean.
Optional: consider using Spring Boot 3’s DefaultConfigurationCustomizer for customizations instead of excluding the auto-configuration.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (1)

58-61: Set client timeouts on the HTTP conduit.
Avoid unbounded waits on external SOAP endpoints by setting connection/receive timeouts.

Apply this diff:

 var client = ClientProxy.getClient(clientObject);
 var http = (HTTPConduit) client.getConduit();
 http.setTlsClientParameters(tlsClientParams);
+// Avoid unbounded network waits
+http.getClient().setConnectionTimeout(10_000); // 10s
+http.getClient().setReceiveTimeout(30_000);    // 30s
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a5f43b and da537c5.

📒 Files selected for processing (10)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (9 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
🧬 Code graph analysis (3)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (2)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (58-235)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (35-98)
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (2)
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (1)
  • Component (33-56)
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (1)
  • Component (41-94)
🔇 Additional comments (24)
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3)

126-132: LGTM: protocol selection is straightforward.

Given the small endpoint set, the nested loop is acceptable.


136-146: Fix getLastBitFromUrl: match on segment boundary, extract single segment, and URL‑decode.

Current logic can return extra path segments (e.g., "CP_1/other"), fail when pathInfix is empty/null, or match unintended occurrences. Harden it.

-    public static String getLastBitFromUrl(String pathInfix, String input) {
-        if (Strings.isNullOrEmpty(input)) {
-            return "";
-        }
-
-        var index = input.indexOf(pathInfix);
-        if (index == -1) {
-            return "";
-        }
-        return input.substring(index + pathInfix.length());
-    }
+    public static String getLastBitFromUrl(String pathInfix, String input) {
+        if (Strings.isNullOrEmpty(pathInfix) || Strings.isNullOrEmpty(input)) {
+            return "";
+        }
+        // Normalize to ensure we match on "/<infix>/"
+        String normInfix = "/" + pathInfix.replaceAll("^/+|/+$", "") + "/";
+        String normInput = input; // already a path (raw or decoded), no query
+        int index = normInput.indexOf(normInfix);
+        if (index < 0) {
+            return "";
+        }
+        String tail = normInput.substring(index + normInfix.length());
+        int slash = tail.indexOf('/');
+        String id = (slash >= 0) ? tail.substring(0, slash) : tail;
+        return java.net.URLDecoder.decode(id, java.nio.charset.StandardCharsets.UTF_8);
+    }

I can add focused unit tests covering:

  • encoded IDs (e.g., CP%2F1),
  • trailing slashes,
  • multiple occurrences of the infix,
  • missing/empty infix or path.
    Want me to draft them?

53-53: Confirm pathInfix wiring and value normalization
OcppWebSocketHandshakeHandler is now constructed in OcppWebSocketConfiguration.java with the new pathInfix argument. Verify that steveProperties.getPaths().getWebsocketMapping() and getRouterEndpointPath() produce a correctly normalized prefix (no duplicate, leading or trailing slashes).

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (3)

25-25: Import looks correct for the new constructor signature.
Using EndpointImpl explicitly is fine here.


49-53: Defensive null check for endpoints is missing.
Constructor will NPE in initServerLookupMap when endpoints is null.

Apply:

 public MediatorInInterceptor(List<EndpointImpl> endpoints) {
   super(Phase.POST_STREAM);
   super.addBefore(StaxInInterceptor.class.getName());
-  actualServers = initServerLookupMap(endpoints);
+  actualServers = initServerLookupMap(java.util.Objects.requireNonNull(endpoints, "endpoints must not be null"));
 }

61-69: Null InputStream and fragile mark/reset can drop requests.
message.getContent(InputStream.class) may be null (NPE), and using bis.available() as read limit often yields 0, making reset() fail.

Apply:

-            var is = message.getContent(InputStream.class);
-            var bis = new BufferedInputStream(is);
-            bis.mark(bis.available());
+            var is = message.getContent(InputStream.class);
+            if (is == null) {
+                log.warn("No InputStream on message; cannot route");
+                return;
+            }
+            var bis = new BufferedInputStream(is);
+            // mark enough bytes to parse envelope + first body element
+            bis.mark(64 * 1024);
             message.setContent(InputStream.class, bis);
 
-            var encoding = (String) message.get(Message.ENCODING);
-            var reader = StaxUtils.createXMLStreamReader(bis, encoding);
-            var xmlReader = new DepthXMLStreamReader(reader);
+            var encoding = (String) message.get(Message.ENCODING);
+            var reader = StaxUtils.createXMLStreamReader(bis, encoding);
+            var xmlReader = new DepthXMLStreamReader(reader);

Optional: move bis.reset() into a finally block and log a warning if reset fails without aborting the chain.

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

28-28: Spring bean registration — LGTM

Annotating this utility with @component (and adding the import) is appropriate and makes it injectable where needed.

Also applies to: 42-42

steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

72-76: Property assertion may be brittle; assert as boolean and verify test override exists.

Use typed access and ensure a test override actually sets it to true.

-            assertThat(environment.getProperty("steve.ocpp.auto-register-unknown-stations"))
-                    .isEqualTo("true");
+            assertThat(environment.getProperty("steve.ocpp.auto-register-unknown-stations", Boolean.class))
+                    .isTrue();

Run to verify a test override exists:

#!/bin/bash
set -euo pipefail
echo "Search for a test-specific override setting the property to true"
rg -n -C2 -g '!**/target/**' -P --type=yaml 'steve:\s*\n\s*ocpp:\s*\n\s*auto-register-unknown-stations:\s*true' || true
fd -HI 'application-test.*' || true
rg -n -C2 -P --type=properties '^steve\.ocpp\.auto-register-unknown-stations\s*=\s*true' || true
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)

80-85: LGTM: Proper per-test setup of DB and JSON path.

The DI-based jsonPath computation and __DatabasePreparer__ lifecycle look correct.

steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)

72-73: LGTM: soapPath computed via DI-backed properties.

Path derivation aligns with Helpers and the new config.


40-41: Same Helpers address normalization caveat as JSON tests.

Ensure getSoapPath uses a normalized host (see comment in StressTest).


68-78: No change required: tearDown signature is correct DatabasePreparer.cleanUp() does not declare any checked exceptions, so omitting throws Exception compiles as is.

Likely an incorrect or invalid review comment.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (1)

34-37: Validation uses Spring’s ConstraintValidatorFactory
No direct Hibernate-Validator factory usage in production code and spring-boot-starter-validation is on the classpath; test-only setup of LocalValidatorFactoryBean doesn’t affect runtime.

steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (4)

1-40: LGTM! Standard Spring Boot application entry point with proper licensing.

The class structure and Spring Boot annotations are correctly implemented. The GPL license header is appropriate for an open-source project.


42-51: LGTM! Proper initialization of logging and timezone configuration.

The static initializer correctly configures:

  • Apache CXF to use SLF4J logging
  • JBoss logging provider for Hibernate validator
  • Default JVM timezone to UTC for consistency

This ensures predictable behavior across different deployment environments.


53-59: LGTM! Useful startup diagnostics.

The event listener provides valuable runtime information about timezone configuration, which helps with debugging date/time related issues in a distributed environment.


61-63: LGTM! Standard Spring Boot main method.

The main method correctly delegates to SpringApplication.run with proper parameter handling.

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

26-26: LGTM! Updated import for SteveProperties migration.

The import correctly reflects the migration from SteveConfiguration to SteveProperties-based configuration.


76-77: LGTM! Added dependencies for properties-based configuration.

The addition of SteveProperties and PasswordEncoder dependencies supports the migration from legacy configuration to Spring Boot's properties-based approach.


94-100: LGTM! Properly migrated to SteveProperties-based configuration.

The code correctly:

  • Retrieves web API header value from steveProperties.getWebApi().getHeaderValue()
  • Retrieves auth credentials from steveProperties.getAuth().getUsername() and steveProperties.getAuth().getPassword()
  • Handles null/blank header values appropriately
  • Uses proper password encoding for both web and API passwords
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4)

29-35: Imports look correct for Spring Boot 3.5 and exception propagation.
No concerns with using ServerProperties and Ssl here.


47-51: Constructor wiring LGTM.
Null-safe handoff to create(serverProperties.getSsl()) with exceptions surfaced at startup is acceptable.


76-93: NPE risk when key-store is not configured; add trust-store support.
ssl.isEnabled() can be true while ssl.getKeyStore() is null (e.g., relying on system CAs or only a trust store). Unconditionally calling keyStoreFromFile(...) will NPE and blocks valid configurations. Also, trust-store values are ignored.

Apply this diff to make keystore/truststore handling optional and null-safe:

-    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
-            throws GeneralSecurityException, IOException {
-        if (ssl == null || !ssl.isEnabled()) {
-            return null;
-        }
-        var tlsClientParams = new TLSClientParameters();
-
-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
-                .usingTLS()
-                .usingDefaultAlgorithm()
-                .usingKeyManagerPasswordFromKeyStore()
-                .buildMergedWithSystem()
-                .getSocketFactory();
-        tlsClientParams.setSSLSocketFactory(socketFactory);
-
-        return tlsClientParams;
-    }
+    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
+            throws GeneralSecurityException, IOException {
+        if (ssl == null || !ssl.isEnabled()) {
+            return null;
+        }
+        var tlsClientParams = new TLSClientParameters();
+
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder
+                    .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
+                    .usingKeyManagerPasswordFromKeyStore();
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        var socketFactory = builder
+                .usingTLS()
+                .usingDefaultAlgorithm()
+                .buildMergedWithSystem()
+                .getSocketFactory();
+        tlsClientParams.setSSLSocketFactory(socketFactory);
+
+        return tlsClientParams;
+    }

Optionally, if you expect distinct private-key passwords, we can extend this to honor ssl.getKeyPassword() when present.


76-93: No SSL bundle configuration detected: no server.ssl.* properties found in application configuration, so manual TLSClientParameters instantiation remains appropriate. If you later introduce server.ssl.bundle (Spring Boot 3.1+), consider using SslBundles to produce the SSLSocketFactory.

Comment on lines 88 to 96
try {
app.stop();
if (app != null) {
app.close();
}
} finally {
__DatabasePreparer__.cleanUp();
if (databasePreparer != null) {
databasePreparer.cleanUp();
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Clean DB before closing the Spring context to avoid “pool is closed” failures.

__DatabasePreparer__ uses DSLContext from the app. Closing the context first can invalidate the DataSource before cleanup runs.

-            try {
-                if (app != null) {
-                    app.close();
-                }
-            } finally {
-                if (databasePreparer != null) {
-                    databasePreparer.cleanUp();
-                }
-            }
+            try {
+                if (databasePreparer != null) {
+                    databasePreparer.cleanUp();
+                }
+            } finally {
+                if (app != null) {
+                    app.close();
+                }
+            }
📝 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
try {
app.stop();
if (app != null) {
app.close();
}
} finally {
__DatabasePreparer__.cleanUp();
if (databasePreparer != null) {
databasePreparer.cleanUp();
}
}
try {
if (databasePreparer != null) {
databasePreparer.cleanUp();
}
} finally {
if (app != null) {
app.close();
}
}
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/StressTest.java around lines 88 to 96,
the DatabasePreparer.cleanup() is being invoked in the finally block after the
application context is closed which can close the DataSource and cause "pool is
closed" errors; move the databasePreparer.cleanUp() call to run before
app.close() (i.e., if databasePreparer != null call cleanUp() first, then if app
!= null call app.close()), keep null checks and preserve exception-safety (wrap
each call to cleanUp() and close() so one failing does not skip the other).

Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (2)
Dockerfile (2)

73-79: Container won’t bind to ${PORT}: wrong Spring Boot properties.

-Dhttp.port and -Dserver.host are ignored by Spring Boot; use server.port and server.address. Also server.gzip.enabled is not a Boot property; use server.compression.enabled.

-ENV JAVA_TOOL_OPTIONS="-Dserver.host=0.0.0.0 -Dhttp.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
+ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1.2,TLSv1.3' -Dserver.compression.enabled=false"

61-67: Avoid defaulting to weak TLS versions.

Re-enabling TLSv1/1.1 downgrades security. Keep TLSv1.2/1.3 only (as in the fix above).

🧹 Nitpick comments (7)
.github/workflows/main.yml (2)

54-55: Remove spring-boot.run.profiles; use spring.profiles.active (or drop entirely for build).

spring-boot.run.profiles only affects spring-boot:run, not package. Prefer spring.profiles.active or omit.

Apply:

-            -Dspring-boot.run.profiles=test,dev \
+            -Dspring.profiles.active=test,dev \

57-63: Make the app start probe robust (avoid fixed sleep).

Replace fixed sleep with a readiness loop to reduce flakiness.

-          sleep 30
-          elinks -dump -no-references http://localhost:8080/manager/signin
+          for i in $(seq 1 60); do
+            if curl -fsS http://localhost:8080/manager/signin >/dev/null; then break; fi
+            sleep 1
+          done
+          elinks -dump -no-references http://localhost:8080/manager/signin
Dockerfile (2)

81-83: Remove stale libs copy for executable WAR.

spring-boot-maven-plugin packages dependencies inside the WAR; target/libs is not used here.

-COPY --from=builder --chown=app:app /code/steve/target/libs/ /app/libs/

34-41: Unused build ARG.

PORT ARG in builder stage is unused.

-ARG PORT=8180
steve/pom.xml (3)

108-121: Redundant logging deps; rely on Spring Boot managed logging.

logback-classic, slf4j-api, and jcl-over-slf4j are provided by Boot BOM/starters. Keeping them risks version drift.

-    <dependency>
-      <groupId>ch.qos.logback</groupId>
-      <artifactId>logback-classic</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>jcl-over-slf4j</artifactId>
-    </dependency>

90-96: Prefer Boot-managed transitive deps; remove direct Jackson and Hikari.

jackson-databind/annotations come via spring-boot-starter-web; HikariCP via spring-boot-starter-jdbc. Keep mysql-connector-j.

-    <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-databind</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-annotations</artifactId>
-    </dependency>
...
-    <dependency>
-      <groupId>com.zaxxer</groupId>
-      <artifactId>HikariCP</artifactId>
-    </dependency>

Also applies to: 98-101, 153-161, 163-167


122-142: Avoid mixing Boot’s Jetty starter with direct Jetty modules unless needed.

spring-boot-starter-jetty already aligns Jetty versions. Extra jetty-* deps may cause conflicts. Keep only specific extras (e.g., jetty-rewrite) if required.

Option A (lean): remove direct jetty-* deps and let starter manage.

Option B (if features required): keep only needed artifacts and ensure versions are managed by Boot (no explicit versions).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da537c5 and b60e002.

📒 Files selected for processing (8)
  • .github/workflows/main.yml (1 hunks)
  • Dockerfile (1 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (5 hunks)
  • steve/src/main/resources/application-dev.yml (1 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/logback-spring.xml (1 hunks)
  • steve/src/test/resources/application-test.yml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • steve/src/main/resources/application-dev.yml
  • steve/src/test/resources/application-test.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • steve/README.md
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • .github/workflows/main.yml
  • steve/pom.xml
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve/src/main/resources/application.yml
  • steve/src/main/resources/logback-spring.xml
  • steve/pom.xml
🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

🪛 GitHub Actions: build and run tests
steve/pom.xml

[error] 1-1: Maven build failed: No goals have been specified for this build. The command is missing a valid lifecycle phase or goal. Command: './mvnw -B -V -PuseRealDatabase -DskipTests=false -Dmaven.javadoc.skip=true -Ddb.jdbc.url="jdbc:mysql://127.0.0.1:3306/${DB_SCHEMA}?sslMode=PREFERRED&serverTimezone=UTC" -Ddb.schema="${DB_SCHEMA}" -Ddb.user="${DB_USER}" -Ddb.*** -Dspring-boot.run.profiles=test,dev clean package --file pom.xml'

⏰ 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). (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (5)
steve/src/main/resources/logback-spring.xml (1)

50-51: Don't ship TRACE/DEBUG security/web logging to prod.

The global TRACE/DEBUG loggers here override the dev/test INFO overrides declared above, so every profile—including prod—runs with extremely verbose Spring Security/Web logging, risking log flooding and sensitive detail leakage. Please scope these verbose levels to the dev/test profile (or drop them) and keep prod at INFO+. See the outstanding bot comment for the same issue.

-    <logger name="org.springframework.security" level="TRACE"/>
-    <logger name="org.springframework.web" level="DEBUG"/>

Then bump the dev/test overrides to the desired verbosity, for example:

-        <logger name="org.springframework.security" level="INFO"/>
-        <logger name="org.springframework.web.servlet" level="INFO"/>
+        <logger name="org.springframework.security" level="TRACE"/>
+        <logger name="org.springframework.web" level="DEBUG"/>
Dockerfile (1)

73-76: Healthcheck likely probes wrong port.

Given the wrong props above, app would listen on 8080 while healthcheck hits ${PORT} (8180). After fixing props, healthcheck aligns; otherwise it will always fail.

steve/pom.xml (3)

225-232: Disable Maven filtering for application.yml to avoid breaking Spring placeholders.

Already flagged previously; reiterating. Filtering may replace ${...} in YAML at build-time.

Example:

-    <resources>
-      <resource>
-        <filtering>true</filtering>
-        <directory>src/main/resources</directory>
-        <includes>
-          <include>application.yml</include>
-          <include>logback-spring.xml</include>
-        </includes>
-      </resource>
-    </resources>
+    <resources>
+      <resource>
+        <filtering>false</filtering>
+        <directory>src/main/resources</directory>
+        <includes>
+          <include>application.yml</include>
+          <include>logback-spring.xml</include>
+        </includes>
+      </resource>
+    </resources>

241-264: Use WAR overlays instead of unpacking UI into target dir.

This unpack approach is fragile; prefer maven-war-plugin overlays. This was noted earlier.

-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-steve-ui-jsp-webapp</id>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <phase>process-resources</phase>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>de.rwth.idsg</groupId>
-                  <artifactId>steve-ui-jsp</artifactId>
-                  <version>${project.version}</version>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-                  <includes>WEB-INF/**,static/**</includes>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <overlays>
+            <overlay>
+              <groupId>de.rwth.idsg</groupId>
+              <artifactId>steve-ui-jsp</artifactId>
+              <type>war</type>
+            </overlay>
+          </overlays>
+        </configuration>
+      </plugin>

Note: add dependency de.rwth.idsg:steve-ui-jsp with war if not already resolved as WAR.


58-83: Good: Jetty chosen via Boot starter; Tomcat excluded.

This aligns with the WAR + Jetty direction.

Confirm no conflicting embedded container on classpath at runtime (e.g., direct Tomcat deps).

Copy link

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
steve-core/pom.xml (1)

18-31: Remove spring-webmvc from steve-core and relocate to the app module
steve-core contains no @Controller/@RestController – drop the direct spring-webmvc dependency (and its spring-jcl exclusion) and let the app’s spring-boot-starter-web provide the web stack and logging bridge.

🧹 Nitpick comments (4)
steve-core/pom.xml (2)

45-48: Remove explicit jakarta.mail-api; spring-boot-starter-mail already provides it

Redundant direct dependency risks version skew against Boot’s managed version.

-    <dependency>
-      <groupId>jakarta.mail</groupId>
-      <artifactId>jakarta.mail-api</artifactId>
-    </dependency>

32-43: Avoid Boot starters in library module (prefer narrow deps)

If steve-core is intended as a library, depend on Spring modules (e.g., spring-security-core, jakarta.validation-api) instead of Boot starters to avoid dragging auto-config/logging into downstreams.

Example:

-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-validation</artifactId>
-    </dependency>
+    <dependency>
+      <groupId>jakarta.validation</groupId>
+      <artifactId>jakarta.validation-api</artifactId>
+    </dependency>

-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-security</artifactId>
-    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-core</artifactId>
+    </dependency>
.github/workflows/main.yml (1)

57-64: Prefer readiness probe over fixed sleep

Poll an HTTP endpoint until ready to reduce flakes; avoid killall impacting other Java processes.

-          java -Djava.net.preferIPv4Stack=true -jar steve/target/steve.war &
-          sleep 30
-          elinks -dump -no-references http://localhost:8080/manager/signin
-          killall java
+          java -Djava.net.preferIPv4Stack=true -jar steve/target/steve.war &
+          for i in {1..30}; do
+            if curl -fsS http://localhost:8080/actuator/health >/dev/null 2>&1; then break; fi
+            sleep 2
+          done
+          elinks -dump -no-references http://localhost:8080/manager/signin
+          pkill -f 'steve/target/steve\.war' || true
Dockerfile (1)

77-77: Rename the gzip toggle to Spring Boot’s property name

Spring Boot ignores server.gzip.enabled, so the flag is now a no-op. Either drop it or switch to server.compression.enabled=false to keep the intent clear.

-ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
+ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.compression.enabled=false"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b60e002 and 6f29fed.

📒 Files selected for processing (5)
  • .github/workflows/main.yml (1 hunks)
  • Dockerfile (1 hunks)
  • steve-api/pom.xml (1 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve/pom.xml (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • steve-api/pom.xml
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • Dockerfile
  • steve/pom.xml
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • .github/workflows/main.yml
  • steve/pom.xml
⏰ 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.5.21)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-24.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-22.04, 24, mysql:8.0)
🔇 Additional comments (7)
steve/pom.xml (4)

197-226: Use WAR overlay instead of unpacking steve-ui-jsp into target dir

Unpacking into ${project.build.directory}/${project.build.finalName} is fragile; the WAR plugin may overwrite that directory.

Replace dependency-plugin execution with a war overlay:

   <plugins>
     <plugin>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
-    <plugin>
-      <groupId>org.apache.maven.plugins</groupId>
-      <artifactId>maven-dependency-plugin</artifactId>
-      <executions>
-        <execution>
-          <id>unpack-steve-ui-jsp-webapp</id>
-          <goals>
-            <goal>unpack</goal>
-          </goals>
-          <phase>process-resources</phase>
-          <configuration>
-            <artifactItems>
-              <artifactItem>
-                <groupId>de.rwth.idsg</groupId>
-                <artifactId>steve-ui-jsp</artifactId>
-                <version>${project.version}</version>
-                <overWrite>true</overWrite>
-                <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-                <includes>WEB-INF/**,static/**</includes>
-              </artifactItem>
-            </artifactItems>
-          </configuration>
-        </execution>
-      </executions>
-    </plugin>
+    <plugin>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-war-plugin</artifactId>
+      <configuration>
+        <overlays>
+          <overlay>
+            <groupId>de.rwth.idsg</groupId>
+            <artifactId>steve-ui-jsp</artifactId>
+            <type>war</type>
+          </overlay>
+        </overlays>
+      </configuration>
+    </plugin>
   </plugins>

58-88: LGTM on Boot web/Jetty setup

Excluding Tomcat and adding starter-jetty is consistent with WAR packaging and the run command.


124-134: Test scopes look correct

spring-boot-starter-test and spring-security-test are test-scoped as expected.


186-195: Disable Maven filtering for application.yml to avoid placeholder/secret bake-in

Current filtering=true will resolve ${...} in application.yml at build time, breaking Spring’s runtime property resolution and potentially baking secrets.

Safer split: filter only logback, keep application.yml unfiltered.

   <resources>
-    <resource>
-      <filtering>true</filtering>
-      <directory>src/main/resources</directory>
-      <includes>
-        <include>application.yml</include>
-        <include>logback-spring.xml</include>
-      </includes>
-    </resource>
+    <resource>
+      <directory>src/main/resources</directory>
+      <filtering>false</filtering>
+      <includes>
+        <include>application.yml</include>
+      </includes>
+    </resource>
+    <resource>
+      <directory>src/main/resources</directory>
+      <filtering>true</filtering>
+      <includes>
+        <include>logback-spring.xml</include>
+      </includes>
+    </resource>
   </resources>
.github/workflows/main.yml (1)

46-55: Maven command formatting fixed

Multiline args and goal placement look correct now.

Dockerfile (2)

81-81: Copying the WAR matches the packaging switch

Looks good to mirror the new WAR packaging in the image build.


89-89: Confirm the WAR remains fully executable
The verification script failed due to an undefined JAVA_HOME; please set JAVA_HOME and run:

jar tf steve/target/steve.war | grep -q 'org/springframework/boot/loader/WarLauncher.class'

to ensure Spring Boot’s loader classes are present.

@juherr juherr force-pushed the backport/steve-1822 branch 2 times, most recently from 4b9e2db to bad9eaa Compare September 25, 2025 16:37
Copy link

@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: 10

Caution

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

⚠️ Outside diff range comments (7)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)

156-170: Keep buildRequest accepting a String (or call toString() before concatenation).

soapPath is a URI, but the SOAP envelope needs its textual form. Either call path.toString() when building the payload or simply keep the method parameter type as String. As written, concatenating a URI is fine for the <To> value, but the caller now has to pass a URI, which forced the compile error above. Revert the signature to String (or convert to String locally) for compatibility.

Here’s one option:

-    private static String buildRequest(
-            URI path, String chargeBoxId, int transactionId, String idTag, OffsetDateTime stop, int meterStop) {
+    private static String buildRequest(
+            String path, String chargeBoxId, int transactionId, String idTag, OffsetDateTime stop, int meterStop) {

If you need to keep URI elsewhere, just pass soapPath.toString() instead.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (1)

130-149: Sensitive data risk: full SOAP payload logging

The custom Slf4jEventSender logs entire payloads, which can include credentials/PII. At minimum, cap size and avoid pretty logging; ideally disable in prod or mask sensitive elements.

Apply a safe cap and avoid verbose formatting:

     public LoggingFeature loggingFeature() {
         var feature = new LoggingFeature();
+        feature.setLimit(8192);           // cap payload size
+        feature.setPrettyLogging(false);  // reduce verbosity
         feature.setSender(new Slf4jEventSender() {
             @Override
             protected String getLogMessage(LogEvent event) {
                 var b = new StringBuilder();
 
                 b.append('\n') // Start from the next line to have the output well-aligned
                         .append("    ExchangeId: ")
                         .append(event.getExchangeId())
                         .append('\n')
-                        .append("    Payload: ")
-                        .append(event.getPayload());
+                        .append("    Payload (truncated): ")
+                        .append(event.getPayload());
 
                 return b.toString();
             }
         });
         return feature;
     }

Operationally, prefer enabling this bean only in dev/test and control CXF logging via application properties (e.g., set logging.level for CXF categories to DEBUG when needed).

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

114-117: Ensure Spring transaction participation (wrap DataSource with TransactionAwareDataSourceProxy).

Without this, jOOQ may bypass Spring-managed transactions.

+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
@@
-                .set(new DataSourceConnectionProvider(
-                        // new org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy()
-                        dataSource))
+                .set(new DataSourceConnectionProvider(
+                        new TransactionAwareDataSourceProxy(dataSource)))
steve-ui-jsp/pom.xml (1)

120-121: Define missing jetty.version property
Add <jetty.version>12.0.11</jetty.version> under <properties> in the parent POM (preferred) or in steve-ui-jsp/pom.xml to prevent build failures.

pom.xml (1)

37-48: Define lombok.version property and specify lombok dependency version

  • Add <lombok.version>1.18.34</lombok.version> under <properties> in pom.xml
  • Add <version>${lombok.version}</version> to the org.projectlombok:lombok <dependency> (scope provided) and ensure the same property is used in <annotationProcessorPaths>
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)

79-86: Fix stray checkstyle text and line length in Javadoc

The comment includes a pasted checkstyle error and exceeds line length.

-    /**
-     * https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configurationCheck failure[checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Check failure: [checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Line is longer than 120 characters (found 121).build and run tests / checkstyleView detailsCode has alerts. Press enter to view.
-     *
-     * Otherwise, defaults come from {@link WebSocketConstants}
-     */
+    /**
+     * See:
+     * https://docs.spring.io/spring-framework/reference/web/websocket/server.html
+     * #websocket-server-runtime-configuration
+     *
+     * Otherwise, defaults come from {@link WebSocketConstants}.
+     */
steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

588-595: Bug: Asserting empty list, then indexing element 0 → IndexOutOfBoundsException.

sampledValues is asserted empty, but sampledValues.get(0) is accessed right after.

-            var sampledValues = meterValue.getSampledValue();
-            assertThat(sampledValues).isEmpty();
+            var sampledValues = meterValue.getSampledValue();
+            assertThat(sampledValues).isNotEmpty();
             var thisValueFound = false;
             // and check, if it can be found in the DB
             for (var values : details.getValues()) {
                 if (values.getValue().equals(sampledValues.get(0).getValue())) {
🧹 Nitpick comments (29)
steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1)

93-106: Preserve URI semantics instead of string-concatenating the path

Now that pathPrefix is a URI, concatenating it with chargeBoxId by invoking toString() defeats the point and can yield invalid targets (e.g., missing separators, clobbered queries/fragments). Let URI.resolve(...) do the heavy lifting and keep the field typed as URI, so downstream code doesn’t need to re-parse.

-    private final String connectionPath;
+    private final URI connectionUri;
@@
-        this.connectionPath = pathPrefix + chargeBoxId;
+        this.connectionUri = pathPrefix.resolve(chargeBoxId);
@@
-            var connect = client.connect(this, new URI(connectionPath), request);
+            var connect = client.connect(this, connectionUri, request);
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (3)

54-83: Avoid self-invoking @bean from non-@bean method; inject LoggingFeature instead

Calling loggingFeature() inside createDefaultEndpoint relies on @configuration proxying and is brittle if proxyBeanMethods=false. Pass LoggingFeature via bean method parameters and thread it into createDefaultEndpoint.

Apply:

@@
-    public EndpointImpl ocpp12Endpoint(
+    public EndpointImpl ocpp12Endpoint(
             Bus bus,
             MessageIdInterceptor messageIdInterceptor,
             MessageHeaderInterceptor messageHeaderInterceptor,
-            ocpp.cs._2010._08.CentralSystemService ocpp12Server) {
-        return createDefaultEndpoint(
-                bus, messageIdInterceptor, messageHeaderInterceptor, ocpp12Server, "/CentralSystemServiceOCPP12");
+            ocpp.cs._2010._08.CentralSystemService ocpp12Server,
+            LoggingFeature loggingFeature) {
+        return createDefaultEndpoint(
+                bus, messageIdInterceptor, messageHeaderInterceptor, loggingFeature, ocpp12Server, "/CentralSystemServiceOCPP12");
     }
@@
-    public EndpointImpl ocpp15Endpoint(
+    public EndpointImpl ocpp15Endpoint(
             Bus bus,
             MessageIdInterceptor messageIdInterceptor,
             MessageHeaderInterceptor messageHeaderInterceptor,
-            ocpp.cs._2012._06.CentralSystemService ocpp15Server) {
-        return createDefaultEndpoint(
-                bus, messageIdInterceptor, messageHeaderInterceptor, ocpp15Server, "/CentralSystemServiceOCPP15");
+            ocpp.cs._2012._06.CentralSystemService ocpp15Server,
+            LoggingFeature loggingFeature) {
+        return createDefaultEndpoint(
+                bus, messageIdInterceptor, messageHeaderInterceptor, loggingFeature, ocpp15Server, "/CentralSystemServiceOCPP15");
     }
@@
-    public EndpointImpl ocpp16Endpoint(
+    public EndpointImpl ocpp16Endpoint(
             Bus bus,
             MessageIdInterceptor messageIdInterceptor,
             MessageHeaderInterceptor messageHeaderInterceptor,
-            ocpp.cs._2015._10.CentralSystemService ocpp16Server) {
-        return createDefaultEndpoint(
-                bus, messageIdInterceptor, messageHeaderInterceptor, ocpp16Server, "/CentralSystemServiceOCPP16");
+            ocpp.cs._2015._10.CentralSystemService ocpp16Server,
+            LoggingFeature loggingFeature) {
+        return createDefaultEndpoint(
+                bus, messageIdInterceptor, messageHeaderInterceptor, loggingFeature, ocpp16Server, "/CentralSystemServiceOCPP16");
     }
@@
-    private EndpointImpl createDefaultEndpoint(
+    private EndpointImpl createDefaultEndpoint(
             Bus bus,
             MessageIdInterceptor messageIdInterceptor,
             MessageHeaderInterceptor messageHeaderInterceptor,
+            LoggingFeature loggingFeature,
             Object serviceBean,
             String address) {
         return createEndpoint(
                 bus,
                 serviceBean,
                 address,
                 List.of(messageIdInterceptor, messageHeaderInterceptor),
-                List.of(loggingFeature()));
+                List.of(loggingFeature));
     }

Also applies to: 103-115


87-101: Router endpoint: remove dummy dependency on ocpp12 service

Using ocpp12Server as a placeholder couples the router bean to OCPP 1.2 types and may affect WSDL/service metadata. Prefer publishing the router with a minimal JAX‑WS Provider (or a lightweight POJO endpoint) and attach MediatorInInterceptor there.

Also verify steveProperties.paths.routerEndpointPath starts with “/”. If not, CXF publish(...) may create an unexpected relative address.


99-100: Use List.of() instead of Collections.emptyList() for consistency

Minor style nit; elsewhere List.of(...) is used.

-                List.of(mediator),
-                Collections.emptyList());
+                List.of(mediator),
+                List.of());
steve/README.md (1)

103-104: Fix run command path (module vs repo root).

The artifact is created under steve/target; either cd into steve or adjust the path.

-    java -jar target/steve.war
+    java -jar steve/target/steve.war
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1)

27-28: Make PATH_INFIX a constant.

Use static final for constant semantics.

-    private final String PATH_INFIX = "/steve/websocket/CentralSystemService/";
+    private static final String PATH_INFIX = "/steve/websocket/CentralSystemService/";
steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1)

49-51: Prefer explicit UTC ZoneId for clarity.

Minor readability tweak.

+import java.time.ZoneId;
@@
-        var zoneId = ZoneOffset.UTC;
-        TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
+        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")));
steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (1)

62-62: Avoid recreating the CXF client per phase; cache in a ThreadLocal.

You’re creating a new proxy on each beforeRepeat/toRepeat. Cache it per thread for lower overhead.

-                var client = getForOcpp16(soapPath);
+                client.set(getForOcpp16(soapPath));
@@
-                var client = getForOcpp16(soapPath);
+                var client = this.client.get();

Additions required outside this hunk:

// Add near other ThreadLocals
import ocpp.cs._2015._10.CentralSystemService;
// ...
private final ThreadLocal<CentralSystemService> client = new ThreadLocal<>();

Also applies to: 80-80

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (2)

68-68: Use utf8mb4 instead of utf8.

MySQL’s utf8 is 3-byte; utf8mb4 is the correct full Unicode.

-        hc.addDataSourceProperty(PropertyKey.characterEncoding.getKeyName(), "utf8");
+        hc.addDataSourceProperty(PropertyKey.characterEncoding.getKeyName(), "utf8mb4");

98-101: Property naming nit: “executiveLogging” likely meant “executeLogging”.

Consider renaming the SteveProperties flag to executeLogging (and keeping a legacy binder alias for backward compatibility).

Please confirm the intended application.yml key (e.g., steve.jooq.execute-logging). If you want, I can sketch a migration with @ConstructorBinding + @DeprecatedProperty and relaxed binding aliases.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)

60-66: Reuse the cached client instead of creating a new proxy.

You already set client in beforeRepeat; use it here.

-                var boot = getForOcpp16(soapPath)
+                var boot = client.get()
                         .bootNotification(
                                 new BootNotificationRequest()
                                         .withChargePointVendor(getRandomString())
                                         .withChargePointModel(getRandomString()),
                                 chargeBoxId.get());
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)

101-108: Suppress unchecked cast or use typed factory to avoid warnings.

Cast from f.create() to T is unchecked. Add @SuppressWarnings("unchecked") or use f.create(serviceClass).

-    private static <T> T createBean(URI endpointAddress, Class<T> serviceClass) {
+    @SuppressWarnings("unchecked")
+    private static <T> T createBean(URI endpointAddress, Class<T> serviceClass) {
         var f = new JaxWsProxyFactoryBean();
         ...
         return (T) f.create();
     }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)

65-74: Reuse client to reduce overhead (optional).

getForOcpp16(soapPath) is called multiple times; create once and reuse in this method to cut proxy creation cost.

Also applies to: 89-92

steve/src/test/resources/application-test.yml (1)

1-6: Avoid hard-coded DB creds in committed test config.

Prefer env overrides (SPRING_DATASOURCE_URL/USERNAME/PASSWORD) or Testcontainers-based config to reduce local coupling and secret sprawl.

Do tests rely on a locally running MySQL? If not, consider switching to Testcontainers.

steve-api/pom.xml (1)

23-25: Mark jakarta.servlet-api as provided.

For WAR/container deployments, servlet API should not be packaged into runtime artifacts.

     <dependency>
       <groupId>jakarta.servlet</groupId>
       <artifactId>jakarta.servlet-api</artifactId>
+      <scope>provided</scope>
     </dependency>
steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

42-56: Prefer externalized config over System.setProperty.

Set springdoc.* in application.yml/application-test.yml instead of static block to avoid global side effects in tests.

steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

65-76: Avoid global system property; use SpringApplication profiles

Use application-scoped profile to prevent cross-test side effects.

-        System.setProperty("spring.profiles.active", "test");
+        var spring = new SpringApplication(SteveApplication.class);
+        spring.setAdditionalProfiles("test");
...
-            app = SpringApplication.run(SteveApplication.class);
+            app = spring.run();
steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (1)

41-49: Inject UnidentifiedIncomingObjectService instead of new

Prefer DI for configurability and testability.

-    private final UnidentifiedIncomingObjectService unknownChargePointService =
-            new UnidentifiedIncomingObjectService(100);
+    private final UnidentifiedIncomingObjectService unknownChargePointService;
...
-    public ChargePointRegistrationService(
-            ChargePointRepository chargePointRepository, SteveProperties steveProperties) {
+    public ChargePointRegistrationService(
+            ChargePointRepository chargePointRepository,
+            SteveProperties steveProperties,
+            UnidentifiedIncomingObjectService unknownChargePointService) {
         this.chargePointRepository = chargePointRepository;
         this.autoRegisterUnknownStations = steveProperties.getOcpp().isAutoRegisterUnknownStations();
+        this.unknownChargePointService = unknownChargePointService;
     }
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)

74-76: Avoid wildcard CORS for WebSocket

Externalize allowed origins to properties or restrict to known origins.

-        registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
-                .setHandshakeHandler(handshakeHandler)
-                .setAllowedOrigins("*");
+        registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
+                .setHandshakeHandler(handshakeHandler)
+                // TODO: replace with property-driven allowed origins
+                .setAllowedOriginPatterns("https://your.domain.example");
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)

60-68: Return Validator instead of ValidatorFactory for clarity

Avoids bean type ambiguity and matches getBean(Validator.class).

     @Configuration
     static class ValidationConfig {
         @Bean
-        public ValidatorFactory validator(ApplicationContext applicationContext) {
-            var factory = new LocalValidatorFactoryBean();
-            factory.setApplicationContext(applicationContext);
-            return factory;
+        public Validator validator(ApplicationContext applicationContext) {
+            var factory = new LocalValidatorFactoryBean();
+            factory.setApplicationContext(applicationContext);
+            return factory;
         }
     }
steve/src/main/resources/application.yml (2)

41-46: Avoid committing default credentials

Shipping admin/1234 in the default profile is risky; prefer empty values with clear docs or use a separate sample config.


14-18: Non-standard logging property

logging.appender is not a Spring Boot standard key; manage appenders in logback-spring.xml or use logging.file.name/path.

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

55-58: Satisfy NullAway: mark optional fields nullable and set sane defaults

Annotate optional fields and default wsSessionSelectStrategy to avoid initialization warnings.

-    private String steveVersion;
-    private String profile;
+    private @Nullable String steveVersion;
+    private @Nullable String profile;
@@
     public static class Jooq {
         private boolean executiveLogging;
-        private String schemaSource;
+        private @Nullable String schemaSource;
         private @Nullable String schema;
     }
@@
     public static class Ocpp {
         private boolean autoRegisterUnknownStations;
         private @Nullable String chargeBoxIdValidationRegex;
-        private String wsSessionSelectStrategy;
+        private String wsSessionSelectStrategy = "ALWAYS_LAST";
     }

Also applies to: 63-68, 82-87

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

615-621: Unused helper; consider removal.

nowWithoutMillis() is unused in this class.

-    /**
-     * https://github.com/steve-community/steve/issues/1371
-     */
-    private static OffsetDateTime nowWithoutMillis() {
-        return OffsetDateTime.now().with(ChronoField.MILLI_OF_SECOND, 0);
-    }
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (1)

123-139: Tighten exception assertion to only the call that should throw.

Wrapping both BootNotification (which should not throw) and Authorize in one lambda hides intent and leaves unreachable assertions.

-        assertThatExceptionOfType(WebServiceException.class).isThrownBy(() -> {
-            var boot = client.bootNotification(
-                    new ocpp.cs._2015._10.BootNotificationRequest()
-                            .withChargePointVendor(getRandomString())
-                            .withChargePointModel(getRandomString()),
-                    getRandomString());
-            assertThat(boot).isNotNull();
-            assertThat(boot.getStatus()).isEqualTo(ocpp.cs._2015._10.RegistrationStatus.REJECTED);
-
-            var auth = client.authorize(
-                    new ocpp.cs._2015._10.AuthorizeRequest().withIdTag(getRandomString()), getRandomString());
-            assertThat(auth).isNotNull();
-            assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2015._10.AuthorizationStatus.INVALID);
-        });
+        var boot = client.bootNotification(
+                new ocpp.cs._2015._10.BootNotificationRequest()
+                        .withChargePointVendor(getRandomString())
+                        .withChargePointModel(getRandomString()),
+                getRandomString());
+        assertThat(boot).isNotNull();
+        assertThat(boot.getStatus()).isEqualTo(ocpp.cs._2015._10.RegistrationStatus.REJECTED);
+
+        assertThatExceptionOfType(WebServiceException.class).isThrownBy(() ->
+                client.authorize(
+                        new ocpp.cs._2015._10.AuthorizeRequest().withIdTag(getRandomString()),
+                        getRandomString())
+        );
steve-jooq/pom.xml (1)

26-33: Reduce duplicate/managed deps; rely on Boot’s BOM where possible.

When using spring-boot-starter-jooq, the following are usually redundant or BOM-managed: org.jooq:jooq, HikariCP, spring-context, slf4j-api, jackson-databind.

Consider removing these explicit deps unless this module intentionally shades/isolates versions. Ensure compilation still passes after removal and that jOOQ runtime/codegen versions stay aligned with Boot’s managed versions.

Also applies to: 47-53, 55-65

steve/pom.xml (3)

70-72: Clarify deployment model (embedded Jetty vs external container) and align scopes

  • Executable WAR with embedded Jetty: keep spring-boot-starter-jetty (compile) but don’t add duplicate jetty-server dependency.
  • Deploying to external servlet container: mark spring-boot-starter-jetty as provided to avoid conflicts.

Two options (pick one):

# Option A: Executable WAR (embedded Jetty) — remove duplicate jetty-server
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jetty</artifactId>
     </dependency>
@@
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-    </dependency>
# Option B: WAR for external container — set provided scope
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jetty</artifactId>
+      <scope>provided</scope>
     </dependency>

Also applies to: 99-107


100-103: Drop duplicate Jetty dependency

spring-boot-starter-jetty already pulls jetty-server; keeping both invites version skew.

Apply:

-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-    </dependency>

124-129: Reduce testclasspath bloat; rely on spring-boot-starter-test

starter-test already brings JUnit Jupiter, AssertJ, JSON Path, Mockito (with Byte Buddy). Remove redundant explicit deps unless directly needed.

Apply:

     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-engine</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-params</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>net.bytebuddy</groupId>
-      <artifactId>byte-buddy</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.jayway.jsonpath</groupId>
-      <artifactId>json-path</artifactId>
-      <scope>test</scope>
-    </dependency>

If you directly use Byte Buddy APIs or custom JUnit engines, keep only those specific ones.

Also applies to: 146-174

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f29fed and bad9eaa.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (65)
  • .github/workflows/main.yml (1 hunks)
  • AGENTS.md (2 hunks)
  • Dockerfile (1 hunks)
  • pom.xml (5 hunks)
  • steve-api/pom.xml (1 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (2 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (6 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application-dev.yml (1 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/logback.xml (0 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (9 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (6 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
  • steve/src/test/resources/application-test.yml (1 hunks)
💤 Files with no reviewable changes (12)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve/src/main/resources/config/logback.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
  • steve/src/main/resources/config/main.properties
  • steve-ocpp/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve-ocpp-transport-websocket/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
🚧 Files skipped from review as they are similar to previous changes (16)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • .github/workflows/main.yml
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • AGENTS.md
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve-core/pom.xml
  • steve/src/main/resources/application-dev.yml
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • Dockerfile
  • steve-ocpp-transport-soap/pom.xml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve/src/test/resources/application-test.yml
  • steve/src/main/resources/logback-spring.xml
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • pom.xml
  • steve/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/main/resources/application.yml
  • steve/README.md
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • steve/src/test/resources/application-test.yml
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • pom.xml
  • steve/pom.xml
  • steve-jooq/pom.xml
  • steve/README.md
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
📚 Learning: 2025-09-15T15:31:15.967Z
Learnt from: juherr
PR: juherr/evolve#91
File: steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java:125-129
Timestamp: 2025-09-15T15:31:15.967Z
Learning: In the steve-core OCPP project, JAXB bindings are customized in ocpp_binding.xml to map xs:dateTime XML types directly to java.time.OffsetDateTime using the JavaDateTimeConverter adapter, eliminating the need for XMLGregorianCalendar conversions that would normally be required in standard JAXB bindings.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • steve-jooq/pom.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
🧬 Code graph analysis (4)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (2)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (61-238)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java (1)
  • RequiredArgsConstructor (56-213)
steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1)
  • RequiredArgsConstructor (313-317)
🪛 GitHub Actions: build and run tests
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java

[warning] 76-76: NullAway: initializer method does not guarantee @nonnull field chargePointsService (line 70) is initialized along all control-flow paths

steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java

[warning] 30-30: NullAway: initializer method does not guarantee @nonnull fields steveVersion (line 56), profile (line 57) are initialized along all control-flow paths.


[warning] 63-63: NullAway: initializer method does not guarantee @nonnull field schemaSource (line 66) is initialized along all control-flow paths.


[warning] 82-82: NullAway: initializer method does not guarantee @nonnull field wsSessionSelectStrategy (line 86) is initialized along all control-flow paths.

🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

🔇 Additional comments (41)
steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)

31-31: Explicitly targeting the String constructor looks good.

The (String) null cast neatly disambiguates the new overloads so the test still exercises the default-regex path. No further changes needed here.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (3)

49-52: LGTM: exposing MessageIdInterceptor as a bean

Good extraction; clear reuse across endpoints.


117-128: Confirm directionality of interceptors

Only inbound interceptors are attached. If MessageHeaderInterceptor or MessageIdInterceptor must also affect outbound messages, add them to getOutInterceptors() as well.


58-59: Ensure bean availability for MessageHeaderInterceptor

These bean method parameters require a MessageHeaderInterceptor bean elsewhere. Verify it is defined and in the same ApplicationContext.

Also applies to: 68-69, 78-79

steve/README.md (1)

76-80: Config move to application.yml: LGTM

The docs now point to application.yml and list key knobs. Looks consistent with the PR’s property-driven bootstrapping.

steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1)

32-96: Tests correctly adapted to new getLastBitFromUrl signature.

Coverage looks good across edge cases (empty, null, percent, dash, spaces, extra slash).

steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1)

41-64: Spring Boot entrypoint: LGTM

Clean bootstrapping, excludes JooqAutoConfiguration, sets CXF/validator logging and UTC default TZ, and logs at startup.

steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (1)

48-50: Main entry change: LGTM

Switch to no-arg construction and attack() works with the new path resolution approach.

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

46-52: DataSource bean wiring: LGTM

Delegates to factory with Boot DataSourceProperties and SteveProperties TZ.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)

44-45: Main entry change: LGTM

No-arg constructor aligns with new config approach.

steve/src/main/resources/logback-spring.xml (1)

50-51: Remove global TRACE/DEBUG for Spring Security/Web; scope to dev/test.

These global loggers override the dev/test INFO settings and will apply in prod, risking noise and sensitive leakage.

-    <logger name="org.springframework.security" level="TRACE"/>
-    <logger name="org.springframework.web" level="DEBUG"/>
+    <!-- Limit higher verbosity to dev/test -->
+    <springProfile name="dev,test">
+        <logger name="org.springframework.security" level="TRACE"/>
+        <logger name="org.springframework.web" level="DEBUG"/>
+    </springProfile>

Alternatively, adjust the existing dev,test block and delete these globals:

@@
-        <logger name="org.springframework.security" level="INFO"/>
-        <logger name="org.springframework.web.servlet" level="INFO"/>
+        <logger name="org.springframework.security" level="TRACE"/>
+        <logger name="org.springframework.web" level="DEBUG"/>
steve-ocpp-1-x/pom.xml (1)

24-59: Confirm CXF and JAXB-plugins version properties are defined upstream.
Using ${cxf.version} and ${jaxb-plugins.version} aligns with central management; ensure those properties are declared in your parent POM or imported BOM (e.g. cxf-bom), and add any missing definitions or BOM imports.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)

49-51: Confirm soapPath availability/type.

attack() now relies on a field soapPath. Ensure StressTest defines soapPath as a URI and initializes it before use.

pom.xml (4)

178-208: Annotation processor path uses undefined ${lombok.version}.

Even with the property added, ensure the version matches the dependency and your IDE APT setup to avoid mismatches.


98-101: CXF BOM import looks good.

Centralizes CXF versions; 4.1.3 aligns with Jakarta EE 10/JDK17+.

Double-check transitive CXF artifacts used across modules are covered by the BOM.


220-236: Inline license header OK.

No functional impact; Spotless should keep formatting consistent.


5-10: Spring Boot 3.5.6 is released and aligns with Spring Framework 6.2.x. Compatibility verified.

steve-api/pom.xml (1)

33-35: Consolidation to spring-boot-starter-test looks good.

Brings JUnit 5, Mockito, AssertJ under one starter.

steve-ui-jsp/pom.xml (2)

96-106: Resource relocation LGTM.

Copying from src/main/webapp with explicit includes is correct for JSP packaging.


129-132: Updated JSPC paths LGTM.

Matches the new webapp layout.

steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

58-77: Switching to SteveProperties for version is good.

Avoids tight coupling to legacy configuration.

steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)

70-87: Mirror the hardening for WS URI builder (NPEs/IPv6).

Same nullability and IPv6 pitfalls as SOAP builder. Use ws/wss scheme per SSL and guard nulls.

-    public static URI getWsPath(ServerProperties serverProperties, SteveProperties steveProperties)
-            throws URISyntaxException {
-        var scheme = "ws";
-        if (serverProperties.getSsl().isEnabled()) {
-            scheme = "wss";
-        }
-
-        return new URI(
-                scheme,
-                null,
-                serverProperties.getAddress().getHostName(),
-                serverProperties.getPort(),
-                serverProperties.getServlet().getContextPath()
-                        + steveProperties.getPaths().getWebsocketMapping()
-                        + steveProperties.getPaths().getRouterEndpointPath() + "/",
-                null,
-                null);
-    }
+    public static URI getWsPath(ServerProperties serverProperties, SteveProperties steveProperties)
+            throws URISyntaxException {
+        var ssl = serverProperties.getSsl();
+        var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "wss" : "ws";
+
+        var addr = serverProperties.getAddress();
+        var host = (addr != null) ? addr.getHostAddress() : "localhost";
+        if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
+            host = "[" + host + "]";
+        }
+
+        Integer port = serverProperties.getPort();
+        int p = (port != null) ? port : 8080;
+
+        var servlet = serverProperties.getServlet();
+        var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
+
+        String path = ctx
+                + steveProperties.getPaths().getWebsocketMapping()
+                + steveProperties.getPaths().getRouterEndpointPath()
+                + "/";
+
+        return new URI(scheme, null, host, p, path, null, null);
+    }
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (1)

21-21: LGTM: Migrates to SteveProperties cleanly

Constructor migration and path usage look correct.

Also applies to: 42-53

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

64-75: LGTM: pathInfix derived from properties and passed through

Good move away from hard-coded PATH_INFIX; handler wiring is consistent.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (1)

21-24: LGTM: constructor injection and null/empty short-circuit

Cleaner injection and early return; implementation is fine.

Also applies to: 34-38, 46-55

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

61-64: LGTM: config-driven session select strategy

Using SteveProperties for strategy selection aligns with the PR direction.

steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

89-97: Close order: clean DB before closing Spring context

Avoid “pool is closed” by cleaning before app.close().

-        } finally {
-            try {
-                if (app != null) {
-                    app.close();
-                }
-            } finally {
-                if (databasePreparer != null) {
-                    databasePreparer.cleanUp();
-                }
-            }
-        }
+        } finally {
+            try {
+                if (databasePreparer != null) {
+                    databasePreparer.cleanUp();
+                }
+            } finally {
+                if (app != null) {
+                    app.close();
+                }
+            }
+        }
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (2)

47-51: Good: constructor now null-safe for ServerProperties.getSsl()

Passing nullable Ssl to create(...) prevents NPE when SSL is unset.


83-90: Null-unsafe keystore and missing trust store support

Calling keyStoreFromFile with nulls will fail when only trust store is configured; add null checks and honor optional trust store.

-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
-                .usingTLS()
-                .usingDefaultAlgorithm()
-                .usingKeyManagerPasswordFromKeyStore()
-                .buildMergedWithSystem()
-                .getSocketFactory();
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder.keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword());
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        var socketFactory = builder
+                .usingTLS()
+                .usingDefaultAlgorithm()
+                .usingKeyManagerPasswordFromKeyStore()
+                .buildMergedWithSystem()
+                .getSocketFactory();
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (2)

83-88: LGTM: jsonPath derived from properties fixes previous null endpoint

Building the WS URI from ServerProperties/SteveProperties in setUp() is correct and keeps tests environment-driven.


151-176: LGTM: Versioned JSON tests cover 1.2/1.5/1.6 happy paths

Solid assertions on registration and authorization responses.

steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (2)

63-160: LGTM: Property-driven paths and CSRF exemptions are correctly wired

Manager/API mappings, resource permits, and login/logout flows look consistent with SteveProperties.


162-173: LGTM: Separate API key chain with stateless session

SecurityMatcher on API path and BasicAuthenticationFilter setup are appropriate.

steve/src/main/resources/application.yml (1)

7-9: Quote Maven @...@ tokens to make YAML valid

Unquoted scalars starting with @ break YAML parsing; quote them so IDEs/tests can load unfiltered YAML.

-    url: @db.jdbc.url@
-    username: @db.user@
-    password: @db.password@
+    url: "@db.jdbc.url@"
+    username: "@db.user@"
+    password: "@db.password@"
@@
-  version: @project.version@
+  version: "@project.version@"

Also applies to: 38-38

steve-jooq/pom.xml (1)

111-119: Verify that ${flyway.version}, ${jooq.version}, ${mysql.version} are defined.

These properties must be set in a parent or here; otherwise Maven will fail.

Run to confirm and locate definitions:

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

echo "Searching for property definitions..."
rg -nP '<properties>.*?(</properties>)' -g 'pom.xml' -C2 || true
rg -nP '<(mysql\.version|jooq\.version|flyway\.version)>\s*[^<]+' -S || true

echo
echo "References in POMs:"
rg -nP '\$\{(mysql\.version|jooq\.version|flyway\.version)\}' -S -C2

Also applies to: 183-196, 231-234

steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)

64-75: LGTM: Instance-based preparer with injected DSLContext.

The move away from static/global state improves test isolation and clarity.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

299-299: Fix: LinkedHashMap has no lastEntry(); compute the last timestamp directly.

Replace the invalid call with a computed value.

-        var faultedTime = timeStatusMap.lastEntry().getKey().plusMinutes(1);
+        var faultedTime = startingTime.plusMinutes(ChargePointStatus.values().length);
steve/pom.xml (4)

203-226: Use WAR overlays for steve-ui-jsp instead of unpacking into build dir

Unpacking into ${project.build.directory}/${project.build.finalName} is fragile; the war plugin may overwrite it.

Apply:

-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-steve-ui-jsp-webapp</id>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <phase>process-resources</phase>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>de.rwth.idsg</groupId>
-                  <artifactId>steve-ui-jsp</artifactId>
-                  <version>${project.version}</version>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-                  <includes>WEB-INF/**,static/**</includes>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <overlays>
+            <overlay>
+              <groupId>de.rwth.idsg</groupId>
+              <artifactId>steve-ui-jsp</artifactId>
+              <type>war</type>
+            </overlay>
+          </overlays>
+        </configuration>
+      </plugin>

186-194: Disable Maven filtering for application.yml to avoid placeholder/secret injection

application.yml should be resolved by Spring at runtime, not Maven at build time. Split resources so only logback-spring.xml (if required) is filtered.

Apply this diff:

   <resources>
-    <resource>
-      <filtering>true</filtering>
-      <directory>src/main/resources</directory>
-      <includes>
-        <include>application.yml</include>
-        <include>logback-spring.xml</include>
-      </includes>
-    </resource>
+    <resource>
+      <directory>src/main/resources</directory>
+      <filtering>false</filtering>
+      <includes>
+        <include>application.yml</include>
+      </includes>
+    </resource>
+    <resource>
+      <directory>src/main/resources</directory>
+      <filtering>true</filtering>
+      <includes>
+        <include>logback-spring.xml</include>
+      </includes>
+    </resource>
   </resources>

197-201: Ensure spring-boot-maven-plugin version is managed
No version is declared in steve/pom.xml (lines 197–201); verify that your parent POM’s pins spring-boot-maven-plugin. If not, add an explicit version.


86-88: CXF BOM already manages this dependency
The root pom.xml imports org.apache.cxf:cxf-bom (lines 96–102), so cxf-spring-boot-starter-jaxws inherits its version. To update, bump the cxf.version property in the parent POM.

Comment on lines +148 to 153
var endpointDeque = lookupTable.get(chargeBoxId);
if (endpointDeque == null) {
return 0;
} else {
return endpointDeque.size();
}
return endpointDeque.size();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Synchronize getSize() with per-id lock

Read of non-thread-safe Deque must be guarded by the same lock.

-        var endpointDeque = lookupTable.get(chargeBoxId);
-        if (endpointDeque == null) {
-            return 0;
-        }
-        return endpointDeque.size();
+        var l = locks.get(chargeBoxId);
+        l.lock();
+        try {
+            var endpointDeque = lookupTable.get(chargeBoxId);
+            if (endpointDeque == null) {
+                return 0;
+            }
+            return endpointDeque.size();
+        } finally {
+            l.unlock();
+        }
🤖 Prompt for AI Agents
In
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
around lines 148 to 153, the method reads a non-thread-safe Deque without
holding the per-id lock; wrap the lookupTable.get(chargeBoxId) and the
subsequent size() call inside the same per-id lock used elsewhere (e.g., obtain
the lock object for chargeBoxId then synchronize on it or acquire its
ReentrantLock) so that the Deque access is guarded; if the lock is null or not
found, preserve the existing null/zero semantics, and ensure you release the
lock properly (or use a try/finally when locking).

Comment on lines 97 to 100
var req = RequestBuilder.post(soapPath)
.addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
.setEntity(new StringEntity(body, contentType))
.build();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Switch RequestBuilder.post back to a string URI.

RequestBuilder.post(...) in Apache HttpClient 4.5.x only accepts a String URI. With soapPath now being a URI, this call no longer compiles. Convert the URI to a string before passing it to RequestBuilder.post (or call post() and then .setUri(soapPath)).

Apply this fix:

-        var req = RequestBuilder.post(soapPath)
+        var req = RequestBuilder.post(soapPath.toString())
                 .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
                 .setEntity(new StringEntity(body, contentType))
                 .build();
📝 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
var req = RequestBuilder.post(soapPath)
.addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
.setEntity(new StringEntity(body, contentType))
.build();
var req = RequestBuilder.post(soapPath.toString())
.addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
.setEntity(new StringEntity(body, contentType))
.build();
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java around
lines 97 to 100, the call RequestBuilder.post(soapPath) fails because
RequestBuilder.post in HttpClient 4.5.x expects a String URI, while soapPath is
a URI; convert soapPath to a string (e.g. soapPath.toString()) before passing it
to RequestBuilder.post, or alternatively call RequestBuilder.post() with no args
and then call .setUri(soapPath) on the builder to set the URI.

Comment on lines +75 to +76
assertThat(environment.getProperty("steve.ocpp.auto-register-unknown-stations"))
.isEqualTo("true");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify test property override for auto-register-unknown-stations

Assertion expects true; ensure application-test.yml (or @TestPropertySource) sets it to true.


🏁 Script executed:

#!/bin/bash
# Look for test overrides setting the property to true
rg -n -C2 -g '!**/target/**' -P --type=yaml 'auto-register-unknown-stations\s*:\s*true' || true
rg -n -C2 -g '!**/target/**' -P --type=properties 'steve\.ocpp\.auto-register-unknown-stations\s*=\s*true' || true
fd -HI 'application-test.*\.(ya?ml|properties)' || true

Length of output: 229


🏁 Script executed:

#!/bin/bash
rg -nC2 'auto-register-unknown-stations' steve/src/test/resources/application-test.yml || true

Length of output: 0


Verify override of steve.ocpp.auto-register-unknown-stations in tests
src/test/resources/application-test.yml doesn’t set steve.ocpp.auto-register-unknown-stations, so the assertion may fail; add

steve:
  ocpp:
    auto-register-unknown-stations: true
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/StressTest.java around lines 75-76 the
test asserts that
environment.getProperty("steve.ocpp.auto-register-unknown-stations") is "true"
but src/test/resources/application-test.yml does not define that property; add
the property under the test YAML by adding
steve.ocpp.auto-register-unknown-stations: true (nesting under steve -> ocpp) so
the environment returns "true" and the assertion reliably passes.

@juherr juherr force-pushed the backport/steve-1822 branch from bad9eaa to 80efa0c Compare September 25, 2025 17:15
Copy link

@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 (1)
steve-jooq/pom.xml (1)

176-194: Declare or inherit jOOQ version property
jooq.version isn’t declared in any POM here; add it under <properties> in steve-parent or ensure it’s provided by your parent POM (e.g. Spring Boot parent) so builds don’t fail.

🧹 Nitpick comments (7)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)

65-92: Consider reusing the SOAP client within attackInternal.

You’re recreating the CXF client three times before the stress loop starts. Grabbing it once keeps the intent clear and avoids redundant stub creation.

Apply this diff if you agree:

-        var boot = getForOcpp16(soapPath)
-                .bootNotification(
+        var client = getForOcpp16(soapPath);
+        var boot = client.bootNotification(
                 new BootNotificationRequest()
                         .withChargePointVendor(getRandomString())
                         .withChargePointModel(getRandomString()),
                 chargeBoxId);
@@
-        var start = getForOcpp16(soapPath)
-                .startTransaction(
+        var start = client.startTransaction(
                 new StartTransactionRequest()
                         .withConnectorId(connectorId)
                         .withIdTag(idTag)
                         .withTimestamp(startDateTime)
                         .withMeterStart(meterStart),
                 chargeBoxId);
steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1)

93-97: Use URI.resolve for reliable connectionPath construction
Replace the implicit concatenation with a URI resolve to avoid missing slashes:

-        this.connectionPath = pathPrefix + chargeBoxId;
+        this.connectionPath = pathPrefix.resolve(chargeBoxId).toString();
steve-core/pom.xml (1)

45-48: Remove redundant jakarta.mail-api dependency
spring-boot-starter-mail already brings in the Jakarta Mail API and its implementation transitively; drop the explicit <dependency> on jakarta.mail:jakarta.mail-api.

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

166-168: getACopy() is a shallow copy; callers can observe concurrent mutation.

Return a snapshot (deep copy of deques) to avoid iteration issues and surprising visibility.

Apply:

-    public Map<String, Deque<SessionContext>> getACopy() {
-        return ImmutableMap.copyOf(lookupTable);
-    }
+    public Map<String, Deque<SessionContext>> getACopy() {
+        var copy = new ConcurrentHashMap<String, Deque<SessionContext>>();
+        lookupTable.forEach((id, dq) -> {
+            var l = locks.get(id);
+            l.lock();
+            try {
+                copy.put(id, new ArrayDeque<>(dq));
+            } finally {
+                l.unlock();
+            }
+        });
+        return ImmutableMap.copyOf(copy);
+    }
steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (2)

70-75: Log actionable context when registration_status mapping fails

Include the raw DB value and chargeBoxId to aid diagnosis.

-            } catch (Exception e) {
-                // in cases where the database entry (string) is altered, and therefore cannot be converted to enum
-                log.error("Exception happened", e);
-                return Optional.empty();
-            }
+            } catch (Exception e) {
+                // in cases where the database entry (string) is altered, and therefore cannot be converted to enum
+                log.error("Invalid registration_status '{}' for chargeBoxId '{}'", status.get(), chargeBoxId, e);
+                return Optional.empty();
+            }

83-91: Minor: prefer List.of over Collections.singletonList

More idiomatic on Java 17.

-            chargePointRepository.addChargePointList(Collections.singletonList(chargeBoxId));
+            chargePointRepository.addChargePointList(List.of(chargeBoxId));
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)

135-149: Manual record population may be error-prone.

The manual field-by-field population of OcppTagActivityRecord could become inconsistent if the DTO structure changes. Consider using a mapping library or builder pattern.

 public OcppTagActivityRecord getOcppTagRecord(String idTag) {
     var impl = new OcppTagRepositoryImpl(dslContext);
     var dto = impl.getRecord(idTag).orElseThrow();
-    var activity = new OcppTagActivityRecord();
-    activity.setOcppTagPk(dto.getOcppTagPk());
-    activity.setIdTag(dto.getIdTag());
-    activity.setParentIdTag(dto.getParentIdTag());
-    activity.setExpiryDate(toLocalDateTime(dto.getExpiryDate()));
-    activity.setInTransaction(dto.isInTransaction());
-    activity.setBlocked(dto.isBlocked());
-    activity.setMaxActiveTransactionCount(dto.getMaxActiveTransactionCount());
-    activity.setActiveTransactionCount(dto.getActiveTransactionCount());
-    activity.setNote(dto.getNote());
-    return activity;
+    return new OcppTagActivityRecord()
+            .setOcppTagPk(dto.getOcppTagPk())
+            .setIdTag(dto.getIdTag())
+            .setParentIdTag(dto.getParentIdTag())
+            .setExpiryDate(toLocalDateTime(dto.getExpiryDate()))
+            .setInTransaction(dto.isInTransaction())
+            .setBlocked(dto.isBlocked())
+            .setMaxActiveTransactionCount(dto.getMaxActiveTransactionCount())
+            .setActiveTransactionCount(dto.getActiveTransactionCount())
+            .setNote(dto.getNote());
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bad9eaa and 9f00bfd.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (67)
  • .github/workflows/main.yml (1 hunks)
  • AGENTS.md (2 hunks)
  • Dockerfile (1 hunks)
  • pom.xml (5 hunks)
  • steve-api/pom.xml (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (6 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (7 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application-dev.yml (1 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/logback.xml (0 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (8 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (6 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
  • steve/src/test/resources/application-test.yml (1 hunks)
💤 Files with no reviewable changes (13)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/resources/config/logback.xml
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve/src/main/resources/config/main.properties
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve-ocpp-transport-websocket/pom.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
  • steve-ocpp/pom.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
🚧 Files skipped from review as they are similar to previous changes (12)
  • steve-api/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve/src/test/resources/application-test.yml
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve-ocpp-1-x/pom.xml
  • steve-ui-jsp/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/README.md
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve-ocpp-transport-soap/pom.xml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve/src/main/resources/application-dev.yml
  • Dockerfile
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • AGENTS.md
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • pom.xml
  • steve/src/main/resources/application.yml
  • steve/src/main/resources/logback-spring.xml
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/pom.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • .github/workflows/main.yml
  • steve-jooq/pom.xml
  • AGENTS.md
  • pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/pom.xml
📚 Learning: 2025-09-15T15:31:15.967Z
Learnt from: juherr
PR: juherr/evolve#91
File: steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java:125-129
Timestamp: 2025-09-15T15:31:15.967Z
Learning: In the steve-core OCPP project, JAXB bindings are customized in ocpp_binding.xml to map xs:dateTime XML types directly to java.time.OffsetDateTime using the JavaDateTimeConverter adapter, eliminating the need for XMLGregorianCalendar conversions that would normally be required in standard JAXB bindings.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • steve-jooq/pom.xml
  • AGENTS.md
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
🧬 Code graph analysis (6)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (4)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)
  • Slf4j (39-74)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (61-237)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (1)
  • ActiveProfiles (56-136)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java (1)
  • RequiredArgsConstructor (56-213)
steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1)
  • RequiredArgsConstructor (313-317)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)
  • Configuration (39-121)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (1)
  • ActiveProfiles (56-136)
🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

🪛 GitHub Actions: build and run tests
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java

[warning] 75-75: NullAway: initializer method does not guarantee @nonnull field chargePointsService is initialized along all control-flow paths

🔇 Additional comments (82)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (3)

72-88: Consistent soapPath usage looks correct.

Running all SOAP invocations through the shared soapPath aligns this test with the new configuration bootstrap, so this part looks good to me.


156-170: URI-aware SOAP envelope looks good.

Accepting a URI here and embedding it into the To header keeps the payload in sync with the new configuration approach. No issues spotted.


97-100: Compile error: RequestBuilder.post still needs a String.

RequestBuilder.post(URI) doesn’t exist in HttpClient 4.5.x, so this line won’t compile. Please convert the URI to a string (or set the URI on the builder afterwards) before building the request.

-        var req = RequestBuilder.post(soapPath)
+        var req = RequestBuilder.post(soapPath.toString())
                 .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
                 .setEntity(new StringEntity(body, contentType))
                 .build();
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (2)

41-47: Spring bean registration looks good

Registering LogFileRetriever as a component gives us clean DI without altering its construction semantics. No concerns here.


89-101: Appender iteration update looks correct

Switching to a while loop and collecting every FileAppender path handles multi-appender setups gracefully. 👍

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

69-84: Fix NullAway by assigning chargePointsService in setUp.

NullAway is still complaining (see pipeline warning) because the field can appear null when we build the controller. Inject the Mockito mock through the @BeforeEach method and assign it to the field before using it.

-    @Mock
-    private ChargePointsService chargePointsService;
+    private ChargePointsService chargePointsService;
 
     @BeforeEach
-    public void setUp() {
+    public void setUp(@Mock ChargePointsService chargePointsService) {
+        this.chargePointsService = chargePointsService;
         var ctx = new AnnotationConfigApplicationContext();
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)

44-44: soapPath is initialised in attack() before use; manual main() entry is safe.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)

49-51: No-arg StressTest wiring looks good.

Switching to the default constructor plays nicely with the new StressTest plumbing—no concerns here.

steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1)

89-91: LGTM! Clean API improvement for URI handling.

The constructor signature change from String pathPrefix to URI pathPrefix is appropriate for the Spring Boot migration and provides better type safety for URL handling.

steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2)

80-80: LGTM! Consistent with the OcppJsonChargePoint API change.

The update to pass jsonPath (presumably a URI) instead of path aligns with the constructor signature change in OcppJsonChargePoint.


64-65: jsonPath is initialized in parent StressTest (line 82)
No further action required.

steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (2)

62-62: LGTM! Consistent usage of soapPath for SOAP client configuration.

The changes correctly use soapPath instead of the previous path parameter, maintaining consistency throughout the test execution.

Also applies to: 80-80


49-50: soapPath initialization confirmed in StressTest.attack()
soapPath is set via getSoapPath(...) in the parent attack() method before attackInternal() runs. No changes required.

steve/src/main/resources/application-dev.yml (1)

1-9: LGTM! Development configuration is appropriate.

The development profile correctly disables compression for debugging and enables verbose jOOQ logging to help trace SQL execution during development.

Dockerfile (3)

81-82: LGTM! WAR packaging changes correctly implemented.

The switch from JAR to WAR packaging is properly reflected in both the COPY command and the subsequent CMD execution.


89-89: LGTM! Correct WAR execution command.

The CMD properly executes the Spring Boot WAR file with appropriate memory settings.


77-77: Spring Boot property naming fix needed.

The Java system properties for server configuration should use Spring Boot's standard property names. The current -Dserver.address and -Dserver.port are incorrect.

Apply this diff to fix the property names:

-ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
+ENV JAVA_TOOL_OPTIONS="-Dspring.application.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.compression.enabled=false"

Actually, for the server address binding, Spring Boot uses server.address (not spring.application.address). Let me correct that:

-ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
+ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.compression.enabled=false"

Note: Changed -Dserver.gzip.enabled=false to -Dserver.compression.enabled=false to match Spring Boot 3.x property naming.

Likely an incorrect or invalid review comment.

.github/workflows/main.yml (3)

50-50: Spring profiles configuration looks correct.

Using -Dspring.profiles.active=test,dev properly activates both test and dev Spring profiles during the build, which aligns with the new Spring Boot configuration approach.


60-60: WAR execution correctly updated.

The change from steve.jar to steve.war properly reflects the packaging migration to WAR format.


49-49: Adjust useRealDatabase profile scope

The useRealDatabase Maven profile is defined only in steve-jooq/pom.xml, so invoking mvn -PuseRealDatabase at the repo root won’t activate it.
Options:

  • Move the <profile> definition into the root pom.xml
  • Or run Maven in the steve-jooq module (e.g. set working-directory: steve-jooq in the workflow)

.github/workflows/main.yml:49

steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (4)

39-43: Clean migration to SteveProperties-based configuration.

The removal of static dependencies and adoption of constructor injection with SteveProperties aligns well with Spring Boot's configuration model and improves testability.


46-46: Proper removal of checked exception.

Removing throws ServletException is correct as the method no longer performs operations that could throw this checked exception.


49-52: Spring context initialization looks good.

The simplified context creation using component scanning is appropriate for Spring Boot applications and eliminates the previous complex initialization logic.


57-57: Properties-driven URL mappings properly implemented.

Good use of SteveProperties to centralize URL mapping configuration. This makes the application more configurable and maintainable.

Also applies to: 62-62, 72-72

steve-core/pom.xml (3)

32-35: LGTM! Spring Boot starter for validation properly added.

The addition of spring-boot-starter-validation brings in all necessary validation dependencies including Hibernate Validator.


36-39: LGTM! Spring Security starter properly integrated.

The spring-boot-starter-security dependency appropriately replaces the manual Spring Security configuration dependency.


40-43: LGTM! Mail starter simplifies email configuration.

Using spring-boot-starter-mail provides auto-configuration for mail services, simplifying the setup.

steve/src/main/resources/logback-spring.xml (2)

37-48: dev/test INFO overrides are neutralized by later global loggers.

Because the global TRACE/DEBUG (Lines 50–51) are active for all profiles, the INFO levels here won’t take effect in dev/test.

Use the patch in my previous comment (remove global TRACE/DEBUG, add them inside dev,test; add conservative INFO in prod).


50-51: Don’t enable TRACE/DEBUG globally; restrict to dev/test.

These two loggers apply in all profiles (including prod) and will flood logs and risk sensitive detail leakage.

Apply:

-    <logger name="org.springframework.security" level="TRACE"/>
-    <logger name="org.springframework.web" level="DEBUG"/>
+    <springProfile name="dev,test">
+        <logger name="org.springframework.security" level="TRACE"/>
+        <logger name="org.springframework.web" level="DEBUG"/>
+    </springProfile>
+
+    <springProfile name="prod">
+        <logger name="org.springframework.security" level="INFO"/>
+        <logger name="org.springframework.web" level="INFO"/>
+    </springProfile>
steve-jooq/pom.xml (1)

21-35: Good: adopt spring-boot-starter-jooq, defer flyway starter.

Using Boot’s jOOQ starter simplifies runtime wiring; keeping flyway-core (without the starter) is fine for Boot 3.x.

Please confirm Flyway runs at app startup as expected (Boot auto-config should pick up flyway-core on classpath).

steve/pom.xml (4)

186-201: Avoid filtering application.yml to prevent unintended placeholder substitution and secret baking.

Filtering may resolve Spring placeholders (${...}) at build time.

Apply:

-    <resources>
-      <resource>
-        <filtering>true</filtering>
-        <directory>src/main/resources</directory>
-        <includes>
-          <include>application.yml</include>
-        </includes>
-      </resource>
-      <resource>
-        <filtering>false</filtering>
-        <directory>src/main/resources</directory>
-        <excludes>
-          <exclude>application-*.yml</exclude>
-          <exclude>logback-spring.xml</exclude>
-        </excludes>
-      </resource>
-    </resources>
+    <resources>
+      <resource>
+        <filtering>false</filtering>
+        <directory>src/main/resources</directory>
+      </resource>
+    </resources>

210-234: Use WAR overlays instead of unpacking into target dir.

Unpacking into ${project.build.directory}/${project.build.finalName} is fragile; the WAR plugin may overwrite it.

Apply:

-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-steve-ui-jsp-webapp</id>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <phase>process-resources</phase>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>de.rwth.idsg</groupId>
-                  <artifactId>steve-ui-jsp</artifactId>
-                  <version>${project.version}</version>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-                  <includes>WEB-INF/**,static/**</includes>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <overlays>
+            <overlay>
+              <groupId>de.rwth.idsg</groupId>
+              <artifactId>steve-ui-jsp</artifactId>
+              <type>war</type>
+            </overlay>
+          </overlays>
+        </configuration>
+      </plugin>

86-88: cxf-spring-boot-starter-jaxws looks good.

Nice consolidation on the CXF Boot starter to simplify SOAP wiring.


29-34: Remove DB credentials from the POM.

Storing db.user/password/url in the POM risks secret leakage (effective POM, artifact filtering).

Apply:

-  <properties>
-    <db.user>steve</db.user>
-    <db.password>changeme</db.password>
-    <db.schema>stevedb</db.schema>
-    <db.jdbc.url>jdbc:mysql://localhost:3306/${db.schema}</db.jdbc.url>
-  </properties>
+  <!-- Database connection is configured via application.yml or environment variables. -->

And define them in application.yml or via env (SPRING_DATASOURCE_URL/USERNAME/PASSWORD).

steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)

31-31: Constructor change reflected correctly.

Passing (String) null aligns with the new constructor path; tests remain valid.

AGENTS.md (1)

71-72: Config location note is correct.

Good call-out that defaults moved to application.yml.

steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (2)

27-33: Test updates correctly reflect new API signature.

Using a constant PATH_INFIX keeps tests readable and aligned with production.


64-97: Good coverage of edge cases.

URLs with extra slashes, spaces, and percent signs are verified; behavior looks consistent.

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

147-153: Guard getSize() with the per-id lock.

Reading ArrayDeque.size() without locking races with writers; can yield inconsistent results.

Apply:

-    public int getSize(String chargeBoxId) {
-        var endpointDeque = lookupTable.get(chargeBoxId);
-        if (endpointDeque == null) {
-            return 0;
-        }
-        return endpointDeque.size();
-    }
+    public int getSize(String chargeBoxId) {
+        var l = locks.get(chargeBoxId);
+        l.lock();
+        try {
+            var endpointDeque = lookupTable.get(chargeBoxId);
+            return endpointDeque == null ? 0 : endpointDeque.size();
+        } finally {
+            l.unlock();
+        }
+    }
pom.xml (2)

178-208: Ensure lombok.version is defined in the parent POM
The Lombok annotation‐processor dependency uses ${lombok.version}; confirm your Spring Boot parent POM defines this property, or hardcode the version here.


5-10: Spring Boot parent 3.5.6 is valid and up-to-date; no changes required.

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

38-38: Improved per-element validation approach for list items.

The migration from @NotEmpty @ChargeBoxId private List<String> to @NotEmpty private List<@ChargeBoxId String> correctly leverages the TYPE_USE target added to the @ChargeBoxId annotation. This approach provides cleaner, more idiomatic validation of individual list elements, replacing the need for a separate list validator class.

steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2)

91-93: LGTM! Configuration migration to SteveProperties is consistent.

The refactoring from SteveConfiguration to SteveProperties aligns with the broader Spring Boot migration pattern throughout the codebase.


107-107: Good use of type inference.

Using var for the local objectMapper variable improves readability while maintaining type safety through the compiler's inference.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (2)

28-28: Correct addition of TYPE_USE target for per-element validation.

Adding TYPE_USE to the @Target annotation enables the @ChargeBoxId annotation to be applied to type uses, which is essential for validating individual elements within collections (e.g., List<@ChargeBoxId String>). This is a standard Jakarta Bean Validation pattern.

Also applies to: 35-35


37-37: Simplified validator configuration is appropriate.

Removing ChargeBoxIdListValidator from the validatedBy attribute and keeping only ChargeBoxIdValidator is correct since the TYPE_USE target now enables per-element validation directly, eliminating the need for a separate list validator.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (3)

41-48: Well-structured constructor injection for configuration.

The constructor-based dependency injection pattern using SteveProperties is cleaner and more testable than field injection with @Value. The delegation pattern with the overloaded constructor accepting the regex string directly is good for both Spring DI and unit testing scenarios.


56-61: Correct null handling in validation context.

The null check returning true is the standard Bean Validation pattern - null values should be validated by @NotNull constraints, not by custom validators. This separation of concerns is correct.


63-74: No action needed: project targets Java ≥ 17
The pom.xml sets <java.version>21</java.version> and the README requires JDK 17 or newer, so String.strip() is fully supported.

steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

59-59: Clean migration to SteveProperties.

The parameter change from SteveConfiguration to SteveProperties and the corresponding update to use steveProperties.getSteveVersion() are consistent with the broader configuration refactoring pattern across the codebase.

Also applies to: 76-76

steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (2)

45-49: Constructor migration to SteveProperties: LGTM

Clean switch from @value to typed properties; constructor wiring looks correct.


51-63: Proper lock release via try/finally: LGTM

Prevents leaked locks under exceptions.

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

49-54: Wiring pathInfix via constructor: LGTM

Keeps handler decoupled from static constants.


136-146: Harden getLastBitFromUrl: require segment match and URL-decode

Current indexOf may match partial infix (e.g., "ocpp" in "ocppx") and returns the entire tail; also misses URL-decoding.

-    public static String getLastBitFromUrl(String pathInfix, String input) {
-        if (Strings.isNullOrEmpty(input)) {
-            return "";
-        }
-
-        var index = input.indexOf(pathInfix);
-        if (index == -1) {
-            return "";
-        }
-        return input.substring(index + pathInfix.length());
-    }
+    public static String getLastBitFromUrl(String pathInfix, String input) {
+        if (Strings.isNullOrEmpty(pathInfix) || Strings.isNullOrEmpty(input)) {
+            return "";
+        }
+        var normInfix = "/" + pathInfix.replaceAll("^/+|/+$", "") + "/";
+        var index = input.indexOf(normInfix);
+        if (index < 0) {
+            return "";
+        }
+        var tail = input.substring(index + normInfix.length());
+        var slash = tail.indexOf('/');
+        var id = (slash >= 0) ? tail.substring(0, slash) : tail;
+        return java.net.URLDecoder.decode(id, java.nio.charset.StandardCharsets.UTF_8);
+    }
steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (1)

76-79: Switch to SteveProperties and PasswordEncoder: LGTM

Aligns with new config model and consistent encoder usage.

steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)

83-88: Compute jsonPath from properties: LGTM

Removes hard-coding and aligns with the new config.

Please confirm Helpers.getWsPath returns a trailing slash so OcppJsonChargePoint URL composition remains correct.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (1)

76-93: Null-safe SSL and optional trust store/keystore handling

Calling keyStoreFromFile unconditionally will NPE if keystore is unset; also omit trust store when configured.

-    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
-            throws GeneralSecurityException, IOException {
-        if (ssl == null || !ssl.isEnabled()) {
-            return null;
-        }
-        var tlsClientParams = new TLSClientParameters();
-
-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
-                .usingTLS()
-                .usingDefaultAlgorithm()
-                .usingKeyManagerPasswordFromKeyStore()
-                .buildMergedWithSystem()
-                .getSocketFactory();
-        tlsClientParams.setSSLSocketFactory(socketFactory);
-
-        return tlsClientParams;
-    }
+    private static @Nullable TLSClientParameters create(@Nullable Ssl ssl)
+            throws GeneralSecurityException, IOException {
+        if (ssl == null || !ssl.isEnabled()) {
+            return null;
+        }
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder
+                    .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
+                    .usingKeyManagerPasswordFromKeyStore();
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        var socketFactory = builder
+                .usingTLS()
+                .usingDefaultAlgorithm()
+                .buildMergedWithSystem()
+                .getSocketFactory();
+
+        var tlsClientParams = new TLSClientParameters();
+        tlsClientParams.setSSLSocketFactory(socketFactory);
+        return tlsClientParams;
+    }
steve/src/main/resources/application.yml (1)

7-9: Quote the @...@ tokens to keep the YAML valid.

Also applies to: 38-38

steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (3)

59-59: LGTM! Proper dependency injection.

The migration from static PATH_INFIX to SteveProperties-driven configuration is well-implemented.


64-66: Dynamic path construction looks correct.

The pathInfix computation properly combines websocket mapping with router endpoint path.


67-72: No changes needed: constructor signature already accepts pathInfix.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (2)

49-53: Validate constructor input early.


62-69: Fragile mark/reset: available() can be 0 → reset() may fail and drop the request.

steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (4)

51-68: Fix NPEs/IPv6 and wrong mapping in SOAP URI builder.


70-87: Same issues for WS URL (NPEs, IPv6, null host/port).


89-99: LGTM! Clean API refactor to use URI.

The migration from String-based paths to URI-based endpoints is a good improvement for type safety.


101-108: Good refactoring with generic type safety.

The createBean method properly uses generics and eliminates code duplication across OCPP versions.

steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (5)

59-61: Simplified password encoder configuration.

The direct BCryptPasswordEncoder instantiation removes external dependencies and is more straightforward.


64-66: Property-driven path configuration.

The migration to SteveProperties for path derivation provides better centralized configuration management.


73-82: Enhanced security with dispatcher type matching.

The addition of dispatcher type matchers (FORWARD, INCLUDE, ERROR) provides better security coverage for different request types.


147-148: Request cache disabled appropriately.

Disabling request cache makes sense for the stateless nature of SOAP/API endpoints.


165-167: Clean API filter chain configuration.

The property-driven API mapping configuration aligns well with the overall refactoring approach.

steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)

58-65: Proper Spring Boot test setup with DI.

The migration to @SpringBootTest with proper dependency injection is a good improvement over static initialization.


70-75: Per-test lifecycle ensures isolation.

The @BeforeEach/@AfterEach pattern with database preparation ensures proper test isolation.


84-84: URI-based client creation is type-safe.

The switch from String paths to URI improves type safety and integrates well with the Helpers refactor.

Also applies to: 102-102, 123-123

steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)

64-65: Good migration to instance-based design.

The switch from static to instance methods with proper dependency injection via @requiredargsconstructor improves testability and maintainability.

Also applies to: 72-72


99-109: Static accessors for test constants.

Good design choice to keep identifier accessors as static while migrating operations to instance methods.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (4)

75-77: Proper Spring Boot test setup.

The migration to @SpringBootTest with active profiles provides better integration testing capabilities.


84-94: Clean dependency injection setup.

The @Autowired fields provide proper Spring context integration for the test.


96-101: Proper test setup with per-test lifecycle.

The migration to per-test setup with proper database preparation ensures test isolation and matches the pattern used in other migrated tests.


259-264: **LinkedHashMap doesn't have lastEntry() method.**Based on the search results, LinkedHashMap gained lastEntry() and firstEntry() methods starting from Java 21. However, this codebase appears to be using an earlier Java version where these methods don't exist.

LinkedHashMap doesn't have lastEntry() method in pre-Java 21.

The code uses timeStatusMap.lastEntry().getKey() but LinkedHashMap doesn't provide any method to get first, last or any specific object in versions before Java 21. Use direct computation instead.

-        var faultedTime = timeStatusMap.lastEntry().getKey().plusMinutes(1);
+        var faultedTime = startingTime.plusMinutes(ChargePointStatus.values().length);
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (1)

85-86: Use the SOAP endpoint when creating the OCPP 1.6 client.

getForOcpp16 instantiates a SOAP (HTTP/S) JAX-WS client; giving it the WebSocket URI returned by getWsPath will fail at runtime. Please switch back to the SOAP path helper so the test hits the correct transport.

-import static de.rwth.idsg.steve.utils.Helpers.getWsPath;
+import static de.rwth.idsg.steve.utils.Helpers.getSoapPath;
...
-    public void test() throws URISyntaxException {
-        var client = getForOcpp16(getWsPath(serverProperties, steveProperties));
+    public void test() throws URISyntaxException {
+        var client = getForOcpp16(getSoapPath(serverProperties, steveProperties));

Comment on lines 227 to 233
public void updateApiPassword(WebUserForm form) {
String newPassword = null;
if (form.getApiPassword() != null && !form.getApiPassword().isEmpty()) {
newPassword = encoder.encode(form.getApiPassword());
newPassword = passwordEncoder.encode(form.getApiPassword());
}
webUserRepository.changeApiPassword(form.getWebUserPk(), newPassword);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Invalidate API user cache after apiPassword change

Otherwise loadUserByUsernameForApi can serve stale credentials for up to 10 minutes.

     public void updateApiPassword(WebUserForm form) {
         String newPassword = null;
         if (form.getApiPassword() != null && !form.getApiPassword().isEmpty()) {
             newPassword = passwordEncoder.encode(form.getApiPassword());
         }
         webUserRepository.changeApiPassword(form.getWebUserPk(), newPassword);
+        userCache.invalidate(form.getWebUsername());
     }
📝 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 void updateApiPassword(WebUserForm form) {
String newPassword = null;
if (form.getApiPassword() != null && !form.getApiPassword().isEmpty()) {
newPassword = encoder.encode(form.getApiPassword());
newPassword = passwordEncoder.encode(form.getApiPassword());
}
webUserRepository.changeApiPassword(form.getWebUserPk(), newPassword);
}
public void updateApiPassword(WebUserForm form) {
String newPassword = null;
if (form.getApiPassword() != null && !form.getApiPassword().isEmpty()) {
newPassword = passwordEncoder.encode(form.getApiPassword());
}
webUserRepository.changeApiPassword(form.getWebUserPk(), newPassword);
userCache.invalidate(form.getWebUsername());
}
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
around lines 227-233, after updating the API password you must evict the cached
API user so loadUserByUsernameForApi doesn't return stale credentials; add a
cache eviction call immediately after
webUserRepository.changeApiPassword(form.getWebUserPk(), newPassword) (e.g.
cacheManager.getCache("<api-user-cache-name>").evict(form.getUsername()) or
userCache.evict(form.getWebUserPk()/form.getUsername()) depending on how the
cache is keyed and named), ensuring you use the same cache name and key strategy
as loadUserByUsernameForApi and always evict even when newPassword is null.

Comment on lines +106 to +118
public Endpoint routerEndpoint(
SteveProperties steveProperties,
Bus bus,
// TODO find another dummy service to remove to the strong dependency to ocpp12
ocpp.cs._2010._08.CentralSystemService ocpp12Server,
List<EndpointImpl> ocppEndpoints) {
var mediator = new MediatorInInterceptor(ocppEndpoints);
return createEndpoint(
bus,
ocpp12Server,
steveProperties.getPaths().getRouterEndpointPath(),
List.of(mediator),
Collections.emptyList());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Router interceptor sees only immutable endpoints list

MediatorInInterceptor calls setEndpoints and mutates the collection it receives. Passing Spring’s injection result wrapped in List.of(...) means it gets an immutable list and will throw UnsupportedOperationException once the router comes up. Provide a mutable collection instead.

-        return createEndpoint(
-                bus,
-                ocpp12Server,
-                steveProperties.getPaths().getRouterEndpointPath(),
-                List.of(mediator),
-                Collections.emptyList());
+        return createEndpoint(
+                bus,
+                ocpp12Server,
+                steveProperties.getPaths().getRouterEndpointPath(),
+                new ArrayList<>(List.of(mediator)),
+                Collections.emptyList());
🤖 Prompt for AI Agents
In
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
around lines 106 to 118, the code passes an immutable list (List.of(mediator))
into MediatorInInterceptor which later mutates the collection and will throw
UnsupportedOperationException; change the immutable list to a mutable collection
(e.g., wrap the single-element list in a new ArrayList) so the interceptor can
modify it without error.

@juherr juherr force-pushed the backport/steve-1822 branch from 9f00bfd to 5da65ca Compare September 26, 2025 06:33
Copy link

@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 (4)
steve-jooq/pom.xml (1)

110-116: Unresolved Maven properties break the build.
${flyway.version}, ${jooq.version}, ${mysql.version} (and ${testcontainers.version} used later) are not defined in parent or this module.

Apply one of:

  • Option A (module‑local properties):
   <properties>
     <db.schema>stevedb</db.schema>
     <db.schema-source>stevedb</db.schema-source>
     <db.user>steve</db.user>
     <db.password>changeme</db.password>
     <!-- In Mysql: schema == database (http://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_schema) -->
     <db.jdbc.url>jdbc:mysql://localhost:3306/${db.schema}?sslMode=PREFERRED&amp;serverTimezone=UTC</db.jdbc.url>
+    <!-- Versions for plugins/deps not managed by Spring Boot -->
+    <mysql.version>8.4.0</mysql.version>
+    <jooq.version>3.20.6</jooq.version>
+    <flyway.version>10.21.0</flyway.version>
+    <testcontainers.version>1.20.4</testcontainers.version>
   </properties>
  • Option B (preferred globally): define these in the parent pom.xml so all modules share them. See separate comment in parent POM. Based on learnings

Also applies to: 182-193, 230-231

pom.xml (2)

178-208: maven-compiler: unresolved ${lombok.version}.
Without the property, the build will fail to resolve the annotationProcessorPaths entry.

Once the property is added (see above), this section is OK.


37-48: Add missing lombok.version property
pom.xml (): add

<lombok.version>1.18.34</lombok.version>

so the maven-compiler-plugin’s annotationProcessorPaths can resolve ${lombok.version}.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

583-599: Test bug: asserting empty list but indexing into it

sampledValues is asserted empty, then sampledValues.get(0) is used. This will either fail the assertion or throw IndexOutOfBoundsException. Assert non-empty instead.

Apply this diff:

-            assertThat(sampledValues).isEmpty();
+            assertThat(sampledValues).isNotEmpty();
🧹 Nitpick comments (16)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)

55-76: Guard against null client on first use.

ThreadLocal.get() returns null until set() runs on the current thread. There is a short window between client.set(getForOcpp16(soapPath)) and the first client.get() call in the same method where the current thread hasn’t been populated yet, so the dereference at client.get().bootNotification(...) risks an NPE. Capture the return value of getForOcpp16() into a local variable and reuse it for the rest of the method, or switch to a ThreadLocal.withInitial(...) to guarantee a non-null client before dereferencing.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (1)

46-48: Limit visibility of test-only constructor

If the String constructor is used only in tests, reduce visibility (package-private) or annotate with a testing hint to avoid exposing it as part of the public API.

Apply this diff:

-    public ChargeBoxIdValidator(String regexFromConfig) {
+    ChargeBoxIdValidator(String regexFromConfig) { // intended for tests and non-Spring usage
         this.pattern = Pattern.compile(Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig);
     }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (3)

91-91: Prefer JDK List.of over Guava Lists.newArrayList

Reduces dependency surface in tests.

Apply:

-        sendBoot(client, Lists.newArrayList(chargeBox1, chargeBox2));
+        sendBoot(client, List.of(chargeBox1, chargeBox2));

And remove the Guava import:

-import com.google.common.collect.Lists;

104-122: Helper method consistency (static vs instance)

sendBoot and sendAuth are static, sendStartTx is instance. For consistency, consider making all three instance methods (or all static if they don’t need state).


84-85: Name the test for intent clarity

Rename test() to a descriptive name (e.g., concurrentTx_isRejectedOnSecondChargeBox) or add @DisplayName for readability.

.github/workflows/main.yml (1)

60-63: Replace fixed sleep with a readiness probe.

Avoid flakiness by waiting until HTTP is up instead of sleeping 30s.

-          sleep 30
-          elinks -dump -no-references http://localhost:8080/manager/signin
+          for i in {1..60}; do
+            if elinks -dump -no-references http://localhost:8080/manager/signin >/dev/null 2>&1; then
+              break
+            fi
+            sleep 1
+          done
+          elinks -dump -no-references http://localhost:8080/manager/signin
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (1)

47-51: Constructor throws checked exceptions; confirm bean init behavior.

Throwing from a @component constructor will fail context refresh if SSL config is bad. If that’s desired, OK; otherwise, catch and log, or initialize lazily on first client creation.

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

64-66: Normalize composed path to avoid missing/double slashes.
If properties contain/omit leading or trailing “/”, pathInfix may be malformed. Consider normalizing segments or assert formats in SteveProperties.

Example normalization:

-        var pathInfix = steveProperties.getPaths().getWebsocketMapping()
-                + steveProperties.getPaths().getRouterEndpointPath() + "/";
+        var paths = steveProperties.getPaths();
+        var ws = paths.getWebsocketMapping();
+        ws = (ws.startsWith("/")) ? ws : "/" + ws;
+        ws = ws.endsWith("/") ? ws.substring(0, ws.length() - 1) : ws;
+        var router = paths.getRouterEndpointPath();
+        router = router.startsWith("/") ? router.substring(1) : router;
+        var pathInfix = ws + "/" + router + "/";
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)

101-108: Avoid unchecked cast in createBean.
Use Class.cast to remove the need for @SuppressWarnings.

Apply:

-    private static <T> T createBean(URI endpointAddress, Class<T> serviceClass) {
+    private static <T> T createBean(URI endpointAddress, Class<T> serviceClass) {
         var f = new JaxWsProxyFactoryBean();
         f.setBindingId(SOAPBinding.SOAP12HTTP_BINDING);
         f.getFeatures().add(new WSAddressingFeature());
         f.setAddress(endpointAddress.toString());
         f.setServiceClass(serviceClass);
-        return (T) f.create();
+        return serviceClass.cast(f.create());
     }
steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (1)

91-97: Startup-time internet probe permanently fixes strategy

If the network is transiently down at startup, you’ll lock in DummyReleaseCheckService for the whole run. Consider property-gating (e.g., steve.release-check.enabled) or scheduling a retry to flip to GithubReleaseCheckService when connectivity returns.

Do you want a small ConditionalOnProperty-based refactor?

steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

59-59: Switch static System properties to application.yml

Prefer configuration over code so values are visible/overridable and initialized early by Boot.

Add to application.yml:

springdoc:
  api-docs:
    path: /manager/v3/api-docs
  swagger-ui:
    path: /manager/swagger-ui/index.html
    tags-sorter: alpha
    operations-sorter: alpha
  writer-with-order-by-keys: true
  paths-to-match: /api/**

Then remove the static block.

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

93-100: Header-value driven apiPassword only set on first boot

afterStart exits early if any ADMIN exists, so changing steve.web-api.header-value won’t update stored apiPassword for existing admins.

Consider: if header-value is present, reconcile and update apiPassword for the configured admin on startup.

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

166-168: Return a safe snapshot rather than exposing live, mutable Deques

copyOf(lookupTable) still exposes internal Deques to callers. Return copies per entry to prevent external mutation and reduce race windows.

Apply:

-        return ImmutableMap.copyOf(lookupTable);
+        var copy = new java.util.HashMap<String, Deque<SessionContext>>(lookupTable.size());
+        lookupTable.forEach((k, v) -> copy.put(k, new ArrayDeque<>(v)));
+        return copy;
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)

90-93: Make tearDown null-safe

Guard cleanup to avoid NPE if setUp fails.

Apply:

-    public void tearDown() {
-        databasePreparer.cleanUp();
+    public void tearDown() {
+        if (databasePreparer != null) {
+            databasePreparer.cleanUp();
+        }
     }
steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (1)

59-61: Prefer DelegatingPasswordEncoder for compatibility

Keeps support for prefixed hashes and eases migrations.

Apply:

-    public PasswordEncoder passwordEncoder() {
-        return new BCryptPasswordEncoder();
-    }
+    public PasswordEncoder passwordEncoder() {
+        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+    }
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)

67-71: Consider deriving schema dynamically instead of a hard-coded constant

Using a fixed SCHEMA_TO_TRUNCATE risks failures when test DB/schema changes. Prefer reading the active schema from the configured connection (e.g., ctx.meta() or SELECT DATABASE()).

I can draft a helper that resolves the current schema and filters tables accordingly if you want.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f00bfd and 5da65ca.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (69)
  • .github/workflows/main.yml (1 hunks)
  • AGENTS.md (2 hunks)
  • Dockerfile (1 hunks)
  • pom.xml (5 hunks)
  • steve-api/pom.xml (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (6 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (7 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application-dev.yml (1 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/logback.xml (0 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (8 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (6 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (4 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
  • steve/src/test/resources/application-test.yml (1 hunks)
💤 Files with no reviewable changes (13)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve/src/main/resources/config/main.properties
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
  • steve/src/main/resources/config/logback.xml
  • steve-ocpp/pom.xml
  • steve-ocpp-transport-websocket/pom.xml
🚧 Files skipped from review as they are similar to previous changes (18)
  • steve/src/main/resources/application-dev.yml
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve/src/test/resources/application-test.yml
  • steve/src/main/resources/logback-spring.xml
  • steve/README.md
  • Dockerfile
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve-core/pom.xml
  • steve-ocpp-transport-soap/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve-ui-jsp/pom.xml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • pom.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/main/resources/application.yml
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • AGENTS.md
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve/pom.xml
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • .github/workflows/main.yml
  • pom.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • AGENTS.md
  • steve-jooq/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/pom.xml
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • AGENTS.md
  • steve-jooq/pom.xml
🧬 Code graph analysis (5)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java (1)
  • Getter (30-45)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java (1)
  • RequiredArgsConstructor (56-213)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)
  • Slf4j (39-74)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (61-237)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

🪛 GitHub Actions: analyze and review code
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java

[error] 27-27: Spotless formatting violation. The field 'name' has incorrect spacing. Run 'mvn spotless:apply' to fix code style issues in this file.

⏰ 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). (19)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
  • 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-24.04, 21, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.5.21)
  • GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.4.30)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
  • GitHub Check: build (ubuntu-22.04, 24, mariadb:10.3)
  • 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-22.04, 24, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
  • GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
🔇 Additional comments (61)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)

97-100: Restore String-based URI for RequestBuilder.post.

RequestBuilder.post(...) in HttpClient 4.5.x only accepts a String; passing the URI directly breaks compilation. Convert soapPath to a string before calling post(...).

Apply this diff to fix the issue:

-        var req = RequestBuilder.post(soapPath)
+        var req = RequestBuilder.post(soapPath.toString())
                 .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
                 .setEntity(new StringEntity(body, contentType))
                 .build();
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (1)

42-53: Constructor migration to SteveProperties looks solid

The switch to constructor-injected SteveProperties ties this DTO directly to the new YAML-bound configuration while preserving the expected endpoint concatenations. Nicely aligned with the broader configuration refactor.

steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3)

48-50: Simplified CLI bootstrap works

The direct no-arg construction ties in with the new Spring-managed defaults and keeps the stress harness runnable from the command line. Looks good.


61-64: Using soapPath aligns with the shared stress-test base

Pulling the SOAP endpoint from the superclass instead of the old per-instance path keeps this test in sync with the rest of the backport.


79-82: Consistent client creation

Repeating the same soapPath-backed client setup in toRepeat() keeps behavior aligned with the initialization logic above. All good.

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

69-84: Initialize chargePointsService before building the controller

NullAway still sees chargePointsService as potentially null when the controller is constructed, matching the earlier warning. Please bind the Mockito mock inside setUp so the field is definitely assigned.

-    @Mock
-    private ChargePointsService chargePointsService;
+    private ChargePointsService chargePointsService;
@@
-    @BeforeEach
-    public void setUp() {
+    @BeforeEach
+    public void setUp(@Mock ChargePointsService mockService) {
+        this.chargePointsService = mockService;
         var ctx = new AnnotationConfigApplicationContext();
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

90-97: Clean the database before closing the Spring context

__DatabasePreparer__ relies on the DSLContext from the still-open application context. Calling app.close() first shuts down the DataSource, so databasePreparer.cleanUp() will fail with “pool is closed”, leaving the database dirty and breaking subsequent tests. Run the cleanup while the context is alive, then close the context.

-            try {
-                if (app != null) {
-                    app.close();
-                }
-            } finally {
-                if (databasePreparer != null) {
-                    databasePreparer.cleanUp();
-                }
-            }
+            try {
+                if (databasePreparer != null) {
+                    databasePreparer.cleanUp();
+                }
+            } finally {
+                if (app != null) {
+                    app.close();
+                }
+            }
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2)

63-65: Confirm jsonPath initialization for standalone runs

Line 64 now relies on the no-arg constructor to populate jsonPath implicitly before attack() executes. Please double-check that StressTest initializes jsonPath when this main is launched outside any Spring context; otherwise a null path will break the websocket bootstrap.


80-81: jsonPath switch matches new property binding

Line 81 correctly pulls the websocket path from the shared jsonPath property, keeping this stress helper aligned with the new configuration flow.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (4)

41-44: Constructor injection via SteveProperties looks good

Using constructor injection for the regex source centralizes config and ensures immutability of the compiled Pattern.


56-61: Nulls are treated as valid — ensure callers add @NotNull when needed

By design, null returns true here. For collection element validation (e.g., List<@ChargeBoxId String>), add @NotNull at the use site to reject null items. See ChargePointBatchInsertForm.


68-74: LGTM: strict whitespace and regex matching

strip() equality check correctly rejects leading/trailing whitespace; full-string regex match via Pattern#matches is appropriate.


41-48: No changes required: Spring Boot auto-configures a SpringConstraintValidatorFactory-backed validator

spring-boot-starter-validation pulls in ValidationAutoConfiguration, which registers a LocalValidatorFactoryBean using SpringConstraintValidatorFactory, so your @Autowired constructor will be invoked.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (3)

84-87: Fixed: correct SOAP endpoint and checked exception handling

Switch to getSoapPath and declaring throws URISyntaxException resolves the earlier mismatch and unchecked exception. LGTM.


56-58: Spring Boot test wiring looks correct

Active test profile, SpringBootTest(DEFINED_PORT), and autowired ServerProperties/SteveProperties/DSLContext are appropriate for this integration test and align with the helpers.

If your CI shares the test host, confirm DEFINED_PORT won’t collide with parallel builds. If collisions occur, consider RANDOM_PORT plus @LocalServerPort and a helper variant that accepts an explicit port.

Also applies to: 62-70, 22-22, 46-46


60-60: Ignore static-init concern for REGISTERED_OCPP_TAG

getRegisteredOcppTag() simply returns a static final literal (“id_tag_…”); it does not touch the DB or DSLContext, so class-load initialization can’t flake.

Likely an incorrect or invalid review comment.

.github/workflows/main.yml (1)

49-55: Maven flags now well-formed; confirm profile exists.

Command structure looks correct. Ensure -PuseRealDatabase is defined in a POM (root or module), else drop it.

#!/bin/bash
# Verify the profile definition
rg -nP '(?s)<profile>\s*<id>\s*useRealDatabase\s*</id>' -C2 --glob '!**/target/**' -g 'pom.xml'
AGENTS.md (3)

100-100: Fix typo: it’s a WAR, not a JAR.

Use “WAR file” in the sentence to match the .war artifact.

-A runnable JAR file will be created at `steve/target/steve.war`.
+A runnable WAR file will be created at `steve/target/steve.war`.

71-71: Config path update verified; no remaining main.properties references.


106-112: Executable WAR confirmed: POM uses <packaging>war</packaging> and includes spring-boot-maven-plugin with no provided‐scope Tomcat—java -jar steve/target/steve.war is valid as shown.

steve-ocpp-1-x/pom.xml (2)

53-59: jaxb-plugins.version property is already defined in the parent POM
The root pom.xml declares <jaxb-plugins.version>4.0.11</jaxb-plugins.version>.


24-24: Confirmed cxf.version is defined and consistent
Property <cxf.version> is declared in the parent POM as 4.1.3 and used for both the CXF BOM import and the cxf-codegen-plugin; no changes required.

steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)

57-73: Validate and normalize URL mapping patterns. Confirm that paths.rootMapping is configured with the proper trailing slash or wildcard for both DispatcherServlet.addMapping() and the security filter’s addMappingForUrlPatterns()—don’t naively append "*" (e.g. avoid "/app*").

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (1)

76-93: Avoid NPE and support optional trust/keystores in TLS setup.

create(...) unconditionally accesses ssl.getKeyStore(); this can NPE if SSL is enabled without a keystore. Make keystore/truststore optional.

-        var socketFactory = SslContextBuilder.builder()
-                .keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
-                .usingTLS()
-                .usingDefaultAlgorithm()
-                .usingKeyManagerPasswordFromKeyStore()
-                .buildMergedWithSystem()
-                .getSocketFactory();
+        var builder = SslContextBuilder.builder();
+        if (ssl.getKeyStore() != null) {
+            builder = builder.keyStoreFromFile(ssl.getKeyStore(), ssl.getKeyStorePassword())
+                    .usingKeyManagerPasswordFromKeyStore();
+        }
+        if (ssl.getTrustStore() != null) {
+            builder = builder.trustStoreFromFile(ssl.getTrustStore(), ssl.getTrustStorePassword());
+        }
+        var socketFactory = builder
+                .usingTLS()
+                .usingDefaultAlgorithm()
+                .buildMergedWithSystem()
+                .getSocketFactory();
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (2)

59-59: DI via SteveProperties looks good.
Field injection of SteveProperties aligns with the migration to property‑driven config.


85-85: Jetty upgrade strategy with var is fine.
No functional issues; concise and clear.

steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (3)

44-44: Minor: var usage in tests is fine.
No concerns.


51-68: Harden SOAP URI builder (NPEs, null port/context, IPv6).
getSsl(), getAddress(), getPort(), getServlet()/getContextPath() may be null; IPv6 needs brackets. This can NPE or build invalid URIs.

Apply:

-    public static URI getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties)
-            throws URISyntaxException {
-        var scheme = "http";
-        if (serverProperties.getSsl().isEnabled()) {
-            scheme = "https";
-        }
-
-        return new URI(
-                scheme,
-                null,
-                serverProperties.getAddress().getHostName(),
-                serverProperties.getPort(),
-                serverProperties.getServlet().getContextPath()
-                        + steveProperties.getPaths().getSoapMapping()
-                        + steveProperties.getPaths().getRouterEndpointPath() + "/",
-                null,
-                null);
-    }
+    public static URI getSoapPath(ServerProperties serverProperties, SteveProperties steveProperties)
+            throws URISyntaxException {
+        var ssl = serverProperties.getSsl();
+        var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "https" : "http";
+
+        var addr = serverProperties.getAddress();
+        var host = (addr != null) ? addr.getHostAddress() : "localhost";
+        if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
+            host = "[" + host + "]"; // IPv6
+        }
+
+        Integer port = serverProperties.getPort();
+        int p = (port != null) ? port : 8080;
+
+        var servlet = serverProperties.getServlet();
+        var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
+
+        var soap = steveProperties.getPaths().getSoapMapping();
+        soap = (soap != null) ? soap : "";
+        var router = steveProperties.getPaths().getRouterEndpointPath();
+        router = (router != null) ? router : "";
+
+        String path = ctx + soap + router + "/";
+        return new URI(scheme, null, host, p, path, null, null);
+    }

70-87: Harden WS URI builder (same issues as SOAP).
Mirror the null/IPv6 handling; choose wss when SSL enabled.

Apply:

-    public static URI getWsPath(ServerProperties serverProperties, SteveProperties steveProperties)
-            throws URISyntaxException {
-        var scheme = "ws";
-        if (serverProperties.getSsl().isEnabled()) {
-            scheme = "wss";
-        }
-
-        return new URI(
-                scheme,
-                null,
-                serverProperties.getAddress().getHostName(),
-                serverProperties.getPort(),
-                serverProperties.getServlet().getContextPath()
-                        + steveProperties.getPaths().getWebsocketMapping()
-                        + steveProperties.getPaths().getRouterEndpointPath() + "/",
-                null,
-                null);
-    }
+    public static URI getWsPath(ServerProperties serverProperties, SteveProperties steveProperties)
+            throws URISyntaxException {
+        var ssl = serverProperties.getSsl();
+        var scheme = (ssl != null && Boolean.TRUE.equals(ssl.isEnabled())) ? "wss" : "ws";
+
+        var addr = serverProperties.getAddress();
+        var host = (addr != null) ? addr.getHostAddress() : "localhost";
+        if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
+            host = "[" + host + "]"; // IPv6
+        }
+
+        Integer port = serverProperties.getPort();
+        int p = (port != null) ? port : 8080;
+
+        var servlet = serverProperties.getServlet();
+        var ctx = (servlet != null && servlet.getContextPath() != null) ? servlet.getContextPath() : "";
+
+        var ws = steveProperties.getPaths().getWebsocketMapping();
+        ws = (ws != null) ? ws : "";
+        var router = steveProperties.getPaths().getRouterEndpointPath();
+        router = (router != null) ? router : "";
+
+        String path = ctx + ws + router + "/";
+        return new URI(scheme, null, host, p, path, null, null);
+    }
steve-jooq/pom.xml (1)

27-29: Adopting spring-boot-starter-jooq is good.
This aligns with the Spring Boot migration and reduces explicit versioning.

pom.xml (1)

5-10: Switching to Spring Boot parent is fine.
Centralizes dependency management and aligns with the Boot migration.

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

490-491: Null‑tolerant enum mapping looks good.
lookupLiteral(actor.getName()) avoids IllegalArgumentException from valueOf and keeps null propagation. Confirm TRANSACTION_STOP.EVENT_ACTOR allows NULL; otherwise consider a fallback (e.g., manual) to avoid insert failures.

steve-api/pom.xml (2)

34-36: LGTM: Consolidated tests via spring-boot-starter-test

Starter brings JUnit Jupiter, Mockito, AssertJ, JSONPath, etc. Good simplification.


23-26: Manage servlet API version
The jakarta.servlet-api dependency in steve-api/pom.xml (lines 23–26) has no <version> and isn’t covered by your parent’s dependencyManagement. Confirm an imported BOM (e.g., cxf-bom) provides this version; otherwise add an explicit <version> or include it under <dependencyManagement>.

steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (1)

107-113: LGTM: Jackson customizations on existing MVC converter

Module discovery, ignoring unknown props, and ISO dates are correctly set on Spring’s ObjectMapper.

steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1)

39-63: LGTM: Boot entrypoint, CXF SLF4J wiring, UTC default TZ

Good centralization; ApplicationStartedEvent log is harmless and informative.

steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

76-76: LGTM: Version sourced from SteveProperties

OpenAPI.version reflects steve.version property.

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

45-49: LGTM: Property-driven auto-register flag

Constructor now reads from SteveProperties; behavior unchanged besides wiring.

steve/src/main/resources/application.yml (1)

7-9: Quote @...@ tokens to keep YAML valid

Unquoted @...@ is invalid YAML. Quote them so IDEs/parsers don’t choke before Maven filtering.

Apply:

-    url: @db.jdbc.url@
-    username: @db.user@
-    password: @db.password@
+    url: "@db.jdbc.url@"
+    username: "@db.user@"
+    password: "@db.password@"
...
-  version: @project.version@
+  version: "@project.version@"

Also applies to: 38-38

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

227-233: Invalidate API-user cache after apiPassword change

Without eviction, loadUserByUsernameForApi may serve stale credentials for up to 10 minutes.

Apply:

     public void updateApiPassword(WebUserForm form) {
         String newPassword = null;
         if (form.getApiPassword() != null && !form.getApiPassword().isEmpty()) {
             newPassword = passwordEncoder.encode(form.getApiPassword());
         }
         webUserRepository.changeApiPassword(form.getWebUserPk(), newPassword);
+        userCache.invalidate(form.getWebUsername());
     }
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (2)

61-64: Constructor migration to SteveProperties looks good

Property-based strategy wiring is clear and keeps config centralized.


148-153: Guard Deque read with the per-id lock to avoid races

Accessing ArrayDeque without the same lock used for add/remove is unsafe; size() can be inconsistent.

Apply:

-        var endpointDeque = lookupTable.get(chargeBoxId);
-        if (endpointDeque == null) {
-            return 0;
-        }
-        return endpointDeque.size();
+        var l = locks.get(chargeBoxId);
+        l.lock();
+        try {
+            var endpointDeque = lookupTable.get(chargeBoxId);
+            return endpointDeque == null ? 0 : endpointDeque.size();
+        } finally {
+            l.unlock();
+        }
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)

71-88: WS path initialization via properties: good

Autowired ServerProperties/SteveProperties and getWsPath usage correctly derive jsonPath.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (2)

49-53: Validate constructor input

Null endpoints will NPE later; fail fast with a clear message.

Apply:

     public MediatorInInterceptor(List<EndpointImpl> endpoints) {
         super(Phase.POST_STREAM);
         super.addBefore(StaxInInterceptor.class.getName());
-        actualServers = initServerLookupMap(endpoints);
+        java.util.Objects.requireNonNull(endpoints, "endpoints must not be null");
+        actualServers = initServerLookupMap(endpoints);
     }

61-69: Fix fragile mark/reset and handle missing InputStream

available() can be 0 causing reset() failure; also handle null stream.

Apply:

-            var is = message.getContent(InputStream.class);
-            var bis = new BufferedInputStream(is);
-            bis.mark(bis.available());
+            var is = message.getContent(InputStream.class);
+            if (is == null) {
+                log.warn("No InputStream on message; cannot route");
+                return;
+            }
+            var bis = new BufferedInputStream(is);
+            // mark enough bytes to parse envelope and first body element
+            bis.mark(64 * 1024);
             message.setContent(InputStream.class, bis);
 
-            var encoding = (String) message.get(Message.ENCODING);
-            var reader = StaxUtils.createXMLStreamReader(bis, encoding);
-            var xmlReader = new DepthXMLStreamReader(reader);
+            var encoding = (String) message.get(Message.ENCODING);
+            var reader = StaxUtils.createXMLStreamReader(bis, encoding);
+            var xmlReader = new DepthXMLStreamReader(reader);
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

99-109: Guard render mapping when generator schema is absent

Without checking schemaSource, jOOQ may map the empty/default schema and rewrite all unqualified references.

Apply:

-        var dbSchema = steveProperties.getJooq().getSchema();
-        if (dbSchema != null && !dbSchema.equals(steveProperties.getJooq().getSchemaSource())) {
+        var dbSchema = steveProperties.getJooq().getSchema();
+        var schemaSource = steveProperties.getJooq().getSchemaSource();
+        if (dbSchema != null && schemaSource != null && !dbSchema.equals(schemaSource)) {
             // This is needed if the schema in the database is different from the one
             // that was used to generate the jOOQ classes.
             settings = settings.withRenderMapping(new RenderMapping()
                     .withSchemata(new MappedSchema()
-                            .withInput(steveProperties.getJooq().getSchemaSource())
-                            .withOutput(steveProperties.getJooq().getSchema())));
+                            .withInput(schemaSource)
+                            .withOutput(dbSchema)));
         }
steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (1)

164-172: BasicAuthenticationFilter usage OK ApiAuthenticationManager implements both AuthenticationManager and AuthenticationEntryPoint.

steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (4)

51-53: Spring Boot test wiring looks good

Test profile + DEFINED_PORT context are correctly applied for CXF endpoints.


58-69: Per-test DI and path derivation LGTM

Autowired ServerProperties/SteveProperties/DSLContext with per-test soapPath and DB setup is correct and consistent with Helpers.getSoapPath usage.

Small gotcha: Helpers.getSoapPath calls serverProperties.getAddress().getHostName(). If server.address isn’t configured, getAddress() can be null. Please confirm application-test.yml sets server.address (e.g., 127.0.0.1) or adjust Helpers to default to localhost when null. Based on learnings


70-80: Good test lifecycle

Per-test prepare/cleanUp ensures isolation for DB-backed integration tests.


84-98: SOAP client usage via router path is correct

Creating the client with soapPath and asserting expected statuses is aligned with the new mediator/router setup.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (2)

84-106: Per-test DB setup and router-based client LGTM

Assertions around auto-register disabled and unregistered CP behavior look correct with the interceptor logic.


259-265: Fix: LinkedHashMap has no lastEntry(); won’t compile

Replace lastEntry() usage with a computed timestamp from startingTime.

Apply this diff:

-        var faultedTime = timeStatusMap.lastEntry().getKey().plusMinutes(1);
+        var faultedTime = startingTime.plusMinutes(ChargePointStatus.values().length);

Also applies to: 299-307

steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)

64-73: Instance-based preparer is a solid improvement

Switching to DI/DSLContext instance methods removes static coupling and fits per-test lifecycle well.


111-119: Repository accessors read neatly

Direct repository calls via injected DSLContext are appropriate for tests.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2)

106-119: Router endpoint bean wiring looks consistent

Mediator receives ocppEndpoints; interceptors registration is fine. Address uses steveProperties paths as expected.

Please confirm MediatorInInterceptor does not rely on mutating the passed ocppEndpoints List; if it does, ensure Spring injects a mutable List (it typically does). If mutation is needed, wrap with new ArrayList<>(ocppEndpoints). Based on learnings


136-147: Publishing endpoints explicitly is acceptable here

Since you construct EndpointImpl directly, calling publish(address) is appropriate (as opposed to using EndpointImplFactoryBean which auto-publishes). No change needed.

steve/pom.xml (4)

186-201: Reassess filtering of application.yml

Filtering true can substitute ${...} tokens intended for Spring at runtime (e.g., keystore paths/passwords), risking secrets baked into artifacts or broken placeholders.

Please confirm application.yml contains only Maven tokens you intend to resolve at build time. If not, disable filtering for it or switch Maven filtering delimiters. I can script-check placeholders across application.yml variants if helpful.


90-98: Verify version management for httpclient5

httpclient5 has no explicit version here—ensure steve-parent’s dependencyManagement (or an imported BOM) pins it to a safe version.

Run a quick check in the parent POM and dependencyManagement before merging.


29-34: Avoid DB creds in POM; move to env/application.yml

Keeping db.user/password/url in the POM risks leakage via effective POM and build logs, and can be baked into filtered resources. Move them to application.yml or environment variables and reference via Spring placeholders.

I can propose an application.yml snippet using ${DB_URL}/${DB_USER}/${DB_PASSWORD} env vars and remove these POM properties if you’d like.


210-233: Replace unpack-with-dependency-plugin with WAR overlay for steve-ui-jsp

Unpacking into ${project.build.directory}/${project.build.finalName} is fragile and can be overwritten by the WAR build. Use a WAR overlay.

Apply this diff to remove the unpack execution and configure maven-war-plugin overlays:

-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>unpack-steve-ui-jsp-webapp</id>
-            <goals>
-              <goal>unpack</goal>
-            </goals>
-            <phase>process-resources</phase>
-            <configuration>
-              <artifactItems>
-                <artifactItem>
-                  <groupId>de.rwth.idsg</groupId>
-                  <artifactId>steve-ui-jsp</artifactId>
-                  <version>${project.version}</version>
-                  <overWrite>true</overWrite>
-                  <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
-                  <includes>WEB-INF/**,static/**</includes>
-                </artifactItem>
-              </artifactItems>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <configuration>
+          <overlays>
+            <overlay>
+              <groupId>de.rwth.idsg</groupId>
+              <artifactId>steve-ui-jsp</artifactId>
+              <type>war</type>
+            </overlay>
+          </overlays>
+        </configuration>
+      </plugin>

Comment on lines +74 to 76
registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
.setHandshakeHandler(handshakeHandler)
.setAllowedOrigins("*");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not allow all origins ("*") for WebSocket.
Open CORS weakens security. Prefer configured origin patterns (env/property) and avoid "*" in production.

Example:

-        registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
-                .setHandshakeHandler(handshakeHandler)
-                .setAllowedOrigins("*");
+        var handlerRegistration = registry
+                .addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
+                .setHandshakeHandler(handshakeHandler);
+        // Configure via properties; fallback to localhost in dev
+        var origins = steveProperties.getSecurity() != null
+                ? steveProperties.getSecurity().getAllowedOriginPatterns()
+                : java.util.List.of("http://localhost:*", "https://localhost:*");
+        handlerRegistration.setAllowedOriginPatterns(origins.toArray(String[]::new));
📝 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
registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
.setHandshakeHandler(handshakeHandler)
.setAllowedOrigins("*");
var handlerRegistration = registry
.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
.setHandshakeHandler(handshakeHandler);
// Configure via properties; fallback to localhost in dev
var origins = steveProperties.getSecurity() != null
? steveProperties.getSecurity().getAllowedOriginPatterns()
: java.util.List.of("http://localhost:*", "https://localhost:*");
handlerRegistration.setAllowedOriginPatterns(origins.toArray(String[]::new));
🤖 Prompt for AI Agents
In
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
around lines 74-76 the WebSocket CORS is set to allow all origins ("*"); replace
this with a configurable allowed-origins value sourced from application
properties or env (e.g. a comma-separated list or pattern), load and validate
the configured origins (provide a sensible default like localhost for dev), and
call setAllowedOrigins(...) with that value instead of "*" so production does
not permit unrestricted cross-origin access.

Comment on lines +43 to +46
private final SteveProperties steveProperties;

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
public void onStartup(ServletContext servletContext) {
Copy link

@coderabbitai coderabbitai bot Sep 26, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Blocking: WebApplicationInitializer cannot use constructor injection.

Servlet container instantiates WebConfig via no-arg constructor; the required-args ctor will fail at startup. Fetch SteveProperties from the Spring context instead.

-    private final SteveProperties steveProperties;
+    // obtain SteveProperties from the Spring context after refresh
@@
-    public void onStartup(ServletContext servletContext) {
+    public void onStartup(ServletContext servletContext) {
         log.info("Initializing");
@@
-        var springContext = new AnnotationConfigWebApplicationContext();
-        springContext.scan("de.rwth.idsg.steve.config");
-        servletContext.addListener(new ContextLoaderListener(springContext));
+        var springContext = new AnnotationConfigWebApplicationContext();
+        springContext.scan("de.rwth.idsg.steve.config");
+        springContext.refresh();
+        var props = springContext.getBean(SteveProperties.class);
+        servletContext.addListener(new ContextLoaderListener(springContext));
@@
-        web.addMapping(steveProperties.getPaths().getRootMapping());
+        web.addMapping(props.getPaths().getRootMapping());
@@
-        cxf.addMapping(steveProperties.getPaths().getSoapMapping() + "/*");
+        cxf.addMapping(props.getPaths().getSoapMapping() + "/*");
@@
-                        steveProperties.getPaths().getRootMapping() + "*");
+                        props.getPaths().getRootMapping() + "*");

Additionally, remove @requiredargsconstructor from the class annotations (outside this hunk).

📝 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 final SteveProperties steveProperties;
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
public void onStartup(ServletContext servletContext) {
// obtain SteveProperties from the Spring context after refresh
@Override
public void onStartup(ServletContext servletContext) {
log.info("Initializing");
var springContext = new AnnotationConfigWebApplicationContext();
springContext.scan("de.rwth.idsg.steve.config");
springContext.refresh();
var props = springContext.getBean(SteveProperties.class);
servletContext.addListener(new ContextLoaderListener(springContext));
web.addMapping(props.getPaths().getRootMapping());
cxf.addMapping(props.getPaths().getSoapMapping() + "/*");
// other existing mappings
web.addMapping(props.getPaths().getRootMapping() + "*");
}

Copy link
Owner Author

Choose a reason for hiding this comment

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

@coderabbitai Are you sure? Could you point me a documentation reference?

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@juherr You're right to question this! Let me provide the documentation references.

Based on the official Spring Framework documentation and source code analysis, SpringServletContainerInitializer is responsible for instantiating and delegating the ServletContext to any user-defined WebApplicationInitializer implementations.

The key evidence comes from examining how SpringServletContainerInitializer actually instantiates WebApplicationInitializer classes. It sorts them in a certain order and instantiates them in a certain order using waiClass.newInstance(), which requires a no-argument constructor.

For someone still getting this type of error, it may be because of unavailability of NoArgs Constructor in your main SpringBootApplication. Because of this tomcat is not able to initialise the Servlet. Add a NoArgs constructor in your main class.

However, I need to clarify an important distinction: Embedded servlet containers do not directly execute the ServletContainerInitializer interface or Spring's WebApplicationInitializer interface. This is an intentional design decision intended to reduce the risk that third party libraries designed to run inside a war may break Spring Boot applications. If you need to perform servlet context initialization in a Spring Boot application, you should register a bean that implements the ServletContextInitializer interface.

Since your project packages as a WAR (from the pom.xml changes), and you're using WebApplicationInitializer, this suggests deployment to an external servlet container where the traditional instantiation mechanism applies, requiring a no-arg constructor.

The solution would be to either:

  1. Remove @requiredargsconstructor and obtain SteveProperties from the Spring context after it's created, or
  2. Use ServletContextInitializer instead if deploying to embedded containers

Would you like me to suggest the specific implementation approach?


🌐 Web query:

Spring Boot WebApplicationInitializer constructor injection dependency injection

💡 Result:

Short answer: you can’t rely on Spring to perform constructor (or @Autowired) injection into a WebApplicationInitializer instance—those initializer instances are created by the Servlet container (Spring’s SPI loader), not by the Spring ApplicationContext. Use the initializer only to build/register your Spring ApplicationContext(s) and then obtain/inject beans from that context. [1][2]

Why and practical options

  • Why: WebApplicationInitializer is discovered/instantiated by the Servlet container (SpringServletContainerInitializer), so Spring hasn’t created the initializer as a managed bean and won’t inject dependencies into its constructor or fields. [1][2]
  • Recommended patterns:
    • In onStartup(...) create an AnnotationConfigWebApplicationContext, register your @configuration classes, and pass that context to DispatcherServlet / ContextLoaderListener; then fetch beans from that context as needed. Example: new AnnotationConfigWebApplicationContext(); ctx.register(AppConfig.class);. [1]
    • Extend AbstractAnnotationConfigDispatcherServletInitializer (a Spring-provided base) which manages contexts for you. [1]
    • For Spring Boot war deployments, extend SpringBootServletInitializer to bootstrap the app in the container. (Again, do DI inside the created ApplicationContext, not via the initializer’s constructor.)
    • If you need logic after context is ready, implement an @component that listens for ContextRefreshedEvent / @eventlistener and inject services there. [2]
    • If you must register servlets/filters/listeners as Spring beans, register them in the Spring context and use the container’s mechanisms to register instances (or let Spring Boot register them). Avoid expecting @Autowired inside Servlet-created listener/initializer instances. [2][3]

Sources

  • Spring WebApplicationInitializer Javadoc (explanation and examples). [1]
  • StackOverflow threads and common guidance: WebApplicationInitializer/ServletContextListener instances are not autowired; use context-created beans or lifecycle events after startup. [2]
  • Spring core docs on DI best practices (constructor injection recommendation and context lifecycle). [3]

References
[1] Spring WebApplicationInitializer Javadoc.
[2] StackOverflow: “You can not use @Autowired inside of WebApplicationInitializer” and related answers.
[3] Spring Framework reference — dependency injection guidance.


🌐 Web query:

WebApplicationInitializer servlet container instantiation mechanism Spring Boot

💡 Result:

Short answer:

  • In a traditional (war deployed) Servlet 3+ container the container calls ServletContainerInitializer; Spring provides SpringServletContainerInitializer which is annotated with @HandlesTypes(WebApplicationInitializer). The container passes discovered WebApplicationInitializer classes to it; Spring instantiates them (respecting @Order/Ordered), then calls each WebApplicationInitializer.onStartup(ServletContext) to register DispatcherServlets, filters, listeners, etc. [1][2].

  • Spring Boot running as an embedded server does NOT let the embedded container run ServletContainerInitializer/WebApplicationInitializer directly. Instead Spring Boot creates a ServletWebServerApplicationContext and uses ServletContextInitializer beans (and @ServletComponentScan for @WebServlet/@WebFilter/@weblistener) to programmatically configure the ServletContext. For traditional WARs SpringBootServletInitializer implements WebApplicationInitializer and bootstraps a SpringApplication when onStartup is invoked by the container. [3][4][5]

References:

  • Spring’s SpringServletContainerInitializer / WebApplicationInitializer behavior. [1]
  • Spring Boot docs: embedded containers use ServletWebServerApplicationContext and ServletContextInitializer (embedded containers don’t execute ServletContainerInitializer/WebApplicationInitializer). [2]
  • SpringBootServletInitializer (WAR support: implements WebApplicationInitializer and runs SpringApplication). [3]

([1] SpringServletContainerInitializer docs; [2] Spring Boot — Servlet context initialization / embedded container docs; [3] SpringBootServletInitializer API).


🌐 Web query:

"Spring Boot" "WebApplicationInitializer" "constructor injection" dependency injection

💡 Result:

Short answer: No — your WebApplicationInitializer instance is created by the servlet container (via SpringServletContainerInitializer), not by the Spring IoC container, so you cannot rely on Spring constructor/@Autowired injection into the WebApplicationInitializer itself. Instead create an ApplicationContext inside onStartup (or register Spring-managed servlets/listeners/filters that receive injected beans) and obtain/inject beans from that context. [1][2][3]

References:

  • WebApplicationInitializer javadoc (shows code-based initialization and that you build/use your own application contexts). [1]
  • SpringServletContainerInitializer javadoc (it instantiates WebApplicationInitializer implementations and calls onStartup). [2]
  • Spring Boot docs on dependency injection (use constructor injection for Spring-managed beans; WebApplicationInitializer is not one). [3]

🌐 Web query:

SpringServletContainerInitializer instantiate WebApplicationInitializer constructor

💡 Result:

SpringServletContainerInitializer (loaded by the Servlet container) receives the Set<Class<?>> of WebApplicationInitializer types and instantiates each implementation reflectively using its no-arg constructor, sorts the instances (if they implement Ordered or use @order / annotation-aware comparator), then calls onStartup(ServletContext) on each instance.

Sources: Spring javadoc for SpringServletContainerInitializer (describes instantiation, sorting and delegation). [1]

References

  • SpringServletContainerInitializer Javadoc — explanation of reflective instantiation, sorting and invocation of WebApplicationInitializer.onStartup. [1]

[1] https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/SpringServletContainerInitializer.html


@juherr Yes, I'm certain about this. Here are the documentation references from the web search results:

Key Documentation References:

WebApplicationInitializer instances are created by the Servlet container (Spring's SPI loader), not by the Spring ApplicationContext, so you can't rely on Spring to perform constructor (or @Autowired) injection into a WebApplicationInitializer instance.

SpringServletContainerInitializer (loaded by the Servlet container) receives the Set<Class<?>> of WebApplicationInitializer types and instantiates each implementation reflectively using its no-arg constructor.

Why This Matters:

  1. Instantiation mechanism: WebApplicationInitializer is discovered/instantiated by the Servlet container (SpringServletContainerInitializer), so Spring hasn't created the initializer as a managed bean and won't inject dependencies into its constructor or fields.

  2. Spring Boot context: While Spring Boot running as an embedded server does NOT let the embedded container run ServletContainerInitializer/WebApplicationInitializer directly, since your project packages as a WAR (from the pom.xml changes), it will be deployed to external servlet containers where the traditional instantiation mechanism applies.

Recommended Solution:

In onStartup(...) create an AnnotationConfigWebApplicationContext, register your @configuration classes, and pass that context to DispatcherServlet / ContextLoaderListener; then fetch beans from that context as needed.

Your current code with @RequiredArgsConstructor and constructor injection will fail during deployment because the servlet container expects a no-arg constructor when instantiating WebApplicationInitializer implementations.

The fix I suggested earlier (obtaining SteveProperties from the Spring context after creation) follows the documented pattern and will work correctly.


🧠 Learnings used
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Comment on lines +44 to +46
username: admin
password: 1234
# The header key and value for Web API access using API key authorization.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove insecure default credentials from default profile

Shipping admin/1234 in application.yml is unsafe and risks accidental exposure.

Options:

  • Move steve.auth block to application-dev.yml and keep defaults empty in application.yml.
  • Or make them env-driven with a non-functional default and add a startup check.

Minimal change here:

 steve:
   auth:
     username: admin
-    password: 1234
+    password: "change-me" # Override via env/property for any non-dev environment

Follow-up: add a startup guard to fail if password is "change-me". Want a patch for that?

📝 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
username: admin
password: 1234
# The header key and value for Web API access using API key authorization.
steve:
auth:
username: admin
password: "change-me" # Override via env/property for any non-dev environment
# The header key and value for Web API access using API key authorization.
🤖 Prompt for AI Agents
In steve/src/main/resources/application.yml lines 44-46, remove the hardcoded
insecure default credentials (admin/1234) and replace them with either empty
values or placeholders that read from environment vars (e.g., leave
username/password blank or set to a non-functional placeholder like "change-me"
and reference env-driven properties), or move the steve.auth block into
application-dev.yml so defaults are not shipped; additionally, implement a
startup guard (in application bootstrap) that checks the resolved password and
fails fast if it is empty or equals "change-me" to prevent running with a weak
default.

@juherr juherr force-pushed the backport/steve-1822 branch from 5da65ca to df893a3 Compare September 29, 2025 11:24
Copy link

@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: 7

Caution

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

⚠️ Outside diff range comments (4)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)

104-141: Do not share a single HttpUriRequest across threads; build per iteration.
Reusing the same req in multiple threads is not thread-safe and can cause nondeterministic failures. Build the request inside toRepeat().

Apply:

@@
-        var runnable = new StressTester.Runnable() {
+        var runnable = new StressTester.Runnable() {
@@
             public void toRepeat() {
@@
-                try {
-                    httpClient.execute(req, httpResponse -> {
+                try {
+                    var req = RequestBuilder.post()
+                            .setUri(soapPath)
+                            .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
+                            .setEntity(new StringEntity(body, contentType))
+                            .build();
+                    httpClient.execute(req, httpResponse -> {
                         if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                             throw new RuntimeException("Not OK");
                         }
                         return null;
                     });
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }

Also remove the earlier top-level req declaration (Lines 97–101) when applying this change.

steve-jooq/pom.xml (1)

76-93: Pin Flyway and jOOQ plugin versions
Both flyway-maven-plugin (steve-jooq/pom.xml, lines 76–93) and jooq-codegen-maven (lines 131–139) lack <version> declarations and aren’t managed in the parent <pluginManagement>. Add explicit <version>…</version> entries or centralize their versions to ensure reproducible builds.

pom.xml (1)

137-141: Pin Lombok version and unify across POM
Define a <lombok.version> property (e.g. 1.18.34) in the <properties> section and reference ${lombok.version} in both the org.projectlombok:lombok dependency (provided scope) and the <annotationProcessorPaths> entry to avoid unresolved property errors and ensure consistent versioning.

steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (1)

133-145: Unconditional transactionId > 0 assertion breaks for CONCURRENT_TX.

When expected is CONCURRENT_TX, a new transaction must not be created; some stacks return 0 or reuse the existing id. Make the assertion conditional and assert invariants instead.

Apply this diff:

     private void sendStartTx(CentralSystemService client, String chargeBoxId, AuthorizationStatus expected) {
         var start = client.startTransaction(
                 new StartTransactionRequest()
                         .withConnectorId(2)
                         .withIdTag(REGISTERED_OCPP_TAG)
                         .withTimestamp(OffsetDateTime.now())
                         .withMeterStart(0),
                 chargeBoxId);
         assertThat(start).isNotNull();
         assertThat(start.getIdTagInfo().getStatus()).isEqualTo(expected);
-        assertThat(start.getTransactionId()).isGreaterThan(0);
-        assertThat(databasePreparer.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction())
-                .isTrue();
+        if (expected == AuthorizationStatus.ACCEPTED) {
+            assertThat(start.getTransactionId()).isGreaterThan(0);
+            assertThat(databasePreparer.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()).isTrue();
+        } else if (expected == AuthorizationStatus.CONCURRENT_TX) {
+            // Assert invariants: tag remains in exactly one active transaction; do not require a positive new id.
+            var tag = databasePreparer.getOcppTagRecord(REGISTERED_OCPP_TAG);
+            assertThat(tag.getInTransaction()).isTrue();
+            assertThat(tag.getActiveTransactionCount()).isEqualTo(1);
+        }
🧹 Nitpick comments (57)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (1)

27-30: Note mapping usage before renaming name field

  • getName() is used in Jooq mappers (TransactionMapper, OcppServerRepositoryImpl) to map DB literals; renaming it to wireName would require updating those mappings.
  • No Jackson-based serialization of this enum found; only apply @JsonValue/@JsonCreator if you intend to emit lower-case values in JSON.
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (5)

63-65: Standalone run now relies on external config; fail fast if jsonPath is missing.

Dropping the path constructor means main() depends on StressTest to populate jsonPath. Add a preflight null‑check to surface misconfiguration early.

Apply:

@@
     protected void attackInternal() throws Exception {
+        java.util.Objects.requireNonNull(jsonPath, "jsonPath must be configured (e.g., via properties) for JSON OCPP endpoint");
         final var idTags = getRandomStrings(ID_TAG_COUNT);

85-92: Process BootNotification before subsequent traffic.

prepare(...) enqueues but you don’t call process() here. If the server requires a successful Boot before other messages, flush now to reduce flakiness.

                         bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED),
                         error -> fail());
+                // Ensure boot is acknowledged before proceeding
+                chargePoint.process();

124-128: Test assumes authorization will NOT be accepted — brittle across environments.

Random id tags may be accepted in some setups. Make the assertion resilient (e.g., assert non‑null status or allow both outcomes).

-                        response -> assertThat(response.getIdTagInfo().getStatus())
-                                .isNotEqualTo(AuthorizationStatus.ACCEPTED),
+                        response -> assertThat(response.getIdTagInfo().getStatus()).isNotNull(),

145-147: Guard on missing transactionId before sending CHARGING/Meter/Stop.

If StartTransaction is rejected or not processed, transactionId stays -1 and later requests are invalid. Fail fast and skip the rest of the cycle.

                 // wait for StartTransactionResponse to arrive, since we need the transactionId from now on
                 chargePoint.process();
+                if (transactionId.get() < 0) {
+                    fail("StartTransactionResponse did not provide a transactionId");
+                    return;
+                }

191-193: Null‑safety and ThreadLocal hygiene in afterRepeat.

Avoid NPEs on setup failures and clear the ThreadLocal to prevent leaks.

-                threadLocalChargePoint.get().close();
+                var cp = threadLocalChargePoint.get();
+                if (cp != null) {
+                    cp.close();
+                }
+                threadLocalChargePoint.remove();
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (3)

27-27: Make PATH_INFIX a constant and consider trailing-slash edge cases

Declare as static final and add coverage where pathInfix lacks a trailing slash to prevent coupling to a specific formatting.

Apply this minimal change:

-    private final String PATH_INFIX = "/steve/websocket/CentralSystemService/";
+    private static final String PATH_INFIX = "/steve/websocket/CentralSystemService/";

Optionally add tests where pathInfix is "/steve/websocket/CentralSystemService" (no trailing slash) to ensure getLastBitFromUrl either normalizes or clearly documents expectations.


32-32: Good API adaptation; reduce duplication via a tiny helper (or parameterize)

All call sites correctly pass the new pathInfix argument. To DRY the tests, introduce a helper and use it at each call site.

Option A — small helper (no new deps):

 public class OcppWebSocketHandshakeHandlerTest {
 
-    private final String PATH_INFIX = "/steve/websocket/CentralSystemService/";
+    private static final String PATH_INFIX = "/steve/websocket/CentralSystemService/";
+
+    private static String extract(String in) {
+        return OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+    }
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);
@@
-        var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
+        var out = extract(in);

Option B — parameterize (if junit-jupiter-params is on classpath): replace the repeated tests with a single @ParameterizedTest using @CsvSource. I can provide the full diff if you prefer this route.

Also applies to: 39-39, 46-46, 54-54, 61-61, 68-68, 75-75, 82-82, 89-89, 96-96


29-35: Add missing edge-case tests for getLastBitFromUrl
• URL without the infix at all → expect empty result
• URL containing query/fragment (e.g., “…/BBEI12?x=1#y”) → should strip extras and return “BBEI12”
pathInfix without trailing slash → behavior must match version with slash
null pathInfix → enforce and document defined behavior ([empty string] or [IllegalArgumentException])

(All existing calls now use the two-arg API; no single-arg usages remain.)

steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (4)

55-56: Avoid using the root context as the DispatcherServlet’s context.

Passing the root AnnotationConfigWebApplicationContext to DispatcherServlet collapses the parent/child hierarchy and can cause refresh/order issues. Prefer a dedicated child web context with the root as parent.

Recommended pattern:

  • Keep the current root context + ContextLoaderListener.
  • Create a new AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext();
  • webCtx.setParent(springContext);
  • Register/scan your MVC config in webCtx, then new DispatcherServlet(webCtx).

Please confirm which package/config class should be registered in the web context so we can provide an exact diff.


57-57: Normalize root mapping to avoid accidental /* or missing slash.

If rootMapping already contains /*, web.addMapping(rootMapping) may be invalid, and rootMapping + "*" below could yield /**/*. Normalize once and reuse.

Example:

@@
-        web.addMapping(props.getPaths().getRootMapping());
+        var root = normalizeServletPath(props.getPaths().getRootMapping()); // e.g., "/" or "/steve/"
+        web.addMapping(root);

Add helper:

+    private static String normalizeServletPath(String p) {
+        if (p == null || p.isBlank()) return "/";
+        // strip any trailing "/*"
+        if (p.endsWith("/*")) p = p.substring(0, p.length()-2);
+        // ensure trailing "/"
+        if (!p.endsWith("/")) p = p + "/";
+        return p;
+    }

62-62: Ensure SOAP mapping is consistent (/foo/* exactly once).

If soapMapping already includes /*, appending another /* results in /soap/*/*. Normalize similarly to root mapping.

Example:

-        cxf.addMapping(props.getPaths().getSoapMapping() + "/*");
+        var soap = normalizeServletPath(props.getPaths().getSoapMapping());
+        cxf.addMapping(soap + "*");

65-72: Register Spring Security filter before others (isMatchAfter=false) and consider explicit DelegatingFilterProxy instance.

addMappingForUrlPatterns(..., true, ...) appends after existing filters. Security is typically expected to run first. Use false. Also consider creating the proxy with the explicit bean name to avoid relying on defaults.

-                .addMappingForUrlPatterns(
-                        EnumSet.allOf(DispatcherType.class),
-                        true,
-                        root + "*");
+                .addMappingForUrlPatterns(
+                        EnumSet.allOf(DispatcherType.class),
+                        false,
+                        root + "*");

Optional:

-        servletContext
-                .addFilter(
-                        AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
-                        DelegatingFilterProxy.class.getName())
+        servletContext
+                .addFilter(
+                        AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
+                        new DelegatingFilterProxy(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME))
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (4)

57-57: Avoid leaking CXF proxies via ThreadLocal; clear after each run.

Create per‑thread client in beforeRepeat is fine, but call ThreadLocal.remove() in afterRepeat to release the proxy.

Apply:

@@
-            public void afterRepeat() {}
+            public void afterRepeat() {
+                txId.remove();
+                txRequest.remove();
+                chargeBoxId.remove();
+                client.remove();
+            }

Also applies to: 85-87


60-66: Add a null‑check for BootNotification response for clearer failures.

Guard before dereferencing getStatus().

-                var boot = client.get()
+                var boot = client.get()
                         .bootNotification(
                                 new BootNotificationRequest()
                                         .withChargePointVendor(getRandomString())
                                         .withChargePointModel(getRandomString()),
                                 chargeBoxId.get());
+                assertThat(boot).as("BootNotification response").isNotNull();
                 assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED);

75-77: Assert transactionId is present to catch server regressions early.

Currently, a null transactionId would only fail on equality checks if expectations differ. Assert it explicitly in sendStartTx.

@@
     private static Integer sendStartTx(CentralSystemService client, StartTransactionRequest req, String chargeBoxId) {
         var start = client.startTransaction(req, chargeBoxId);
         assertThat(start).isNotNull();
+        assertThat(start.getTransactionId()).as("StartTransaction.transactionId").isNotNull();
         return start.getTransactionId();
     }

Also applies to: 94-98


34-34: Be consistent with static imports for getRandomString.

Either drop the static import or use it consistently; prefer using the static import for brevity.

-                chargeBoxId.set(Helpers.getRandomString());
+                chargeBoxId.set(getRandomString());
@@
-                        .withIdTag(Helpers.getRandomString())
+                        .withIdTag(getRandomString())

Also applies to: 58-58, 71-71

steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (3)

90-96: Duplicate noAccess rules

You permit “noAccess” in two places. Consider keeping one to reduce rule churn.

Also applies to: 139-141


30-33: Imports cleanup

If adopting the DelegatingPasswordEncoder diff, keep DelegatingPasswordEncoder import; otherwise drop unused imports (PasswordEncoderFactories, DelegatingPasswordEncoder) to satisfy Spotless/checkstyle.


167-171: Use httpBasic() DSL instead of manual BasicAuthenticationFilter
Cleaner ordering and explicit entry-point configuration.

-        return http.securityMatcher(steveProperties.getPaths().getApiMapping() + "/**")
-                .csrf(k -> k.disable())
-                .sessionManagement(k -> k.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
-                .addFilter(new BasicAuthenticationFilter(apiAuthenticationManager, apiAuthenticationManager))
-                .authorizeHttpRequests(k -> k.anyRequest().authenticated())
-                .build();
+        return http.securityMatcher(steveProperties.getPaths().getApiMapping() + "/**")
+                .csrf(csrf -> csrf.disable())
+                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                .authenticationManager(apiAuthenticationManager)
+                .httpBasic(b -> b.authenticationEntryPoint(apiAuthenticationManager))
+                .authorizeHttpRequests(ahr -> ahr.anyRequest().authenticated())
+                .build();
steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (5)

254-257: Reuse existing connectorPkQuery instead of re-querying.

You already have connectorPkQuery in scope. Avoid the extra round‑trip here.

-            var connectorPk = getConnectorPkFromConnector(ctx, p.getChargeBoxId(), p.getConnectorId());
+            var connectorPk = connectorPkQuery
+                    .fetchOptional()
+                    .map(org.jooq.Record1::value1)
+                    .orElseThrow(() -> new SteveException.InternalError(
+                            "No connectorPk for %s/%d".formatted(p.getChargeBoxId(), p.getConnectorId())));

262-265: Prefer INSERT..SELECT over scalar subquery in INSERT.

Passing a subselect via set(CONNECTOR_STATUS.CONNECTOR_PK, connectorPkQuery) may compile to a scalar subquery; dialects differ on null/0‑row semantics. An INSERT..SELECT avoids ambiguity and lets you detect “0 rows” cleanly.

Refactor insertConnectorStatus (see separate comment) and keep this call site as-is; it will benefit automatically. Please verify on your target DB that the current form can’t insert NULL into CONNECTOR_PK when the subselect yields no row.


297-303: Same subselect-in-INSERT concern on stop path.

When TRANSACTION_PK is unknown, the scalar subquery may yield NULL; depending on the dialect this can cause a NOT NULL violation or silently insert a NULL. Using INSERT..SELECT will either insert 1 row or 0 rows deterministically.

No change needed here if insertConnectorStatus switches to INSERT..SELECT; please confirm behavior on your DB (expect 0 inserted rows when TRANSACTION_PK not found).


369-376: Make insertConnectorStatus use INSERT..SELECT and check the affected row count.

This avoids scalar subquery edge cases and gives visibility when nothing is inserted.

-    private void insertConnectorStatus(
-            DSLContext ctx,
-            SelectConditionStep<Record1<Integer>> connectorPkQuery,
-            LocalDateTime timestamp,
-            TransactionStatusUpdate statusUpdate) {
-        try {
-            ctx.insertInto(CONNECTOR_STATUS)
-                    .set(CONNECTOR_STATUS.CONNECTOR_PK, connectorPkQuery)
-                    .set(CONNECTOR_STATUS.STATUS_TIMESTAMP, timestamp)
-                    .set(CONNECTOR_STATUS.STATUS, statusUpdate.getStatus())
-                    .set(CONNECTOR_STATUS.ERROR_CODE, statusUpdate.getErrorCode())
-                    .execute();
-        } catch (Exception e) {
-            log.error("Exception occurred", e);
-        }
-    }
+    private void insertConnectorStatus(
+            DSLContext ctx,
+            SelectConditionStep<Record1<Integer>> connectorPkQuery,
+            LocalDateTime timestamp,
+            TransactionStatusUpdate statusUpdate) {
+        try {
+            int rows = ctx.insertInto(
+                            CONNECTOR_STATUS,
+                            CONNECTOR_STATUS.CONNECTOR_PK,
+                            CONNECTOR_STATUS.STATUS_TIMESTAMP,
+                            CONNECTOR_STATUS.STATUS,
+                            CONNECTOR_STATUS.ERROR_CODE)
+                    .select(ctx.select(
+                            org.jooq.impl.DSL.field(connectorPkQuery),
+                            org.jooq.impl.DSL.val(timestamp),
+                            org.jooq.impl.DSL.val(statusUpdate.getStatus()),
+                            org.jooq.impl.DSL.val(statusUpdate.getErrorCode())))
+                    .execute();
+            if (rows == 0) {
+                log.debug("Connector status not inserted: no connectorPk resolved by query.");
+            }
+        } catch (Exception e) {
+            log.error("Exception occurred", e);
+        }
+    }

Optionally generalize the parameter type to Select<? extends Record1> for broader call‑site flexibility.

Also applies to: 375-379


486-491: Confirm literal mapping for TransactionStopEventActor.

lookupLiteral(actor.getName()) assumes actor.getName() matches the DB literal exactly. If names can differ (case/alias), add a safe fallback.

-        return TransactionStopEventActor.lookupLiteral(actor.getName());
+        var mapped = TransactionStopEventActor.lookupLiteral(actor.getName());
+        return mapped != null ? mapped : TransactionStopEventActor.valueOf(actor.name());
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (2)

92-96: Prefer explicit SOAP content type constant (optional).
To avoid the Guava dependency here and be explicit, consider:

+import java.nio.charset.StandardCharsets;
@@
-        var contentType = ContentType.create(
-                MediaType.SOAP_XML_UTF_8.type(),
-                MediaType.SOAP_XML_UTF_8.charset().orNull());
+        var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8);

156-171: Escape XML values to avoid malformed envelopes.
Path, chargeBoxId, idTag can contain characters needing XML escaping; escape them before concatenation.

+import org.apache.commons.text.StringEscapeUtils;
@@
-                + "<To xmlns=\"http://www.w3.org/2005/08/addressing\">"
-                + path + "</To>"
+                + "<To xmlns=\"http://www.w3.org/2005/08/addressing\">"
+                + StringEscapeUtils.escapeXml11(path.toString()) + "</To>"
@@
-                + "</ReplyTo><chargeBoxIdentity xmlns=\"urn://Ocpp/Cs/2015/10/\">"
-                + chargeBoxId + "</chargeBoxIdentity>" + "</soap:Header>"
+                + "</ReplyTo><chargeBoxIdentity xmlns=\"urn://Ocpp/Cs/2015/10/\">"
+                + StringEscapeUtils.escapeXml11(chargeBoxId) + "</chargeBoxIdentity>" + "</soap:Header>"
@@
-                + "<idTag>"
-                + idTag + "</idTag>" + "<timestamp>"
+                + "<idTag>"
+                + StringEscapeUtils.escapeXml11(idTag) + "</idTag>" + "<timestamp>"

Optional: generate a unique MessageID per request (UUID) if the server expects uniqueness.

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

87-96: Simplify removal with Iterator to avoid extra variable

You’re already under the lock; using Iterator.remove() avoids the extra toRemove variable and a second structural operation.

-            SessionContext toRemove = null;
-            for (var context : endpointDeque) {
-                if (context.getSession().getId().equals(session.getId())) {
-                    toRemove = context;
-                    break;
-                }
-            }
-
-            if (toRemove != null) {
-                // 1. Cancel the ping task
-                toRemove.getPingSchedule().cancel(true);
-                // 2. Delete from collection
-                if (endpointDeque.remove(toRemove)) {
+            for (var it = endpointDeque.iterator(); it.hasNext(); ) {
+                var ctx = it.next();
+                if (ctx.getSession().getId().equals(session.getId())) {
+                    // 1. Cancel the ping task
+                    ctx.getPingSchedule().cancel(true);
+                    // 2. Delete from collection
+                    it.remove();
                     log.debug(
                             "A SessionContext is removed for chargeBoxId '{}'. Store size: {}",
                             chargeBoxId,
                             endpointDeque.size());
-                }
-                // 3. Delete empty collection from lookup table in order to correctly calculate
-                // the number of connected chargeboxes with getNumberOfChargeBoxes()
-                if (endpointDeque.isEmpty()) {
-                    lookupTable.remove(chargeBoxId);
-                }
+                    // 3. Remove empty deque from map to keep counts correct
+                    if (endpointDeque.isEmpty()) {
+                        lookupTable.remove(chargeBoxId);
+                    }
+                    break;
+                }
             }

Also applies to: 101-107, 108-123


131-141: Ensure selection strategy is fast/non-blocking while lock is held

getSession holds the per-id lock during wsSessionSelectStrategy.getSession(...). Confirm strategies don’t block or perform I/O; if any might, consider extracting minimal data under lock and releasing before heavier work.


166-168: Shallow copy exposes mutable, non-thread-safe Deques

ImmutableMap.copyOf(lookupTable) freezes keys->values mapping but values remain mutable ArrayDeque. Callers iterating without the lock can see races. Consider returning sizes or deep-copied, unmodifiable Deques, or document the locking contract.

steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)

31-31: New constructor usage is fine; add a test for custom regex path

Instantiation with (String) null is OK. Add one test that injects a restrictive pattern and asserts both accept/reject cases to cover the config-driven path.

steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

76-76: Defensive default for version to avoid null/blank.

If steveProperties.getSteveVersion() can be null in some runs (tests/dev), set a safe default.

Apply this diff:

+import java.util.Objects;
@@
-                        .version(steveProperties.getSteveVersion()))
+                        .version(Objects.requireNonNullElse(steveProperties.getSteveVersion(), "dev")))
steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (1)

91-94: Likely wrong argument to InternetChecker.

Passing steveProperties.getSteveVersion() to InternetChecker.isInternetAvailable(...) looks semantically off. Confirm the expected parameter. If the intent is “skip GitHub check when offline,” prefer a no‑arg probe or a known URL/host rather than a version string.

Example:

if (InternetChecker.isInternetAvailable()) {
    return new GithubReleaseCheckService(steveProperties);
}

or (if a URL is required) use a configured endpoint property instead of the version.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (1)

35-37: Single validator only: confirm list/collection use sites.

Removing the list validator means callers must annotate elements (TYPE_USE) rather than collections. Verify forms/endpoints that previously relied on list validation now apply per‑element constraints.

If needed, add examples/tests demonstrating List<@ChargeBoxId String> on DTOs to prevent regressions.

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

59-59: Property name likely a typo (“executiveLogging”).

jOOQ setting is “execute logging.” If SteveProperties intentionally uses isExecutiveLogging(), fine—but consider renaming to isExecuteLogging() for clarity and consistency.

steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (1)

104-113: Skip unpublished endpoints defensively

If an EndpointImpl hasn’t been published yet, getServer() may be null. Skip with a warning to avoid NPEs.

     private static Map<String, Server> initServerLookupMap(List<EndpointImpl> endpoints) {
         var actualServers = new HashMap<String, Server>();
         for (var endpoint : endpoints) {
-            var server = endpoint.getServer();
-            var serverNamespace =
-                    server.getEndpoint().getEndpointInfo().getName().getNamespaceURI();
-            actualServers.put(serverNamespace, server);
+            var server = endpoint.getServer();
+            if (server == null) {
+                log.warn("Endpoint {} not published yet; skipping", endpoint);
+                continue;
+            }
+            var serverNamespace = server.getEndpoint().getEndpointInfo().getName().getNamespaceURI();
+            actualServers.put(serverNamespace, server);
         }
         return actualServers;
     }
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (1)

46-48: Optional: precompile default pattern and surface bad regex early

Minor ergonomics:

  • Use a precompiled DEFAULT_PATTERN for the fallback.
  • Optionally catch PatternSyntaxException to fail-fast with a config error message.
-    public ChargeBoxIdValidator(String regexFromConfig) {
-        this.pattern = Pattern.compile(Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig);
-    }
+    private static final Pattern DEFAULT_PATTERN = Pattern.compile(REGEX);
+
+    public ChargeBoxIdValidator(String regexFromConfig) {
+        if (Strings.isNullOrEmpty(regexFromConfig)) {
+            this.pattern = DEFAULT_PATTERN;
+        } else {
+            // Let PatternSyntaxException surface or wrap with a clearer config message if preferred
+            this.pattern = Pattern.compile(regexFromConfig);
+        }
+    }
steve/src/main/resources/application.yml (1)

26-31: Verify custom logging.appender usage.

Spring Boot does not recognize logging.appender; if this is only for logback-spring.xml property injection it’s fine, otherwise it’s a no-op. Consider moving under a steve.logging.* namespace to avoid confusion.

steve-ui-jsp/pom.xml (2)

46-54: Align JSTL API/impl versions.

You declare API 3.0.2 and implementation 3.0.1; prefer matching versions to avoid subtle classpath issues.

     <dependency>
       <groupId>org.eclipse.jetty.ee10</groupId>
       <artifactId>jetty-ee10-apache-jsp</artifactId>
     </dependency>
-    <dependency>
-      <groupId>jakarta.servlet.jsp.jstl</groupId>
-      <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
-      <version>3.0.2</version>
-    </dependency>
-    <dependency>
-      <groupId>org.glassfish.web</groupId>
-      <artifactId>jakarta.servlet.jsp.jstl</artifactId>
-      <version>3.0.1</version>
-    </dependency>
+    <dependency>
+      <groupId>jakarta.servlet.jsp.jstl</groupId>
+      <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
+      <version>3.0.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.glassfish.web</groupId>
+      <artifactId>jakarta.servlet.jsp.jstl</artifactId>
+      <version>3.0.2</version>
+    </dependency>

96-105: Resource layout and JSPC fragment: verify packaging integration.

Copying WEB-INF/web.xml into classes and generating WEB-INF/web.xml-frag is fine, but ensure the top-level WAR build merges or prefers the fragment appropriately to avoid duplicate descriptors.

If only the fragment is needed, consider excluding the full web.xml from resources here and let the WAR module own it.

Also applies to: 129-132

steve-jooq/pom.xml (1)

241-303: TC-based codegen profile looks solid; minor consistency nits.

  • Consider pinning containerImage to a digest for reproducibility.
  • Ensure ${mysql.version} matches the chosen MySQL 8.x line.
pom.xml (1)

150-152: Restrict external repositories unless strictly required.

JitPack increases supply‑chain risk and slows builds. If no artifacts are sourced from JitPack, remove this repository entry.

-    <repository>
-      <id>JitPack</id>
-      <url>https://jitpack.io</url>
-    </repository>
steve/pom.xml (2)

99-107: Prefer relying on spring-boot-starter-jetty; drop direct jetty-server/jetty-rewrite unless you need APIs not in the starter.

Direct Jetty deps can cause version skew with Boot’s managed stack. Keep them only if you truly require them.

-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-rewrite</artifactId>
-    </dependency>

124-175: Trim duplicate test dependencies; spring-boot-starter-test already brings JUnit, Mockito, AssertJ, JSONPath.

Remove the explicit artifacts to reduce version drift and speed up dependency resolution.

     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.springframework.security</groupId>
-      <artifactId>spring-security-test</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty.websocket</groupId>
-      <artifactId>jetty-websocket-jetty-client</artifactId>
-      <scope>test</scope>
-    </dependency>
+    <!-- keep security + jetty client as needed -->
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>jetty-websocket-jetty-client</artifactId>
+      <scope>test</scope>
+    </dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-engine</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-params</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-junit-jupiter</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>net.bytebuddy</groupId>
-      <artifactId>byte-buddy</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.jayway.jsonpath</groupId>
-      <artifactId>json-path</artifactId>
-      <scope>test</scope>
-    </dependency>
steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (1)

60-66: Avoid recreating JAX‑WS client on every call; cache per thread.

Creating a proxy each time is expensive and can skew stress timings. Initialize once in beforeRepeat and reuse in toRepeat.

@@
-        var runnable = new StressTester.Runnable() {
+        var runnable = new StressTester.Runnable() {
@@
-            private final ThreadLocal<String> threadLocalChargeBoxId = new ThreadLocal<>();
+            private final ThreadLocal<String> threadLocalChargeBoxId = new ThreadLocal<>();
+            private final ThreadLocal<ocpp.cs._2015._10.CentralSystemService> tlClient = new ThreadLocal<>();
@@
             @Override
             public void beforeRepeat() {
-                var client = getForOcpp16(soapPath);
+                tlClient.set(getForOcpp16(soapPath));
                 var localRandom = ThreadLocalRandom.current();
@@
             @Override
             public void toRepeat() {
-                var client = getForOcpp16(soapPath);
+                var client = tlClient.get();

Also applies to: 79-81

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

75-85: Don’t spin up an ApplicationContext just to get a Validator.

This leaks a context and adds overhead. Instantiate LocalValidatorFactoryBean directly.

 @BeforeEach
-    public void setUp() {
-        var ctx = new AnnotationConfigApplicationContext();
-        ctx.register(ValidationConfig.class);
-        ctx.register(SteveProperties.class);
-        ctx.refresh();
-
-        var validator = ctx.getBean(Validator.class);
-
-        mockMvc = buildMockMvc(MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
-                .setValidator(validator));
-    }
+    public void setUp() {
+        var validator = new LocalValidatorFactoryBean();
+        validator.afterPropertiesSet();
+        mockMvc = buildMockMvc(
+                MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
+                        .setValidator(validator));
+    }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)

65-71: Micro‑opt: reuse the SOAP client instance.

You call getForOcpp16(soapPath) twice before creating the thread‑local client. Slightly reduce overhead by reusing a local var.

-        var boot = getForOcpp16(soapPath)
+        var client = getForOcpp16(soapPath);
+        var boot = client
                 .bootNotification( ... );
@@
-        var start = getForOcpp16(soapPath)
+        var start = client
                 .startTransaction( ... );
@@
-                threadLocalClient.set(getForOcpp16(soapPath));
+                threadLocalClient.set(getForOcpp16(soapPath));

Also applies to: 73-81, 89-93

steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

61-63: Naming: jsonPath actually holds a WebSocket URL.

Consider renaming jsonPath → wsPath for clarity in follow‑up PRs. No behavior change.

Also applies to: 82-83

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

136-146: Harden chargeBoxId extraction from URL.

Normalize slashes, take only the first segment after pathInfix, and decode percent‑encoded IDs to avoid false matches.

-    public static String getLastBitFromUrl(String pathInfix, String input) {
-        if (Strings.isNullOrEmpty(input)) {
-            return "";
-        }
-
-        var index = input.indexOf(pathInfix);
-        if (index == -1) {
-            return "";
-        }
-        return input.substring(index + pathInfix.length());
-    }
+    public static String getLastBitFromUrl(String pathInfix, String input) {
+        if (Strings.isNullOrEmpty(pathInfix) || Strings.isNullOrEmpty(input)) {
+            return "";
+        }
+        var normInfix = "/" + pathInfix.replaceAll("^/+|/+$", "") + "/";
+        var normInput = input; // raw path, no query
+        var index = normInput.indexOf(normInfix);
+        if (index < 0) {
+            return "";
+        }
+        var tail = normInput.substring(index + normInfix.length());
+        var slash = tail.indexOf('/');
+        var id = (slash >= 0) ? tail.substring(0, slash) : tail;
+        return java.net.URLDecoder.decode(id, java.nio.charset.StandardCharsets.UTF_8);
+    }

125-134: Minor: pre‑index endpoints by protocol version for O(n) → O(1) selection.

If list grows, consider a Map<String, AbstractWebSocketEndpoint> keyed by version value.

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

49-51: Unused constant (optional)

PING_INTERVAL is defined but unused; remove it or use it where appropriate to avoid drift.

-    public static final Duration PING_INTERVAL = Duration.ofMinutes(15);
     public static final Duration IDLE_TIMEOUT = Duration.ofHours(2);
     public static final int MAX_MSG_SIZE = 8_388_608; // 8 MB for max message size
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)

101-108: Suppress unchecked cast in factory (optional)

Add a suppression to avoid IDE noise on the generic cast.

-    private static <T> T createBean(URI endpointAddress, Class<T> serviceClass) {
+    @SuppressWarnings("unchecked")
+    private static <T> T createBean(URI endpointAddress, Class<T> serviceClass) {
         var f = new JaxWsProxyFactoryBean();
         f.setBindingId(SOAPBinding.SOAP12HTTP_BINDING);
         f.getFeatures().add(new WSAddressingFeature());
         f.setAddress(endpointAddress.toString());
         f.setServiceClass(serviceClass);
         return (T) f.create();
     }
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (3)

90-94: Make teardown null‑safe.

Protect against rare init failures so cleanup doesn’t NPE.

     @AfterEach
     public void teardown() {
-        databasePreparer.cleanUp();
+        if (databasePrearer != null) {
+            databasePreparer.cleanUp();
+        }
     }

64-71: Commented Testcontainers block: remove or enable.

Either delete the dead code or enable the MySQL container with @Serviceconnection to keep tests hermetic in CI.

If you plan to enable it, confirm docker is available in your CI runners and that the connection props are aligned with SteveProperties. I can provide a ready-to-drop config if needed.


73-81: Prefer constructor injection for test dependencies (optional).

Constructor injection improves immutability and makes field initialization explicit; field injection works but is less clear.

-    @Autowired
-    private ServerProperties serverProperties;
-    @Autowired
-    private SteveProperties steveProperties;
-    @Autowired
-    private DSLContext dslContext;
+    private final ServerProperties serverProperties;
+    private final SteveProperties steveProperties;
+    private final DSLContext dslContext;
+
+    @Autowired
+    public Issue73FixTest(ServerProperties serverProperties,
+                          SteveProperties steveProperties,
+                          DSLContext dslContext) {
+        this.serverProperties = serverProperties;
+        this.steveProperties = steveProperties;
+        this.dslContext = dslContext;
+    }
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (2)

156-183: Guard MySQL‑specific FK toggling by SQL dialect.

SET FOREIGN_KEY_CHECKS is MySQL/MariaDB‑specific. Gate it by dialect to avoid failures on other DBs; fall back to plain TRUNCATE otherwise.

+import org.jooq.SQLDialect;
@@
-            var internalCtx = DSL.using(configuration);
-            internalCtx.execute("SET FOREIGN_KEY_CHECKS=0");
-            tables.forEach(t -> internalCtx.truncate(t).execute());
-            internalCtx.execute("SET FOREIGN_KEY_CHECKS=1");
+            var internalCtx = DSL.using(configuration);
+            var family = internalCtx.dialect().family();
+            if (family == SQLDialect.MYSQL || family == SQLDialect.MARIADB) {
+                internalCtx.execute("SET FOREIGN_KEY_CHECKS=0");
+                tables.forEach(t -> internalCtx.truncate(t).execute());
+                internalCtx.execute("SET FOREIGN_KEY_CHECKS=1");
+            } else {
+                tables.forEach(t -> internalCtx.truncate(t).execute());
+            }

149-154: Null‑safe lookup for charge box details.

Avoid potential NPE when the id isn’t present; throw an explicit IllegalArgumentException.

-    public ChargePoint.Details getCBDetails(String chargeboxID) {
+    public ChargePoint.Details getCBDetails(String chargeboxID) {
         var impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl(dslContext));
         var pkMap = impl.getChargeBoxIdPkPair(Collections.singletonList(chargeboxID));
-        int pk = pkMap.get(chargeboxID);
-        return impl.getDetails(pk).orElseThrow();
+        Integer pk = pkMap.get(chargeboxID);
+        if (pk == null) {
+            throw new IllegalArgumentException("Unknown chargeBoxId: " + chargeboxID);
+        }
+        return impl.getDetails(pk).orElseThrow();
     }

Comment on lines +64 to 67
public SecurityFilterChain securityFilterChain(HttpSecurity http, SteveProperties steveProperties)
throws Exception {
var prefix = steveProperties.getPaths().getManagerMapping();

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Authorization on “own password” endpoints won’t match without MVC matchers

Curly‐brace templates like {name} only work with MvcRequestMatcher; as written these matchers won’t fire, and POSTs will fall through to “ADMIN only”, blocking users from changing their own passwords.

Apply:

@@
-    public SecurityFilterChain securityFilterChain(HttpSecurity http, SteveProperties steveProperties)
+    public SecurityFilterChain securityFilterChain(
+            HttpSecurity http,
+            SteveProperties steveProperties,
+            org.springframework.web.servlet.handler.HandlerMappingIntrospector introspector)
             throws Exception {
         var prefix = steveProperties.getPaths().getManagerMapping();
+        var mvc = new org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher.Builder(introspector);
@@
-                        .requestMatchers(prefix + "/webusers/password/{name}")
-                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
-                        .requestMatchers(prefix + "/webusers/apipassword/{name}")
-                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
+                        .requestMatchers(mvc.pattern(prefix + "/webusers/password/{name}"))
+                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))
+                        .requestMatchers(mvc.pattern(prefix + "/webusers/apipassword/{name}"))
+                        .access(new WebExpressionAuthorizationManager("#name == authentication.name"))

Also applies to: 101-104

🤖 Prompt for AI Agents
In steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
around lines 64-67 (and similarly lines 101-104), the authorization rules use
path patterns with curly-brace templates which require MvcRequestMatcher (or
mvcMatchers) to match correctly; update the security matchers to use mvc-based
matching (e.g., HttpSecurity.mvcMatcher/mvcMatchers or create MvcRequestMatcher
instances via a HandlerMappingIntrospector) for those "{...}" endpoints so the
"own password" POST/PUT routes are matched correctly and not routed to the
ADMIN-only rule.

Comment on lines +74 to +77
var environment = app.getEnvironment();
assertThat(environment.getActiveProfiles()).hasSize(1).contains("test");
assertThat(environment.getProperty("steve.ocpp.auto-register-unknown-stations"))
.isEqualTo("true");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure test override sets auto‑register property to true.

This assertion will fail unless tests set steve.ocpp.auto-register-unknown-stations=true.


🏁 Script executed:

#!/bin/bash
# Verify test override exists
rg -n -g '!**/target/**' -P --type=yaml 'steve:\s*\n\s*ocpp:\s*\n\s*auto-register-unknown-stations:\s*true' || true
rg -n -P --type=properties '^steve\.ocpp\.auto-register-unknown-stations\s*=\s*true$' || true
fd -HI 'application-test.*\.(ya?ml|properties)' || true

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Display test configuration to check for ocpp auto-register override
sed -n '1,200p' steve/src/test/resources/application-test.yml

Length of output: 127


Add test override for auto-register-unknown-stations
The application-test.yml (steve/src/test/resources/application-test.yml) doesn’t set steve.ocpp.auto-register-unknown-stations, so the assertion in StressTest.java will fail. Add the following under the root of that file:

steve:
  ocpp:
    auto-register-unknown-stations: true
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/StressTest.java around lines 74 to 77,
the test asserts that the property steve.ocpp.auto-register-unknown-stations
equals "true" but application-test.yml does not define this property; add the
property to steve/src/test/resources/application-test.yml at the root under
steve.ocpp with value true so the test environment picks it up and the assertion
passes.

Comment on lines +74 to 78
public void prepare() {
truncateTables(dslContext, steveProperties.getJooq().getSchemaSource());
insertChargeBox(dslContext);
insertOcppIdTag(dslContext);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify SteveProperties.jooq.schemaSource is the intended property.

Double‑check that getJooq().getSchemaSource() maps to the generated schema name used by jOOQ meta; some configs expose it as “schema”. If misaligned, truncation will fail to find the schema.


🏁 Script executed:

#!/bin/bash
# Inspect SteveProperties and usages of 'schemaSource' vs 'schema'
rg -nP "class\s+SteveProperties\b" -C3 --type=java
rg -nP "schemaSource\b" -n -C2
rg -nP "getSchemaSource\s*\(" -n -C2
rg -nP "steve\.jooq\." -n -C2 --type=java
rg -nP "steve:\s*jooq:" -n -C3 --type=yaml -g '!**/target/**'

Length of output: 14516


Use the runtime schema (Jooq.schema) instead of schemaSource in __DatabasePreparer__.

In both prepare() and cleanUp(), replace

truncateTables(dslContext, steveProperties.getJooq().getSchemaSource());

with something like

String schema = Optional.ofNullable(steveProperties.getJooq().getSchema())
                        .orElse(steveProperties.getJooq().getSchemaSource());
truncateTables(dslContext, schema);

so truncation targets the actual database schema.

🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java around
lines 74 to 78, the truncateTables call uses
steveProperties.getJooq().getSchemaSource() instead of the runtime schema;
update both prepare() and cleanUp() so you compute String schema =
Optional.ofNullable(steveProperties.getJooq().getSchema()).orElse(steveProperties.getJooq().getSchemaSource())
and pass that schema to truncateTables(dslContext, schema) so truncation targets
the actual database schema at runtime.

@juherr juherr force-pushed the backport/steve-1822 branch from df893a3 to f7b7018 Compare September 29, 2025 12:41
@juherr juherr force-pushed the backport/steve-1822 branch from b450472 to fae552f Compare September 29, 2025 12:52
Copy link

@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: 7

Caution

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

⚠️ Outside diff range comments (5)
Dockerfile (1)

73-75: Healthcheck path likely wrong; probes “/” instead of a known healthy endpoint

With server.servlet.context-path=/steve and Boot, / may 404. Prefer /actuator/health (if Actuator enabled) or /steve/.

Apply one of:

-HEALTHCHECK --interval=10s --timeout=3s --retries=20 \
-  CMD curl -fsS "http://127.0.0.1:${PORT}/" || exit 1
+HEALTHCHECK --interval=10s --timeout=3s --retries=20 \
+  CMD curl -fsS "http://127.0.0.1:${PORT}/actuator/health" || exit 1

or:

-  CMD curl -fsS "http://127.0.0.1:${PORT}/" || exit 1
+  CMD curl -fsS "http://127.0.0.1:${PORT}/steve/" || exit 1
steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1)

89-97: Use URI.resolve instead of string concatenation
Replace the connectionPath String field with a URI connectionUri built via a helper that handles trailing-slash logic and encodes the segment. In OcppJsonChargePoint:

  • Change
    private final String connectionPath;
    to
    private final URI connectionUri;
  • In the constructor, replace
    this.connectionPath = pathPrefix + chargeBoxId;
    with
    this.connectionUri = resolvePath(pathPrefix, chargeBoxId);
  • In start(), replace
    new URI(connectionPath)
    with
    connectionUri
  • Add a static helper, e.g.:
    private static URI resolvePath(URI base, String segment) {
        String s = segment.startsWith("/") ? segment.substring(1) : segment;
        String sep = base.getPath() != null && base.getPath().endsWith("/") ? "" : "/";
        return base.resolve(sep + s);
    }
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)

191-193: Guard against NPE in afterRepeat

If beforeRepeat fails, threadLocalChargePoint may be null; close() will NPE. Add a null check and clear the ThreadLocal.

-    public void afterRepeat() {
-        threadLocalChargePoint.get().close();
-    }
+    public void afterRepeat() {
+        var cp = threadLocalChargePoint.get();
+        if (cp != null) {
+            try { cp.close(); } finally { threadLocalChargePoint.remove(); }
+        }
+    }
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

72-79: Bind jOOQ to Spring-managed transactions.

Wrap the DataSource with TransactionAwareDataSourceProxy; otherwise jOOQ may obtain connections outside the active transaction.

+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
@@
-                .set(new DataSourceConnectionProvider(
-                        // new org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy()
-                        dataSource))
+                .set(new DataSourceConnectionProvider(
+                        new TransactionAwareDataSourceProxy(dataSource)))
                 .set(settings);
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (1)

149-169: Avoid logging full SOAP payloads in production; gate LoggingFeature and limit size

Payloads can contain PII (e.g., idTags). Make logging opt-in and cap size.

Option A (property-gated):

+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@
-    @Bean
+    @Bean
+    @ConditionalOnProperty(prefix = "steve.logging.soap", name = "enabled", havingValue = "true")
     public LoggingFeature loggingFeature() {
         var feature = new LoggingFeature();
+        feature.setLimit(64 * 1024); // cap payload size
         feature.setSender(new Slf4jEventSender() {

Option B (dev-only):

-    @Bean
+    @Bean
+    @org.springframework.context.annotation.Profile("dev")
     public LoggingFeature loggingFeature() {

Ensure docs mention how to enable when needed. Based on learnings.

🧹 Nitpick comments (39)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (2)

30-31: Avoid confusion with Enum.name(): prefer a more specific field (e.g., wireName) and add a lookup

Rename the field to better reflect intent and reduce confusion with Enum.name(). Also consider a lookup helper for parsing.

-    private final String name;
+    private final String wireName;

Add the following helper (optional but useful for mapping/parsing):

// place below the field
public static TransactionStopEventActor fromWireName(String value) {
    for (var a : values()) {
        if (a.getWireName().equals(value)) return a;
    }
    throw new IllegalArgumentException("Unknown TransactionStopEventActor: " + value);
}

If JSON should use the wire value, annotate the getter with @jsonvalue (and add the Jackson import) or provide a custom serializer.


27-28: Annotate enum for JSON serialization
Jackson will still serialize this enum using its name() ("STATION"/"MANUAL"). If you intend to expose the lowercase literals (“station”/“manual”), add @JsonValue to getName() (or override toString()) in TransactionStopEventActor.java. The JOOQ mapping already uses actor.getName() with lookupLiteral(…), so no further DB-binding changes are required.

steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (2)

41-47: Bean lifecycle: cached log-path list may go stale after startup

With @component, the constructor caches paths once. If logging appenders/config change at runtime, the list won’t refresh. Consider computing on demand inside getPath() (or refreshing on a context/logback reconfig event). If the intent is “snapshot at boot,” please confirm and add a short comment.


93-101: Deduplicate paths, harden against invalid paths, and return unmodifiable list

Prevents duplicate entries (multiple appenders pointing at the same file), guards against odd InvalidPathException, and avoids accidental mutation.

-        var fileNameList = new ArrayList<Path>();
+        var filePaths = new java.util.LinkedHashSet<Path>();
         while (appenderIterator.hasNext()) {
             var appender = appenderIterator.next();
             var fileName = extractFileName(appender);
             if (fileName != null) {
-                fileNameList.add(Path.of(fileName));
+                try {
+                    filePaths.add(Path.of(fileName));
+                } catch (java.nio.file.InvalidPathException e) {
+                    log.debug("Skipping invalid log file path: {}", fileName, e);
+                }
             }
         }
-        return fileNameList;
+        return List.copyOf(filePaths);
steve/src/main/resources/application.yml (1)

20-20: Prefer utf8mb4 for MySQL/Connector/J

utf8 maps to MySQL’s legacy utf8mb3. Use utf8mb4 to match the DB creation example and avoid truncation.

Apply:

-        characterEncoding: utf8
+        characterEncoding: utf8mb4
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (1)

42-53: Normalize path joins to avoid accidental “//” or missing “/”

Direct string concatenation is brittle if mappings change trailing slashes.

Apply a small helper:

 public class EndpointInfo {
@@
-    public EndpointInfo(SteveProperties steveProperties) {
-        this.webInterface = new ItemsWithInfo(
-                "Access the web interface using", steveProperties.getPaths().getManagerMapping() + "/home");
-        this.ocppSoap = new ItemsWithInfo(
-                "SOAP endpoint for OCPP",
-                steveProperties.getPaths().getSoapMapping()
-                        + steveProperties.getPaths().getRouterEndpointPath());
-        this.ocppWebSocket = new ItemsWithInfo(
-                "WebSocket/JSON endpoint for OCPP",
-                steveProperties.getPaths().getWebsocketMapping()
-                        + steveProperties.getPaths().getRouterEndpointPath() + "/(chargeBoxId)");
-    }
+    public EndpointInfo(SteveProperties steveProperties) {
+        this.webInterface = new ItemsWithInfo(
+                "Access the web interface using",
+                joinPaths(steveProperties.getPaths().getManagerMapping(), "home"));
+        this.ocppSoap = new ItemsWithInfo(
+                "SOAP endpoint for OCPP",
+                joinPaths(steveProperties.getPaths().getSoapMapping(),
+                          steveProperties.getPaths().getRouterEndpointPath()));
+        this.ocppWebSocket = new ItemsWithInfo(
+                "WebSocket/JSON endpoint for OCPP",
+                joinPaths(steveProperties.getPaths().getWebsocketMapping(),
+                          steveProperties.getPaths().getRouterEndpointPath(), "(chargeBoxId)"));
+    }
+
+    private static String joinPaths(String... parts) {
+        String out = "";
+        for (String p : parts) {
+            if (p == null || p.isEmpty()) continue;
+            if (out.endsWith("/")) out = out.substring(0, out.length() - 1);
+            if (!p.startsWith("/")) p = "/" + p;
+            out += p;
+        }
+        return out;
+    }
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (1)

37-37: ChargeBoxId annotation validator update
Verified no remaining collection-level usages of @ChargeBoxId. Consider adding @Documented to the annotation for clarity:

+ import java.lang.annotation.Documented;
@@
- @Constraint(validatedBy = ChargeBoxIdValidator.class)
+ @Documented
+ @Constraint(validatedBy = ChargeBoxIdValidator.class)
steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1)

42-51: Timezone default in static initializer — optional alignment with JSON serialization

Setting JVM default TZ to UTC here is fine. Optionally also set Jackson’s timezone via config (e.g., spring.jackson.time-zone: UTC) to ensure consistent JSON timestamps across services, if not already in application.yml. Based on learnings.

steve/src/main/resources/logback-spring.xml (1)

31-35: Clarify prod appender selection to avoid misconfiguration.

Root in prod uses ${APPENDER} with default “CONSOLE”. If not overridden, prod logs won’t roll. Either default prod to FILE explicitly or validate allowed values.

Option A (explicit prod FILE):

 <springProfile name="default,prod">
-    <root level="${ROOT_LEVEL}">
-        <appender-ref ref="${APPENDER}"/>
-    </root>
+    <root level="${ROOT_LEVEL}">
+        <appender-ref ref="FILE"/>
+    </root>
 </springProfile>

Option B (keep property but validate):

  • Document accepted values: CONSOLE|FILE.
  • Consider Logback conditional <if> to fall back to FILE in prod if value is invalid.

Also applies to: 37-48

steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)

101-108: Optional: set CXF client timeouts to avoid hanging tests.

Default JAX‑WS/CXF client timeouts are infinite. Consider setting reasonable connect/read timeouts on the conduit for test stability.

steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

269-275: Nit: avoid recomputing ChargePointStatus.values() in loop.

Cache the array once for clarity and micro-efficiency.

-        for (int i = 0; i < ChargePointStatus.values().length; i++) {
-            var status = ChargePointStatus.values()[i];
+        var statuses = ChargePointStatus.values();
+        for (int i = 0; i < statuses.length; i++) {
+            var status = statuses[i];
             timeStatusMap.put(startingTime.plusMinutes(i), status);
         }
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1)

27-27: Good alignment with new 2‑arg API; consider parameterizing to reduce duplication.

Introduce a @ParameterizedTest over inputs/expected outputs to keep this suite compact and easier to extend.

Also applies to: 32-32, 39-39, 46-46, 54-54, 60-60, 67-67, 75-75, 82-82, 89-89, 95-95

steve-ocpp-transport-soap/pom.xml (3)

18-21: jakarta.servlet-api without an explicit version — verify it’s managed.

If not managed by steve-parent or an imported BOM, Maven will fail. Either add a version aligned to your target servlet level or keep relying on management if present.

Example (adjust to your managed version if any):

     <dependency>
       <groupId>jakarta.servlet</groupId>
       <artifactId>jakarta.servlet-api</artifactId>
+      <version>6.0.0</version> <!-- or 6.1.x, matching your runtime/BOM -->
       <scope>provided</scope>
     </dependency>

23-32: CXF starter version and container dependency — confirm version management and server presence.

  • No explicit version for cxf-spring-boot-starter-jaxws. Ensure it’s managed by a BOM/parent; otherwise pin it.
  • Excluding spring-boot-starter-tomcat: confirm another web server is brought in (Jetty/Undertow) or that you deploy as WAR to an external container; otherwise the app may lack an HTTP runtime.

Options:

  • Import CXF BOM in dependencyManagement to manage CXF artifacts; or
  • Pin the starter explicitly:
     <dependency>
       <groupId>org.apache.cxf</groupId>
       <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
+      <version>${cxf.version}</version>
       <exclusions>
         <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-tomcat</artifactId>
         </exclusion>
       </exclusions>
     </dependency>

34-36: cxf-rt-features-logging version alignment.

If you keep this explicit dependency, ensure its version is managed (via CXF BOM) or pin it to match the starter’s version to avoid classpath skew.

Example:

     <dependency>
       <groupId>org.apache.cxf</groupId>
       <artifactId>cxf-rt-features-logging</artifactId>
+      <version>${cxf.version}</version>
     </dependency>
steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (1)

91-94: Switch to SteveProperties aligns with the new config model.

Behavior unchanged except sourcing version from steveProperties; looks correct.

If desired, make this bean conditional (e.g., disable via property) to avoid a startup-time network check on certain deployments.

steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)

111-121: Connector indexing: confirm intent (0..N vs 1..N)

Loop runs 0..CONNECTOR_COUNT_PER_CHARGE_BOX inclusive, which emits N+1 notifications and includes connector 0, while later logic selects 1..N. If 0 is intentionally the EVSE/CP status, keep as-is; otherwise change to 1..N.

steve-core/pom.xml (1)

18-31: Don’t exclude spring-jcl from spring-webmvc under Spring Boot

spring-jcl is the bridge Spring uses for logging; excluding it can degrade logging/init. Keep the commons-logging exclusion, remove the spring-jcl exclusion.

       <exclusions>
         <!-- http://docs.spring.io/spring/docs/current/spring-framework-reference/html/overview.html#overview-not-using-commons-logging -->
         <exclusion>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
         </exclusion>
-        <exclusion>
-          <groupId>org.springframework</groupId>
-          <artifactId>spring-jcl</artifactId>
-        </exclusion>
       </exclusions>
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2)

55-58: Verify mapping patterns align with property semantics

If getRootMapping() may be a path prefix (e.g., "/api"), DispatcherServlet usually expects "/api/*" rather than "/api/". Confirm values include the correct wildcards; adjust addMapping() accordingly.

Also applies to: 72-73


49-58: Consider separate root and MVC contexts (optional)

Best practice is a root ApplicationContext (services/config) plus a child DispatcherServlet context (web MVC). Using one shared context works but couples concerns and slows Servlet init.

steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (2)

58-65: Lazy-init the SOAP client per thread to avoid per-iteration re-creation

Reuse the port across repeats for the same thread; reduces overhead in stress runs.

-            public void beforeRepeat() {
-                var client = getForOcpp16(soapPath);
-                tlClient.set(client);
+            public void beforeRepeat() {
+                var client = tlClient.get();
+                if (client == null) {
+                    client = getForOcpp16(soapPath);
+                    tlClient.set(client);
+                }

Optionally clear in afterRepeat():

-            public void afterRepeat() {}
+            public void afterRepeat() { tlClient.remove(); }

Also applies to: 80-83


95-104: Connector indexing: confirm 0..N inclusive vs 1..N

Same as the JSON test: loop includes connector 0 and emits N+1 notifications. Verify intent; adjust if only 1..N is desired.

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

22-36: Optional: simplify test context

SteveProperties isn’t required to create a LocalValidatorFactoryBean; you can drop it from the test context for faster setup.

-        ctx.register(SteveProperties.class);

Also applies to: 59-67, 74-85

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

61-64: Validate wsSessionSelectStrategy at startup (fail-fast or default).

If the property is missing/invalid, fromName(...) may throw or yield unexpected behavior. Prefer explicit validation with a clear error, or choose a documented default.

Would you like a small patch that validates and logs a clear configuration error?


129-144: Consider mapping “no session” to a domain-specific NotFound instead of InternalError.

Throwing InternalError suggests a server fault; “no session for chargeBoxId” is likely a normal not-found condition. Consider a NotFound/ClientError or Optional.


166-168: Optional: return a stable snapshot from getACopy().

ImmutableMap.copyOf(lookupTable) freezes keys but values are live Deques; consumers can see concurrent mutation. Return per-id copied Deques under lock for a consistent snapshot.

Example:

-    return ImmutableMap.copyOf(lookupTable);
+    var b = ImmutableMap.<String, Deque<SessionContext>>builder();
+    lookupTable.forEach((id, dq) -> {
+        var l = locks.get(id);
+        l.lock();
+        try {
+            b.put(id, new ArrayDeque<>(dq));
+        } finally {
+            l.unlock();
+        }
+    });
+    return b.build();
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (1)

34-38: LGTM: ctor injection + empty-is-valid guard.

Constructor-based DI and short-circuiting on null/empty align with Bean Validation patterns.

Minor: you can drop the Spring dependency here by using value == null || value.isEmpty() instead of CollectionUtils.isEmpty(value).

Also applies to: 46-50

steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)

73-75: Avoid hard-coding SQLDialect (support MariaDB/MySQL variants).

Detect dialect from configuration or JDBC metadata, or source it from properties.

Example:

-                .set(SQLDialect.MYSQL)
+                .set(steveProperties.getJooq().getDialect() != null
+                        ? SQLDialect.valueOf(steveProperties.getJooq().getDialect())
+                        : SQLDialect.MYSQL)
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)

64-66: Null-safety for path configuration (defensive).

If any of the path properties are unset, concatenation may yield NPE or malformed mappings. Consider defaults or requireNonNullElse.

-        var pathInfix = steveProperties.getPaths().getWebsocketMapping()
-                + steveProperties.getPaths().getRouterEndpointPath() + "/";
+        var paths = steveProperties.getPaths();
+        var base = java.util.Objects.requireNonNullElse(paths.getWebsocketMapping(), "/websocket/");
+        var router = java.util.Objects.requireNonNullElse(paths.getRouterEndpointPath(), "CentralSystemService");
+        var pathInfix = (base.endsWith("/") ? base : base + "/") + router + "/";

Also applies to: 67-73

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (1)

68-74: Tiny readability nit: rename temp variable.

str1trimmed to self-document intent.

-        String str1 = str.strip();
-        if (!str1.equals(str)) {
+        var trimmed = str.strip();
+        if (!trimmed.equals(str)) {
             return false;
         }
steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1)

63-68: Typo/semantics: executiveLogging likely meant executeLogging (jOOQ term)

jOOQ uses “execute logging”. Consider renaming for clarity and consistency; update usages and YAML key accordingly.

-    public static class Jooq {
-        private boolean executiveLogging;
+    public static class Jooq {
+        private boolean executeLogging;
         private String schemaSource;
         private @Nullable String schema;
     }

If you keep the current field name, align documentation/YAML to avoid confusion.

steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (1)

52-52: Prefer RANDOM_PORT + @LocalServerPort for isolation

DEFINED_PORT can collide on CI or local runs. RANDOM_PORT with @LocalServerPort is safer.

-@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

Add:

// add near other fields
@org.springframework.boot.test.web.server.LocalServerPort
private int port;

And let Helpers use this port (see next comment).

steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (3)

149-154: Avoid NPE if chargeboxID isn’t found

pkMap.get(chargeboxID) may return null. Fail fast with a clear message.

-        int pk = pkMap.get(chargeboxID);
-        return impl.getDetails(pk).orElseThrow();
+        Integer pk = pkMap.get(chargeboxID);
+        if (pk == null) {
+            throw new IllegalArgumentException("Unknown chargeBoxId: " + chargeboxID);
+        }
+        return impl.getDetails(pk).orElseThrow();

156-183: Make truncation toggle dialect-aware (MySQL/MariaDB only)

SET FOREIGN_KEY_CHECKS is MySQL/MariaDB specific. Guard it to keep tests portable.

-            var internalCtx = DSL.using(configuration);
-            internalCtx.execute("SET FOREIGN_KEY_CHECKS=0");
-            tables.forEach(t -> internalCtx.truncate(t).execute());
-            internalCtx.execute("SET FOREIGN_KEY_CHECKS=1");
+            var internalCtx = DSL.using(configuration);
+            boolean fkToggle = switch (internalCtx.dialect().family()) {
+                case MYSQL, MARIADB -> true;
+                default -> false;
+            };
+            if (fkToggle) internalCtx.execute("SET FOREIGN_KEY_CHECKS=0");
+            tables.forEach(t -> internalCtx.truncate(t).execute());
+            if (fkToggle) internalCtx.execute("SET FOREIGN_KEY_CHECKS=1");

To verify supported dialects in CI, please confirm which Testcontainers DBs are used in this module.


21-21: Drop Guava Sets in favor of JDK Set.of (Java 21 baseline)

Simplifies deps in tests and removes an unnecessary import.

-import com.google.common.collect.Sets;
+import java.util.Set;
@@
-        var skipList = Sets.newHashSet(
+        final var skipList = Set.of(
                 SchemaVersion.SCHEMA_VERSION,
                 Settings.SETTINGS,
                 OcppTagActivity.OCPP_TAG_ACTIVITY, // only a view
                 TRANSACTION // only a view
                 );

Also applies to: 157-163

pom.xml (2)

43-49: Align Lombok dependency with the configured annotation processor version

The dependency currently has no version while the processor uses ${lombok.version}. Pin the dependency to the same version to avoid mismatch.

   <properties>
@@
     <lombok.version>1.18.42</lombok.version>
@@
   <dependencies>
     <dependency>
       <groupId>org.jspecify</groupId>
       <artifactId>jspecify</artifactId>
       <version>1.0.0</version>
     </dependency>
     <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
+      <version>${lombok.version}</version>
       <scope>provided</scope>
     </dependency>
   </dependencies>

Also applies to: 145-156, 211-229


169-197: Enforce Java 21 in Maven Enforcer to match the build release

maven-compiler uses release 21; tighten Enforcer from [17,) to [21,) for earlier failure.

-                  <requireJavaVersion>
-                    <version>[17,)</version>
-                  </requireJavaVersion>
+                  <requireJavaVersion>
+                    <version>[21,)</version>
+                  </requireJavaVersion>
steve/pom.xml (2)

99-107: Avoid redundant Jetty artifacts; rely on the starter for jetty-server

spring-boot-starter-jetty already brings jetty-server. Keep jetty-rewrite if needed, but drop the direct jetty-server to prevent version skew.

     <!-- Jetty related dependencies -->
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-rewrite</artifactId>
     </dependency>

184-201: application.yml filtering—exclude keystore.password from filtering
Detected placeholders: ${steve.time-zone-id}, ${keystore.path}, and ${keystore.password}. Since keystore.password is a secret, don’t bake it into the build via Maven filtering. Isolate it by removing it from the filtered resource (e.g., use a separate unfiltered <resource> for secrets) or shift its resolution to runtime via environment variables.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df893a3 and fae552f.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (69)
  • .github/workflows/main.yml (1 hunks)
  • AGENTS.md (2 hunks)
  • Dockerfile (1 hunks)
  • pom.xml (6 hunks)
  • steve-api/pom.xml (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (6 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (2 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (5 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (7 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (6 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application-dev.yml (1 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/logback.xml (0 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (8 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (7 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (7 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
  • steve/src/test/resources/application-test.yml (1 hunks)
💤 Files with no reviewable changes (12)
  • steve/src/main/resources/config/logback.xml
  • steve-ocpp-transport-websocket/pom.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
  • steve-ocpp/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve/src/main/resources/config/main.properties
✅ Files skipped from review due to trivial changes (1)
  • steve-jooq/pom.xml
🚧 Files skipped from review as they are similar to previous changes (13)
  • steve/src/test/resources/application-test.yml
  • .github/workflows/main.yml
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java
  • steve/src/main/resources/application-dev.yml
  • steve/README.md
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
  • steve-ui-jsp/pom.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve-ocpp-1-x/pom.xml
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • AGENTS.md
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • Dockerfile
  • steve/src/main/resources/logback-spring.xml
  • pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve/pom.xml
  • steve/src/main/resources/application.yml
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • AGENTS.md
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • pom.xml
  • steve/pom.xml
  • steve/src/main/resources/application.yml
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • AGENTS.md
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
📚 Learning: 2025-09-15T15:31:15.967Z
Learnt from: juherr
PR: juherr/evolve#91
File: steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java:125-129
Timestamp: 2025-09-15T15:31:15.967Z
Learning: In the steve-core OCPP project, JAXB bindings are customized in ocpp_binding.xml to map xs:dateTime XML types directly to java.time.OffsetDateTime using the JavaDateTimeConverter adapter, eliminating the need for XMLGregorianCalendar conversions that would normally be required in standard JAXB bindings.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
🧬 Code graph analysis (3)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)
  • Slf4j (39-74)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (61-237)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java (1)
  • Getter (30-45)
🪛 GitHub Actions: build and run tests
steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java

[warning] 27-27: MissingSummary: A summary line is required on public/protected Javadocs.

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

[warning] 75-75: NullAway: initializer method does not guarantee @nonnull field chargePointsService (line 69) is initialized along all control-flow paths.

🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

CMD curl -fsS "http://127.0.0.1:${PORT}/" || exit 1

ENV JAVA_TOOL_OPTIONS="-Dserver.host=0.0.0.0 -Dhttp.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not single‑quote -D values; drop insecure TLS protocols; use Boot’s compression property

Single quotes become part of the property values (JDBC URL/protocols). Also prefer TLSv1.2/1.3 and server.compression.enabled.

Apply:

-ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
+ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols=TLSv1.2,TLSv1.3 -Dserver.compression.enabled=false"
📝 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
ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 -Dserver.port=${PORT} -Ddb.jdbc.url='jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC' -Ddb.schema=${DB_DATABASE} -Ddb.user=${DB_USER} -Ddb.password=${DB_PASSWORD} -Djdk.tls.client.protocols='TLSv1,TLSv1.1,TLSv1.2' -Dserver.gzip.enabled=false"
ENV JAVA_TOOL_OPTIONS="-Dserver.address=0.0.0.0 \
-Dserver.port=${PORT} \
-Ddb.jdbc.url=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?sslMode=PREFERRED&serverTimezone=UTC \
-Ddb.schema=${DB_DATABASE} \
-Ddb.user=${DB_USER} \
-Ddb.password=${DB_PASSWORD} \
-Djdk.tls.client.protocols=TLSv1.2,TLSv1.3 \
-Dserver.compression.enabled=false"
🤖 Prompt for AI Agents
In Dockerfile around line 77, the ENV line incorrectly wraps -D values in single
quotes (which become part of the value), exposes insecure TLS protocols, and
uses the deprecated Boot property server.gzip.enabled; remove the single quotes
around the JDBC URL and the tls protocols value so the actual values are passed,
restrict protocols to TLSv1.2,TLSv1.3, and replace server.gzip.enabled=false
with Spring Boot’s server.compression.enabled=false; ensure the entire ENV
assignment preserves correct shell quoting so variables like ${PORT},
${DB_HOST}, ${DB_PORT}, ${DB_DATABASE}, ${DB_USER}, and ${DB_PASSWORD} are
expanded correctly.

Comment on lines 69 to +76
@Mock
private ChargePointsService chargePointsService;

private MockMvcTester mockMvc;

@BeforeEach
public void setup() {
mockMvc = buildMockMvc(MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService)));
public void setUp() {
var ctx = new AnnotationConfigApplicationContext();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Initialize chargePointsService in setUp to satisfy NullAway

@mock with MockitoExtension isn’t recognized by NullAway for field init. Assign explicitly in setUp before using it.

 @BeforeEach
-    public void setUp() {
+    public void setUp() {
+        this.chargePointsService = org.mockito.Mockito.mock(ChargePointsService.class);
         var ctx = new AnnotationConfigApplicationContext();
         ctx.register(ValidationConfig.class);
-        ctx.register(SteveProperties.class);
         ctx.refresh();
 
         var validator = ctx.getBean(Validator.class);
 
         mockMvc = buildMockMvc(MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
                 .setValidator(validator));
     }

Please re-run the build; the NullAway warning at Line 69 should disappear.

Also applies to: 81-85


🏁 Script executed:

#!/bin/bash
set -e
echo "=== Imports & Class Header ==="
sed -n '1,60p' steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
echo "=== @ExtendWith Occurrences ==="
grep -Rn '@ExtendWith' steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java || echo "No @ExtendWith found"
echo "=== setUp Method Body ==="
sed -n '61,120p' steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java

Length of output: 5099


Open mocks in setUp to satisfy NullAway and initialize @Mock fields

Add a call to MockitoAnnotations.openMocks(this) at the start of setUp instead of manual mocking:

 @BeforeEach
 public void setUp() {
+    MockitoAnnotations.openMocks(this);
     var ctx = new AnnotationConfigApplicationContext();
     ctx.register(ValidationConfig.class);
     ctx.refresh();
 
     var validator = ctx.getBean(Validator.class);
 
     mockMvc = buildMockMvc(MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
             .setValidator(validator));
 }

Also add the import:

import org.mockito.MockitoAnnotations;

This ensures the @Mock field is properly initialized and silences NullAway warnings.

🧰 Tools
🪛 GitHub Actions: build and run tests

[warning] 75-75: NullAway: initializer method does not guarantee @nonnull field chargePointsService (line 69) is initialized along all control-flow paths.

🤖 Prompt for AI Agents
In
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
around lines 69 to 76, the @Mock fields are not initialized causing NullAway
warnings; add a call to MockitoAnnotations.openMocks(this) as the first
statement inside setUp() and add the import for org.mockito.MockitoAnnotations
so Mockito will initialize the @Mock-annotated fields before tests run.

Comment on lines +41 to 48
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</dependency>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove duplicate Jakarta Mail API (provided by starter-mail)

spring-boot-starter-mail already brings jakarta.mail-api; keeping both risks classpath duplication/version skew. Drop the direct jakarta.mail-api dependency.

     <dependency>
-      <groupId>jakarta.mail</groupId>
-      <artifactId>jakarta.mail-api</artifactId>
-    </dependency>
+      <!-- removed: provided transitively by spring-boot-starter-mail -->
+    </dependency>

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

🤖 Prompt for AI Agents
In steve-core/pom.xml around lines 41 to 48, there is a duplicate dependency
declaration for jakarta.mail:jakarta.mail-api alongside
spring-boot-starter-mail; remove the explicit jakarta.mail-api dependency entry
so the project relies on the transitive jakarta.mail-api provided by
spring-boot-starter-mail to avoid classpath duplication and version skew,
leaving only the spring-boot-starter-mail dependency block.

Comment on lines +41 to 48
@Autowired
public ChargeBoxIdValidator(SteveProperties steveProperties) {
this(steveProperties.getOcpp().getChargeBoxIdValidationRegex());
}

// Default constructor for Hibernate Validator
// Spring will inject the value from properties
// And then HV will call `initialize`
public ChargeBoxIdValidator() {
initialize(null);
public ChargeBoxIdValidator(String regexFromConfig) {
this.pattern = Pattern.compile(Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Constructor can NPE when properties subtree is missing.

steveProperties.getOcpp() may be null in some profiles; dereferencing it NPEs before reaching the null-safe constructor. Make the autowired ctor null‑safe.

-    @Autowired
-    public ChargeBoxIdValidator(SteveProperties steveProperties) {
-        this(steveProperties.getOcpp().getChargeBoxIdValidationRegex());
-    }
+    @Autowired
+    public ChargeBoxIdValidator(SteveProperties steveProperties) {
+        var regex = (steveProperties != null && steveProperties.getOcpp() != null)
+                ? steveProperties.getOcpp().getChargeBoxIdValidationRegex()
+                : null;
+        this(regex);
+    }
📝 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
@Autowired
public ChargeBoxIdValidator(SteveProperties steveProperties) {
this(steveProperties.getOcpp().getChargeBoxIdValidationRegex());
}
// Default constructor for Hibernate Validator
// Spring will inject the value from properties
// And then HV will call `initialize`
public ChargeBoxIdValidator() {
initialize(null);
public ChargeBoxIdValidator(String regexFromConfig) {
this.pattern = Pattern.compile(Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig);
}
@Autowired
public ChargeBoxIdValidator(SteveProperties steveProperties) {
var regex = (steveProperties != null && steveProperties.getOcpp() != null)
? steveProperties.getOcpp().getChargeBoxIdValidationRegex()
: null;
this(regex);
}
public ChargeBoxIdValidator(String regexFromConfig) {
this.pattern = Pattern.compile(Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig);
}
🤖 Prompt for AI Agents
In
steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
around lines 41 to 48, the @Autowired constructor directly calls
steveProperties.getOcpp() which can be null and cause an NPE; change it to
safely retrieve the regex by first checking if steveProperties is null or
steveProperties.getOcpp() is null and then pass either the configured regex or
null/empty to the delegated constructor so the null‑safe constructor can choose
the default REGEX (i.e., compute a local String regex = (steveProperties != null
&& steveProperties.getOcpp() != null) ?
steveProperties.getOcpp().getChargeBoxIdValidationRegex() : null; and call
this(regex)).

Comment on lines +7 to +9
url: @db.jdbc.url@
username: @db.user@
password: @db.password@
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

YAML won’t parse: quote all @...@ Maven tokens

Unquoted scalars starting with @ are invalid YAML; Boot will fail to start before filtering in many contexts (IDE, tests).

Apply:

-    url: @db.jdbc.url@
-    username: @db.user@
-    password: @db.password@
+    url: "@db.jdbc.url@"
+    username: "@db.user@"
+    password: "@db.password@"
@@
-steve:
-  version: @project.version@
+steve:
+  version: "@project.version@"
@@
-    schema-source: @db.schema@
+    schema-source: "@db.schema@"

Also applies to: 51-51, 55-55

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

🤖 Prompt for AI Agents
In steve/src/main/resources/application.yml around lines 7-9 (also apply same
change at lines 51 and 55), the Maven token scalars like @db.jdbc.url@,
@db.user@ and @db.password@ are unquoted which is invalid YAML and will prevent
Spring Boot from parsing before Maven filtering; wrap each @...@ token in quotes
(e.g. "@db.jdbc.url@") so the YAML is valid while allowing Maven to replace the
tokens during resource filtering.

@Override
public void beforeRepeat() {
threadLocalClient.set(getForOcpp16(path));
threadLocalClient.set(client);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: shared SOAP client assigned to all threads via ThreadLocal — not thread‑safe

threadLocalClient.set(client) assigns the same CentralSystemService instance to every thread. CXF/JAX‑WS client proxies are not guaranteed thread‑safe; message contexts and conduits can be mutated per call. Under load this can cause interleaved headers, timeouts, or sporadic 5xx/soap faults.

Create a per‑thread client and clean up the ThreadLocal:

@@
             public void beforeRepeat() {
-                threadLocalClient.set(client);
+                // Create an isolated client per thread to avoid sharing CXF proxy state
+                threadLocalClient.set(getForOcpp16(soapPath));
             }
@@
-            public void afterRepeat() {}
+            public void afterRepeat() {
+                // Prevent leaks of thread-local references after thread finishes
+                threadLocalClient.remove();
+            }
📝 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
threadLocalClient.set(client);
public void beforeRepeat() {
// Create an isolated client per thread to avoid sharing CXF proxy state
threadLocalClient.set(getForOcpp16(soapPath));
}
public void afterRepeat() {
// Prevent leaks of thread-local references after thread finishes
threadLocalClient.remove();
}
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java around line 90,
the code sets the same CentralSystemService instance into a ThreadLocal via
threadLocalClient.set(client), which shares a single CXF/JAX-WS proxy across
threads and is unsafe; change to create a dedicated client instance per thread
(e.g., use ThreadLocal.withInitial(() -> createNewCentralSystemServiceClient())
or instantiate a new proxy inside each worker thread) and ensure you call
threadLocalClient.remove() (or close/destroy the client) in a finally block or
on thread termination to avoid leaking resources and cross-thread mutation of
message contexts.

Comment on lines 597 to 604
for (var meterValue : meterValues) {
var sampledValues = meterValue.getSampledValue();
assertThat(sampledValues).isEmpty();
assertThat(sampledValues).isNotEmpty();
var thisValueFound = false;
// and check, if it can be found in the DB
for (var values : details.getValues()) {
if (values.getValue().equals(sampledValues.get(0).getValue())) {
if (values.getValue().equals(sampledValues.getFirst().getValue())) {
thisValueFound = true;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid Java 21-only List.getFirst() to keep Java 17 compatibility.

If the project builds on JDK 17 (common with Boot 3.x), getFirst() will not compile. Use index access; you already assert non-empty.

-            var sampledValues = meterValue.getSampledValue();
-            assertThat(sampledValues).isNotEmpty();
+            var sampledValues = meterValue.getSampledValue();
+            assertThat(sampledValues).isNotEmpty();
             var thisValueFound = false;
             // and check, if it can be found in the DB
             for (var values : details.getValues()) {
-                if (values.getValue().equals(sampledValues.getFirst().getValue())) {
+                if (values.getValue().equals(sampledValues.get(0).getValue())) {
                     thisValueFound = true;
                     break;
                 }
             }
📝 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
for (var meterValue : meterValues) {
var sampledValues = meterValue.getSampledValue();
assertThat(sampledValues).isEmpty();
assertThat(sampledValues).isNotEmpty();
var thisValueFound = false;
// and check, if it can be found in the DB
for (var values : details.getValues()) {
if (values.getValue().equals(sampledValues.get(0).getValue())) {
if (values.getValue().equals(sampledValues.getFirst().getValue())) {
thisValueFound = true;
for (var meterValue : meterValues) {
var sampledValues = meterValue.getSampledValue();
assertThat(sampledValues).isNotEmpty();
var thisValueFound = false;
// and check, if it can be found in the DB
for (var values : details.getValues()) {
if (values.getValue().equals(sampledValues.get(0).getValue())) {
thisValueFound = true;
break;
}
}
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java around
lines 597-604, the test uses the Java 21-only list method getFirst() on
sampledValues which breaks Java 17 compatibility; replace that call by accessing
the first element by index (index 0) since you already asserted the list is not
empty, and compare its value the same way so the test compiles on JDK 17.

Copy link

@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: 9

Caution

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

⚠️ Outside diff range comments (3)
steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (1)

70-76: Missing connect/response timeouts — calls may hang despite comment

Only connectionRequestTimeout is set. Without connectTimeout and responseTimeout, slow GitHub responses can block indefinitely, contradicting the stated intent.

Apply:

-        var requestConfig =
-                RequestConfig.custom().setConnectionRequestTimeout(timeout).build();
+        var requestConfig = RequestConfig.custom()
+                .setConnectionRequestTimeout(timeout)  // waiting for a connection from pool
+                .setConnectTimeout(timeout)            // TCP connect timeout
+                .setResponseTimeout(timeout)           // time to wait for the first byte / response
+                .build();

Optionally, set GitHub API version header:

         restTemplate.getInterceptors().add((request, body, execution) -> {
             request.getHeaders().add("User-Agent", userAgent);
             request.getHeaders().setAccept(List.of(MediaType.valueOf("application/vnd.github+json")));
+            request.getHeaders().add("X-GitHub-Api-Version", "2022-11-28");
             return execution.execute(request, body);
         });
steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (1)

104-113: Skip unpublished endpoints and log; avoid NPEs in map build.

EndpointImpl.getServer() can be null if not published.

-    private static Map<String, Server> initServerLookupMap(List<EndpointImpl> endpoints) {
-        var actualServers = new HashMap<String, Server>();
-        for (var endpoint : endpoints) {
-            var server = endpoint.getServer();
-            var serverNamespace =
-                    server.getEndpoint().getEndpointInfo().getName().getNamespaceURI();
-            actualServers.put(serverNamespace, server);
-        }
-        return actualServers;
-    }
+    private static Map<String, Server> initServerLookupMap(List<EndpointImpl> endpoints) {
+        var servers = new HashMap<String, Server>();
+        for (var endpoint : endpoints) {
+            var server = endpoint.getServer();
+            if (server == null) {
+                log.warn("Ignoring unpublished endpoint: {}", endpoint.getAddress());
+                continue;
+            }
+            var ns = server.getEndpoint().getEndpointInfo().getName().getNamespaceURI();
+            servers.put(ns, server);
+        }
+        return servers;
+    }
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)

156-183: Make truncateTables runtime-aware and failure-safe

  • Use internalCtx.meta() to load schemas/tables at runtime instead of DefaultCatalog
  • Wrap the truncate loop in try/finally so SET FOREIGN_KEY_CHECKS=1 always runs on errors
  • Filter tables by name (e.g. t.getName()) against a skipNames set rather than by object equality
🧹 Nitpick comments (34)
steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (3)

254-257: Reservation branch: avoid extra round‑trip (optional).

You already have connectorPkQuery above; instead of re-querying via getConnectorPkFromConnector, derive the scalar once from the existing select to save a DB round‑trip.

-            var connectorPk = getConnectorPkFromConnector(ctx, p.getChargeBoxId(), p.getConnectorId());
+            var connectorPk = connectorPkQuery.fetchOptional()
+                    .map(Record1::value1)
+                    .orElseThrow(() -> new SteveException.InternalError(
+                            "No connectorPk for %s/%d".formatted(p.getChargeBoxId(), p.getConnectorId())));

369-380: Broaden insertConnectorStatus parameter type to reduce coupling.

Accept Select<? extends Record1> instead of SelectConditionStep… to keep the helper generic for any single‑column select.

-    private void insertConnectorStatus(
-            DSLContext ctx,
-            SelectConditionStep<Record1<Integer>> connectorPkQuery,
-            LocalDateTime timestamp,
-            TransactionStatusUpdate statusUpdate) {
+    private void insertConnectorStatus(
+            DSLContext ctx,
+            org.jooq.Select<? extends Record1<Integer>> connectorPkQuery,
+            LocalDateTime timestamp,
+            TransactionStatusUpdate statusUpdate) {

You can optionally add import org.jooq.Select; at the top for brevity.


486-491: lookupLiteral may return null — fail fast with a clear error (recommended).

If actor.getName() doesn’t match the DB enum literal, lookupLiteral returns null and you’ll get a later constraint error. Throwing explicitly here makes the cause obvious and preserves existing failure flow (caught and recorded as “failed”).

-        return TransactionStopEventActor.lookupLiteral(actor.getName());
+        var mapped = TransactionStopEventActor.lookupLiteral(actor.getName());
+        if (mapped == null) {
+            throw new IllegalArgumentException("Unknown TransactionStopEventActor literal: " + actor.getName());
+        }
+        return mapped;

Please confirm TRANSACTION_STOP.EVENT_ACTOR allows NULL; if not, this change improves error clarity without changing behavior.

steve/src/test/resources/application-test.yml (1)

1-5: Avoid hardcoded test DB name and credentials; prefer env-overrides/Testcontainers.

Hardcoding jdbc URL and password can break dev/CI environments and risks accidental reuse. Use property placeholders with env defaults or a Testcontainers-based test profile.

Apply this change:

 spring:
   datasource:
-    url: jdbc:mysql://localhost:3306/stevedb_test_2aa6a783d47d
-    username: steve
-    password: changeme
+    url: ${TEST_DB_URL:jdbc:mysql://localhost:3306/stevedb_test}
+    username: ${TEST_DB_USERNAME:steve}
+    password: ${TEST_DB_PASSWORD:}
+# Optional: explicitly set driver if auto-detection is an issue
+#    driver-class-name: com.mysql.cj.jdbc.Driver

If you rely on jOOQ/Flyway during tests, confirm the DB is reachable in CI. Based on learnings.

steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2)

59-73: Verify servlet/filter URL patterns; guard against double slashes or trailing wildcards.

String concatenations assume certain trailing slashes. If soapMapping already ends with /* or rootMapping ends with /*, resulting patterns could be invalid or duplicated.

Consider normalizing:

- cxf.addMapping(props.getPaths().getSoapMapping() + "/*");
+ cxf.addMapping(normalizeSuffix(props.getPaths().getSoapMapping(), "/*"));
...
- .addMappingForUrlPatterns(..., props.getPaths().getRootMapping() + "*");
+ .addMappingForUrlPatterns(..., normalizeSuffix(props.getPaths().getRootMapping(), "*"));

Where normalizeSuffix(path, suffix) appends suffix only when not already present.


45-73: Alternative: Prefer SpringBootServletInitializer/ServletContextInitializer with Boot.

Given the Boot migration, consider replacing manual WebApplicationInitializer wiring with:

  • SpringBootServletInitializer for WAR deployment, or
  • Register ServletContextInitializer beans and let Boot auto-configure DispatcherServlet, Security filter, and CXF starter.

This reduces custom init complexity and aligns with Boot 3.5 conventions.

steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (2)

41-47: Consider lazy initialization to avoid early logging-setup timing issues.

As a Spring @Component, the constructor runs at context creation. If logging appenders aren’t fully configured yet, discovery may miss file appenders. Initialize on first use or mark the bean @Lazy.

Example:

-@Component
+@Component
+@org.springframework.context.annotation.Lazy
 public class LogFileRetriever {
     private final List<Path> logPathList;
 
     public LogFileRetriever() {
-        logPathList = getActiveLogFilePaths();
+        this.logPathList = getActiveLogFilePaths();
     }

89-102: Return an unmodifiable list; minor cleanup.

Make the collected paths unmodifiable to prevent accidental mutation and small robustness for invalid paths.

Apply:

-    private List<Path> getActiveLogFilePaths() {
+    private List<Path> getActiveLogFilePaths() {
         var logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
         var appenderIterator = logger.iteratorForAppenders();
 
         var fileNameList = new ArrayList<Path>();
         while (appenderIterator.hasNext()) {
             var appender = appenderIterator.next();
             var fileName = extractFileName(appender);
             if (fileName != null) {
-                fileNameList.add(Path.of(fileName));
+                try {
+                    fileNameList.add(Path.of(fileName));
+                } catch (RuntimeException ex) {
+                    log.debug("Skipping invalid log file path '{}': {}", fileName, ex.toString());
+                }
             }
         }
-        return fileNameList;
+        return List.copyOf(fileNameList);
     }
steve-core/pom.xml (1)

46-48: Consider removing redundant jakarta.mail-api dependency.

The spring-boot-starter-mail already provides Jakarta Mail API implementation. The explicit jakarta.mail-api dependency may be redundant and could potentially cause version conflicts.

Verify if the explicit API dependency is needed:

#!/bin/bash
# Check if jakarta.mail-api is used directly in steve-core
rg -n "jakarta\.mail" steve-core/src --type java -C2
steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)

31-31: Prefer explicit factory over null-cast; add empty-config coverage

The (String) null cast disambiguates the ctor, but it’s a bit brittle. Consider a small factory/helper for the “no-config” path (or a named static method) to improve readability. Also add a test for empty-string config to ensure parity with null.

Example addition:

+    @Test
+    public void testEmptyRegexConfigBehavesLikeNull() {
+        var v = new ChargeBoxIdValidator("");
+        assertThat(v.isValid("TesT")).isTrue();
+        assertThat(v.isValid(" test")).isFalse();
+    }
steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (1)

112-127: Guard against non-SemVer tags to avoid IllegalArgumentException

If a release tag deviates from strict SemVer, Version.parse(...) will throw and escape the RestClientException catch-path.

Apply:

-        var build = Version.parse(buildVersion);
-        var github = Version.parse(githubVersion);
+        Version build;
+        Version github;
+        try {
+            build = Version.parse(buildVersion);
+            github = Version.parse(githubVersion);
+        } catch (IllegalArgumentException e) {
+            log.warn("Unable to parse versions (build='{}', github='{}')", buildVersion, githubVersion, e);
+            return ReleaseReport.builder()
+                    .moreRecent(false)
+                    .githubVersion(githubVersion)
+                    .downloadUrl(decideDownloadUrl(response))
+                    .htmlUrl(response.getHtmlUrl())
+                    .build();
+        }
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java (1)

39-39: Consider rejecting duplicates if business rules require unique IDs

If duplicates should be rejected at input time, add @UniqueElements.

Example:

 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
+import org.hibernate.validator.constraints.UniqueElements;

-    @NotEmpty private List<@NotNull @ChargeBoxId String> idList;
+    @NotEmpty
+    @UniqueElements
+    private List<@NotNull @ChargeBoxId String> idList;
steve-ui-jsp/pom.xml (1)

129-132: jspc wiring looks correct; consider failing fast on JSP errors

Paths align with src/main/webapp and fragment under target/classes/WEB-INF. To surface issues earlier, enable fail-on-error if not default.

Example:

             <configuration>
               <webAppSourceDirectory>${basedir}/src/main/webapp</webAppSourceDirectory>
               <webXml>${basedir}/src/main/webapp/WEB-INF/web.xml</webXml>
               <webXmlFragment>${basedir}/target/classes/WEB-INF/web.xml-frag</webXmlFragment>
+              <failOnError>true</failOnError>
             </configuration>
steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (1)

91-97: ReleaseCheckService wired via SteveProperties: LGTM; consider centralizing HTTP settings

Change is consistent with the migration. Given GithubReleaseCheckService builds its own client, consider injecting a centrally configured RestTemplate (timeouts, proxy, TLS) to avoid drift across services.

steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (1)

42-56: Avoid setting SpringDoc config via System properties; move to application.yml

Static System.setProperty calls are order-sensitive and override environment. Prefer declarative config under springdoc.* in application.yml (paths-to-match, api-docs.path, swagger-ui.path, sorters, writer-with-order-by-keys). Keep this class focused on OpenAPI bean. I can draft the YAML block if helpful.

steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (2)

35-36: Consider allowing PARAMETER target as well

Adding PARAMETER enables annotating method parameters (e.g., controller endpoints) directly.

-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
@@
-@Target({TYPE_USE, FIELD, METHOD})
+@Target({TYPE_USE, FIELD, METHOD, PARAMETER})

35-38: Remove unused ChargeBoxIdListValidator
No field-level @ChargeBoxId on collections was found; the standalone ChargeBoxIdListValidator class is now dead code and can be removed.

steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1)

53-59: Add a startup guard for insecure default admin password

Given application.yml ships a placeholder/default, fail fast if the resolved password is empty/"change-me"/"1234". Suggest an ApplicationRunner bean here.

@@
-import org.springframework.context.event.EventListener;
+import org.springframework.context.event.EventListener;
+import org.springframework.context.annotation.Bean;
+import org.springframework.boot.ApplicationRunner;
+import de.rwth.idsg.steve.config.SteveProperties;
@@
     public void logStartup() {
@@
     }
 
+    @Bean
+    ApplicationRunner securityPasswordGuard(SteveProperties props) {
+        return args -> {
+            var pwd = props.getAuth().getPassword();
+            if (pwd == null || pwd.isBlank() || "change-me".equals(pwd) || "1234".equals(pwd)) {
+                throw new IllegalStateException("Refusing to start with insecure admin password");
+            }
+        };
+    }
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (2)

52-60: Name nit: “executive-logging” likely meant “execute-logging”

Property name reads “executive-logging”; if intentional, ignore. If not, consider renaming in SteveProperties + YAML to “execute-logging”.


72-81: Make DataSource transaction-aware for jOOQ

Wrap the DataSource to participate in Spring-managed transactions, or jOOQ may run outside tx boundaries.

+import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
@@
-                .set(new DataSourceConnectionProvider(
-                        // new org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy()
-                        dataSource))
+                .set(new DataSourceConnectionProvider(
+                        new TransactionAwareDataSourceProxy(dataSource)))
steve/src/main/resources/application.yml (1)

27-31: logging.appender is not a standard Spring Boot property

Use logging.file.name/logging.file.path or a logback-spring.xml. As-is, logging.appender: FILE is ignored.

Example:

 logging:
   level:
     root: INFO
-  appender: FILE
+  file:
+    name: logs/steve.log
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)

74-77: Prefer boolean property binding in the assertion.

Avoid string comparison; read as Boolean with default.

-            assertThat(environment.getProperty("steve.ocpp.auto-register-unknown-stations"))
-                    .isEqualTo("true");
+            assertThat(environment.getProperty(
+                    "steve.ocpp.auto-register-unknown-stations", Boolean.class, false))
+                    .isTrue();
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)

59-67: Avoid creating a Spring context just to get a Validator; remove leak and simplify setup.

Build LocalValidatorFactoryBean directly; no need for AnnotationConfigApplicationContext here.

-    @Configuration
-    static class ValidationConfig {
-        @Bean
-        public LocalValidatorFactoryBean validator(ApplicationContext applicationContext) {
-            var factory = new LocalValidatorFactoryBean();
-            factory.setApplicationContext(applicationContext);
-            return factory;
-        }
-    }
+    // Validation can be wired without spinning up a Spring context for tests.

@@
-    public void setUp() {
-        var ctx = new AnnotationConfigApplicationContext();
-        ctx.register(ValidationConfig.class);
-        ctx.register(SteveProperties.class);
-        ctx.refresh();
-
-        var validator = ctx.getBean(Validator.class);
-
-        mockMvc = buildMockMvc(MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
-                .setValidator(validator));
-    }
+    public void setUp() {
+        var validator = new LocalValidatorFactoryBean();
+        validator.afterPropertiesSet();
+        mockMvc = buildMockMvc(
+                MockMvcBuilders.standaloneSetup(new ChargePointsRestController(chargePointsService))
+                        .setValidator(validator));
+    }

Also applies to: 75-85

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

125-133: Consider comma‑separated Sec‑WebSocket‑Protocol values

Some clients send a single header like “ocpp1.6, ocpp1.5”. To be robust, split on commas per header entry and trim before matching.

-    private @Nullable AbstractWebSocketEndpoint selectEndpoint(List<String> requestedProtocols) {
-        for (var requestedProtocol : requestedProtocols) {
-            for (var item : endpoints) {
-                if (item.getVersion().getValue().equals(requestedProtocol)) {
-                    return item;
-                }
-            }
-        }
-        return null;
-    }
+    private @Nullable AbstractWebSocketEndpoint selectEndpoint(List<String> requestedProtocols) {
+        for (var headerVal : requestedProtocols) {
+            for (var token : headerVal.split(",")) {
+                var requested = token.trim();
+                for (var item : endpoints) {
+                    if (item.getVersion().getValue().equals(requested)) {
+                        return item;
+                    }
+                }
+            }
+        }
+        return null;
+    }
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)

83-90: Guard against null ServerProperties.address in getWsPath

Helpers.getWsPath uses serverProperties.getAddress().getHostName(); if address is null (Boot defaults), tests will NPE. Ensure test profile sets server.address or update Helpers to default to “localhost” when address is null.

steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (1)

70-76: Same as JSON tests: ensure getSoapPath won’t NPE when address is unset

Either set server.address in test profile or make Helpers.getSoapPath default to “localhost” when ServerProperties.getAddress() is null.

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

64-66: Normalize slashes when composing pathInfix (defensive).

Guard against accidental double slashes if properties carry trailing/leading slashes.

-        var pathInfix = steveProperties.getPaths().getWebsocketMapping()
-                + steveProperties.getPaths().getRouterEndpointPath() + "/";
+        var base = steveProperties.getPaths().getWebsocketMapping();
+        var router = steveProperties.getPaths().getRouterEndpointPath();
+        var pathInfix = (base.endsWith("/") ? base : base + "/")
+                + (router.startsWith("/") ? router.substring(1) : router)
+                + (router.endsWith("/") ? "" : "/");
steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (2)

154-156: Clear ThreadLocal to avoid lingering refs in long-lived pools.

Remove the per-thread proxy after use to prevent accidental retention if threads are reused.

-            public void afterRepeat() {}
+            public void afterRepeat() {
+                tlClient.remove();
+                threadLocalChargeBoxId.remove();
+            }

82-83: Optional: guard against null client in toRepeat.

Defensive check prevents NPE if beforeRepeat fails early.

-                var client = tlClient.get();
+                var client = tlClient.get();
+                if (client == null) {
+                    throw new IllegalStateException("Client not initialized for thread");
+                }
steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1)

53-61: Add Security.allowedOriginPatterns to back WebSocket CORS config.

Expose a list for allowed origin patterns; default to localhost for dev.

Add:

@@
     private WebApi webApi = new WebApi();
     private Jooq jooq = new Jooq();
+    private Security security = new Security();
@@
     public static class Ocpp {
         private boolean autoRegisterUnknownStations;
         private @Nullable String chargeBoxIdValidationRegex;
         private String wsSessionSelectStrategy;
     }
+
+    @Data
+    public static class Security {
+        private java.util.List<String> allowedOriginPatterns =
+                java.util.List.of("http://localhost:*", "https://localhost:*");
+    }
steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (1)

75-77: Defined port can collide in CI; consider RANDOM_PORT with @LocalServerPort

Using WebEnvironment.DEFINED_PORT risks collisions in parallel CI. If feasible, switch to RANDOM_PORT and resolve the port via @LocalServerPort (and adapt Helpers.getSoapPath to accept the actual port). Optional if your CI enforces single-threaded tests.

steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)

101-108: Optional: add client timeouts to avoid test hangs

CXF proxies default to no/large timeouts; a stalled endpoint can hang tests. Consider setting reasonable connect/read timeouts.

// after creating proxy:
var proxy = (T) f.create();
var client = org.apache.cxf.frontend.ClientProxy.getClient(proxy);
var http = (org.apache.cxf.transport.http.HTTPConduit) client.getConduit();
var policy = new org.apache.cxf.transports.http.configuration.HTTPClientPolicy();
policy.setConnectionTimeout(10_000);
policy.setReceiveTimeout(20_000);
http.setClient(policy);
return proxy;
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (1)

64-71: Remove or re‑enable the commented Testcontainers block.

Leaving this commented can confuse future readers. Either wire it back with @Container/@DynamicPropertySource or drop it.

steve/pom.xml (1)

99-108: Remove redundant Jetty direct dependencies when using Boot’s Jetty starter.

spring-boot-starter-jetty already manages jetty-server and jetty-rewrite. The direct deps can cause version drift.

-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-rewrite</artifactId>
-    </dependency>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df893a3 and fae552f.

⛔ Files ignored due to path filters (24)
  • steve-ui-jsp/src/main/webapp/static/css/DroidSerif.ttf is excluded by !**/*.ttf
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_diagonals-thick_20_666666_40x40.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_flat_10_000000_40x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_f6f6f6_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_100_fdf5ce_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_glass_65_ffffff_1x400.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_222222_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_228ef1_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ef8c08_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffd27a_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/css/images/ui-icons_ffffff_256x240.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/favicon.ico is excluded by !**/*.ico
  • steve-ui-jsp/src/main/webapp/static/images/info.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/logo2.png is excluded by !**/*.png
  • steve-ui-jsp/src/main/webapp/static/images/offline-icon.svg is excluded by !**/*.svg
  • steve-ui-jsp/src/main/webapp/static/js/jquery-2.0.3.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui-timepicker-addon.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/jquery-ui.min.js is excluded by !**/*.min.js
  • steve-ui-jsp/src/main/webapp/static/js/stupidtable.min.js is excluded by !**/*.min.js
📒 Files selected for processing (69)
  • .github/workflows/main.yml (1 hunks)
  • AGENTS.md (2 hunks)
  • Dockerfile (1 hunks)
  • pom.xml (6 hunks)
  • steve-api/pom.xml (2 hunks)
  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java (2 hunks)
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6 hunks)
  • steve-core/pom.xml (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (0 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (6 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java (1 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java (3 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java (2 hunks)
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java (0 hunks)
  • steve-jooq/pom.xml (4 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (2 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (5 hunks)
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java (0 hunks)
  • steve-ocpp-1-x/pom.xml (2 hunks)
  • steve-ocpp-transport-soap/pom.xml (1 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (2 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java (4 hunks)
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (4 hunks)
  • steve-ocpp-transport-websocket/pom.xml (0 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java (3 hunks)
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java (7 hunks)
  • steve-ocpp/pom.xml (0 hunks)
  • steve-ui-jsp/pom.xml (2 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java (3 hunks)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2 hunks)
  • steve/README.md (2 hunks)
  • steve/pom.xml (6 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/Application.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java (1 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (4 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (2 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java (0 hunks)
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (0 hunks)
  • steve/src/main/resources/application-dev.yml (1 hunks)
  • steve/src/main/resources/application.yml (1 hunks)
  • steve/src/main/resources/config/logback.xml (0 hunks)
  • steve/src/main/resources/config/main.properties (0 hunks)
  • steve/src/main/resources/logback-spring.xml (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (8 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (5 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java (24 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (7 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (3 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (2 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (7 hunks)
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1 hunks)
  • steve/src/test/resources/application-test.yml (1 hunks)
💤 Files with no reviewable changes (12)
  • steve-ocpp-transport-websocket/pom.xml
  • steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java
  • steve/src/main/java/de/rwth/idsg/steve/config/PropertyConfig.java
  • steve-ocpp/pom.xml
  • steve/src/main/resources/config/logback.xml
  • steve-jooq/src/main/java/de/rwth/idsg/steve/utils/FlywayMigrationRunner.java
  • steve/src/main/java/de/rwth/idsg/steve/JettyServer.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java
  • steve/src/main/java/de/rwth/idsg/steve/Application.java
  • steve/src/main/java/de/rwth/idsg/steve/utils/PropertiesFileLoader.java
  • steve/src/main/resources/config/main.properties
🚧 Files skipped from review as they are similar to previous changes (15)
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java
  • .github/workflows/main.yml
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java
  • AGENTS.md
  • steve/src/main/resources/application-dev.yml
  • Dockerfile
  • steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/TransactionStopEventActor.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java
  • steve-ocpp-1-x/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
  • steve/src/main/resources/logback-spring.xml
  • steve/README.md
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Java source code must be formatted using the Spotless Maven plugin (run spotless:apply/spotless:check before committing)

Files:

  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
  • steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java
  • steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/dto/ChargePointBatchInsertForm.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxId.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java
  • steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java
  • steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
  • steve-ocpp-transport-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
  • steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java
  • steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve/src/main/resources/config/main.properties : Default application configuration resides in steve/src/main/resources/config/main.properties; edit this file or override via command-line properties

Applied to files:

  • steve-api/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java
  • steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java
  • steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java
  • steve-core/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java
  • steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
  • steve/src/test/resources/application-test.yml
  • pom.xml
  • steve/pom.xml
  • steve/src/main/java/de/rwth/idsg/steve/SteveApplication.java
  • steve/src/main/resources/application.yml
📚 Learning: 2025-09-02T20:01:57.746Z
Learnt from: CR
PR: juherr/evolve#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T20:01:57.746Z
Learning: Applies to steve-jooq/src/main/resources/db/migration/**/*.sql : Place Flyway database migration scripts under steve-jooq/src/main/resources/db/migration

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve-jooq/pom.xml
📚 Learning: 2025-08-29T07:17:42.970Z
Learnt from: juherr
PR: juherr/evolve#62
File: steve/README.md:119-121
Timestamp: 2025-08-29T07:17:42.970Z
Learning: SteVe project uses jOOQ which requires live database connection during Maven build process to run Flyway migrations and generate Java code from database schema in the generate-sources phase.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
  • steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java
  • steve/src/test/java/de/rwth/idsg/steve/StressTest.java
  • steve/src/test/resources/application-test.yml
  • pom.xml
  • steve/pom.xml
  • steve-jooq/pom.xml
  • steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
  • steve/src/main/resources/application.yml
📚 Learning: 2025-09-15T15:31:15.967Z
Learnt from: juherr
PR: juherr/evolve#91
File: steve-core/src/main/java/de/rwth/idsg/steve/ocpp/task/SetChargingProfileTask.java:125-129
Timestamp: 2025-09-15T15:31:15.967Z
Learning: In the steve-core OCPP project, JAXB bindings are customized in ocpp_binding.xml to map xs:dateTime XML types directly to java.time.OffsetDateTime using the JavaDateTimeConverter adapter, eliminating the need for XMLGregorianCalendar conversions that would normally be required in standard JAXB bindings.

Applied to files:

  • steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
🧬 Code graph analysis (3)
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
steve-api/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-jooq/src/main/java/de/rwth/idsg/steve/jooq/config/JooqConfiguration.java (1)
  • Configuration (35-82)
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (3)
steve/src/main/java/de/rwth/idsg/steve/config/WebConfig.java (1)
  • Slf4j (39-74)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
  • Slf4j (61-237)
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
  • Helpers (37-109)
🪛 GitHub Actions: build and run tests
steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java

[warning] 30-30: [NullAway] initializer method does not guarantee @nonnull fields timeZoneId (line 55), steveVersion (line 56), profile (line 57) are initialized along all control-flow paths.

🪛 YAMLlint (1.37.1)
steve/src/main/resources/application.yml

[error] 7-7: syntax error: found character '@' that cannot start any token

(syntax)

Comment on lines +55 to +57
private String timeZoneId;
private String steveVersion;
private String profile;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix NullAway: annotate nullable or provide defaults for uninitialized fields.

These fields are bound at runtime; static analysis flags them. Either supply safe defaults or mark as @nullable. Minimal change shown:

-    private String timeZoneId;
-    private String steveVersion;
-    private String profile;
+    private @Nullable String timeZoneId;
+    private @Nullable String steveVersion;
+    private @Nullable String profile;

Optionally, add @validated and constraints if you want to enforce presence at startup.

📝 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 String timeZoneId;
private String steveVersion;
private String profile;
private @Nullable String timeZoneId;
private @Nullable String steveVersion;
private @Nullable String profile;
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java
around lines 55 to 57, three String fields (timeZoneId, steveVersion, profile)
are uninitialized and flagged by NullAway; annotate each field with a nullable
annotation (e.g., javax.annotation.Nullable or
org.jetbrains.annotations.Nullable) or initialize them to safe defaults (e.g.,
empty string) to satisfy static analysis; if you prefer runtime validation
instead, annotate the class with @Validated and add @NotBlank/@NotNull
constraints to the fields and ensure the appropriate validation
dependency/imports are present.

<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.jdbc.version}</version>
<version>${mysql.version}</version>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify consistency of property references.

The change from ${mysql.jdbc.version} to ${mysql.version} should be consistent across all Maven properties. Based on past review comments, these properties should now be defined in the parent POM.

Verify the property definitions are available:

Also applies to: 192-192, 230-230


🏁 Script executed:

#!/bin/bash
# Check if mysql.version, jooq.version, flyway.version are defined in parent
rg -n "mysql\.version|jooq\.version|flyway\.version" pom.xml -A1 -B1

Length of output: 0


🏁 Script executed:

#!/bin/bash
# List all POMs to identify parent
find . -type f -name pom.xml

# Search for mysql.version, jooq.version, flyway.version definitions in each POM
rg -n "<(mysql\.version|jooq\.version|flyway\.version)>" -C1 -g 'pom.xml' .

Length of output: 258


🏁 Script executed:

#!/bin/bash
# Search for property definitions of mysql.version, jooq.version, and flyway.version
rg -n "<mysql\.version>" -g '*.xml'
rg -n "<jooq\.version>" -g '*.xml'
rg -n "<flyway\.version>" -g '*.xml'

Length of output: 0


Declare missing Maven properties in the parent POM.
The properties mysql.version, jooq.version, and flyway.version aren’t defined in any pom.xml; add them under <properties> in the root pom.xml so ${mysql.version} (and the others) can resolve.

🤖 Prompt for AI Agents
In steve-jooq/pom.xml around line 115, the build references ${mysql.version}
(and other modules reference ${jooq.version} and ${flyway.version}) but those
properties are not declared; open the root/parent pom.xml and add a <properties>
block declaring mysql.version, jooq.version, and flyway.version with the desired
version values (pick the project-approved versions), then re-run mvn to ensure
the placeholders resolve.

Comment on lines 79 to 83
/**
* See Spring docs:
* https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configurationCheck failure[checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Check failure: [checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Line is longer than 120 characters (found 121).build and run tests / checkstyleView detailsCode has alerts. Press enter to view.
*
* Otherwise, defaults come from {@link WebSocketConstants}
*/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Javadoc and remove pasted build output; wrap long URL.

Current block includes CI “Check failure...” text and exceeds 120 chars; this fails Checkstyle.

-    /**
-     * See Spring docs:
-     * https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configurationCheck failure[checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Check failure: [checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Line is longer than 120 characters (found 121).build and run tests / checkstyleView detailsCode has alerts. Press enter to view.
-     * Otherwise, defaults come from {@link WebSocketConstants}
-     */
+    /**
+     * See Spring docs:
+     * https://docs.spring.io/spring-framework/reference/web/websocket/server.html
+     * #websocket-server-runtime-configuration
+     * Otherwise, defaults come from {@link WebSocketConstants}.
+     */
📝 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
/**
* See Spring docs:
* https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configurationCheck failure[checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Check failure: [checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Line is longer than 120 characters (found 121).build and run tests / checkstyleView detailsCode has alerts. Press enter to view.
*
* Otherwise, defaults come from {@link WebSocketConstants}
*/
/**
* See Spring docs:
* https://docs.spring.io/spring-framework/reference/web/websocket/server.html
* #websocket-server-runtime-configuration
* Otherwise, defaults come from {@link WebSocketConstants}.
*/
🤖 Prompt for AI Agents
In
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
around lines 79 to 83, the Javadoc contains pasted CI/build output and an
overlong URL that exceeds the 120-character checkstyle limit; remove the
extraneous "Check failure..." text and any build output, reformat the Javadoc so
the reference to the Spring docs is wrapped into multiple shorter lines (or use
{@code <a href="...">...}</code> if preferred) keeping each line under 120
chars, and ensure the remaining sentence about defaults referencing {@link
WebSocketConstants} remains properly formatted as Javadoc.

Comment on lines +61 to 64
public SessionContextStoreImpl(SteveProperties steveProperties) {
this.wsSessionSelectStrategy =
WsSessionSelectStrategyEnum.fromName(steveProperties.getOcpp().getWsSessionSelectStrategy());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Validate or default the session selection strategy.

If the property is absent/invalid, fromName(...) may throw; consider a default or a clear error.

  • Option A: default
this.wsSessionSelectStrategy =
    WsSessionSelectStrategyEnum.fromName(
        Objects.requireNonNullElse(steveProperties.getOcpp().getWsSessionSelectStrategy(), "ROUND_ROBIN"));
  • Option B: validate early with a helpful message.

Please confirm SteveProperties defines a non‑null default. If unsure, run:


🏁 Script executed:

#!/bin/bash
rg -n -C3 --type=java 'wsSessionSelectStrategy' steve | sed -n '1,200p'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Locate the SteveProperties class
rg -n --type=java 'class SteveProperties' -g '*.java'

# Find where getOcpp() is defined to inspect nested Ocpp properties
rg -n 'getOcpp' -C3 --type=java -g '*.java'

# Search for any default for wsSessionSelectStrategy in properties classes
rg -n 'wsSessionSelectStrategy' -C5 --type=java .

Length of output: 63822


Validate or default the session selection strategy
SteveProperties.wsSessionSelectStrategy has no default, so getWsSessionSelectStrategy() can return null or invalid and cause fromName(...) to throw at runtime. Provide an explicit default or pre-validate the value before calling fromName().

  • Option A: default to ROUND_ROBIN
this.wsSessionSelectStrategy =
    WsSessionSelectStrategyEnum.fromName(
        Objects.requireNonNullElse(
            steveProperties.getOcpp().getWsSessionSelectStrategy(),
            "ROUND_ROBIN"));
  • Option B: validate early and fail fast
String strategy = steveProperties.getOcpp().getWsSessionSelectStrategy();
Preconditions.checkArgument(strategy != null, "wsSessionSelectStrategy must be set");
this.wsSessionSelectStrategy = WsSessionSelectStrategyEnum.fromName(strategy);
🤖 Prompt for AI Agents
In
steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java
around lines 61 to 64, the call to WsSessionSelectStrategyEnum.fromName(...)
uses a value from SteveProperties that can be null or invalid and thus may throw
at runtime; change the constructor to either (A) supply a safe default when the
configured value is null (e.g. use Objects.requireNonNullElse(...,
"ROUND_ROBIN") or equivalent) before calling fromName, or (B) validate early and
fail fast by checking the string is non-null (and optionally matches allowed
names) and throw a clear IllegalArgumentException/Preconditions.checkArgument if
not set/invalid; implement one of these two approaches so fromName is never
called with a null or unexpected value.

Comment on lines +96 to +105
<resource>
<targetPath>.</targetPath>
<filtering>false</filtering>
<directory>src/main/webapp</directory>
<includes>
<include>static/**</include>
<include>WEB-INF/web.xml</include>
<include>WEB-INF/views/**</include>
</includes>
</resource>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify resource copy doesn’t duplicate WAR content or shadow packaging

Copying src/main/webapp/** into target/classes is fine for jspc, but can double-include assets if this module (or an aggregator WAR) also packages src/main/webapp via maven-war-plugin.

Run:


🏁 Script executed:

#!/bin/bash
# Check packaging and WAR plugin config in this module and parents
rg -nC2 '<packaging>\s*war\s*</packaging>|maven-war-plugin' pom.xml **/pom.xml

Length of output: 161


Prevent double-inclusion of src/main/webapp in WAR
This module uses <packaging>war</packaging>, so the default War plugin already includes src/main/webapp at the WAR root. Copying these files into target/classes via your <resource> block will include them again under WEB-INF/classes. Remove or isolate this resource declaration to only the JSPC execution, or configure the War plugin’s <webResources>/excludes to avoid duplicating assets.

🤖 Prompt for AI Agents
In steve-ui-jsp/pom.xml around lines 96 to 105, the <resource> block is copying
src/main/webapp into target/classes causing the webapp to be packaged twice;
remove this resource entry or restrict it so it only applies to the JSPC
execution (e.g., move it into a profile or execution-specific configuration), or
alternatively remove it and instead configure the maven-war-plugin with
<webResources> or <excludes> to prevent duplicating src/main/webapp in
WEB-INF/classes.

Comment on lines +73 to 80
var start = client.startTransaction(
new StartTransactionRequest()
.withConnectorId(connectorId)
.withIdTag(idTag)
.withTimestamp(startDateTime)
.withMeterStart(meterStart),
chargeBoxId);
assertThat(start).isNotNull();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Start/stop mismatch across the stress loop (start outside, stop inside).

startTransaction runs once before the loop, while stopTransaction is invoked inside toRepeat(). With REPEAT_COUNT_PER_THREAD > 1, subsequent iterations will attempt to stop an already‑stopped transaction or send MeterValues after stop. Consider:

  • Move startTransaction into beforeRepeat() (or toRepeat()) and store the transactionId in a ThreadLocal, then stop within the same iteration; or
  • Keep start outside and move stop after the loop (once), and only send MeterValues inside the loop.

Note: Using OffsetDateTime for timestamps is correct and matches the project’s JAXB customization that maps xs:dateTime to OffsetDateTime. Based on learnings.


🏁 Script executed:

#!/bin/bash
set -e

# Print top of Issue72.java for context
echo "=== Issue72.java (1-200) ==="
sed -n '1,200p' steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java

echo -e "\n=== toRepeat occurrences ==="
rg -n "toRepeat" -C3 steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java

echo -e "\n=== stopTransaction occurrences ==="
rg -n "stopTransaction" -C3 steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java

echo -e "\n=== REPEAT_COUNT_PER_THREAD occurrences in Java ==="
rg -n "REPEAT_COUNT_PER_THREAD" -C1 --type java

Length of output: 7234


🏁 Script executed:

#!/bin/bash
set -e
echo "=== StressTester.test() implementation ==="
rg -n "test\\s*\\(" -C3 steve/src/test/java/de/rwth/idsg/steve/utils/StressTester.java

Length of output: 341


🏁 Script executed:

#!/bin/bash
# Print first 200 lines of StressTester.java to inspect loop logic
sed -n '1,200p' steve/src/test/java/de/rwth/idsg/steve/utils/StressTester.java

Length of output: 2289


Correct transactional flow in Issue72.java

startTransaction is called once before the stress loop but stopTransaction runs inside toRepeat(), so with REPEAT_COUNT_PER_THREAD = 50 you stop the same transaction repeatedly and send MeterValues after it’s stopped. Either move startTransaction into beforeRepeat()/toRepeat() so each iteration has its own start–stop pair, or keep the start outside and invoke stopTransaction in afterRepeat() (once after the loop).

🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java around lines
73-80, the test calls startTransaction once before the stress loop but calls
stopTransaction inside toRepeat(), causing the same transaction to be stopped
repeatedly and MeterValues to be sent after stop; fix by making start/stop pair
per iteration or stop once after the loop: either move the startTransaction call
into beforeRepeat() or toRepeat() so each iteration creates its own transaction
and corresponding stopTransaction, or keep the single startTransaction outside
the loop and move stopTransaction into afterRepeat() so it is invoked only once;
also ensure MeterValues are sent only while the transaction is active and any
transactionId used in iterations is unique or correctly scoped.

@Override
public void beforeRepeat() {
threadLocalClient.set(getForOcpp16(path));
threadLocalClient.set(client);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Create a per‑thread client instance instead of reusing the same proxy.

Use a fresh proxy per thread to avoid concurrency issues.

Apply this diff:

-                threadLocalClient.set(client);
+                // Create a fresh proxy per thread to avoid CXF proxy concurrency issues
+                threadLocalClient.set(getForOcpp16(soapPath));
📝 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
threadLocalClient.set(client);
// Create a fresh proxy per thread to avoid CXF proxy concurrency issues
threadLocalClient.set(getForOcpp16(soapPath));
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java around line 90,
the test currently sets a shared proxy client into threadLocalClient
(threadLocalClient.set(client)); instead create a fresh client/proxy instance
per thread to avoid concurrency issues: change the code to instantiate a new
client/proxy inside the thread before calling threadLocalClient.set(...) (or
initialize threadLocalClient with ThreadLocal.withInitial(...) returning a newly
created client), ensuring each thread uses its own independent client instance
and not a reused shared proxy.

Comment on lines +309 to 310
var faultedTime = timeStatusMap.lastEntry().getKey().plusMinutes(1);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Java 21 API used: LinkedHashMap.lastEntry() breaks on Java 17

LinkedHashMap.lastEntry() is also Java 21+. Use an alternative that works on 17, e.g., track the last key during insertion or use an iterator over entrySet().

-        var faultedTime = timeStatusMap.lastEntry().getKey().plusMinutes(1);
+        var it = timeStatusMap.entrySet().iterator();
+        java.util.Map.Entry<ZonedDateTime,?> last = null;
+        while (it.hasNext()) last = it.next();
+        var faultedTime = (last != null ? last.getKey() : startingTime).plusMinutes(1);

🏁 Script executed:

rg -n "timeStatusMap" -C3 -t java .

Length of output: 2975


Replace Java 21 API usage of LinkedHashMap.lastEntry() to support Java 17
LinkedHashMap.lastEntry() is only available since Java 21 (via SequencedMap) and won’t compile under Java 17—use entrySet() iteration (or track the last key during insertion) to retrieve the last key instead (e.g., in steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java:309). (docs.oracle.com)

🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java around
line 309, the code uses LinkedHashMap.lastEntry() which is Java 21-only and
fails to compile on Java 17; replace this call by obtaining the last entry via
iteration over the map's entrySet (or by tracking the last key during
insertion). Concretely, iterate the map's entrySet() to find the final Map.Entry
(or store the lastKey when filling the map) and then call
getKey().plusMinutes(1) on that key to compute faultedTime, ensuring no Java 21
APIs are used.

Comment on lines +74 to +78
var environment = app.getEnvironment();
assertThat(environment.getActiveProfiles()).hasSize(1).contains("test");
assertThat(environment.getProperty("steve.ocpp.auto-register-unknown-stations"))
.isEqualTo("true");

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify test override for auto‑register property.

Assertion expects "true". Please ensure application‑test config sets it accordingly.


🏁 Script executed:

#!/bin/bash
# Check test overrides for the property
echo "Searching for steve.ocpp.auto-register-unknown-stations: true in test resources..."
rg -n -C2 -g '!**/target/**' -P --type=yaml 'steve:\s*\n\s*ocpp:\s*\n\s*auto-register-unknown-stations\s*:\s*true' steve/src/test/resources || true
rg -n -C2 -g '!**/target/**' -P --type=properties '^steve\.ocpp\.auto-register-unknown-stations\s*=\s*true$' steve/src/test/resources || true

Length of output: 117


🏁 Script executed:

#!/bin/bash
echo "Listing all test resource files:"
find steve/src/test/resources -type f -maxdepth 2
echo
echo "Searching for 'auto-register-unknown-stations' in test resources:"
rg -n 'auto-register-unknown-stations' -C2 steve/src/test/resources || true

Length of output: 145


Add missing test override for auto-register property

The application-test.yml under steve/src/test/resources does not define steve.ocpp.auto-register-unknown-stations. Add the following to ensure the test passes:

steve:
  ocpp:
    auto-register-unknown-stations: true
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/StressTest.java around lines 74 to 78,
the test expects the property steve.ocpp.auto-register-unknown-stations to be
defined but application-test.yml in steve/src/test/resources does not set it;
update application-test.yml to include the YAML entry for
steve.ocpp.auto-register-unknown-stations set to true under the steve.ocpp
hierarchy so the environment property resolves and the assertion passes.

@juherr juherr merged commit 501ce3e into main Sep 29, 2025
30 checks passed
@juherr juherr deleted the backport/steve-1822 branch September 29, 2025 19:18
@coderabbitai coderabbitai bot mentioned this pull request Oct 11, 2025
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