diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f04fa4c55..8c0e1c0f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: - name: Start the app and visit signin web page run: | sudo apt-get install -y elinks - java -Djava.net.preferIPv4Stack=true -jar target/steve.jar & + java -Djava.net.preferIPv4Stack=true -jar target/steve.war & sleep 30 elinks -dump -no-references http://localhost:8080/steve/manager/signin killall java diff --git a/Dockerfile b/Dockerfile index eac7b53a5..001d53af1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,5 +22,5 @@ COPY . /code # Build and run steve, requires a db to be available on port 3306 CMD dockerize -wait tcp://mariadb:3306 -timeout 60s && \ ./mvnw clean package -Pdocker -Djdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2" && \ - java -XX:MaxRAMPercentage=85 -jar target/steve.jar + java -XX:MaxRAMPercentage=85 -jar target/steve.war diff --git a/README.md b/README.md index d21dab9b6..37652c204 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ SteVe is designed to run standalone, a java servlet container / web server (e.g. 4. Build SteVe: - To compile SteVe simply use Maven. A runnable `jar` file containing the application and configuration will be created in the subdirectory `steve/target`. + To compile SteVe simply use Maven. A runnable `war` file containing the application and configuration will be created in the subdirectory `steve/target`. ``` # ./mvnw package @@ -93,7 +93,7 @@ SteVe is designed to run standalone, a java servlet container / web server (e.g. To start the application run (please do not run SteVe as root): ``` - # java -jar target/steve.jar + # java -jar target/steve.war ``` # Docker diff --git a/k8s/docker/Dockerfile b/k8s/docker/Dockerfile index 05f20eeb3..f5def7630 100644 --- a/k8s/docker/Dockerfile +++ b/k8s/docker/Dockerfile @@ -42,11 +42,11 @@ RUN ./mvnw clean package -Pkubernetes -Djdk.tls.client.protocols="TLSv1,TLSv1.1, FROM base AS release # Copy relevant files from the build stage -COPY --from=build /code/target/steve.jar /app/steve.jar +COPY --from=build /code/target/steve.jar /app/steve.war COPY --from=build /code/target/libs /app/libs # Expose any necessary ports (example: 8080) EXPOSE 8080 # Define the entrypoint for the release stage -CMD ["java", "-jar", "/app/steve.jar"] +CMD ["java", "-jar", "/app/steve.war"] diff --git a/pom.xml b/pom.xml index 403d05c4d..f076b1719 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,14 @@ de.rwth.idsg steve 3.9.0-SNAPSHOT - jar + war + + + org.springframework.boot + spring-boot-starter-parent + 3.5.5 + + SteVe SteckdosenVerwaltung @@ -35,16 +42,7 @@ 21 UTF-8 - 3.19.18 - 11.12.0 4.1.3 - 6.2.0 - 6.5.3 - 9.4.0 - 12.1.1 - 1.18.40 - 2.19.2 - 2.0.17 5.0.0 @@ -99,6 +97,7 @@ src/main/resources true + application.yml application**.properties logback-spring**.xml @@ -157,52 +156,6 @@ - - org.apache.maven.plugins - maven-enforcer-plugin - 3.6.1 - - - enforce-java - - enforce - - - - - [21,) - - - [3.3.9,) - - - - - - - - maven-compiler-plugin - 3.14.0 - - ${java.version} - ${java.version} - - - org.projectlombok - lombok - ${lombok.version} - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.4 - - org.apache.maven.plugins @@ -239,24 +192,6 @@ - - io.github.git-commit-id - git-commit-id-maven-plugin - 9.0.2 - - - initialize - - revision - - - - - false - false - - - org.codehaus.mojo @@ -270,7 +205,6 @@ - src/main/resources/application.properties src/main/resources/application-${envName}.properties @@ -280,60 +214,32 @@ org.apache.maven.plugins - maven-dependency-plugin - 3.8.1 - - - copy-dependencies - prepare-package - - copy-dependencies - - - ${project.build.directory}/libs - runtime - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.4.2 + maven-compiler-plugin - - - true - libs/ - de.rwth.idsg.steve.Application - - + + + org.projectlombok + lombok + + - - - org.eclipse.jetty.ee10 - jetty-ee10-jspc-maven-plugin - ${jetty.version} - - - compile - jspc - - jspc - - - ${basedir}/target/classes/webapp/WEB-INF/web.xml-frag - - - + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + org.flywaydb flyway-maven-plugin - ${flyway.version} @@ -386,7 +292,6 @@ org.jooq jooq-codegen-maven - ${jooq.version} @@ -413,7 +318,7 @@ com.mysql mysql-connector-j - ${mysql.jdbc.version} + ${mysql.version} @@ -492,37 +397,43 @@ - - - - org.springframework - spring-framework-bom - ${spring.version} - import - pom - - - org.junit - junit-bom - 5.13.4 - pom - import - - - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.8.13 + org.springframework.boot + spring-boot-starter-web - tomcat-embed-el - org.apache.tomcat.embed + spring-boot-starter-tomcat + org.springframework.boot + + org.springframework.boot + spring-boot-starter-jetty + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.13 + com.github.goekay @@ -533,21 +444,6 @@ com.github.steve-community ocpp-jaxb 0.0.9 - - - jakarta.activation - com.sun.activation - - - jakarta.activation-api - jakarta.activation - - - - - jakarta.mail - jakarta.mail-api - 2.1.3 org.jetbrains @@ -559,7 +455,6 @@ org.projectlombok lombok - ${lombok.version} provided @@ -567,130 +462,33 @@ joda-time 2.14.0 - - org.hibernate.validator - hibernate-validator - 8.0.2.Final - - - com.google.guava - guava - 33.4.0-jre - + com.fasterxml.jackson.core jackson-databind - ${jackson.version} com.fasterxml.jackson.module jackson-module-jakarta-xmlbind-annotations - ${jackson.version} com.fasterxml.jackson.core jackson-annotations - ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-joda - ${jackson.version} org.apache.httpcomponents.client5 httpclient5 - 5.5 - - - jakarta.websocket - jakarta.websocket-api - 2.2.0 - - - jakarta.websocket - jakarta.websocket-client-api - 2.2.0 - - - jakarta.servlet - jakarta.servlet-api - 6.1.0 - - - jakarta.servlet.jsp.jstl - jakarta.servlet.jsp.jstl-api - 3.0.2 - - - org.glassfish.web - jakarta.servlet.jsp.jstl - 3.0.1 - - - - - org.springframework - spring-webmvc - - - - commons-logging - commons-logging - - - org.springframework - spring-jcl - - - - - org.springframework - spring-websocket - - - org.springframework.security - spring-security-web - ${spring.security.version} - - - org.springframework.security - spring-security-config - ${spring.security.version} - - - - - ch.qos.logback - logback-classic - 1.5.18 - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - + org.apache.cxf - cxf-rt-frontend-jaxws - ${cxf.version} - - - org.apache.cxf - cxf-rt-transports-http - ${cxf.version} - - - org.apache.cxf - cxf-rt-transports-http-hc + cxf-spring-boot-starter-jaxws ${cxf.version} @@ -699,104 +497,50 @@ ${cxf.version} - - - org.eclipse.jetty - jetty-server - ${jetty.version} - - - org.eclipse.jetty.ee10 - jetty-ee10-webapp - ${jetty.version} - - - org.eclipse.jetty.ee10 - jetty-ee10-annotations - ${jetty.version} - + org.eclipse.jetty.ee10 jetty-ee10-apache-jsp - ${jetty.version} - - - - - - org.eclipse.jetty - jetty-rewrite - ${jetty.version} + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api - org.eclipse.jetty.ee10.websocket - jetty-ee10-websocket-jetty-server - ${jetty.version} + org.glassfish.web + jakarta.servlet.jsp.jstl com.mysql mysql-connector-j - ${mysql.jdbc.version} - - - com.zaxxer - HikariCP - 7.0.2 org.jooq jooq - ${jooq.version} - org.springframework - spring-test - test - - - org.eclipse.jetty.websocket - jetty-websocket-jetty-client - ${jetty.version} - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.mockito - mockito-junit-jupiter - 5.19.0 + org.springframework.boot + spring-boot-starter-test test - net.bytebuddy - byte-buddy - 1.17.7 + org.springframework.security + spring-security-test test - org.hamcrest - hamcrest - 3.0 + org.eclipse.jetty.websocket + jetty-websocket-jetty-client test - com.jayway.jsonpath - json-path - 2.9.0 + org.apache.cxf + cxf-rt-transports-http-hc + ${cxf.version} test @@ -815,6 +559,11 @@ 1.29 + + com.google.guava + guava + 33.4.0-jre + org.owasp.encoder encoder-jakarta-jsp diff --git a/src/main/java/de/rwth/idsg/steve/Application.java b/src/main/java/de/rwth/idsg/steve/Application.java deleted file mode 100644 index 55c141183..000000000 --- a/src/main/java/de/rwth/idsg/steve/Application.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2025 SteVe Community Team - * All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.rwth.idsg.steve; - -import de.rwth.idsg.steve.utils.LogFileRetriever; -import lombok.extern.slf4j.Slf4j; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - -import java.nio.file.Path; -import java.util.Optional; -import java.util.TimeZone; - -/** - * @author Sevket Goekay - * @since 14.01.2015 - */ -@Slf4j -public class Application { - - private final JettyServer server = new JettyServer(); - - public static void main(String[] args) throws Exception { - // For Hibernate validator - System.setProperty("org.jboss.logging.provider", "slf4j"); - - SteveConfiguration sc = SteveConfiguration.CONFIG; - log.info("Loaded the properties. Starting with the '{}' profile", sc.getProfile()); - - TimeZone.setDefault(TimeZone.getTimeZone(sc.getTimeZoneId())); - DateTimeZone.setDefault(DateTimeZone.forID(sc.getTimeZoneId())); - log.info("Date/time zone of the application is set to {}. Current date/time: {}", sc.getTimeZoneId(), DateTime.now()); - - Optional path = LogFileRetriever.INSTANCE.getPath(); - boolean loggingToFile = path.isPresent(); - if (loggingToFile) { - System.out.println("Log file: " + path.get().toAbsolutePath()); - } - - Application app = new Application(); - - try { - app.start(); - app.join(); - } catch (Exception e) { - log.error("Application failed to start", e); - - if (loggingToFile) { - System.err.println("Application failed to start"); - e.printStackTrace(); - } - - app.stop(); - } - } - - public void start() throws Exception { - server.start(); - } - - public void join() throws Exception { - server.join(); - } - - public void stop() throws Exception { - server.stop(); - } -} diff --git a/src/main/java/de/rwth/idsg/steve/JettyServer.java b/src/main/java/de/rwth/idsg/steve/JettyServer.java deleted file mode 100644 index cdb046aee..000000000 --- a/src/main/java/de/rwth/idsg/steve/JettyServer.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2025 SteVe Community Team - * All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.rwth.idsg.steve; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jetty.ee10.webapp.WebAppContext; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.server.ForwardedRequestCustomizer; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.SecureRequestCustomizer; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.gzip.GzipHandler; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - -/** - * @author Sevket Goekay - * @since 12.12.2014 - */ -@Slf4j -public class JettyServer { - - private Server server; - - private static final int MIN_THREADS = 4; - private static final int MAX_THREADS = 50; - - private static final long STOP_TIMEOUT = TimeUnit.SECONDS.toMillis(5); - private static final long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(1); - - // scan all jars in the classpath for ServletContainerInitializers - // (e.g. Spring's WebApplicationInitializer) - private static final String SCAN_PATTERN = ".*\\.jar$|.*/classes/.*"; - - /** - * A fully configured Jetty Server instance - */ - private void prepare() throws IOException { - - // === jetty.xml === - // Setup Threadpool - QueuedThreadPool threadPool = new QueuedThreadPool(); - threadPool.setMinThreads(MIN_THREADS); - threadPool.setMaxThreads(MAX_THREADS); - - // Server - server = new Server(threadPool); - - // Scheduler - server.addBean(new ScheduledExecutorScheduler()); - - // HTTP Configuration - HttpConfiguration httpConfig = new HttpConfiguration(); - httpConfig.setSecureScheme(HttpScheme.HTTPS.asString()); - httpConfig.setSecurePort(CONFIG.getJetty().getHttpsPort()); - httpConfig.setOutputBufferSize(32768); - httpConfig.setRequestHeaderSize(8192); - httpConfig.setResponseHeaderSize(8192); - httpConfig.setSendServerVersion(false); - httpConfig.setSendDateHeader(false); - httpConfig.setSendXPoweredBy(false); - - // make sure X-Forwarded-For headers are picked up if set (e.g. by a load balancer) - // https://github.com/steve-community/steve/pull/570 - httpConfig.addCustomizer(new ForwardedRequestCustomizer()); - - // Extra options - server.setDumpAfterStart(false); - server.setDumpBeforeStop(false); - server.setStopAtShutdown(true); - server.setStopTimeout(STOP_TIMEOUT); - - if (CONFIG.getJetty().isHttpEnabled()) { - server.addConnector(httpConnector(httpConfig)); - } - - if (CONFIG.getJetty().isHttpsEnabled()) { - server.addConnector(httpsConnector(httpConfig)); - } - - server.setHandler(getWebApp()); - } - - private ServerConnector httpConnector(HttpConfiguration httpConfig) { - // === jetty-http.xml === - ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); - http.setHost(CONFIG.getJetty().getServerHost()); - http.setPort(CONFIG.getJetty().getHttpPort()); - http.setIdleTimeout(IDLE_TIMEOUT); - return http; - } - - private ServerConnector httpsConnector(HttpConfiguration httpConfig) { - // === jetty-https.xml === - // SSL Context Factory - SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setKeyStorePath(CONFIG.getJetty().getKeyStorePath()); - sslContextFactory.setKeyStorePassword(CONFIG.getJetty().getKeyStorePassword()); - sslContextFactory.setKeyManagerPassword(CONFIG.getJetty().getKeyStorePassword()); - - // SSL HTTP Configuration - HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); - httpsConfig.addCustomizer(new SecureRequestCustomizer()); - - // SSL Connector - ServerConnector https = new ServerConnector(server, - new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), - new HttpConnectionFactory(httpsConfig)); - https.setHost(CONFIG.getJetty().getServerHost()); - https.setPort(CONFIG.getJetty().getHttpsPort()); - https.setIdleTimeout(IDLE_TIMEOUT); - return https; - } - - private Handler getWebApp() throws IOException { - var webAppContext = new WebAppContext(); - webAppContext.setContextPath(CONFIG.getContextPath()); - webAppContext.setBaseResourceAsString(new ClassPathResource("webapp").getURI().toString()); - - // if during startup an exception happens, do not swallow it, throw it - webAppContext.setThrowUnavailableOnStartupException(true); - - // Disable directory listings if no index.html is found. - webAppContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); - - // Crucial for Spring's WebApplicationInitializer to be discovered - // and for the DispatcherServlet to be initialized. - // It tells Jetty to scan for classes implementing WebApplicationInitializer. - // The pattern ensures that Jetty finds the Spring classes in the classpath. - // - // https://jetty.org/docs/jetty/12.1/programming-guide/maven-jetty/jetty-maven-plugin.html - // https://jetty.org/docs/jetty/12.1/operations-guide/annotations/index.html#og-container-include-jar-pattern - webAppContext.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", SCAN_PATTERN); - - if (CONFIG.getJetty().isGzipEnabled()) { - // Wraps the whole web app in a gzip handler to make Jetty return compressed content - // http://www.eclipse.org/jetty/documentation/current/gzip-filter.html - return new GzipHandler(webAppContext); - } else { - return webAppContext; - } - } - - /** - * Starts the Jetty Server instance - */ - public void start() throws Exception { - prepare(); - - if (server != null) { - server.start(); - } - } - - /** - * Join the server thread with the current thread - */ - public void join() throws Exception { - if (server != null) { - server.join(); - } - } - - public void stop() throws Exception { - if (server != null) { - server.stop(); - } - } -} diff --git a/src/main/java/de/rwth/idsg/steve/SteveApplication.java b/src/main/java/de/rwth/idsg/steve/SteveApplication.java new file mode 100644 index 000000000..7ea0151e6 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/SteveApplication.java @@ -0,0 +1,68 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve; + +import de.rwth.idsg.steve.config.SteveProperties; +import lombok.extern.slf4j.Slf4j; +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.logging.Slf4jLogger; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.EventListener; + +import java.util.TimeZone; + +/** + * @author Sevket Goekay + * @since 19.09.2025 + */ +@Slf4j +@SpringBootApplication(exclude = {JooqAutoConfiguration.class}) +public class SteveApplication { + + static { + // Apache CXF + LogUtils.setLoggerClass(Slf4jLogger.class); + + // For Hibernate validator + System.setProperty("org.jboss.logging.provider", "slf4j"); + + TimeZone.setDefault(TimeZone.getTimeZone(SteveProperties.TIME_ZONE_ID)); + DateTimeZone.setDefault(DateTimeZone.forID(SteveProperties.TIME_ZONE_ID)); + } + + @EventListener(ApplicationStartedEvent.class) + public void logStartup() { + log.info("Date/time zone of the application is set to {}. Current date/time: {}", SteveProperties.TIME_ZONE_ID, DateTime.now()); + } + + public static void main(String[] args) throws Exception { + start(args); + } + + public static ConfigurableApplicationContext start(String... args) throws Exception { + return SpringApplication.run(SteveApplication.class, args); + } + +} diff --git a/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java b/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java deleted file mode 100644 index 4a8d52e7d..000000000 --- a/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2025 SteVe Community Team - * All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.rwth.idsg.steve; - -import de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategy; -import de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategyEnum; -import de.rwth.idsg.steve.utils.PropertiesFileLoader; -import lombok.Builder; -import lombok.Getter; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; - -/** - * @author Sevket Goekay - * @since 19.08.2014 - */ -@Getter -public enum SteveConfiguration { - CONFIG; - - // Root mapping for Spring - private final String springMapping = "/"; - // Web frontend - private final String springManagerMapping = "/manager"; - // Mapping for CXF SOAP services - private final String cxfMapping = "/services"; - // Mapping for Web APIs - private final String apiMapping = "/api"; - // Dummy service path - private final String routerEndpointPath = "/CentralSystemService"; - // Time zone for the application and database connections - private final String timeZoneId = "UTC"; // or ZoneId.systemDefault().getId(); - - // ------------------------------------------------------------------------- - // application.properties - // ------------------------------------------------------------------------- - - private final String contextPath; - private final String steveVersion; - private final String gitDescribe; - private final String profile; - private final Ocpp ocpp; - private final Auth auth; - private final WebApi webApi; - private final DB db; - private final Jetty jetty; - - SteveConfiguration() { - // Load the profile in order to load the correct application-.properties file - String profileTemp = new PropertiesFileLoader("application.properties").getString("profile"); - - PropertiesFileLoader p = new PropertiesFileLoader("application-" + profileTemp.toLowerCase() + ".properties"); - - contextPath = sanitizeContextPath(p.getOptionalString("context.path")); - steveVersion = p.getString("steve.version"); - gitDescribe = useFallbackIfNotSet(p.getOptionalString("git.describe"), null); - - profile = p.getString("profile"); - System.setProperty("spring.profiles.active", profile); - - jetty = Jetty.builder() - .serverHost(p.getString("server.host")) - .gzipEnabled(p.getBoolean("server.gzip.enabled")) - .httpEnabled(p.getBoolean("http.enabled")) - .httpPort(p.getInt("http.port")) - .httpsEnabled(p.getBoolean("https.enabled")) - .httpsPort(p.getInt("https.port")) - .keyStorePath(p.getOptionalString("keystore.path")) - .keyStorePassword(p.getOptionalString("keystore.password")) - .build(); - - db = DB.builder() - .ip(p.getString("db.ip")) - .port(p.getInt("db.port")) - .schema(p.getString("db.schema")) - .userName(p.getString("db.user")) - .password(p.getString("db.password")) - .sqlLogging(p.getBoolean("db.sql.logging")) - .build(); - - PasswordEncoder encoder = new BCryptPasswordEncoder(); - - auth = Auth.builder() - .passwordEncoder(encoder) - .userName(p.getString("auth.user")) - .encodedPassword(encoder.encode(p.getString("auth.password"))) - .build(); - - webApi = WebApi.builder() - .headerKey(p.getOptionalString("webapi.key")) - .headerValue(p.getOptionalString("webapi.value")) - .build(); - - ocpp = Ocpp.builder() - .autoRegisterUnknownStations(p.getOptionalBoolean("auto.register.unknown.stations")) - .chargeBoxIdValidationRegex(p.getOptionalString("charge-box-id.validation.regex")) - .wsSessionSelectStrategy( - WsSessionSelectStrategyEnum.fromName(p.getString("ws.session.select.strategy"))) - .build(); - - validate(); - } - - public String getSteveCompositeVersion() { - if (gitDescribe == null) { - return steveVersion; - } else { - return steveVersion + "-g" + gitDescribe; - } - } - - private static String useFallbackIfNotSet(String value, String fallback) { - if (value == null) { - // if the property is optional, value will be null - return fallback; - } else if (value.startsWith("${")) { - // property value variables start with "${" (if maven is not used, the value will not be set) - return fallback; - } else { - return value; - } - } - - private String sanitizeContextPath(String s) { - if (s == null || "/".equals(s)) { - return ""; - - } else if (s.startsWith("/")) { - return s; - - } else { - return "/" + s; - } - } - - private void validate() { - if (!(jetty.httpEnabled || jetty.httpsEnabled)) { - throw new IllegalArgumentException( - "HTTP and HTTPS are both disabled. Well, how do you want to access the server, then?"); - } - } - - // ------------------------------------------------------------------------- - // Class declarations - // ------------------------------------------------------------------------- - - // Jetty configuration - @Builder @Getter - public static class Jetty { - private final String serverHost; - private final boolean gzipEnabled; - - // HTTP - private final boolean httpEnabled; - private final int httpPort; - - // HTTPS - private final boolean httpsEnabled; - private final int httpsPort; - private final String keyStorePath; - private final String keyStorePassword; - } - - // Database configuration - @Builder @Getter - public static class DB { - private final String ip; - private final int port; - private final String schema; - private final String userName; - private final String password; - private final boolean sqlLogging; - } - - // Credentials for Web interface access - @Builder @Getter - public static class Auth { - private final PasswordEncoder passwordEncoder; - private final String userName; - private final String encodedPassword; - } - - @Builder @Getter - public static class WebApi { - private final String headerKey; - private final String headerValue; - } - - // OCPP-related configuration - @Builder @Getter - public static class Ocpp { - private final boolean autoRegisterUnknownStations; - private final String chargeBoxIdValidationRegex; - private final WsSessionSelectStrategy wsSessionSelectStrategy; - } - -} diff --git a/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java index 64eb1b030..0e04f5367 100644 --- a/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java @@ -18,7 +18,6 @@ */ package de.rwth.idsg.steve.config; -import de.rwth.idsg.steve.SteveConfiguration; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; @@ -26,18 +25,8 @@ import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; -import org.springdoc.core.configuration.SpringDocConfiguration; -import org.springdoc.core.properties.SwaggerUiConfigProperties; -import org.springdoc.core.properties.SwaggerUiOAuthProperties; -import org.springdoc.webmvc.core.configuration.SpringDocWebMvcConfiguration; -import org.springdoc.webmvc.ui.SwaggerConfig; -import org.springdoc.webmvc.ui.SwaggerUiHome; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.FilterType; -import org.springframework.context.annotation.Import; import java.util.List; @@ -48,16 +37,6 @@ * @since 15.09.2022 */ @Configuration -@ComponentScan(basePackages = {"org.springdoc"}, - // exclude because SwaggerUiHome's root redirect clashes with our own RootRedirectController - excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SwaggerUiHome.class) -) -@Import({SpringDocConfiguration.class, - SpringDocWebMvcConfiguration.class, - SwaggerConfig.class, - SwaggerUiConfigProperties.class, - SwaggerUiOAuthProperties.class, - JacksonAutoConfiguration.class}) public class ApiDocsConfiguration { static { @@ -77,7 +56,7 @@ public class ApiDocsConfiguration { } @Bean - public OpenAPI apiDocs() { + public OpenAPI apiDocs(SteveProperties steveProperties) { String title = "SteVe REST API Documentation"; String securityName = "basicAuth"; @@ -95,7 +74,7 @@ public OpenAPI apiDocs() { .name("GPL-3.0") .url("https://github.com/steve-community/steve/blob/master/LICENSE.txt") ) - .version(SteveConfiguration.CONFIG.getSteveVersion()) + .version(steveProperties.getVersion()) ) // https://stackoverflow.com/a/68185254 .servers(List.of(new Server().url("/").description("Default Server URL"))) diff --git a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java index 78aecdf4b..e6364bc30 100644 --- a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java @@ -24,7 +24,6 @@ import com.mysql.cj.conf.PropertyKey; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.service.DummyReleaseCheckService; import de.rwth.idsg.steve.service.GithubReleaseCheckService; import de.rwth.idsg.steve.service.ReleaseCheckService; @@ -36,33 +35,28 @@ import org.jooq.impl.DSL; import org.jooq.impl.DataSourceConnectionProvider; import org.jooq.impl.DefaultConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; +import org.springframework.context.annotation.Primary; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; -import jakarta.validation.Validator; - import javax.sql.DataSource; import java.util.List; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * Configuration and beans of Spring Framework. * @@ -80,15 +74,13 @@ public class BeanConfiguration implements WebMvcConfigurer { * https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration */ @Bean - public DataSource dataSource() { - SteveConfiguration.DB dbConfig = CONFIG.getDb(); - + public HikariDataSource dataSource(DataSourceProperties properties) { HikariConfig hc = new HikariConfig(); // set standard params - hc.setJdbcUrl("jdbc:mysql://" + dbConfig.getIp() + ":" + dbConfig.getPort() + "/" + dbConfig.getSchema()); - hc.setUsername(dbConfig.getUserName()); - hc.setPassword(dbConfig.getPassword()); + hc.setJdbcUrl(properties.getUrl()); + hc.setUsername(properties.getUsername()); + hc.setPassword(properties.getPassword()); // set non-standard params hc.addDataSourceProperty(PropertyKey.cachePrepStmts.getKeyName(), true); @@ -96,7 +88,7 @@ public DataSource dataSource() { hc.addDataSourceProperty(PropertyKey.prepStmtCacheSize.getKeyName(), 250); hc.addDataSourceProperty(PropertyKey.prepStmtCacheSqlLimit.getKeyName(), 2048); hc.addDataSourceProperty(PropertyKey.characterEncoding.getKeyName(), "utf8"); - hc.addDataSourceProperty(PropertyKey.connectionTimeZone.getKeyName(), CONFIG.getTimeZoneId()); + hc.addDataSourceProperty(PropertyKey.connectionTimeZone.getKeyName(), SteveProperties.TIME_ZONE_ID); hc.addDataSourceProperty(PropertyKey.useSSL.getKeyName(), true); // https://github.com/steve-community/steve/issues/736 @@ -118,7 +110,8 @@ public DataSource dataSource() { * - http://stackoverflow.com/questions/32848865/jooq-dslcontext-correct-autowiring-with-spring */ @Bean - public DSLContext dslContext(DataSource dataSource) { + public DSLContext dslContext(DataSource dataSource, + SteveProperties steveProperties) { Settings settings = new Settings() // Normally, the records are "attached" to the Configuration that created (i.e. fetch/insert) them. // This means that they hold an internal reference to the same database connection that was used. @@ -126,7 +119,7 @@ public DSLContext dslContext(DataSource dataSource) { // operations. We do not use or need that. .withAttachRecords(false) // To log or not to log the sql queries, that is the question - .withExecuteLogging(CONFIG.getDb().isSqlLogging()); + .withExecuteLogging(steveProperties.getJooq().isExecutiveLogging()); // Configuration for JOOQ org.jooq.Configuration conf = new DefaultConfiguration() @@ -161,11 +154,6 @@ public DelegatingTaskExecutor asyncTaskExecutor() { return new DelegatingTaskExecutor(executor); } - @Bean - public Validator validator() { - return new LocalValidatorFactoryBean(); - } - /** * There might be instances deployed in a local/closed network with no internet connection. In such situations, * it is unnecessary to try to access Github every time, even though the request will time out and result @@ -173,9 +161,9 @@ public Validator validator() { * steps and return a "no new version" report immediately. */ @Bean - public ReleaseCheckService releaseCheckService() { + public ReleaseCheckService releaseCheckService(SteveProperties steveProperties) { if (InternetChecker.isInternetAvailable()) { - return new GithubReleaseCheckService(); + return new GithubReleaseCheckService(steveProperties); } else { return new DummyReleaseCheckService(); } @@ -232,6 +220,7 @@ public void extendMessageConverters(List> converters) { * {@link WebMvcConfigurationSupport#requestMappingHandlerAdapter(ContentNegotiationManager, FormattingConversionService, org.springframework.validation.Validator)}. */ @Bean + @Primary public ObjectMapper jacksonObjectMapper(RequestMappingHandlerAdapter requestMappingHandlerAdapter) { return requestMappingHandlerAdapter.getMessageConverters().stream() .filter(converter -> converter instanceof MappingJackson2HttpMessageConverter) diff --git a/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java index 158f8aa51..a2cb555db 100644 --- a/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java @@ -24,24 +24,16 @@ import de.rwth.idsg.steve.ocpp.soap.MessageIdInterceptor; import lombok.RequiredArgsConstructor; import org.apache.cxf.Bus; -import org.apache.cxf.bus.spring.SpringBus; -import org.apache.cxf.common.logging.LogUtils; -import org.apache.cxf.common.logging.Slf4jLogger; import org.apache.cxf.feature.Feature; import org.apache.cxf.interceptor.Interceptor; -import org.apache.cxf.jaxws.JaxWsServerFactoryBean; +import org.apache.cxf.jaxws.EndpointImpl; import org.apache.cxf.message.Message; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.Collection; import java.util.Collections; import java.util.List; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; - /** * Configuration and beans related to OCPP. * @@ -52,46 +44,55 @@ @Configuration public class OcppConfiguration { - static { - LogUtils.setLoggerClass(Slf4jLogger.class); - } - + private final Bus bus; private final ocpp.cs._2010._08.CentralSystemService ocpp12Server; private final ocpp.cs._2012._06.CentralSystemService ocpp15Server; private final ocpp.cs._2015._10.CentralSystemService ocpp16Server; private final MessageHeaderInterceptor messageHeaderInterceptor; - @Bean(name = Bus.DEFAULT_BUS_ID, destroyMethod = "shutdown") - public SpringBus cxf() { - SpringBus bus = new SpringBus(); - configure(bus); - return bus; + private final MessageIdInterceptor messageIdInterceptor = new MessageIdInterceptor(); + + @Bean + public EndpointImpl ocpp12Endpoint() { + return createDefaultEndpoint(ocpp12Server, "/CentralSystemServiceOCPP12"); } - private void configure(Bus bus) { - List> interceptors = asList(new MessageIdInterceptor(), messageHeaderInterceptor); - List logging = singletonList(LoggingFeatureProxy.INSTANCE.get()); + @Bean + public EndpointImpl ocpp15Endpoint() { + return createDefaultEndpoint(ocpp15Server, "/CentralSystemServiceOCPP15"); + } + + @Bean + public EndpointImpl ocpp16Endpoint() { + return createDefaultEndpoint(ocpp16Server, "/CentralSystemServiceOCPP16"); + } - createOcppService(bus, ocpp12Server, "/CentralSystemServiceOCPP12", interceptors, logging); - createOcppService(bus, ocpp15Server, "/CentralSystemServiceOCPP15", interceptors, logging); - createOcppService(bus, ocpp16Server, "/CentralSystemServiceOCPP16", interceptors, logging); + /** + * Just a dummy service to route incoming messages to the appropriate service version. + */ + @Bean + public EndpointImpl routerEndpoint(EndpointImpl ocpp12Endpoint, + EndpointImpl ocpp15Endpoint, + EndpointImpl ocpp16Endpoint) { + var mediator = new MediatorInInterceptor(List.of(ocpp12Endpoint, ocpp15Endpoint, ocpp16Endpoint)); + return createEndpoint( + ocpp12Server, SteveProperties.ROUTER_ENDPOINT_PATH, List.of(mediator), Collections.emptyList() + ); + } - // Just a dummy service to route incoming messages to the appropriate service version. This should be the last - // one to be created, since in MediatorInInterceptor we go over created/registered services and build a map. - // - List> mediator = singletonList(new MediatorInInterceptor(bus)); - createOcppService(bus, ocpp12Server, CONFIG.getRouterEndpointPath(), mediator, Collections.emptyList()); + private EndpointImpl createDefaultEndpoint(Object serviceBean, String address) { + return createEndpoint( + serviceBean, address, List.of(messageIdInterceptor, messageHeaderInterceptor), List.of(LoggingFeatureProxy.INSTANCE.get()) + ); } - private void createOcppService(Bus bus, Object serviceBean, String address, - List> interceptors, - Collection features) { - JaxWsServerFactoryBean f = new JaxWsServerFactoryBean(); - f.setBus(bus); - f.setServiceBean(serviceBean); - f.setAddress(address); - f.getFeatures().addAll(features); - f.getInInterceptors().addAll(interceptors); - f.create(); + private EndpointImpl createEndpoint(Object serviceBean, String address, + List> interceptors, + List features) { + EndpointImpl endpoint = new EndpointImpl(bus, serviceBean); + endpoint.getInInterceptors().addAll(interceptors); + endpoint.getFeatures().addAll(features); + endpoint.publish(address); + return endpoint; } } diff --git a/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java index 6da0f5981..005cdfc1b 100644 --- a/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java @@ -26,14 +26,13 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 07.01.2015 @@ -53,12 +52,12 @@ public class SecurityConfiguration { */ @Bean public PasswordEncoder passwordEncoder() { - return CONFIG.getAuth().getPasswordEncoder(); + return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - final String prefix = CONFIG.getSpringManagerMapping(); + final String prefix = SteveProperties.SPRING_MANAGER_MAPPING; return http .authorizeHttpRequests( @@ -66,7 +65,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers( "/", // we have RootRedirectController to redirect "/" to "/manager" "/static/**", - CONFIG.getCxfMapping() + "/**", + SteveProperties.CXF_MAPPING + "/**", WebSocketConfiguration.PATH_INFIX + "**", "/WEB-INF/views/**" // https://github.com/spring-projects/spring-security/issues/13285#issuecomment-1579097065 ).permitAll() @@ -75,7 +74,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // SOAP stations are making POST calls for communication. even though the following path is permitted for // all access, there is a global default behaviour from spring security: enable CSRF for all POSTs. // we need to disable CSRF for SOAP paths explicitly. - .csrf(c -> c.ignoringRequestMatchers(CONFIG.getCxfMapping() + "/**")) + .csrf(c -> c.ignoringRequestMatchers(SteveProperties.CXF_MAPPING + "/**")) .sessionManagement(req -> req.invalidSessionUrl(prefix + "/signin")) .formLogin(req -> req.loginPage(prefix + "/signin").permitAll()) .logout(req -> req.logoutUrl(prefix + "/signout")) @@ -85,7 +84,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti @Bean @Order(1) public SecurityFilterChain apiKeyFilterChain(HttpSecurity http, ApiAuthenticationManager apiAuthenticationManager) throws Exception { - return http.securityMatcher(CONFIG.getApiMapping() + "/**") + return http.securityMatcher(SteveProperties.API_MAPPING + "/**") .csrf(k -> k.disable()) .sessionManagement(k -> k.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilter(new BasicAuthenticationFilter(apiAuthenticationManager, apiAuthenticationManager)) diff --git a/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java b/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java new file mode 100644 index 000000000..95f9a88a1 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java @@ -0,0 +1,70 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2025 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.config; + +import de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategyEnum; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author Sevket Goekay + * @since 19.08.2014 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "steve") +public class SteveProperties { + + // Web frontend + public static final String SPRING_MANAGER_MAPPING = "/manager"; + // Mapping for CXF SOAP services + public static final String CXF_MAPPING = "/services"; + // Mapping for Web APIs + public static final String API_MAPPING = "/api"; + // Dummy service path + public static final String ROUTER_ENDPOINT_PATH = "/CentralSystemService"; + // Time zone for the application and database connections + public static final String TIME_ZONE_ID = "UTC"; // or ZoneId.systemDefault().getId(); + + String version; + Auth auth = new Auth(); + Jooq jooq = new Jooq(); + Ocpp ocpp = new Ocpp(); + + @Data + public static class Jooq { + boolean executiveLogging; + } + + @Data + public static class Auth { + String username; + String password; + String webApiKey; + String webApiSecret; + } + + @Data + public static class Ocpp { + WsSessionSelectStrategyEnum wsSessionSelectStrategy; + boolean autoRegisterUnknownStations; + String chargeBoxIdValidationRegex; + } +} diff --git a/src/main/java/de/rwth/idsg/steve/config/WebConfig.java b/src/main/java/de/rwth/idsg/steve/config/WebConfig.java deleted file mode 100644 index 353990a8a..000000000 --- a/src/main/java/de/rwth/idsg/steve/config/WebConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve - * Copyright (C) 2013-2025 SteVe Community Team - * All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.rwth.idsg.steve.config; - -import lombok.extern.slf4j.Slf4j; -import org.apache.cxf.transport.servlet.CXFServlet; -import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; -import org.springframework.web.WebApplicationInitializer; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; - -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; - -import java.util.EnumSet; - -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - -/** - * Jetty will automatically detect this class because of {@link de.rwth.idsg.steve.JettyServer#SCAN_PATTERN} and - * use it to initialize the Spring and servlets and filters. - * - * @author Sevket Goekay - * @since 17.09.2025 - */ -@Slf4j -public class WebConfig implements WebApplicationInitializer { - - @Override - public void onStartup(ServletContext servletContext) throws ServletException { - log.info("Initializing"); - - // Spring root context - AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext(); - springContext.scan("de.rwth.idsg.steve.config"); - servletContext.addListener(new ContextLoaderListener(springContext)); - - // Spring MVC - var dispatcher = servletContext.addServlet("spring-dispatcher", new DispatcherServlet(springContext)); - dispatcher.setLoadOnStartup(1); - dispatcher.addMapping(CONFIG.getSpringMapping()); - - // Apache CXF - var cxf = servletContext.addServlet("cxf", new CXFServlet()); - cxf.setLoadOnStartup(1); - cxf.addMapping(CONFIG.getCxfMapping() + "/*"); - - // add spring security - servletContext - .addFilter(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, "org.springframework.web.filter.DelegatingFilterProxy") - .addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, CONFIG.getSpringMapping() + "*"); - } -} diff --git a/src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java b/src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java index 3a9294fcc..0da47c3d8 100644 --- a/src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java @@ -24,6 +24,7 @@ import de.rwth.idsg.steve.ocpp.ws.ocpp15.Ocpp15WebSocketEndpoint; import de.rwth.idsg.steve.ocpp.ws.ocpp16.Ocpp16WebSocketEndpoint; import de.rwth.idsg.steve.service.ChargePointRegistrationService; +import de.rwth.idsg.steve.web.validation.ChargeBoxIdValidator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.jetty.websocket.core.WebSocketConstants; @@ -47,6 +48,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { private final ChargePointRegistrationService chargePointRegistrationService; + private final ChargeBoxIdValidator chargeBoxIdValidator; private final Ocpp12WebSocketEndpoint ocpp12WebSocketEndpoint; private final Ocpp15WebSocketEndpoint ocpp15WebSocketEndpoint; private final Ocpp16WebSocketEndpoint ocpp16WebSocketEndpoint; @@ -59,6 +61,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { OcppWebSocketHandshakeHandler handshakeHandler = new OcppWebSocketHandshakeHandler( + chargeBoxIdValidator, defaultHandshakeHandler(), Lists.newArrayList(ocpp16WebSocketEndpoint, ocpp15WebSocketEndpoint, ocpp12WebSocketEndpoint), chargePointRegistrationService diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java b/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java index c47e52e88..df899ebcf 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/soap/ClientProvider.java @@ -26,15 +26,13 @@ import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.ws.addressing.WSAddressingFeature; import org.jetbrains.annotations.Nullable; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.web.server.Ssl; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import jakarta.xml.ws.soap.SOAPBinding; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; - -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 21.10.2015 @@ -44,12 +42,12 @@ public class ClientProvider { @Nullable private final TLSClientParameters tlsClientParams; - public ClientProvider() { - if (shouldInitSSL()) { - tlsClientParams = new TLSClientParameters(); - tlsClientParams.setSSLSocketFactory(setupSSL()); - } else { - tlsClientParams = null; + public ClientProvider(ServerProperties serverProperties) { + Ssl ssl = serverProperties.getSsl(); + try { + tlsClientParams = create(ssl); + } catch (Exception e) { + throw new RuntimeException(e); } } @@ -76,24 +74,29 @@ private static JaxWsProxyFactoryBean getBean(String endpointAddress) { return f; } - private static boolean shouldInitSSL() { - return CONFIG.getJetty().getKeyStorePath() != null && CONFIG.getJetty().getKeyStorePassword() != null; - } + private static TLSClientParameters create(Ssl ssl) throws Exception { + if (ssl == null || !ssl.isEnabled()) { + return null; + } - private static SSLSocketFactory setupSSL() { - SSLContext ssl; - try { - String keyStorePath = CONFIG.getJetty().getKeyStorePath(); - String keyStorePwd = CONFIG.getJetty().getKeyStorePassword(); - ssl = SslContextBuilder.builder() - .keyStoreFromFile(keyStorePath, keyStorePwd) - .usingTLS() - .usingDefaultAlgorithm() - .usingKeyManagerPasswordFromKeyStore() - .buildMergedWithSystem(); - } catch (Exception e) { - throw new RuntimeException(e); + String keyStorePath = ssl.getKeyStore(); + String keyStorePwd = ssl.getKeyStorePassword(); + + boolean shouldInit = StringUtils.hasLength(keyStorePath) && StringUtils.hasLength(keyStorePwd); + if (!shouldInit) { + return null; } - return ssl.getSocketFactory(); + + var socketFactory = SslContextBuilder.builder() + .keyStoreFromFile(keyStorePath, keyStorePwd) + .usingTLS() + .usingDefaultAlgorithm() + .usingKeyManagerPasswordFromKeyStore() + .buildMergedWithSystem() + .getSocketFactory(); + + var tlsClientParams = new TLSClientParameters(); + tlsClientParams.setSSLSocketFactory(socketFactory); + return tlsClientParams; } } diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java b/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java index f12175117..20927e333 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java @@ -19,16 +19,14 @@ package de.rwth.idsg.steve.ocpp.soap; import lombok.extern.slf4j.Slf4j; -import org.apache.cxf.Bus; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.binding.soap.SoapVersion; import org.apache.cxf.endpoint.Server; -import org.apache.cxf.endpoint.ServerRegistry; import org.apache.cxf.interceptor.StaxInInterceptor; +import org.apache.cxf.jaxws.EndpointImpl; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; -import org.apache.cxf.service.model.EndpointInfo; import org.apache.cxf.staxutils.DepthXMLStreamReader; import org.apache.cxf.staxutils.StaxUtils; @@ -42,8 +40,6 @@ import java.util.List; import java.util.Map; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * Taken from http://cxf.apache.org/docs/service-routing.html and modified. */ @@ -52,10 +48,10 @@ public class MediatorInInterceptor extends AbstractPhaseInterceptor private final Map actualServers; - public MediatorInInterceptor(Bus bus) { + public MediatorInInterceptor(List endpoints) { super(Phase.POST_STREAM); super.addBefore(StaxInInterceptor.class.getName()); - actualServers = initServerLookupMap(bus); + actualServers = initServerLookupMap(endpoints); } public final void handleMessage(SoapMessage message) { @@ -91,7 +87,9 @@ public final void handleMessage(SoapMessage message) { Server targetServer = actualServers.get(schemaNamespace); // Redirect the request - if (targetServer != null) { + if (targetServer == null) { + log.warn("No server mapped for namespace '{}'", schemaNamespace); + } else { targetServer.getDestination().getMessageObserver().onMessage(message); } @@ -105,30 +103,11 @@ public final void handleMessage(SoapMessage message) { * redirect to the version-specific implementation according to the namespace * of the incoming message. */ - private static Map initServerLookupMap(Bus bus) { - String exceptionMsg = "The services are not created and/or registered to the bus yet."; - - ServerRegistry serverRegistry = bus.getExtension(ServerRegistry.class); - if (serverRegistry == null) { - throw new RuntimeException(exceptionMsg); - } - - List temp = serverRegistry.getServers(); - if (temp.isEmpty()) { - throw new RuntimeException(exceptionMsg); - } - - Map actualServers = new HashMap<>(temp.size() - 1); - for (Server server : temp) { - EndpointInfo info = server.getEndpoint().getEndpointInfo(); - String address = info.getAddress(); - - // exclude the 'dummy' routing server - if (CONFIG.getRouterEndpointPath().equals(address)) { - continue; - } - - String serverNamespace = info.getName().getNamespaceURI(); + private static Map initServerLookupMap(List endpoints) { + Map actualServers = new HashMap<>(); + for (EndpointImpl endpoint : endpoints) { + Server server = endpoint.getServer(); + String serverNamespace = server.getEndpoint().getEndpointInfo().getName().getNamespaceURI(); actualServers.put(serverNamespace, server); } return actualServers; diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java b/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java index 2636670a0..c2dacf771 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java @@ -20,6 +20,7 @@ import com.google.common.base.Strings; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.config.WebSocketConfiguration; import de.rwth.idsg.steve.ocpp.OcppTransport; import de.rwth.idsg.steve.ocpp.OcppVersion; @@ -64,9 +65,9 @@ public abstract class AbstractWebSocketEndpoint extends ConcurrentWebSocketHandl private final OcppServerRepository ocppServerRepository; private final FutureResponseContextStore futureResponseContextStore; private final IncomingPipeline pipeline; + private final SessionContextStore sessionContextStore; private final Logger log = LoggerFactory.getLogger(getClass()); - private final SessionContextStore sessionContextStore = new SessionContextStoreImpl(); private final List> connectedCallbackList = new ArrayList<>(); private final List> disconnectedCallbackList = new ArrayList<>(); private final Object sessionContextLock = new Object(); @@ -75,11 +76,13 @@ public AbstractWebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, + SteveProperties steveProperties, AbstractTypeStore typeStore) { this.asyncTaskScheduler = asyncTaskScheduler; this.ocppServerRepository = ocppServerRepository; this.futureResponseContextStore = futureResponseContextStore; this.pipeline = new IncomingPipeline(new Deserializer(futureResponseContextStore, typeStore), this); + this.sessionContextStore = new SessionContextStoreImpl(steveProperties.getOcpp().getWsSessionSelectStrategy()); connectedCallbackList.add((chargeBoxId) -> applicationEventPublisher.publishEvent(new OcppStationWebSocketConnected(chargeBoxId))); disconnectedCallbackList.add((chargeBoxId) -> applicationEventPublisher.publishEvent(new OcppStationWebSocketDisconnected(chargeBoxId))); diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java b/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java index 963ff23ff..89eee32e7 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java @@ -48,8 +48,7 @@ @RequiredArgsConstructor public class OcppWebSocketHandshakeHandler implements HandshakeHandler { - private static final ChargeBoxIdValidator CHARGE_BOX_ID_VALIDATOR = new ChargeBoxIdValidator(); - + private final ChargeBoxIdValidator chargeBoxIdValidator; private final DefaultHandshakeHandler delegate; private final List endpoints; private final ChargePointRegistrationService chargePointRegistrationService; @@ -72,7 +71,7 @@ public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse respons // ------------------------------------------------------------------------- String chargeBoxId = getLastBitFromUrl(request.getURI().getPath()); - boolean isValid = CHARGE_BOX_ID_VALIDATOR.isValid(chargeBoxId); + boolean isValid = chargeBoxIdValidator.isValid(chargeBoxId); if (!isValid) { log.error("ChargeBoxId '{}' violates the configured pattern.", chargeBoxId); response.setStatusCode(HttpStatus.BAD_REQUEST); @@ -118,9 +117,9 @@ public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse respons } private AbstractWebSocketEndpoint selectEndpoint(List requestedProtocols ) { - for (String requestedProcotol : requestedProtocols) { + for (String requestedProtocol : requestedProtocols) { for (AbstractWebSocketEndpoint item : endpoints) { - if (item.getVersion().getValue().equals(requestedProcotol)) { + if (item.getVersion().getValue().equals(requestedProtocol)) { return item; } } diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java b/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java index 6b6696ca7..e584a64fe 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/ws/SessionContextStoreImpl.java @@ -23,6 +23,7 @@ import de.rwth.idsg.steve.SteveException; import de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategy; import de.rwth.idsg.steve.ocpp.ws.data.SessionContext; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.springframework.web.socket.WebSocketSession; @@ -37,13 +38,12 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.locks.Lock; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 17.03.2015 */ @Slf4j +@RequiredArgsConstructor public class SessionContextStoreImpl implements SessionContextStore { /** @@ -54,7 +54,7 @@ public class SessionContextStoreImpl implements SessionContextStore { private final Striped locks = Striped.lock(16); - private final WsSessionSelectStrategy wsSessionSelectStrategy = CONFIG.getOcpp().getWsSessionSelectStrategy(); + private final WsSessionSelectStrategy wsSessionSelectStrategy; @Override public void add(String chargeBoxId, WebSocketSession session, ScheduledFuture pingSchedule) { diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java b/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java index dc330c6f8..383de53b7 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java @@ -21,6 +21,7 @@ import de.rwth.idsg.ocpp.jaxb.RequestType; import de.rwth.idsg.ocpp.jaxb.ResponseType; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.soap.CentralSystemService12_SoapServer; @@ -52,8 +53,9 @@ public Ocpp12WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, - CentralSystemService12_SoapServer server) { - super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, Ocpp12TypeStore.INSTANCE); + CentralSystemService12_SoapServer server, + SteveProperties steveProperties) { + super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, steveProperties, Ocpp12TypeStore.INSTANCE); this.server = server; } diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java b/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java index ee4d72b18..f3765a487 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java @@ -21,6 +21,7 @@ import de.rwth.idsg.ocpp.jaxb.RequestType; import de.rwth.idsg.ocpp.jaxb.ResponseType; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.soap.CentralSystemService15_SoapServer; @@ -53,8 +54,9 @@ public Ocpp15WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, - CentralSystemService15_SoapServer server) { - super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, Ocpp15TypeStore.INSTANCE); + CentralSystemService15_SoapServer server, + SteveProperties steveProperties) { + super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, steveProperties, Ocpp15TypeStore.INSTANCE); this.server = server; } diff --git a/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java b/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java index d0cdeab29..6c0517c55 100644 --- a/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java +++ b/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java @@ -21,6 +21,7 @@ import de.rwth.idsg.ocpp.jaxb.RequestType; import de.rwth.idsg.ocpp.jaxb.ResponseType; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.soap.CentralSystemService16_SoapServer; @@ -53,8 +54,9 @@ public Ocpp16WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, - CentralSystemService16_SoapServer server) { - super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, Ocpp16TypeStore.INSTANCE); + CentralSystemService16_SoapServer server, + SteveProperties steveProperties) { + super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, steveProperties, Ocpp16TypeStore.INSTANCE); this.server = server; } diff --git a/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java b/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java index 64e2c9588..dab61e9bb 100644 --- a/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.service; import com.google.common.util.concurrent.Striped; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.repository.ChargePointRepository; import de.rwth.idsg.steve.service.dto.UnidentifiedIncomingObject; import lombok.RequiredArgsConstructor; @@ -31,8 +32,6 @@ import java.util.Optional; import java.util.concurrent.locks.Lock; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 15.09.2025 @@ -43,7 +42,7 @@ public class ChargePointRegistrationService { private final UnidentifiedIncomingObjectService unknownChargePointService = new UnidentifiedIncomingObjectService(100); - private final boolean autoRegisterUnknownStations = CONFIG.getOcpp().isAutoRegisterUnknownStations(); + private final SteveProperties steveProperties; private final Striped isRegisteredLocks = Striped.lock(16); private final ChargePointRepository chargePointRepository; @@ -84,7 +83,7 @@ private Optional getRegistrationStatusInternal(String charge } // 2. ok, this chargeBoxId is unknown. exit if auto-register is disabled - if (!autoRegisterUnknownStations) { + if (!steveProperties.getOcpp().isAutoRegisterUnknownStations()) { return Optional.empty(); } diff --git a/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java b/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java index 74c08c71b..afde05f4d 100644 --- a/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java +++ b/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.datatype.joda.JodaModule; import com.github.zafarkhaja.semver.Version; -import de.rwth.idsg.steve.SteveConfiguration; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.web.dto.ReleaseReport; import de.rwth.idsg.steve.web.dto.ReleaseResponse; import lombok.extern.slf4j.Slf4j; @@ -37,7 +37,6 @@ import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; -import jakarta.annotation.PostConstruct; import java.io.File; import java.util.Collections; @@ -58,9 +57,12 @@ public class GithubReleaseCheckService implements ReleaseCheckService { private static final String TAG_NAME_PREFIX = "steve-"; private static final String FILE_SEPARATOR = File.separator; + private final SteveProperties steveProperties; private final RestTemplate restTemplate; - public GithubReleaseCheckService() { + public GithubReleaseCheckService(SteveProperties steveProperties) { + this.steveProperties = steveProperties; + var timeout = Timeout.ofMilliseconds(API_TIMEOUT_IN_MILLIS); var connectionConfig = ConnectionConfig.custom().setConnectTimeout(timeout).build(); @@ -99,10 +101,10 @@ public ReleaseReport check() { // Private helpers // ------------------------------------------------------------------------- - private static ReleaseReport getReport(ReleaseResponse response) { + private ReleaseReport getReport(ReleaseResponse response) { String githubVersion = extractVersion(response); - Version build = Version.valueOf(SteveConfiguration.CONFIG.getSteveVersion()); + Version build = Version.valueOf(steveProperties.getVersion()); Version github = Version.valueOf(githubVersion); boolean isGithubMoreRecent = github.greaterThan(build); diff --git a/src/main/java/de/rwth/idsg/steve/service/WebUserService.java b/src/main/java/de/rwth/idsg/steve/service/WebUserService.java index f2305e94e..00d95eab0 100644 --- a/src/main/java/de/rwth/idsg/steve/service/WebUserService.java +++ b/src/main/java/de/rwth/idsg/steve/service/WebUserService.java @@ -22,7 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import de.rwth.idsg.steve.SteveConfiguration; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.repository.WebUserRepository; import jooq.steve.db.tables.records.WebUserRecord; import lombok.RequiredArgsConstructor; @@ -39,6 +39,7 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.stereotype.Service; @@ -69,6 +70,8 @@ public class WebUserService implements UserDetailsManager { private final ObjectMapper jacksonObjectMapper; private final WebUserRepository webUserRepository; + private final SteveProperties steveProperties; + private final PasswordEncoder passwordEncoder; private final SecurityContextHolderStrategy securityContextHolderStrategy = getContextHolderStrategy(); private final Cache userCache = CacheBuilder.newBuilder() @@ -82,15 +85,15 @@ public void afterStart(ContextRefreshedEvent event) { return; } - var headerVal = SteveConfiguration.CONFIG.getWebApi().getHeaderValue(); + var headerVal = steveProperties.getAuth().getWebApiSecret(); - var encodedApiPassword = StringUtils.isEmpty(headerVal) + var encodedApiPassword = StringUtils.isBlank(headerVal) ? null - : SteveConfiguration.CONFIG.getAuth().getPasswordEncoder().encode(headerVal); + : passwordEncoder.encode(headerVal); var user = new WebUserRecord() - .setUsername(SteveConfiguration.CONFIG.getAuth().getUserName()) - .setPassword(SteveConfiguration.CONFIG.getAuth().getEncodedPassword()) + .setUsername(steveProperties.getAuth().getUsername()) + .setPassword(passwordEncoder.encode(steveProperties.getAuth().getPassword())) .setApiPassword(encodedApiPassword) .setEnabled(true) .setAuthorities(toJson(AuthorityUtils.createAuthorityList("ADMIN"))); diff --git a/src/main/java/de/rwth/idsg/steve/utils/InternetChecker.java b/src/main/java/de/rwth/idsg/steve/utils/InternetChecker.java index 26fd40208..70667878b 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/InternetChecker.java +++ b/src/main/java/de/rwth/idsg/steve/utils/InternetChecker.java @@ -18,7 +18,6 @@ */ package de.rwth.idsg.steve.utils; -import de.rwth.idsg.steve.SteveConfiguration; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -49,7 +48,7 @@ public final class InternetChecker { ); static { - System.setProperty("http.agent", "SteVe/" + SteveConfiguration.CONFIG.getSteveCompositeVersion()); + System.setProperty("http.agent", "SteVe (github)"); } /** diff --git a/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java b/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java index 4592d9f86..daf6e17ae 100644 --- a/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java +++ b/src/main/java/de/rwth/idsg/steve/utils/LogFileRetriever.java @@ -93,7 +93,7 @@ private List getActiveLogFilePaths() { Iterator> appenderIterator = logger.iteratorForAppenders(); List fileNameList = new ArrayList<>(); - if (appenderIterator.hasNext()) { + while (appenderIterator.hasNext()) { var appender = appenderIterator.next(); String fileName = extractFileName(appender); if (fileName != null) { diff --git a/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java b/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java index 58f286a19..711ff6883 100644 --- a/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java +++ b/src/main/java/de/rwth/idsg/steve/web/controller/AboutSettingsController.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.web.controller; import de.rwth.idsg.steve.NotificationFeature; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.repository.GenericRepository; import de.rwth.idsg.steve.repository.SettingsRepository; import de.rwth.idsg.steve.service.MailService; @@ -40,8 +41,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * One controller for about and settings pages * @@ -57,6 +56,7 @@ public class AboutSettingsController { private final SettingsRepository settingsRepository; private final MailService mailService; private final ReleaseCheckService releaseCheckService; + private final SteveProperties steveProperties; // ------------------------------------------------------------------------- // Paths @@ -73,7 +73,7 @@ public class AboutSettingsController { public String getAbout(Model model, @RequestHeader(HttpHeaders.HOST) String host, HttpServletRequest request) { String scheme = request.getScheme(); - model.addAttribute("version", CONFIG.getSteveVersion()); + model.addAttribute("version", steveProperties.getVersion()); model.addAttribute("db", genericRepository.getDBVersion()); model.addAttribute("logFile", logController.getLogFilePath()); model.addAttribute("systemTime", DateTime.now()); diff --git a/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java b/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java index 0bee84a5e..49da5a555 100644 --- a/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java +++ b/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdListValidator.java @@ -18,17 +18,24 @@ */ package de.rwth.idsg.steve.web.validation; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; + import java.util.List; /** * @author Sevket Goekay * @since 21.01.2016 */ +@Component +@RequiredArgsConstructor public class ChargeBoxIdListValidator implements ConstraintValidator> { - private static final ChargeBoxIdValidator VALIDATOR = new ChargeBoxIdValidator(); + private final ChargeBoxIdValidator validator; @Override public void initialize(ChargeBoxId constraintAnnotation) { @@ -37,8 +44,11 @@ public void initialize(ChargeBoxId constraintAnnotation) { @Override public boolean isValid(List value, ConstraintValidatorContext context) { + if (CollectionUtils.isEmpty(value)) { + return true; // null or empty is valid, because it is another constraint's responsibility + } for (String s : value) { - if (!VALIDATOR.isValid(s, context)) { + if (!validator.isValid(s, context)) { return false; } } diff --git a/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java b/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java index 5c21ad58f..de2aade67 100644 --- a/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java +++ b/src/main/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidator.java @@ -19,7 +19,10 @@ package de.rwth.idsg.steve.web.validation; import com.google.common.base.Strings; -import de.rwth.idsg.steve.SteveConfiguration; +import de.rwth.idsg.steve.config.SteveProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; @@ -29,10 +32,21 @@ * @author Sevket Goekay * @since 21.01.2016 */ +@Component public class ChargeBoxIdValidator implements ConstraintValidator { private static final String REGEX = "[^=/()<>]*"; - private static final Pattern PATTERN = Pattern.compile(getRegexToUse()); + + private final Pattern pattern; + + @Autowired + public ChargeBoxIdValidator(SteveProperties steveProperties) { + this(steveProperties.getOcpp().getChargeBoxIdValidationRegex()); + } + + public ChargeBoxIdValidator(String regexFromConfig) { + this.pattern = Pattern.compile(Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig); + } @Override public void initialize(ChargeBoxId idTag) { @@ -57,11 +71,6 @@ public boolean isValid(String str) { return false; } - return PATTERN.matcher(str).matches(); - } - - private static String getRegexToUse() { - String regexFromConfig = SteveConfiguration.CONFIG.getOcpp().getChargeBoxIdValidationRegex(); - return Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig; + return pattern.matcher(str).matches(); } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 60393536b..27f4f6926 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -62,7 +62,5 @@ auto.register.unknown.stations = false charge-box-id.validation.regex = ### DO NOT MODIFY ### -steve.version = ${project.version} -git.describe = ${git.commit.id.describe} db.sql.logging = true profile = dev diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 0a5f04886..0417a1d60 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -62,7 +62,5 @@ auto.register.unknown.stations = false charge-box-id.validation.regex = ### DO NOT MODIFY ### -steve.version = ${project.version} -git.describe = ${git.commit.id.describe} db.sql.logging = false profile = prod diff --git a/src/main/resources/application-kubernetes.properties b/src/main/resources/application-kubernetes.properties index a6585dddd..c236425f0 100644 --- a/src/main/resources/application-kubernetes.properties +++ b/src/main/resources/application-kubernetes.properties @@ -62,7 +62,5 @@ auto.register.unknown.stations = false charge-box-id.validation.regex = ### DO NOT MODIFY ### -steve.version = ${project.version} -git.describe = ${git.commit.id.describe} db.sql.logging = false profile = prod diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 1d52f855c..4b420f783 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -62,7 +62,5 @@ auto.register.unknown.stations = false charge-box-id.validation.regex = ### DO NOT MODIFY ### -steve.version = ${project.version} -git.describe = ${git.commit.id.describe} db.sql.logging = false profile = prod diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index a16d52434..cf7436299 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -62,7 +62,5 @@ auto.register.unknown.stations = false charge-box-id.validation.regex = ### DO NOT MODIFY ### -steve.version = ${project.version} -git.describe = ${git.commit.id.describe} db.sql.logging = true profile = test diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index b9fe48da6..000000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -### DO NOT MODIFY ### -profile = ${envName} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 000000000..78641ba79 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,33 @@ +spring: + profiles.active: @envName@ + application: + name: steve + datasource: + url: jdbc:mysql://${db.ip}:${db.port}/${db.schema} + username: ${db.user} + password: ${db.password} + +server: + address: ${server.host} + port: ${http.port} + servlet.context-path: /${context.path} + compression: + enabled: ${server.gzip.enabled} + ssl: + enabled: ${https.enabled} + key-store: ${keystore.path} + key-store-password: ${keystore.password} + +steve: + version: @project.version@ + jooq: + executive-logging: ${db.sql.logging} + auth: + username: ${auth.user} + password: ${auth.password} + web-api-key: ${webapi.key} + web-api-secret: ${webapi.value} + ocpp: + ws-session-select-strategy: ${ws.session.select.strategy} + auto-register-unknown-stations: ${auto.register.unknown.stations} + charge-box-id-validation-regex: ${charge-box-id.validation.regex} diff --git a/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java b/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java index a3554a77c..52e3fbafd 100644 --- a/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java +++ b/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java @@ -30,11 +30,16 @@ import ocpp.cs._2015._10.HeartbeatResponse; import ocpp.cs._2015._10.RegistrationStatus; import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; -import org.junit.jupiter.api.AfterAll; +import org.jooq.DSLContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; import static de.rwth.idsg.steve.utils.Helpers.getRandomString; @@ -43,6 +48,8 @@ * @since 21.03.2018 */ @Slf4j +@ActiveProfiles(profiles = "test") +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) public class ApplicationJsonTest { private static final String PATH = "ws://localhost:8080/steve/websocket/CentralSystemService/"; @@ -50,23 +57,20 @@ public class ApplicationJsonTest { private static final String REGISTERED_CHARGE_BOX_ID = __DatabasePreparer__.getRegisteredChargeBoxId(); private static final String REGISTERED_OCPP_TAG = __DatabasePreparer__.getRegisteredOcppTag(); - private static Application app; + @Autowired + private DSLContext dslContext; - @BeforeAll - public static void init() throws Exception { - Assertions.assertEquals("test", SteveConfiguration.CONFIG.getProfile()); - __DatabasePreparer__.prepare(); + private __DatabasePreparer__ databasePreparer; - app = new Application(); - app.start(); + @BeforeEach + public void setup() { + databasePreparer = new __DatabasePreparer__(dslContext); + databasePreparer.prepare(); } - @AfterAll - public static void destroy() throws Exception { - if (app != null) { - app.stop(); - } - __DatabasePreparer__.cleanUp(); + @AfterEach + public void teardown() { + databasePreparer.cleanUp(); } @Test diff --git a/src/test/java/de/rwth/idsg/steve/ApplicationTest.java b/src/test/java/de/rwth/idsg/steve/ApplicationTest.java index 52e5c8f18..909f0cff4 100644 --- a/src/test/java/de/rwth/idsg/steve/ApplicationTest.java +++ b/src/test/java/de/rwth/idsg/steve/ApplicationTest.java @@ -27,10 +27,16 @@ import ocpp.cs._2010._08.BootNotificationResponse; import ocpp.cs._2010._08.RegistrationStatus; import ocpp.cs._2012._06.CentralSystemService; -import org.junit.jupiter.api.AfterAll; +import org.jooq.DSLContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.ActiveProfiles; import jakarta.xml.ws.WebServiceException; @@ -45,29 +51,31 @@ * @since 10.03.2018 */ @Slf4j +@ActiveProfiles(profiles = "test") +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) public class ApplicationTest { private static final String REGISTERED_CHARGE_BOX_ID = __DatabasePreparer__.getRegisteredChargeBoxId(); private static final String REGISTERED_OCPP_TAG = __DatabasePreparer__.getRegisteredOcppTag(); - private static final String path = getPath(); - private static Application app; + @Autowired + private ServerProperties serverProperties; + @Autowired + private DSLContext dslContext; - @BeforeAll - public static void init() throws Exception { - Assertions.assertEquals("test", SteveConfiguration.CONFIG.getProfile()); - __DatabasePreparer__.prepare(); + private __DatabasePreparer__ databasePreparer; + private String path; - app = new Application(); - app.start(); + @BeforeEach + public void setup() { + databasePreparer = new __DatabasePreparer__(dslContext); + databasePreparer.prepare(); + path = getPath(serverProperties); } - @AfterAll - public static void destroy() throws Exception { - if (app != null) { - app.stop(); - } - __DatabasePreparer__.cleanUp(); + @AfterEach + public void teardown() { + databasePreparer.cleanUp(); } @Test diff --git a/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java b/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java similarity index 84% rename from src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java rename to src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java index 1632a78a8..4532c7cb8 100644 --- a/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java +++ b/src/test/java/de/rwth/idsg/steve/OperationalSoapOCPP16Test.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve; +import de.rwth.idsg.steve.config.SteveProperties; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.soap.MessageHeaderInterceptor; import de.rwth.idsg.steve.repository.ReservationStatus; @@ -52,15 +53,21 @@ import ocpp.cs._2015._10.StopTransactionRequest; import ocpp.cs._2015._10.StopTransactionResponse; import org.joda.time.DateTime; -import org.junit.jupiter.api.AfterAll; +import org.jooq.DSLContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.ActiveProfiles; import jakarta.xml.ws.WebServiceException; + import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import static de.rwth.idsg.steve.utils.Helpers.getForOcpp16; @@ -72,41 +79,40 @@ * @since 22.03.18 */ @Slf4j -public class OperationalTestSoapOCPP16 { +@ActiveProfiles(profiles = "test") +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) +public class OperationalSoapOCPP16Test { private static final String REGISTERED_CHARGE_BOX_ID = __DatabasePreparer__.getRegisteredChargeBoxId(); private static final String REGISTERED_CHARGE_BOX_ID_2 = __DatabasePreparer__.getRegisteredChargeBoxId2(); private static final String REGISTERED_OCPP_TAG = __DatabasePreparer__.getRegisteredOcppTag(); - private static final String path = getPath(); private static final int numConnectors = 5; - private static Application app; - - @BeforeAll - public static void initClass() throws Exception { - Assertions.assertEquals("test", SteveConfiguration.CONFIG.getProfile()); - app = new Application(); - app.start(); - } + @Autowired + private ServerProperties serverProperties; + @Autowired + private SteveProperties steveProperties; + @Autowired + private DSLContext dslContext; - @AfterAll - public static void destroyClass() throws Exception { - app.stop(); - } + private __DatabasePreparer__ databasePreparer; + private String path; @BeforeEach - public void init() throws Exception { - __DatabasePreparer__.prepare(); + public void setup() { + databasePreparer = new __DatabasePreparer__(dslContext); + databasePreparer.prepare(); + path = getPath(serverProperties); } @AfterEach - public void destroy() throws Exception { - __DatabasePreparer__.cleanUp(); + public void teardown() { + databasePreparer.cleanUp(); } @Test public void testUnregisteredCP() { - Assertions.assertFalse(SteveConfiguration.CONFIG.getOcpp().isAutoRegisterUnknownStations()); + Assertions.assertFalse(steveProperties.getOcpp().isAutoRegisterUnknownStations()); CentralSystemService client = getForOcpp16(path); @@ -127,14 +133,14 @@ public void testUnregisteredCP() { * * In case of BootNotification, the expected behaviour is to set RegistrationStatus.REJECTED in response, as done * by {@link CentralSystemService16_Service#bootNotification(BootNotificationRequest, String, OcppProtocol)}. - * Therefore, no exception. This case is tested by {@link OperationalTestSoapOCPP16#testUnregisteredCP()} already. + * Therefore, no exception. This case is tested by {@link OperationalSoapOCPP16Test#testUnregisteredCP()} already. * * WS/JSON stations cannot connect at all if they are not registered, as ensured by {@link OcppWebSocketUpgrader}. */ @Test public void testUnregisteredCPWithInterceptor() { Assertions.assertThrows(WebServiceException.class, () -> { - Assertions.assertFalse(SteveConfiguration.CONFIG.getOcpp().isAutoRegisterUnknownStations()); + Assertions.assertFalse(steveProperties.getOcpp().isAutoRegisterUnknownStations()); CentralSystemService client = getForOcpp16(path); @@ -150,7 +156,7 @@ public void testRegisteredCP() { initStationWithBootNotification(client); - ChargePoint.Details details = __DatabasePreparer__.getCBDetails(REGISTERED_CHARGE_BOX_ID); + ChargePoint.Details details = databasePreparer.getCBDetails(REGISTERED_CHARGE_BOX_ID); Assertions.assertTrue(details.getChargeBox().getOcppProtocol().contains("ocpp1.6")); } @@ -186,26 +192,26 @@ public void testInTransactionStatusOfIdTag() { new StartTransactionRequest() .withConnectorId(2) .withIdTag(REGISTERED_OCPP_TAG) - .withTimestamp(DateTime.now()) + .withTimestamp(nowWithoutMillis()) .withMeterStart(0), REGISTERED_CHARGE_BOX_ID ); Assertions.assertNotNull(start); Assertions.assertTrue(start.getTransactionId() > 0); - Assertions.assertTrue(__DatabasePreparer__.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()); + Assertions.assertTrue(databasePreparer.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()); StopTransactionResponse stop = client.stopTransaction( new StopTransactionRequest() .withTransactionId(start.getTransactionId()) - .withTimestamp(DateTime.now()) + .withTimestamp(nowWithoutMillis()) .withIdTag(REGISTERED_OCPP_TAG) .withMeterStop(30), REGISTERED_CHARGE_BOX_ID ); Assertions.assertNotNull(stop); - Assertions.assertFalse(__DatabasePreparer__.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()); + Assertions.assertFalse(databasePreparer.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()); } /** @@ -226,7 +232,7 @@ public void testAuthorizationStatus() { new StartTransactionRequest() .withConnectorId(2) .withIdTag(REGISTERED_OCPP_TAG) - .withTimestamp(DateTime.now()) + .withTimestamp(nowWithoutMillis()) .withMeterStart(0), REGISTERED_CHARGE_BOX_ID); Assertions.assertTrue(start1.getTransactionId() > 0); @@ -248,7 +254,7 @@ public void testAuthorizationStatus() { new StartTransactionRequest() .withConnectorId(2) .withIdTag(REGISTERED_OCPP_TAG) - .withTimestamp(DateTime.now()) + .withTimestamp(nowWithoutMillis()) .withMeterStart(0), REGISTERED_CHARGE_BOX_ID_2); Assertions.assertTrue(start2.getTransactionId() > 0); @@ -265,6 +271,13 @@ public void testAuthorizationStatus() { public void testStatusNotification() { CentralSystemService client = getForOcpp16(path); + DateTime startingTime = DateTime.parse("2020-10-01T00:00:00.000Z"); + LinkedHashMap timeStatusMap = new LinkedHashMap<>(); + for (int i = 0; i < ChargePointStatus.values().length; i++) { + ChargePointStatus status = ChargePointStatus.values()[i]; + timeStatusMap.put(startingTime.plusMinutes(i), status); + } + // ------------------------------------------------------------------------- // init the station and verify db connector status values // ------------------------------------------------------------------------- @@ -272,23 +285,23 @@ public void testStatusNotification() { initStationWithBootNotification(client); // test all status enum values - for (ChargePointStatus chargePointStatus : ChargePointStatus.values()) { + for (var entry : timeStatusMap.entrySet()) { // status for numConnectors connectors + connector 0 (main controller of CP) for (int i = 0; i <= numConnectors; i++) { StatusNotificationResponse status = client.statusNotification( new StatusNotificationRequest() .withErrorCode(ChargePointErrorCode.NO_ERROR) - .withStatus(chargePointStatus) + .withStatus(entry.getValue()) .withConnectorId(i) - .withTimestamp(DateTime.now()), + .withTimestamp(entry.getKey()), REGISTERED_CHARGE_BOX_ID ); Assertions.assertNotNull(status); } - List connectorStatusList = __DatabasePreparer__.getChargePointConnectorStatus(); + List connectorStatusList = databasePreparer.getChargePointConnectorStatus(); for (ConnectorStatus connectorStatus : connectorStatusList) { - Assertions.assertEquals(chargePointStatus.value(), connectorStatus.getStatus()); + Assertions.assertEquals(entry.getValue().value(), connectorStatus.getStatus()); Assertions.assertEquals(ChargePointErrorCode.NO_ERROR.value(), connectorStatus.getErrorCode()); } } @@ -298,19 +311,19 @@ public void testStatusNotification() { // ------------------------------------------------------------------------- int faultyConnectorId = 1; + DateTime faultedTime = timeStatusMap.lastEntry().getKey().plusMinutes(1); StatusNotificationResponse statusConnectorError = client.statusNotification( new StatusNotificationRequest() .withErrorCode(ChargePointErrorCode.HIGH_TEMPERATURE) .withStatus(ChargePointStatus.FAULTED) .withConnectorId(faultyConnectorId) - .withTimestamp(DateTime.now()), + .withTimestamp(faultedTime), REGISTERED_CHARGE_BOX_ID ); Assertions.assertNotNull(statusConnectorError); - - List connectorStatusList = __DatabasePreparer__.getChargePointConnectorStatus(); + List connectorStatusList = databasePreparer.getChargePointConnectorStatus(); for (ConnectorStatus connectorStatus : connectorStatusList) { if (connectorStatus.getConnectorId() == faultyConnectorId) { Assertions.assertEquals(ChargePointStatus.FAULTED.value(), connectorStatus.getStatus()); @@ -327,6 +340,7 @@ public void testReservation() { int usedConnectorID = 1; CentralSystemService client = getForOcpp16(path); + DateTime baseTime = nowWithoutMillis(); // ------------------------------------------------------------------------- // init the station and make reservation @@ -335,7 +349,7 @@ public void testReservation() { initStationWithBootNotification(client); initConnectorsWithStatusNotification(client); - int reservationId = __DatabasePreparer__.makeReservation(usedConnectorID); + int reservationId = databasePreparer.makeReservation(usedConnectorID); // ------------------------------------------------------------------------- // startTransaction (invalid reservationId) @@ -347,7 +361,7 @@ public void testReservation() { new StartTransactionRequest() .withConnectorId(usedConnectorID) .withIdTag(REGISTERED_OCPP_TAG) - .withTimestamp(DateTime.now()) + .withTimestamp(baseTime.plusSeconds(1)) .withMeterStart(0) .withReservationId(nonExistingReservationId), REGISTERED_CHARGE_BOX_ID @@ -355,13 +369,13 @@ public void testReservation() { Assertions.assertNotNull(startInvalid); // validate that the transaction is written to db, even though reservation was invalid - List transactions = __DatabasePreparer__.getTransactions(); + List transactions = databasePreparer.getTransactions(); Assertions.assertEquals(1, transactions.size()); Assertions.assertEquals(startInvalid.getTransactionId(), transactions.get(0).getId()); // make sure that this invalid reservation had no side effects { - List reservations = __DatabasePreparer__.getReservations(); + List reservations = databasePreparer.getReservations(); Assertions.assertEquals(1, reservations.size()); Reservation res = reservations.get(0); Assertions.assertEquals(reservationId, res.getId()); @@ -376,7 +390,7 @@ public void testReservation() { new StartTransactionRequest() .withConnectorId(3) .withIdTag(getRandomString()) - .withTimestamp(DateTime.now()) + .withTimestamp(baseTime.plusSeconds(2)) .withMeterStart(0) .withReservationId(reservationId), REGISTERED_CHARGE_BOX_ID @@ -384,7 +398,7 @@ public void testReservation() { Assertions.assertNotNull(startWrongTag); { - List reservations = __DatabasePreparer__.getReservations(); + List reservations = databasePreparer.getReservations(); Assertions.assertEquals(1, reservations.size()); Reservation res = reservations.get(0); Assertions.assertEquals(ReservationStatus.ACCEPTED.value(), res.getStatus()); @@ -399,7 +413,7 @@ public void testReservation() { new StartTransactionRequest() .withConnectorId(usedConnectorID) .withIdTag(REGISTERED_OCPP_TAG) - .withTimestamp(DateTime.now()) + .withTimestamp(baseTime.plusSeconds(3)) .withMeterStart(0) .withReservationId(reservationId), REGISTERED_CHARGE_BOX_ID @@ -408,7 +422,7 @@ public void testReservation() { Integer transactionIdValid = startValidId.getTransactionId(); { - List reservations = __DatabasePreparer__.getReservations(); + List reservations = databasePreparer.getReservations(); Assertions.assertEquals(reservations.size(), 1); Reservation res = reservations.get(0); Assertions.assertEquals(ReservationStatus.USED.value(), res.getStatus()); @@ -423,7 +437,7 @@ public void testReservation() { new StartTransactionRequest() .withConnectorId(usedConnectorID) .withIdTag(REGISTERED_OCPP_TAG) - .withTimestamp(DateTime.now()) + .withTimestamp(baseTime.plusSeconds(4)) .withMeterStart(0) .withReservationId(reservationId), REGISTERED_CHARGE_BOX_ID @@ -431,7 +445,7 @@ public void testReservation() { Assertions.assertNotNull(startValidIdUsedTwice); { - List reservations = __DatabasePreparer__.getReservations(); + List reservations = databasePreparer.getReservations(); Assertions.assertEquals(reservations.size(), 1); Reservation res = reservations.get(0); Assertions.assertEquals(ReservationStatus.USED.value(), res.getStatus()); @@ -485,7 +499,7 @@ private void testBody(List meterValues, List transaction // startTransaction - DateTime startTimeStamp = DateTime.now(); + DateTime startTimeStamp = nowWithoutMillis(); StartTransactionResponse start = client.startTransaction( new StartTransactionRequest() .withConnectorId(usedConnectorID) @@ -498,7 +512,7 @@ private void testBody(List meterValues, List transaction int transactionID = start.getTransactionId(); - List allTransactions = __DatabasePreparer__.getTransactionRecords(); + List allTransactions = databasePreparer.getTransactionRecords(); Assertions.assertEquals(1, allTransactions.size()); { @@ -517,7 +531,7 @@ private void testBody(List meterValues, List transaction .withStatus(ChargePointStatus.CHARGING) .withErrorCode(ChargePointErrorCode.NO_ERROR) .withConnectorId(0) - .withTimestamp(DateTime.now()), + .withTimestamp(nowWithoutMillis()), REGISTERED_CHARGE_BOX_ID ); @@ -537,7 +551,7 @@ private void testBody(List meterValues, List transaction } // stopTransaction - DateTime stopTimeStamp = DateTime.now(); + DateTime stopTimeStamp = nowWithoutMillis(); int stopValue = 30; StopTransactionResponse stop = client.stopTransaction( new StopTransactionRequest() @@ -551,7 +565,7 @@ private void testBody(List meterValues, List transaction { Assertions.assertNotNull(stop); - List transactionsStop = __DatabasePreparer__.getTransactionRecords(); + List transactionsStop = databasePreparer.getTransactionRecords(); Assertions.assertEquals(1, transactionsStop.size()); TransactionRecord t = transactionsStop.get(0); Assertions.assertEquals(stopTimeStamp, t.getStopTimestamp()); @@ -568,7 +582,7 @@ private void testBody(List meterValues, List transaction .withStatus(ChargePointStatus.AVAILABLE) .withErrorCode(ChargePointErrorCode.NO_ERROR) .withConnectorId(usedConnectorID) - .withTimestamp(DateTime.now()), + .withTimestamp(nowWithoutMillis()), REGISTERED_CHARGE_BOX_ID ); Assertions.assertNotNull(statusStop); @@ -591,7 +605,7 @@ private void initConnectorsWithStatusNotification(CentralSystemService client) { .withErrorCode(ChargePointErrorCode.NO_ERROR) .withStatus(ChargePointStatus.AVAILABLE) .withConnectorId(i) - .withTimestamp(DateTime.now()), + .withTimestamp(nowWithoutMillis()), REGISTERED_CHARGE_BOX_ID ); Assertions.assertNotNull(statusBoot); @@ -599,7 +613,7 @@ private void initConnectorsWithStatusNotification(CentralSystemService client) { } private void checkMeterValues(List meterValues, int transactionPk) { - TransactionDetails details = __DatabasePreparer__.getDetails(transactionPk); + TransactionDetails details = databasePreparer.getDetails(transactionPk); // iterate over all created meter values for (MeterValue meterValue : meterValues) { @@ -635,7 +649,14 @@ private List getMeterValues() { } private static MeterValue createMeterValue(String val) { - return new MeterValue().withTimestamp(DateTime.now()) + return new MeterValue().withTimestamp(nowWithoutMillis()) .withSampledValue(new SampledValue().withValue(val)); } + + /** + * https://github.com/steve-community/steve/issues/1371 + */ + private static DateTime nowWithoutMillis() { + return DateTime.now().withMillisOfSecond(0); + } } diff --git a/src/test/java/de/rwth/idsg/steve/StressTest.java b/src/test/java/de/rwth/idsg/steve/StressTest.java index 56ec4d029..f64f2dbd7 100644 --- a/src/test/java/de/rwth/idsg/steve/StressTest.java +++ b/src/test/java/de/rwth/idsg/steve/StressTest.java @@ -22,12 +22,19 @@ import ocpp.cs._2015._10.MeterValue; import ocpp.cs._2015._10.SampledValue; import org.joda.time.DateTime; +import org.jooq.DSLContext; import org.junit.jupiter.api.Assertions; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static de.rwth.idsg.steve.utils.Helpers.getJsonPath; +import static de.rwth.idsg.steve.utils.Helpers.getPath; + /** * @author Sevket Goekay * @since 10.05.2018 @@ -49,21 +56,40 @@ public abstract class StressTest { protected static final int CHARGE_BOX_COUNT = THREAD_COUNT; protected static final int CONNECTOR_COUNT_PER_CHARGE_BOX = 25; + protected String soapPath; + protected String jsonPath; + protected void attack() throws Exception { - Assertions.assertEquals("test", SteveConfiguration.CONFIG.getProfile()); - Assertions.assertTrue(SteveConfiguration.CONFIG.getOcpp().isAutoRegisterUnknownStations()); + System.setProperty("spring.profiles.active", "test"); - __DatabasePreparer__.prepare(); + __DatabasePreparer__ databasePreparer = null; + ConfigurableApplicationContext app = null; - Application app = new Application(); try { - app.start(); + app = SteveApplication.start(); + + ConfigurableEnvironment environment = app.getEnvironment(); + Assertions.assertEquals(new String[]{"test"}, environment.getActiveProfiles()); + Assertions.assertEquals("true", environment.getProperty("steve.ocpp.auto-register-unknown-stations")); + + ServerProperties serverProperties = app.getBean(ServerProperties.class); + soapPath = getPath(serverProperties); + jsonPath = getJsonPath(serverProperties); + + DSLContext dslContext = app.getBean(DSLContext.class); + databasePreparer = new __DatabasePreparer__(dslContext); + databasePreparer.prepare(); + attackInternal(); } finally { try { - app.stop(); + if (app != null) { + app.close(); + } } finally { - __DatabasePreparer__.cleanUp(); + if (databasePreparer != null) { + databasePreparer.cleanUp(); + } } } } diff --git a/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java b/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java index 3517c1792..fb8b42718 100644 --- a/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java +++ b/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java @@ -46,7 +46,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; -import static de.rwth.idsg.steve.utils.Helpers.getJsonPath; import static de.rwth.idsg.steve.utils.Helpers.getRandomString; import static de.rwth.idsg.steve.utils.Helpers.getRandomStrings; @@ -56,7 +55,6 @@ */ public class StressTestJsonOCPP16 extends StressTest { - private static final String PATH = getJsonPath(); private static final OcppVersion VERSION = OcppVersion.V_16; public static void main(String[] args) throws Exception { @@ -76,7 +74,7 @@ public void beforeRepeat() { ThreadLocalRandom localRandom = ThreadLocalRandom.current(); String chargeBoxId = chargeBoxIds.get(localRandom.nextInt(chargeBoxIds.size())); - threadLocalChargePoint.set(new OcppJsonChargePoint(VERSION, chargeBoxId, PATH)); + threadLocalChargePoint.set(new OcppJsonChargePoint(VERSION, chargeBoxId, jsonPath)); OcppJsonChargePoint chargePoint = threadLocalChargePoint.get(); chargePoint.start(); diff --git a/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java b/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java index dc6e79cd2..bb89ac584 100644 --- a/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java +++ b/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java @@ -45,7 +45,6 @@ import java.util.concurrent.ThreadLocalRandom; import static de.rwth.idsg.steve.utils.Helpers.getForOcpp16; -import static de.rwth.idsg.steve.utils.Helpers.getPath; import static de.rwth.idsg.steve.utils.Helpers.getRandomString; import static de.rwth.idsg.steve.utils.Helpers.getRandomStrings; @@ -55,8 +54,6 @@ */ public class StressTestSoapOCPP16 extends StressTest { - private static final String path = getPath(); - public static void main(String[] args) throws Exception { new StressTestSoapOCPP16().attack(); } @@ -71,7 +68,7 @@ protected void attackInternal() throws Exception { @Override public void beforeRepeat() { - CentralSystemService client = getForOcpp16(path); + CentralSystemService client = getForOcpp16(soapPath); ThreadLocalRandom localRandom = ThreadLocalRandom.current(); threadLocalChargeBoxId.set(chargeBoxIds.get(localRandom.nextInt(chargeBoxIds.size()))); @@ -89,7 +86,7 @@ public void beforeRepeat() { @Override public void toRepeat() { - CentralSystemService client = getForOcpp16(path); + CentralSystemService client = getForOcpp16(soapPath); ThreadLocalRandom localRandom = ThreadLocalRandom.current(); String chargeBoxId = threadLocalChargeBoxId.get(); @@ -183,4 +180,4 @@ public void afterRepeat() { tester.test(runnable); tester.shutDown(); } -} \ No newline at end of file +} diff --git a/src/test/java/de/rwth/idsg/steve/issues/Issue72.java b/src/test/java/de/rwth/idsg/steve/issues/Issue72.java index c78e34380..1b097cdb7 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue72.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue72.java @@ -39,7 +39,6 @@ import org.junit.jupiter.api.Assertions; import static de.rwth.idsg.steve.utils.Helpers.getForOcpp16; -import static de.rwth.idsg.steve.utils.Helpers.getPath; import static de.rwth.idsg.steve.utils.Helpers.getRandomString; /** @@ -50,8 +49,6 @@ */ public class Issue72 extends StressTest { - private static final String path = getPath(); - public static void main(String[] args) throws Exception { new Issue72().attack(); } @@ -68,14 +65,14 @@ protected void attackInternal() throws Exception { int meterStart = 444; int meterStop = 99999; - BootNotificationResponse boot = getForOcpp16(path).bootNotification( + BootNotificationResponse boot = getForOcpp16(soapPath).bootNotification( new BootNotificationRequest() .withChargePointVendor(getRandomString()) .withChargePointModel(getRandomString()), chargeBoxId); Assertions.assertEquals(RegistrationStatus.ACCEPTED, boot.getStatus()); - StartTransactionResponse start = getForOcpp16(path).startTransaction( + StartTransactionResponse start = getForOcpp16(soapPath).startTransaction( new StartTransactionRequest() .withConnectorId(connectorId) .withIdTag(idTag) @@ -93,7 +90,7 @@ protected void attackInternal() throws Exception { @Override public void beforeRepeat() { - threadLocalClient.set(getForOcpp16(path)); + threadLocalClient.set(getForOcpp16(soapPath)); } @Override @@ -134,4 +131,4 @@ public void afterRepeat() { tester.test(runnable); tester.shutDown(); } -} \ No newline at end of file +} diff --git a/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java b/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java index f73646e6a..d6370558b 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java @@ -46,7 +46,6 @@ import org.junit.jupiter.api.Assertions; import static de.rwth.idsg.steve.utils.Helpers.getForOcpp16; -import static de.rwth.idsg.steve.utils.Helpers.getPath; import static de.rwth.idsg.steve.utils.Helpers.getRandomString; /** @@ -57,8 +56,6 @@ */ public class Issue72LowLevelSoap extends StressTest { - private static final String path = getPath(); - public static void main(String[] args) throws Exception { new Issue72LowLevelSoap().attack(); } @@ -75,14 +72,14 @@ protected void attackInternal() throws Exception { int meterStart = 444; int meterStop = 99999; - BootNotificationResponse boot = getForOcpp16(path).bootNotification( + BootNotificationResponse boot = getForOcpp16(soapPath).bootNotification( new BootNotificationRequest() .withChargePointVendor(getRandomString()) .withChargePointModel(getRandomString()), chargeBoxId); Assertions.assertEquals(RegistrationStatus.ACCEPTED, boot.getStatus()); - StartTransactionResponse start = getForOcpp16(path).startTransaction( + StartTransactionResponse start = getForOcpp16(soapPath).startTransaction( new StartTransactionRequest() .withConnectorId(connectorId) .withIdTag(idTag) @@ -97,7 +94,7 @@ protected void attackInternal() throws Exception { String body = buildRequest(chargeBoxId, transactionId, idTag, stopDateTime, meterStop); ContentType contentType = ContentType.create(MediaType.SOAP_XML_UTF_8.type(), MediaType.SOAP_XML_UTF_8.charset().orNull()); - HttpUriRequest req = RequestBuilder.post(path) + HttpUriRequest req = RequestBuilder.post(soapPath) .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") .setEntity(new StringEntity(body, contentType)) .build(); @@ -110,7 +107,7 @@ protected void attackInternal() throws Exception { @Override public void beforeRepeat() { - threadLocalClient.set(getForOcpp16(path)); + threadLocalClient.set(getForOcpp16(soapPath)); } @Override @@ -158,12 +155,12 @@ public void afterRepeat() { } } - private static String buildRequest(String chargeBoxId, int transactionId, String idTag, - DateTime stop, int meterStop) { + private String buildRequest(String chargeBoxId, int transactionId, String idTag, + DateTime stop, int meterStop) { return "" + "/StopTransaction" + "urn:uuid:47c9e1d9-a278-4e9c-8f08-565c29d86167" + - "" + path + "" + + "" + soapPath + "" + "
http://www.w3.org/2005/08/addressing/anonymous
" + "
" + chargeBoxId + "" + "
" + @@ -173,4 +170,4 @@ private static String buildRequest(String chargeBoxId, int transactionId, String "" + meterStop + "" + "
"; } -} \ No newline at end of file +} diff --git a/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java b/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java similarity index 66% rename from src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java rename to src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java index 510137781..cde821c0e 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue73FixTest.java @@ -19,8 +19,6 @@ package de.rwth.idsg.steve.issues; import com.google.common.collect.Lists; -import de.rwth.idsg.steve.Application; -import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.utils.__DatabasePreparer__; import ocpp.cs._2015._10.AuthorizationStatus; import ocpp.cs._2015._10.AuthorizeRequest; @@ -32,7 +30,16 @@ import ocpp.cs._2015._10.StartTransactionRequest; import ocpp.cs._2015._10.StartTransactionResponse; import org.joda.time.DateTime; +import org.jooq.DSLContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.ActiveProfiles; import java.util.List; @@ -42,49 +49,53 @@ /** * https://github.com/steve-community/steve/issues/73 + * https://github.com/steve-community/steve/issues/219 * * @author Sevket Goekay * @since 02.07.2018 */ -public class Issue73Fix { +@ActiveProfiles(profiles = "test") +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) +public class Issue73FixTest { private static final String REGISTERED_OCPP_TAG = __DatabasePreparer__.getRegisteredOcppTag(); - private static final String path = getPath(); - - public static void main(String[] args) throws Exception { - Assertions.assertEquals("test", SteveConfiguration.CONFIG.getProfile()); - Assertions.assertTrue(SteveConfiguration.CONFIG.getOcpp().isAutoRegisterUnknownStations()); - - __DatabasePreparer__.prepare(); - - Application app = new Application(); - try { - app.start(); - test(); - } finally { - try { - app.stop(); - } finally { - __DatabasePreparer__.cleanUp(); - } - } + + @Autowired + private ServerProperties serverProperties; + @Autowired + private DSLContext dslContext; + + private __DatabasePreparer__ databasePreparer; + + @BeforeEach + public void setup() { + databasePreparer = new __DatabasePreparer__(dslContext); + databasePreparer.prepare(); } - private static void test() { - ocpp.cs._2015._10.CentralSystemService client = getForOcpp16(path); + @AfterEach + public void teardown() { + databasePreparer.cleanUp(); + } + + @Test + public void test() { + ocpp.cs._2015._10.CentralSystemService client = getForOcpp16(getPath(serverProperties)); - String chargeBox1 = getRandomString(); - String chargeBox2 = getRandomString(); + String chargeBox1 = __DatabasePreparer__.getRegisteredChargeBoxId(); + String chargeBox2 = __DatabasePreparer__.getRegisteredChargeBoxId2(); sendBoot(client, Lists.newArrayList(chargeBox1, chargeBox2)); sendAuth(client, chargeBox1, AuthorizationStatus.ACCEPTED); - sendStartTx(client, chargeBox1); + sendStartTx(client, chargeBox1, AuthorizationStatus.ACCEPTED); sendAuth(client, chargeBox1, AuthorizationStatus.ACCEPTED); - sendAuth(client, chargeBox2, AuthorizationStatus.CONCURRENT_TX); + sendAuth(client, chargeBox2, AuthorizationStatus.ACCEPTED); + + sendStartTx(client, chargeBox2, AuthorizationStatus.CONCURRENT_TX); } private static void sendBoot(CentralSystemService client, List chargeBoxIdList) { @@ -105,7 +116,7 @@ private static void sendAuth(CentralSystemService client, String chargeBoxId, Au Assertions.assertEquals(expected, auth.getIdTagInfo().getStatus()); } - private static void sendStartTx(CentralSystemService client, String chargeBoxId) { + private void sendStartTx(CentralSystemService client, String chargeBoxId, AuthorizationStatus expected) { StartTransactionResponse start = client.startTransaction( new StartTransactionRequest() .withConnectorId(2) @@ -115,7 +126,8 @@ private static void sendStartTx(CentralSystemService client, String chargeBoxId) chargeBoxId ); Assertions.assertNotNull(start); + Assertions.assertEquals(expected, start.getIdTagInfo().getStatus()); Assertions.assertTrue(start.getTransactionId() > 0); - Assertions.assertTrue(__DatabasePreparer__.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()); + Assertions.assertTrue(databasePreparer.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()); } } diff --git a/src/test/java/de/rwth/idsg/steve/issues/Issue81.java b/src/test/java/de/rwth/idsg/steve/issues/Issue81.java index 525a30174..26831ebee 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue81.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue81.java @@ -42,8 +42,6 @@ */ public class Issue81 extends StressTest { - private static final String path = getPath(); - public static void main(String[] args) throws Exception { new Issue81().attack(); } @@ -58,10 +56,10 @@ protected void attackInternal() throws Exception { @Override public void beforeRepeat() { - client.set(getForOcpp16(path)); + client.set(getForOcpp16(soapPath)); chargeBoxId.set(Helpers.getRandomString()); - BootNotificationResponse boot = getForOcpp16(path).bootNotification( + BootNotificationResponse boot = getForOcpp16(soapPath).bootNotification( new BootNotificationRequest() .withChargePointVendor(getRandomString()) .withChargePointModel(getRandomString()), @@ -101,4 +99,4 @@ private static Integer sendStartTx(CentralSystemService client, StartTransaction Assertions.assertNotNull(start); return start.getTransactionId(); } -} \ No newline at end of file +} diff --git a/src/test/java/de/rwth/idsg/steve/utils/Helpers.java b/src/test/java/de/rwth/idsg/steve/utils/Helpers.java index 0135ce56f..a4f8cb6ad 100644 --- a/src/test/java/de/rwth/idsg/steve/utils/Helpers.java +++ b/src/test/java/de/rwth/idsg/steve/utils/Helpers.java @@ -18,16 +18,17 @@ */ package de.rwth.idsg.steve.utils; +import de.rwth.idsg.steve.config.SteveProperties; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.ws.addressing.WSAddressingFeature; +import org.springframework.boot.autoconfigure.web.ServerProperties; import jakarta.xml.ws.soap.SOAPBinding; + import java.util.ArrayList; import java.util.List; import java.util.UUID; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Andreas Heuvels * @since 06.04.18 @@ -46,40 +47,41 @@ public static List getRandomStrings(int size) { return list; } - public static String getPath() { - String prefix; - int port; - - if (CONFIG.getJetty().isHttpEnabled()) { - prefix = "http://"; - port = CONFIG.getJetty().getHttpPort(); - } else if (CONFIG.getJetty().isHttpsEnabled()) { - prefix = "https://"; - port = CONFIG.getJetty().getHttpsPort(); - } else { - throw new RuntimeException(); + /** + * only http:/ and https:/ because serverProperties.getAddress() starts with a slash. + */ + public static String getPath(ServerProperties serverProperties) { + String prefix = "http:/"; + + if (serverProperties.getSsl().isEnabled()) { + prefix = "https:/"; } - return prefix + CONFIG.getJetty().getServerHost() + ":" + port - + CONFIG.getContextPath() + "/services" + CONFIG.getRouterEndpointPath(); + return prefix + + serverProperties.getAddress() + + ":" + + serverProperties.getPort() + + serverProperties.getServlet().getContextPath() + + "/services" + + SteveProperties.ROUTER_ENDPOINT_PATH; } - public static String getJsonPath() { - String prefix; - int port; - - if (CONFIG.getJetty().isHttpEnabled()) { - prefix = "ws://"; - port = CONFIG.getJetty().getHttpPort(); - } else if (CONFIG.getJetty().isHttpsEnabled()) { - prefix = "wss://"; - port = CONFIG.getJetty().getHttpsPort(); - } else { - throw new RuntimeException(); + /** + * only ws:/ and wss:/ because serverProperties.getAddress() starts with a slash. + */ + public static String getJsonPath(ServerProperties serverProperties) { + String prefix = "ws:/"; + + if (serverProperties.getSsl().isEnabled()) { + prefix = "wss:/"; } - return prefix + CONFIG.getJetty().getServerHost() + ":" + port - + CONFIG.getContextPath() + "/websocket/CentralSystemService/"; + return prefix + + serverProperties.getAddress() + + ":" + + serverProperties.getPort() + + serverProperties.getServlet().getContextPath() + + "/websocket/CentralSystemService/"; } public static ocpp.cs._2015._10.CentralSystemService getForOcpp16(String path) { diff --git a/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java b/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java index 5eafc5e90..92165ffe0 100644 --- a/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java +++ b/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java @@ -39,6 +39,7 @@ import jooq.steve.db.tables.Settings; import jooq.steve.db.tables.records.OcppTagActivityRecord; import jooq.steve.db.tables.records.TransactionRecord; +import lombok.RequiredArgsConstructor; import org.joda.time.DateTime; import org.jooq.DSLContext; import org.jooq.Schema; @@ -64,6 +65,7 @@ * @author Sevket Goekay * @since 21.03.2018 */ +@RequiredArgsConstructor public class __DatabasePreparer__ { private static final String SCHEMA_TO_TRUNCATE = "stevedb_test_2aa6a783d47d"; @@ -71,10 +73,9 @@ public class __DatabasePreparer__ { private static final String REGISTERED_CHARGE_BOX_ID_2 = "charge_box_2aa6a783d47d_2"; private static final String REGISTERED_OCPP_TAG = "id_tag_2aa6a783d47d"; - private static final BeanConfiguration beanConfiguration = new BeanConfiguration(); - private static final DSLContext dslContext = beanConfiguration.dslContext(beanConfiguration.dataSource()); + private final DSLContext dslContext; - public static void prepare() { + public void prepare() { runOperation(ctx -> { truncateTables(ctx); insertChargeBox(ctx); @@ -82,7 +83,7 @@ public static void prepare() { }); } - public static int makeReservation(int connectorId) { + public int makeReservation(int connectorId) { ReservationRepositoryImpl r = new ReservationRepositoryImpl(dslContext); InsertReservationParams params = InsertReservationParams.builder() .chargeBoxId(REGISTERED_CHARGE_BOX_ID) @@ -95,8 +96,8 @@ public static int makeReservation(int connectorId) { return reservationId; } - public static void cleanUp() { - runOperation(__DatabasePreparer__::truncateTables); + public void cleanUp() { + runOperation(this::truncateTables); } public static String getRegisteredChargeBoxId() { @@ -111,46 +112,46 @@ public static String getRegisteredOcppTag() { return REGISTERED_OCPP_TAG; } - public static List getTransactions() { + public List getTransactions() { TransactionRepositoryImpl impl = new TransactionRepositoryImpl(dslContext); return impl.getTransactions(new TransactionQueryForm()); } - public static List getTransactionRecords() { + public List getTransactionRecords() { return dslContext.selectFrom(TRANSACTION).fetch(); } - public static List getReservations() { + public List getReservations() { ReservationRepositoryImpl impl = new ReservationRepositoryImpl(dslContext); return impl.getReservations(new ReservationQueryForm()); } - public static List getChargePointConnectorStatus() { + public List getChargePointConnectorStatus() { ChargePointRepositoryImpl impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl()); return impl.getChargePointConnectorStatus(); } - public static TransactionDetails getDetails(int transactionPk) { + public TransactionDetails getDetails(int transactionPk) { TransactionRepositoryImpl impl = new TransactionRepositoryImpl(dslContext); return impl.getDetails(transactionPk); } - public static OcppTagActivityRecord getOcppTagRecord(String idTag) { + public OcppTagActivityRecord getOcppTagRecord(String idTag) { OcppTagRepositoryImpl impl = new OcppTagRepositoryImpl(dslContext); return impl.getRecord(idTag); } - public static ChargePoint.Details getCBDetails(String chargeboxID) { + public ChargePoint.Details getCBDetails(String chargeboxID) { ChargePointRepositoryImpl impl = new ChargePointRepositoryImpl(dslContext, new AddressRepositoryImpl()); Map pkMap = impl.getChargeBoxIdPkPair(Arrays.asList(chargeboxID)); int pk = pkMap.get(chargeboxID); return impl.getDetails(pk); } - private static void runOperation(Consumer consumer) { + private void runOperation(Consumer consumer) { consumer.accept(dslContext); } - private static void truncateTables(DSLContext ctx) { + private void truncateTables(DSLContext ctx) { Set> skipList = Sets.newHashSet( SchemaVersion.SCHEMA_VERSION, Settings.SETTINGS, diff --git a/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java b/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java index 0504e18b5..985dffa8b 100644 --- a/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java +++ b/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java @@ -27,7 +27,7 @@ */ public class ChargeBoxIdValidatorTest { - ChargeBoxIdValidator validator = new ChargeBoxIdValidator(); + ChargeBoxIdValidator validator = new ChargeBoxIdValidator((String) null); @Test public void testNull() {