diff --git a/src/main/java/de/rwth/idsg/steve/Application.java b/src/main/java/de/rwth/idsg/steve/Application.java index 55c141183..7f8f537fd 100644 --- a/src/main/java/de/rwth/idsg/steve/Application.java +++ b/src/main/java/de/rwth/idsg/steve/Application.java @@ -34,7 +34,11 @@ @Slf4j public class Application { - private final JettyServer server = new JettyServer(); + private final JettyServer server; + + public Application(SteveConfiguration config) { + server = new JettyServer(config); + } public static void main(String[] args) throws Exception { // For Hibernate validator @@ -53,7 +57,7 @@ public static void main(String[] args) throws Exception { System.out.println("Log file: " + path.get().toAbsolutePath()); } - Application app = new Application(); + Application app = new Application(sc); try { app.start(); diff --git a/src/main/java/de/rwth/idsg/steve/JettyServer.java b/src/main/java/de/rwth/idsg/steve/JettyServer.java index 3b4117666..37a14e894 100644 --- a/src/main/java/de/rwth/idsg/steve/JettyServer.java +++ b/src/main/java/de/rwth/idsg/steve/JettyServer.java @@ -51,8 +51,6 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 12.12.2014 @@ -60,6 +58,8 @@ @Slf4j public class JettyServer { + private final SteveConfiguration config; + private Server server; private SteveAppContext steveAppContext; @@ -69,6 +69,10 @@ public class JettyServer { private static final long STOP_TIMEOUT = TimeUnit.SECONDS.toMillis(5); private static final long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(1); + public JettyServer(SteveConfiguration config) { + this.config = config; + } + /** * A fully configured Jetty Server instance */ @@ -89,7 +93,7 @@ private void prepare() { // HTTP Configuration HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSecureScheme(HttpScheme.HTTPS.asString()); - httpConfig.setSecurePort(CONFIG.getJetty().getHttpsPort()); + httpConfig.setSecurePort(config.getJetty().getHttpsPort()); httpConfig.setOutputBufferSize(32768); httpConfig.setRequestHeaderSize(8192); httpConfig.setResponseHeaderSize(8192); @@ -107,45 +111,45 @@ private void prepare() { server.setStopAtShutdown(true); server.setStopTimeout(STOP_TIMEOUT); - if (CONFIG.getJetty().isHttpEnabled()) { + if (config.getJetty().isHttpEnabled()) { server.addConnector(httpConnector(httpConfig)); } - if (CONFIG.getJetty().isHttpsEnabled()) { + if (config.getJetty().isHttpsEnabled()) { server.addConnector(httpsConnector(httpConfig)); } - steveAppContext = new SteveAppContext(); + steveAppContext = new SteveAppContext(config); server.setHandler(steveAppContext.getHandlers()); } - private ServerConnector httpConnector(HttpConfiguration httpConfig) { + 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()); + 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) { + 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()); + sslContextFactory.setKeyStorePath(config.getJetty().getKeyStorePath()); + sslContextFactory.setKeyStorePassword(config.getJetty().getKeyStorePassword()); + sslContextFactory.setKeyManagerPassword(config.getJetty().getKeyStorePassword()); // SSL HTTP Configuration - HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); + 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.setHost(config.getJetty().getServerHost()); + https.setPort(config.getJetty().getHttpsPort()); https.setIdleTimeout(IDLE_TIMEOUT); return https; } @@ -198,12 +202,12 @@ private List getConnectorPathList() { } return Arrays.stream(server.getConnectors()) - .map(JettyServer::getConnectorPath) + .map(this::getConnectorPath) .flatMap(Collection::stream) .collect(Collectors.toList()); } - private static List getConnectorPath(Connector c) { + private List getConnectorPath(Connector c) { ServerConnector sc = (ServerConnector) c; final String prefix; @@ -216,12 +220,12 @@ private static List getConnectorPath(Connector c) { Set ips = new HashSet<>(); String host = sc.getHost(); if (host == null || host.equals("0.0.0.0")) { - ips.addAll(getPossibleIpAddresses()); + ips.addAll(getPossibleIpAddresses(config.getJetty().getServerHost())); } else { ips.add(host); } - String layout = "%s://%s:%d" + CONFIG.getContextPath(); + String layout = "%s://%s:%d" + config.getContextPath(); return ips.stream() .map(k -> String.format(layout, prefix, k, sc.getPort())) @@ -245,7 +249,7 @@ private String getElementPrefix(String str, boolean replaceHttp) { /** * Uses different APIs to find out the IP of this machine. */ - private static List getPossibleIpAddresses() { + private static List getPossibleIpAddresses(String serverHost) { final String host = "treibhaus.informatik.rwth-aachen.de"; final List ips = new ArrayList<>(); @@ -292,7 +296,7 @@ private static List getPossibleIpAddresses() { if (ips.isEmpty()) { // Well, we failed to read from system, fall back to main.properties. // Better than nothing - ips.add(CONFIG.getJetty().getServerHost()); + ips.add(serverHost); } return ips; diff --git a/src/main/java/de/rwth/idsg/steve/SteveAppContext.java b/src/main/java/de/rwth/idsg/steve/SteveAppContext.java index 7eb2d06cb..a424bdd00 100644 --- a/src/main/java/de/rwth/idsg/steve/SteveAppContext.java +++ b/src/main/java/de/rwth/idsg/steve/SteveAppContext.java @@ -47,7 +47,6 @@ import java.util.EnumSet; import java.util.HashSet; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; import static de.rwth.idsg.steve.config.WebSocketConfiguration.IDLE_TIMEOUT; import static de.rwth.idsg.steve.config.WebSocketConfiguration.MAX_MSG_SIZE; @@ -57,10 +56,12 @@ */ public class SteveAppContext { + private final SteveConfiguration config; private final AnnotationConfigWebApplicationContext springContext; private final WebAppContext webAppContext; - public SteveAppContext() { + public SteveAppContext(SteveConfiguration config) { + this.config = config; springContext = new AnnotationConfigWebApplicationContext(); springContext.scan("de.rwth.idsg.steve.config"); webAppContext = initWebApp(); @@ -83,7 +84,7 @@ public void configureWebSocket() { } private Handler getWebApp() { - if (!CONFIG.getJetty().isGzipEnabled()) { + if (!config.getJetty().isGzipEnabled()) { return webAppContext; } @@ -94,7 +95,7 @@ private Handler getWebApp() { private WebAppContext initWebApp() { WebAppContext ctx = new WebAppContext(); - ctx.setContextPath(CONFIG.getContextPath()); + ctx.setContextPath(config.getContextPath()); ctx.setBaseResourceAsString(getWebAppURIAsString()); // if during startup an exception happens, do not swallow it, throw it @@ -107,14 +108,14 @@ private WebAppContext initWebApp() { ServletHolder cxf = new ServletHolder("cxf", new CXFServlet()); ctx.addEventListener(new ContextLoaderListener(springContext)); - ctx.addServlet(web, CONFIG.getSpringMapping()); - ctx.addServlet(cxf, CONFIG.getCxfMapping() + "/*"); + ctx.addServlet(web, config.getSpringMapping()); + ctx.addServlet(cxf, config.getCxfMapping() + "/*"); // add spring security ctx.addFilter( // The bean name is not arbitrary, but is as expected by Spring new FilterHolder(new DelegatingFilterProxy(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)), - CONFIG.getSpringMapping() + "*", + config.getSpringMapping() + "*", EnumSet.allOf(DispatcherType.class) ); @@ -128,14 +129,14 @@ private Handler getRedirectHandler() { RedirectPatternRule rule = new RedirectPatternRule(); rule.setTerminating(true); rule.setPattern(redirect); - rule.setLocation(CONFIG.getContextPath() + "/manager/home"); + rule.setLocation(config.getContextPath() + "/manager/home"); rewrite.addRule(rule); } return rewrite; } private HashSet getRedirectSet() { - String path = CONFIG.getContextPath(); + String path = config.getContextPath(); HashSet redirectSet = new HashSet<>(3); redirectSet.add(""); 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 5c61b8a4a..aab2ae23f 100644 --- a/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/ApiDocsConfiguration.java @@ -70,7 +70,7 @@ public class ApiDocsConfiguration { } @Bean - public OpenAPI apiDocs() { + public OpenAPI apiDocs(SteveConfiguration config) { String title = "SteVe REST API Documentation"; String securityName = "basicAuth"; @@ -88,7 +88,7 @@ public OpenAPI apiDocs() { .name("GPL-3.0") .url("https://github.com/steve-community/steve/blob/master/LICENSE.txt") ) - .version(SteveConfiguration.CONFIG.getSteveVersion()) + .version(config.getSteveVersion()) ) // 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 2f7cf8ca2..373d6e3fc 100644 --- a/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java @@ -46,7 +46,6 @@ 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; @@ -56,13 +55,9 @@ 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. * @@ -81,14 +76,18 @@ public class BeanConfiguration implements WebMvcConfigurer { */ @Bean public DataSource dataSource() { - SteveConfiguration.DB dbConfig = CONFIG.getDb(); + SteveConfiguration config = steveConfiguration(); + SteveConfiguration.DB dbConfig = config.getDb(); + return dataSource(dbConfig.getJdbcUrl(), dbConfig.getUserName(), dbConfig.getPassword(), config.getTimeZoneId()); + } + public static DataSource dataSource(String dbUrl, String dbUserName, String dbPassword, String dbTimeZoneId) { HikariConfig hc = new HikariConfig(); // set standard params - hc.setJdbcUrl(dbConfig.getJdbcUrl()); - hc.setUsername(dbConfig.getUserName()); - hc.setPassword(dbConfig.getPassword()); + hc.setJdbcUrl(dbUrl); + hc.setUsername(dbUserName); + hc.setPassword(dbPassword); // set non-standard params hc.addDataSourceProperty(PropertyKey.cachePrepStmts.getKeyName(), true); @@ -96,7 +95,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(), dbTimeZoneId); hc.addDataSourceProperty(PropertyKey.useSSL.getKeyName(), true); // https://github.com/steve-community/steve/issues/736 @@ -126,7 +125,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(steveConfiguration().getDb().isSqlLogging()); // Configuration for JOOQ org.jooq.Configuration conf = new DefaultConfiguration() @@ -161,11 +160,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 @@ -174,8 +168,9 @@ public Validator validator() { */ @Bean public ReleaseCheckService releaseCheckService() { - if (InternetChecker.isInternetAvailable()) { - return new GithubReleaseCheckService(); + var config = steveConfiguration(); + if (InternetChecker.isInternetAvailable(config.getSteveCompositeVersion())) { + return new GithubReleaseCheckService(config); } else { return new DummyReleaseCheckService(); } @@ -245,4 +240,9 @@ public ObjectMapper jacksonObjectMapper(RequestMappingHandlerAdapter requestMapp .map(conv -> ((MappingJackson2HttpMessageConverter) conv).getObjectMapper()) .orElseThrow(() -> new RuntimeException("There is no MappingJackson2HttpMessageConverter in Spring context")); } + + @Bean + public SteveConfiguration steveConfiguration() { + return SteveConfiguration.CONFIG; + } } 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 e3ef84add..b5454f831 100644 --- a/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java @@ -18,9 +18,11 @@ */ package de.rwth.idsg.steve.config; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.ocpp.soap.LoggingFeatureProxy; import de.rwth.idsg.steve.ocpp.soap.MediatorInInterceptor; import de.rwth.idsg.steve.ocpp.soap.MessageIdInterceptor; +import ocpp.cs._2010._08.CentralSystemService; import org.apache.cxf.Bus; import org.apache.cxf.bus.spring.SpringBus; import org.apache.cxf.common.logging.LogUtils; @@ -30,7 +32,6 @@ import org.apache.cxf.jaxws.JaxWsServerFactoryBean; import org.apache.cxf.message.Message; import org.apache.cxf.phase.PhaseInterceptor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -40,7 +41,6 @@ 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; @@ -57,13 +57,24 @@ public class OcppConfiguration { LogUtils.setLoggerClass(Slf4jLogger.class); } - @Autowired private ocpp.cs._2010._08.CentralSystemService ocpp12Server; - @Autowired private ocpp.cs._2012._06.CentralSystemService ocpp15Server; - @Autowired private ocpp.cs._2015._10.CentralSystemService ocpp16Server; + private final SteveConfiguration config; + private final ocpp.cs._2010._08.CentralSystemService ocpp12Server; + private final ocpp.cs._2012._06.CentralSystemService ocpp15Server; + private final ocpp.cs._2015._10.CentralSystemService ocpp16Server; - @Autowired - @Qualifier("messageHeaderInterceptor") - private PhaseInterceptor messageHeaderInterceptor; + private final PhaseInterceptor messageHeaderInterceptor; + + public OcppConfiguration(CentralSystemService ocpp12Server, ocpp.cs._2012._06.CentralSystemService ocpp15Server, + ocpp.cs._2015._10.CentralSystemService ocpp16Server, + @Qualifier("messageHeaderInterceptor") + PhaseInterceptor messageHeaderInterceptor, + SteveConfiguration config) { + this.config = config; + this.ocpp12Server = ocpp12Server; + this.ocpp15Server = ocpp15Server; + this.ocpp16Server = ocpp16Server; + this.messageHeaderInterceptor = messageHeaderInterceptor; + } @PostConstruct public void init() { @@ -77,8 +88,8 @@ public void init() { // 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(springBus())); - createOcppService(ocpp12Server, CONFIG.getRouterEndpointPath(), mediator, Collections.emptyList()); + List> mediator = singletonList(new MediatorInInterceptor(springBus(), config)); + createOcppService(ocpp12Server, config.getRouterEndpointPath(), mediator, Collections.emptyList()); } @Bean(name = Bus.DEFAULT_BUS_ID, destroyMethod = "shutdown") 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 b4e35234a..491e7a918 100644 --- a/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java +++ b/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.config; +import de.rwth.idsg.steve.SteveConfiguration; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -32,7 +33,6 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; import org.springframework.http.HttpMethod; import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -55,13 +55,13 @@ public class SecurityConfiguration { * [2] {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()} */ @Bean - public PasswordEncoder passwordEncoder() { - return CONFIG.getAuth().getPasswordEncoder(); + public PasswordEncoder passwordEncoder(SteveConfiguration config) { + return config.getAuth().getPasswordEncoder(); } @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - final String prefix = CONFIG.getSpringManagerMapping(); + public SecurityFilterChain securityFilterChain(HttpSecurity http, SteveConfiguration config) throws Exception { + final String prefix = config.getSpringManagerMapping(); RequestMatcher toOverview = request -> { String param = request.getParameter("backToOverview"); @@ -73,7 +73,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti req -> req .requestMatchers( "/static/**", - CONFIG.getCxfMapping() + "/**", + config.getCxfMapping() + "/**", WebSocketConfiguration.PATH_INFIX + "**", "/WEB-INF/views/**" // https://github.com/spring-projects/spring-security/issues/13285#issuecomment-1579097065 ).permitAll() @@ -110,7 +110,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(config.getCxfMapping() + "/**")) .sessionManagement( req -> req.invalidSessionUrl(prefix + "/signin") ) @@ -128,8 +128,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti @Bean @Order(1) - public SecurityFilterChain apiKeyFilterChain(HttpSecurity http, ApiAuthenticationManager apiAuthenticationManager) throws Exception { - return http.securityMatcher(CONFIG.getApiMapping() + "/**") + public SecurityFilterChain apiKeyFilterChain(HttpSecurity http, SteveConfiguration config, + ApiAuthenticationManager apiAuthenticationManager) throws Exception { + return http.securityMatcher(config.getApiMapping() + "/**") .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/ValidationConfig.java b/src/main/java/de/rwth/idsg/steve/config/ValidationConfig.java new file mode 100644 index 000000000..9af78c33b --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/config/ValidationConfig.java @@ -0,0 +1,46 @@ +/* + * 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.SteveConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; + +import java.util.Properties; + +@Configuration +public class ValidationConfig { + + // > To avoid these lifecycle issues, mark BFPP-returning @Bean methods as static + // From [the Spring documentation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html) + @Bean + public static PropertySourcesPlaceholderConfigurer valueConfigurer(SteveConfiguration config) { + var configurer = new PropertySourcesPlaceholderConfigurer(); + + var props = new Properties(); + var chargeBoxIdValidationRegex = config.getOcpp().getChargeBoxIdValidationRegex(); + if (chargeBoxIdValidationRegex != null) { + props.put("charge-box-id.validation.regex", chargeBoxIdValidationRegex); + } + configurer.setProperties(props); + + return configurer; + } +} 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 b2fd17e40..fcdf81543 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.springframework.context.annotation.Configuration; @@ -50,6 +51,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { public static final int MAX_MSG_SIZE = 8_388_608; // 8 MB for max message size private final ChargePointRegistrationService chargePointRegistrationService; + private final ChargeBoxIdValidator chargeBoxIdValidator; private final Ocpp12WebSocketEndpoint ocpp12WebSocketEndpoint; private final Ocpp15WebSocketEndpoint ocpp15WebSocketEndpoint; @@ -59,6 +61,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { OcppWebSocketHandshakeHandler handshakeHandler = new OcppWebSocketHandshakeHandler( + chargeBoxIdValidator, new 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 ea0145686..05a4169ed 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 @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.ocpp.soap; import com.oneandone.compositejks.SslContextBuilder; +import de.rwth.idsg.steve.SteveConfiguration; import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; @@ -28,13 +29,9 @@ import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Component; -import jakarta.annotation.PostConstruct; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; import jakarta.xml.ws.soap.SOAPBinding; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 21.10.2015 @@ -42,13 +39,13 @@ @Component public class ClientProvider { - @Nullable private TLSClientParameters tlsClientParams; + @Nullable private final TLSClientParameters tlsClientParams; - @PostConstruct - private void init() { - if (shouldInitSSL()) { + public ClientProvider(SteveConfiguration config) { + SteveConfiguration.Jetty jettyConfig = config.getJetty(); + if (shouldInitSSL(jettyConfig)) { tlsClientParams = new TLSClientParameters(); - tlsClientParams.setSSLSocketFactory(setupSSL()); + tlsClientParams.setSslContext(setupSSL(jettyConfig)); } else { tlsClientParams = null; } @@ -77,16 +74,16 @@ private static JaxWsProxyFactoryBean getBean(String endpointAddress) { return f; } - private static boolean shouldInitSSL() { - return CONFIG.getJetty().getKeyStorePath() != null && CONFIG.getJetty().getKeyStorePassword() != null; + private static boolean shouldInitSSL(SteveConfiguration.Jetty jettyConfig) { + return jettyConfig.getKeyStorePath() != null && !jettyConfig.getKeyStorePath().isBlank() + && jettyConfig.getKeyStorePassword() != null && !jettyConfig.getKeyStorePassword().isBlank(); } - private static SSLSocketFactory setupSSL() { - SSLContext ssl; + private static SSLContext setupSSL(SteveConfiguration.Jetty jettyConfig) { try { - String keyStorePath = CONFIG.getJetty().getKeyStorePath(); - String keyStorePwd = CONFIG.getJetty().getKeyStorePassword(); - ssl = SslContextBuilder.builder() + String keyStorePath = jettyConfig.getKeyStorePath(); + String keyStorePwd = jettyConfig.getKeyStorePassword(); + return SslContextBuilder.builder() .keyStoreFromFile(keyStorePath, keyStorePwd) .usingTLS() .usingDefaultAlgorithm() @@ -95,6 +92,5 @@ private static SSLSocketFactory setupSSL() { } catch (Exception e) { throw new RuntimeException(e); } - return ssl.getSocketFactory(); } } 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..285e8bac2 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 @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.ocpp.soap; +import de.rwth.idsg.steve.SteveConfiguration; import lombok.extern.slf4j.Slf4j; import org.apache.cxf.Bus; import org.apache.cxf.binding.soap.SoapMessage; @@ -42,8 +43,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 +51,10 @@ public class MediatorInInterceptor extends AbstractPhaseInterceptor private final Map actualServers; - public MediatorInInterceptor(Bus bus) { + public MediatorInInterceptor(Bus bus, SteveConfiguration config) { super(Phase.POST_STREAM); super.addBefore(StaxInInterceptor.class.getName()); - actualServers = initServerLookupMap(bus); + actualServers = initServerLookupMap(bus, config); } public final void handleMessage(SoapMessage message) { @@ -105,7 +104,7 @@ 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) { + private static Map initServerLookupMap(Bus bus, SteveConfiguration config) { String exceptionMsg = "The services are not created and/or registered to the bus yet."; ServerRegistry serverRegistry = bus.getExtension(ServerRegistry.class); @@ -124,7 +123,7 @@ private static Map initServerLookupMap(Bus bus) { String address = info.getAddress(); // exclude the 'dummy' routing server - if (CONFIG.getRouterEndpointPath().equals(address)) { + if (config.getRouterEndpointPath().equals(address)) { continue; } 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 0b2da0fd6..022e2f941 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 @@ -63,7 +63,7 @@ public abstract class AbstractWebSocketEndpoint extends ConcurrentWebSocketHandl public static final String CHARGEBOX_ID_KEY = "CHARGEBOX_ID_KEY"; - private final SessionContextStore sessionContextStore = new SessionContextStoreImpl(); + private final SessionContextStore sessionContextStore; private final List> connectedCallbackList = new ArrayList<>(); private final List> disconnectedCallbackList = new ArrayList<>(); private final Object sessionContextLock = new Object(); 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..a62ad006e 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); 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..e30efbc8a 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 @@ -20,11 +20,13 @@ import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Striped; +import de.rwth.idsg.steve.SteveConfiguration; 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.extern.slf4j.Slf4j; import org.joda.time.DateTime; +import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketSession; import java.util.ArrayDeque; @@ -37,13 +39,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 +@Component public class SessionContextStoreImpl implements SessionContextStore { /** @@ -54,7 +55,11 @@ public class SessionContextStoreImpl implements SessionContextStore { private final Striped locks = Striped.lock(16); - private final WsSessionSelectStrategy wsSessionSelectStrategy = CONFIG.getOcpp().getWsSessionSelectStrategy(); + private final WsSessionSelectStrategy wsSessionSelectStrategy; + + public SessionContextStoreImpl(SteveConfiguration config) { + wsSessionSelectStrategy = config.getOcpp().getWsSessionSelectStrategy(); + } @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 bbef07b3f..78897b56b 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 @@ -20,12 +20,14 @@ import de.rwth.idsg.ocpp.jaxb.RequestType; import de.rwth.idsg.ocpp.jaxb.ResponseType; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.soap.CentralSystemService12_SoapServer; import de.rwth.idsg.steve.ocpp.ws.AbstractWebSocketEndpoint; import de.rwth.idsg.steve.ocpp.ws.FutureResponseContextStore; +import de.rwth.idsg.steve.ocpp.ws.SessionContextStoreImpl; import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; @@ -56,12 +58,14 @@ public class Ocpp12WebSocketEndpoint extends AbstractWebSocketEndpoint { private final CentralSystemService12_SoapServer server; private final FutureResponseContextStore futureResponseContextStore; - public Ocpp12WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, + public Ocpp12WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, + OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, - CentralSystemService12_SoapServer server - ) { - super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher); + CentralSystemService12_SoapServer server, + SteveConfiguration config) { + super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, + new SessionContextStoreImpl(config)); this.server = server; this.futureResponseContextStore = futureResponseContextStore; } 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 8fa9e16f8..65464d197 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 @@ -20,12 +20,14 @@ import de.rwth.idsg.ocpp.jaxb.RequestType; import de.rwth.idsg.ocpp.jaxb.ResponseType; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.soap.CentralSystemService15_SoapServer; import de.rwth.idsg.steve.ocpp.ws.AbstractWebSocketEndpoint; import de.rwth.idsg.steve.ocpp.ws.FutureResponseContextStore; +import de.rwth.idsg.steve.ocpp.ws.SessionContextStoreImpl; import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; @@ -60,9 +62,10 @@ public Ocpp15WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, - CentralSystemService15_SoapServer server - ) { - super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher); + CentralSystemService15_SoapServer server, + SteveConfiguration config) { + super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, + new SessionContextStoreImpl(config)); this.server = server; this.futureResponseContextStore = futureResponseContextStore; } @@ -89,7 +92,8 @@ protected ResponseType dispatch(RequestType params, String chargeBoxId) { ResponseType r; if (params instanceof BootNotificationRequest) { - r = server.bootNotificationWithTransport((BootNotificationRequest) params, chargeBoxId, OcppProtocol.V_15_JSON); + r = server.bootNotificationWithTransport((BootNotificationRequest) params, chargeBoxId, + OcppProtocol.V_15_JSON); } else if (params instanceof FirmwareStatusNotificationRequest) { r = server.firmwareStatusNotification((FirmwareStatusNotificationRequest) params, chargeBoxId); 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 e6705eaa9..05d1c1ea5 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 @@ -20,12 +20,15 @@ import de.rwth.idsg.ocpp.jaxb.RequestType; import de.rwth.idsg.ocpp.jaxb.ResponseType; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.config.DelegatingTaskScheduler; import de.rwth.idsg.steve.ocpp.OcppProtocol; import de.rwth.idsg.steve.ocpp.OcppVersion; import de.rwth.idsg.steve.ocpp.soap.CentralSystemService16_SoapServer; import de.rwth.idsg.steve.ocpp.ws.AbstractWebSocketEndpoint; import de.rwth.idsg.steve.ocpp.ws.FutureResponseContextStore; +import de.rwth.idsg.steve.ocpp.ws.SessionContextStore; +import de.rwth.idsg.steve.ocpp.ws.SessionContextStoreImpl; import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; @@ -60,9 +63,10 @@ public Ocpp16WebSocketEndpoint(DelegatingTaskScheduler asyncTaskScheduler, OcppServerRepository ocppServerRepository, FutureResponseContextStore futureResponseContextStore, ApplicationEventPublisher applicationEventPublisher, - CentralSystemService16_SoapServer server - ) { - super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher); + CentralSystemService16_SoapServer server, + SessionContextStore sessionStore) { + super(asyncTaskScheduler, ocppServerRepository, futureResponseContextStore, applicationEventPublisher, + sessionStore); this.server = server; this.futureResponseContextStore = futureResponseContextStore; } @@ -89,7 +93,8 @@ protected ResponseType dispatch(RequestType params, String chargeBoxId) { ResponseType r; if (params instanceof BootNotificationRequest) { - r = server.bootNotificationWithTransport((BootNotificationRequest) params, chargeBoxId, OcppProtocol.V_16_JSON); + r = server.bootNotificationWithTransport((BootNotificationRequest) params, chargeBoxId, + OcppProtocol.V_16_JSON); } else if (params instanceof FirmwareStatusNotificationRequest) { r = server.firmwareStatusNotification((FirmwareStatusNotificationRequest) params, chargeBoxId); diff --git a/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperService.java b/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperService.java index aa8508eb0..bd47f3790 100644 --- a/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperService.java +++ b/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperService.java @@ -30,7 +30,6 @@ import de.rwth.idsg.steve.repository.GenericRepository; import de.rwth.idsg.steve.repository.dto.ChargePointSelect; import de.rwth.idsg.steve.repository.dto.ConnectorStatus; -import de.rwth.idsg.steve.service.dto.UnidentifiedIncomingObject; import de.rwth.idsg.steve.utils.ConnectorStatusCountFilter; import de.rwth.idsg.steve.utils.DateTimeUtils; import de.rwth.idsg.steve.web.dto.ConnectorStatusForm; @@ -51,12 +50,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * @author Sevket Goekay * @since 24.03.2015 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 1046f4e4d..6ca9eb2d4 100644 --- a/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java +++ b/src/main/java/de/rwth/idsg/steve/service/ChargePointRegistrationService.java @@ -19,9 +19,9 @@ package de.rwth.idsg.steve.service; import com.google.common.util.concurrent.Striped; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.repository.ChargePointRepository; import de.rwth.idsg.steve.service.dto.UnidentifiedIncomingObject; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import ocpp.cs._2015._10.RegistrationStatus; import org.springframework.stereotype.Service; @@ -31,19 +31,21 @@ import java.util.Optional; import java.util.concurrent.locks.Lock; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - @Slf4j @Service -@RequiredArgsConstructor public class ChargePointRegistrationService { - private final boolean autoRegisterUnknownStations = CONFIG.getOcpp().isAutoRegisterUnknownStations(); + private final boolean autoRegisterUnknownStations; private final Striped isRegisteredLocks = Striped.lock(16); private final UnidentifiedIncomingObjectService unknownChargePointService = new UnidentifiedIncomingObjectService(100); private final ChargePointRepository chargePointRepository; + public ChargePointRegistrationService(ChargePointRepository chargePointRepository, SteveConfiguration config) { + this.chargePointRepository = chargePointRepository; + this.autoRegisterUnknownStations = config.getOcpp().isAutoRegisterUnknownStations(); + } + public Optional getRegistrationStatus(String chargeBoxId) { Lock l = isRegisteredLocks.get(chargeBoxId); l.lock(); 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 e4a8b16ac..f071bf331 100644 --- a/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java +++ b/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java @@ -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; @@ -60,10 +59,12 @@ public class GithubReleaseCheckService implements ReleaseCheckService { private static final String FILE_SEPARATOR = File.separator; - private RestTemplate restTemplate; + private final SteveConfiguration config; + private final RestTemplate restTemplate; + + public GithubReleaseCheckService(SteveConfiguration config) { + this.config = config; - @PostConstruct - private void init() { var timeout = Timeout.ofMilliseconds(API_TIMEOUT_IN_MILLIS); var connectionConfig = ConnectionConfig.custom().setConnectTimeout(timeout).build(); @@ -89,7 +90,7 @@ private void init() { public ReleaseReport check() { try { ReleaseResponse response = restTemplate.getForObject(API_URL, ReleaseResponse.class); - return getReport(response); + return getReport(response, config.getSteveVersion()); } catch (RestClientException e) { // Fallback to "there is no new version atm". @@ -102,10 +103,10 @@ public ReleaseReport check() { // Private helpers // ------------------------------------------------------------------------- - private static ReleaseReport getReport(ReleaseResponse response) { + private static ReleaseReport getReport(ReleaseResponse response, String buildVersion) { String githubVersion = extractVersion(response); - Version build = Version.valueOf(SteveConfiguration.CONFIG.getSteveVersion()); + Version build = Version.valueOf(buildVersion); Version github = Version.valueOf(githubVersion); boolean isGithubMoreRecent = github.greaterThan(build); diff --git a/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java b/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java index 71721bc0e..c00d987ce 100644 --- a/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java +++ b/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java @@ -75,6 +75,7 @@ public class WebUsersService implements UserDetailsManager { // Because Guava's cache does not accept a null value private static final UserDetails DUMMY_USER = new User("#", "#", Collections.emptyList()); + private final SteveConfiguration config; private final ObjectMapper jacksonObjectMapper; private final WebUserRepository webUserRepository; private final SecurityContextHolderStrategy securityContextHolderStrategy = getContextHolderStrategy(); @@ -91,15 +92,15 @@ public void afterStart(ContextRefreshedEvent event) { return; } - var headerVal = SteveConfiguration.CONFIG.getWebApi().getHeaderValue(); + var headerVal = config.getWebApi().getHeaderValue(); var encodedApiPassword = StringUtils.isEmpty(headerVal) ? null - : SteveConfiguration.CONFIG.getAuth().getPasswordEncoder().encode(headerVal); + : config.getAuth().getPasswordEncoder().encode(headerVal); var user = new WebUserRecord() - .setUsername(SteveConfiguration.CONFIG.getAuth().getUserName()) - .setPassword(SteveConfiguration.CONFIG.getAuth().getEncodedPassword()) + .setUsername(config.getAuth().getUserName()) + .setPassword(config.getAuth().getEncodedPassword()) .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..acf698839 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; @@ -48,18 +47,14 @@ public final class InternetChecker { "https://www.facebook.com" ); - static { - System.setProperty("http.agent", "SteVe/" + SteveConfiguration.CONFIG.getSteveCompositeVersion()); - } - /** * We try every item in the list to compensate for the possibility that one of hosts might be down. If all these * big players are down at the same time, that's okay too, because the end of the world must have arrived, * obviously. */ - public static boolean isInternetAvailable() { + public static boolean isInternetAvailable(String userAgent) { for (String s : HOST_LIST) { - if (isHostAvailable(s)) { + if (isHostAvailable(s, userAgent)) { return true; } } @@ -67,11 +62,12 @@ public static boolean isInternetAvailable() { return false; } - private static boolean isHostAvailable(String str) { + private static boolean isHostAvailable(String str, String userAgent) { try { URL url = new URL(str); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestProperty("Connection", "close"); // otherwise, default setting is "keep-alive" + con.setRequestProperty("User-Agent", userAgent); try { con.setConnectTimeout(CONNECT_TIMEOUT); con.connect(); 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 da0d8478b..8dbddfc23 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.SteveConfiguration; import de.rwth.idsg.steve.repository.GenericRepository; import de.rwth.idsg.steve.repository.SettingsRepository; import de.rwth.idsg.steve.service.MailService; @@ -38,8 +39,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import static de.rwth.idsg.steve.SteveConfiguration.CONFIG; - /** * One controller for about and settings pages * @@ -55,6 +54,7 @@ public class AboutSettingsController { private final SettingsRepository settingsRepository; private final MailService mailService; private final ReleaseCheckService releaseCheckService; + private final SteveConfiguration config; // ------------------------------------------------------------------------- // Paths @@ -69,7 +69,7 @@ public class AboutSettingsController { @GetMapping(value = ABOUT_PATH) public String getAbout(Model model) { - model.addAttribute("version", CONFIG.getSteveVersion()); + model.addAttribute("version", config.getSteveVersion()); 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..c982d2c43 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 @@ -20,15 +20,25 @@ import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + import java.util.List; /** * @author Sevket Goekay * @since 21.01.2016 */ +@Component +// Default constructor for Hibernate Validator +// Spring will inject the value from properties +// And then HV will call `initialize` +@NoArgsConstructor public class ChargeBoxIdListValidator implements ConstraintValidator> { - private static final ChargeBoxIdValidator VALIDATOR = new ChargeBoxIdValidator(); + @Autowired + private ChargeBoxIdValidator validator; @Override public void initialize(ChargeBoxId constraintAnnotation) { @@ -38,7 +48,7 @@ public void initialize(ChargeBoxId constraintAnnotation) { @Override public boolean isValid(List value, ConstraintValidatorContext context) { 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..aac542213 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,9 +19,11 @@ package de.rwth.idsg.steve.web.validation; import com.google.common.base.Strings; -import de.rwth.idsg.steve.SteveConfiguration; +import jakarta.annotation.PostConstruct; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; import java.util.regex.Pattern; @@ -29,14 +31,25 @@ * @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()); + + @Value("${charge-box-id.validation.regex:#{null}}") + private String chargeBoxIdValidationRegex; + private Pattern pattern; + + // Default constructor for Hibernate Validator + // Spring will inject the value from properties + // And then HV will call `initialize` + public ChargeBoxIdValidator() { + initialize(null); + } @Override public void initialize(ChargeBoxId idTag) { - // No-op + pattern = Pattern.compile(getRegexToUse(chargeBoxIdValidationRegex)); } @Override @@ -57,11 +70,10 @@ public boolean isValid(String str) { return false; } - return PATTERN.matcher(str).matches(); + return pattern.matcher(str).matches(); } - private static String getRegexToUse() { - String regexFromConfig = SteveConfiguration.CONFIG.getOcpp().getChargeBoxIdValidationRegex(); - return Strings.isNullOrEmpty(regexFromConfig) ? REGEX : regexFromConfig; + private static String getRegexToUse(String chargeBoxIdValidationRegex) { + return Strings.isNullOrEmpty(chargeBoxIdValidationRegex) ? REGEX : chargeBoxIdValidationRegex; } } diff --git a/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java b/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java new file mode 100644 index 000000000..ce2063802 --- /dev/null +++ b/src/main/java/de/rwth/idsg/steve/web/validation/SpringConstraintValidatorFactory.java @@ -0,0 +1,40 @@ +/* + * 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.web.validation; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SpringConstraintValidatorFactory implements ConstraintValidatorFactory { + + private final AutowireCapableBeanFactory beanFactory; + + @Override + public > T getInstance(Class key) { + return beanFactory.createBean(key); + } + + @Override + public void releaseInstance(ConstraintValidator instance) { } +} diff --git a/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java b/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java index d80820a60..11ea2ea86 100644 --- a/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java +++ b/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java @@ -57,7 +57,7 @@ public static void init() throws Exception { Assertions.assertEquals(ApplicationProfile.TEST, SteveConfiguration.CONFIG.getProfile()); __DatabasePreparer__.prepare(); - app = new Application(); + app = new Application(SteveConfiguration.CONFIG); app.start(); } diff --git a/src/test/java/de/rwth/idsg/steve/ApplicationTest.java b/src/test/java/de/rwth/idsg/steve/ApplicationTest.java index 04bb4048b..35e53c4f8 100644 --- a/src/test/java/de/rwth/idsg/steve/ApplicationTest.java +++ b/src/test/java/de/rwth/idsg/steve/ApplicationTest.java @@ -49,7 +49,7 @@ 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 final String path = getPath(SteveConfiguration.CONFIG); private static Application app; @@ -58,7 +58,7 @@ public static void init() throws Exception { Assertions.assertEquals(ApplicationProfile.TEST, SteveConfiguration.CONFIG.getProfile()); __DatabasePreparer__.prepare(); - app = new Application(); + app = new Application(SteveConfiguration.CONFIG); app.start(); } diff --git a/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java b/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java index 066dac2b9..8b29f9882 100644 --- a/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java +++ b/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java @@ -77,7 +77,7 @@ public class OperationalTestSoapOCPP16 { 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 String path = getPath(SteveConfiguration.CONFIG); private static final int numConnectors = 5; private static Application app; @@ -85,7 +85,7 @@ public class OperationalTestSoapOCPP16 { public static void initClass() throws Exception { Assertions.assertEquals(ApplicationProfile.TEST, SteveConfiguration.CONFIG.getProfile()); - app = new Application(); + app = new Application(SteveConfiguration.CONFIG); app.start(); } diff --git a/src/test/java/de/rwth/idsg/steve/StressTest.java b/src/test/java/de/rwth/idsg/steve/StressTest.java index 01729cb17..ce4fb3676 100644 --- a/src/test/java/de/rwth/idsg/steve/StressTest.java +++ b/src/test/java/de/rwth/idsg/steve/StressTest.java @@ -55,7 +55,7 @@ protected void attack() throws Exception { __DatabasePreparer__.prepare(); - Application app = new Application(); + Application app = new Application(SteveConfiguration.CONFIG); try { app.start(); attackInternal(); diff --git a/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java b/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java index 3517c1792..7518c00e9 100644 --- a/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java +++ b/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java @@ -56,7 +56,7 @@ */ public class StressTestJsonOCPP16 extends StressTest { - private static final String PATH = getJsonPath(); + private static final String PATH = getJsonPath(SteveConfiguration.CONFIG); private static final OcppVersion VERSION = OcppVersion.V_16; public static void main(String[] args) throws Exception { diff --git a/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java b/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java index dc6e79cd2..fb018a5d0 100644 --- a/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java +++ b/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java @@ -55,7 +55,7 @@ */ public class StressTestSoapOCPP16 extends StressTest { - private static final String path = getPath(); + private static final String path = getPath(SteveConfiguration.CONFIG); public static void main(String[] args) throws Exception { new StressTestSoapOCPP16().attack(); 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..4aee54a83 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue72.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue72.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.issues; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.StressTest; import de.rwth.idsg.steve.utils.Helpers; import de.rwth.idsg.steve.utils.StressTester; @@ -50,7 +51,7 @@ */ public class Issue72 extends StressTest { - private static final String path = getPath(); + private static final String path = getPath(SteveConfiguration.CONFIG); public static void main(String[] args) throws Exception { new Issue72().attack(); 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..06a936384 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java @@ -19,6 +19,7 @@ package de.rwth.idsg.steve.issues; import com.google.common.net.MediaType; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.StressTest; import de.rwth.idsg.steve.utils.Helpers; import de.rwth.idsg.steve.utils.StressTester; @@ -57,7 +58,7 @@ */ public class Issue72LowLevelSoap extends StressTest { - private static final String path = getPath(); + private static final String path = getPath(SteveConfiguration.CONFIG); public static void main(String[] args) throws Exception { new Issue72LowLevelSoap().attack(); diff --git a/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java b/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java index b83bcbc7c..b2a8548a7 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java @@ -50,7 +50,7 @@ public class Issue73Fix { private static final String REGISTERED_OCPP_TAG = __DatabasePreparer__.getRegisteredOcppTag(); - private static final String path = getPath(); + private static final String path = getPath(SteveConfiguration.CONFIG); public static void main(String[] args) throws Exception { Assertions.assertEquals(ApplicationProfile.TEST, SteveConfiguration.CONFIG.getProfile()); @@ -58,7 +58,7 @@ public static void main(String[] args) throws Exception { __DatabasePreparer__.prepare(); - Application app = new Application(); + Application app = new Application(SteveConfiguration.CONFIG); try { app.start(); test(); 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..b7e21be4f 100644 --- a/src/test/java/de/rwth/idsg/steve/issues/Issue81.java +++ b/src/test/java/de/rwth/idsg/steve/issues/Issue81.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.issues; +import de.rwth.idsg.steve.SteveConfiguration; import de.rwth.idsg.steve.StressTest; import de.rwth.idsg.steve.utils.Helpers; import de.rwth.idsg.steve.utils.StressTester; @@ -42,7 +43,7 @@ */ public class Issue81 extends StressTest { - private static final String path = getPath(); + private static final String path = getPath(SteveConfiguration.CONFIG); public static void main(String[] args) throws Exception { new Issue81().attack(); 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..0cb3fd9fc 100644 --- a/src/test/java/de/rwth/idsg/steve/utils/Helpers.java +++ b/src/test/java/de/rwth/idsg/steve/utils/Helpers.java @@ -18,6 +18,7 @@ */ package de.rwth.idsg.steve.utils; +import de.rwth.idsg.steve.SteveConfiguration; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.ws.addressing.WSAddressingFeature; @@ -26,8 +27,6 @@ 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 +45,40 @@ public static List getRandomStrings(int size) { return list; } - public static String getPath() { + public static String getPath(SteveConfiguration config) { String prefix; int port; - if (CONFIG.getJetty().isHttpEnabled()) { + if (config.getJetty().isHttpEnabled()) { prefix = "http://"; - port = CONFIG.getJetty().getHttpPort(); - } else if (CONFIG.getJetty().isHttpsEnabled()) { + port = config.getJetty().getHttpPort(); + } else if (config.getJetty().isHttpsEnabled()) { prefix = "https://"; - port = CONFIG.getJetty().getHttpsPort(); + port = config.getJetty().getHttpsPort(); } else { throw new RuntimeException(); } - return prefix + CONFIG.getJetty().getServerHost() + ":" + port - + CONFIG.getContextPath() + "/services" + CONFIG.getRouterEndpointPath(); + return prefix + config.getJetty().getServerHost() + ":" + port + + config.getContextPath() + "/services" + config.getRouterEndpointPath(); } - public static String getJsonPath() { + public static String getJsonPath(SteveConfiguration config) { String prefix; int port; - if (CONFIG.getJetty().isHttpEnabled()) { + if (config.getJetty().isHttpEnabled()) { prefix = "ws://"; - port = CONFIG.getJetty().getHttpPort(); - } else if (CONFIG.getJetty().isHttpsEnabled()) { + port = config.getJetty().getHttpPort(); + } else if (config.getJetty().isHttpsEnabled()) { prefix = "wss://"; - port = CONFIG.getJetty().getHttpsPort(); + port = config.getJetty().getHttpsPort(); } else { throw new RuntimeException(); } - return prefix + CONFIG.getJetty().getServerHost() + ":" + port - + CONFIG.getContextPath() + "/websocket/CentralSystemService/"; + return prefix + config.getJetty().getServerHost() + ":" + port + + config.getContextPath() + "/websocket/CentralSystemService/"; } public static ocpp.cs._2015._10.CentralSystemService getForOcpp16(String path) {