From 9e35e7c26a710839829aa6493dcec2bfc9fe07f1 Mon Sep 17 00:00:00 2001
From: Jan Zickermann <jan.zickermann@dataport.de>
Date: Tue, 4 Mar 2025 12:50:39 +0100
Subject: [PATCH] OZG-4097 Delay validation of api client configuration

---
 .../osiv2/config/ApiClientConfiguration.java  | 45 ++++++++++++++++++
 .../osiv2/config/Osi2PostfachProperties.java  |  6 ---
 .../osiv2/config/Osi2PropertiesValidator.java | 47 -------------------
 ...t.java => ApiClientConfigurationTest.java} | 14 +++---
 4 files changed, 52 insertions(+), 60 deletions(-)
 delete mode 100644 src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidator.java
 rename src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/{Osi2PropertiesValidatorTest.java => ApiClientConfigurationTest.java} (90%)

diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java
index d5403ac..af114b9 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java
@@ -1,5 +1,13 @@
 package de.ozgcloud.nachrichten.postfach.osiv2.config;
 
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validator;
+
 import org.apache.hc.client5.http.auth.AuthScope;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
@@ -28,11 +36,15 @@ import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestClient;
 
+import de.ozgcloud.common.errorhandling.TechnicalException;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.ApiClient;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi;
 import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.QuarantineApi;
 import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.log4j.Log4j2;
 
+@Log4j2
 @Configuration
 @RequiredArgsConstructor
 @ConditionalOnProperty(prefix = Osi2PostfachProperties.PREFIX, name = "enabled", havingValue = "true")
@@ -42,6 +54,8 @@ public class ApiClientConfiguration {
 	private final Osi2PostfachProperties.ApiConfiguration apiConfiguration;
 	private final Osi2PostfachProperties.ProxyConfiguration proxyConfiguration;
 
+	private final Validator validator;
+
 	private static final String CLIENT_REGISTRATION_ID = "osi2";
 
 	@Bean
@@ -55,8 +69,11 @@ public class ApiClientConfiguration {
 	}
 
 	@Bean
+	@SneakyThrows
 	ApiClient apiClient() {
+		getErrorMessageOfViolations();
 		var apiClient = new ApiClient(restClient());
+		LOG.debug("Setting api client base path to {}", apiConfiguration.getUrl());
 		apiClient.setBasePath(apiConfiguration.getUrl());
 		return apiClient;
 	}
@@ -78,6 +95,7 @@ public class ApiClientConfiguration {
 	private ClientHttpRequestFactory createProxyRequestFactory() {
 		var requestFactory = new HttpComponentsClientHttpRequestFactory();
 		if (proxyConfiguration.isEnabled()) {
+			LOG.debug("Using proxy configuration: {}:{}", proxyConfiguration.getHost(), proxyConfiguration.getPort());
 			requestFactory.setHttpClient(
 					HttpClientBuilder.create()
 							.setProxy(new HttpHost(proxyConfiguration.getHost(), proxyConfiguration.getPort()))
@@ -93,6 +111,7 @@ public class ApiClientConfiguration {
 		var username = proxyConfiguration.getUsername();
 		var password = proxyConfiguration.getPassword();
 		if (username != null && password != null) {
+			LOG.debug("Using proxy authentication with username {}", username);
 			credentialsProvider.setCredentials(new AuthScope(proxyConfiguration.getHost(), proxyConfiguration.getPort()),
 					new UsernamePasswordCredentials(username, password.toCharArray()));
 		}
@@ -116,6 +135,8 @@ public class ApiClientConfiguration {
 	}
 
 	private ClientRegistration osi2ClientRegistration() {
+		LOG.debug("Creating client registration with clientId: {}, tokenUri: {}, scope: {}, resource: {}", authConfiguration.getClientId(),
+				authConfiguration.getTokenUri(), authConfiguration.getScope(), authConfiguration.getResource());
 		return ClientRegistration.withRegistrationId(CLIENT_REGISTRATION_ID)
 				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
@@ -161,4 +182,28 @@ public class ApiClientConfiguration {
 		});
 	}
 
+	void getErrorMessageOfViolations() {
+		String violationMessage = Stream.<Optional<String>>of(getErrorMessageOfViolations(authConfiguration),
+						getErrorMessageOfViolations(apiConfiguration),
+						proxyConfiguration.isEnabled() ? getErrorMessageOfViolations(proxyConfiguration) : Optional.empty()
+				).flatMap(Optional::stream)
+				.collect(Collectors.joining(System.lineSeparator()));
+
+		if (!violationMessage.isEmpty()) {
+			throw new TechnicalException(
+					"Configuration of api client is invalid:%s%s".formatted(System.lineSeparator(), violationMessage));
+		}
+	}
+
+	private <T> Optional<String> getErrorMessageOfViolations(T configuration) {
+		return formatConstraintValidation(validator.validate(configuration))
+				.map(message -> "%s is invalid: %s".formatted(configuration.getClass().getSimpleName(), message));
+	}
+
+	private <T> Optional<String> formatConstraintValidation(Set<ConstraintViolation<T>> constraints) {
+		return constraints.stream()
+				.map(violation -> String.format("%s: %s", violation.getPropertyPath(), violation.getMessage()))
+				.reduce((a, b) -> a + ", " + b);
+	}
+
 }
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PostfachProperties.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PostfachProperties.java
index d9bce3e..e4b53fb 100644
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PostfachProperties.java
+++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PostfachProperties.java
@@ -8,7 +8,6 @@ import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
 import org.springframework.context.annotation.Configuration;
 
 import lombok.Getter;
@@ -18,7 +17,6 @@ import lombok.Setter;
 @Setter
 @Configuration
 @ConfigurationProperties(prefix = Osi2PostfachProperties.PREFIX)
-@ConfigurationPropertiesScan
 public class Osi2PostfachProperties {
 	// From de.ozgcloud.nachrichten.NachrichtenManagerConfiguration
 	public static final String GRPC_FILE_MANAGER_NAME = "file-manager";
@@ -29,12 +27,10 @@ public class Osi2PostfachProperties {
 
 	private boolean enabled;
 
-
 	@Getter
 	@Setter
 	@Configuration
 	@ConfigurationProperties(prefix = AuthConfiguration.PREFIX)
-	@ConfigurationPropertiesScan
 	public static class AuthConfiguration {
 		public static final String PREFIX = Osi2PostfachProperties.PREFIX + ".auth";
 
@@ -55,7 +51,6 @@ public class Osi2PostfachProperties {
 	@Setter
 	@Configuration
 	@ConfigurationProperties(prefix = ApiConfiguration.PREFIX)
-	@ConfigurationPropertiesScan
 	public static class ApiConfiguration {
 		public static final String PREFIX = Osi2PostfachProperties.PREFIX + ".api";
 
@@ -75,7 +70,6 @@ public class Osi2PostfachProperties {
 	@Setter
 	@Configuration
 	@ConfigurationProperties(prefix = ProxyConfiguration.PREFIX)
-	@ConfigurationPropertiesScan
 	public static class ProxyConfiguration {
 		public static final String PREFIX = Osi2PostfachProperties.PREFIX + ".proxy";
 
diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidator.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidator.java
deleted file mode 100644
index 19f2454..0000000
--- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package de.ozgcloud.nachrichten.postfach.osiv2.config;
-
-import java.util.Set;
-
-import javax.annotation.PostConstruct;
-
-import jakarta.validation.ConstraintViolation;
-import jakarta.validation.Validator;
-
-import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.nachrichten.postfach.osiv2.ServiceIfOsi2Enabled;
-import lombok.RequiredArgsConstructor;
-
-@ServiceIfOsi2Enabled
-@RequiredArgsConstructor
-public class Osi2PropertiesValidator {
-
-	private final Osi2PostfachProperties.AuthConfiguration authConfiguration;
-	private final Osi2PostfachProperties.ApiConfiguration apiConfiguration;
-	private final Osi2PostfachProperties.ProxyConfiguration proxyConfiguration;
-	private final Validator validator;
-
-	@PostConstruct
-	public void validateConfiguration() {
-		validateConfiguration(authConfiguration);
-		validateConfiguration(apiConfiguration);
-		if (proxyConfiguration.isEnabled()) {
-			validateConfiguration(proxyConfiguration);
-		}
-	}
-
-	private <T> void validateConfiguration(T configuration) {
-		var violations = validator.validate(configuration);
-		if (!violations.isEmpty()) {
-			throw new TechnicalException(
-					"%s is invalid: %s".formatted(configuration.getClass().getSimpleName(), formatConstraintValidation(violations)));
-		}
-	}
-
-	private <T> String formatConstraintValidation(Set<ConstraintViolation<T>> constraints) {
-		return constraints.stream()
-				.map(violation -> String.format("%s: %s", violation.getPropertyPath(), violation.getMessage()))
-				.reduce((a, b) -> a + ", " + b)
-				.orElse("");
-	}
-}
-
diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidatorTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfigurationTest.java
similarity index 90%
rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidatorTest.java
rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfigurationTest.java
index 0a22469..95bbcef 100644
--- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/Osi2PropertiesValidatorTest.java
+++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfigurationTest.java
@@ -17,7 +17,7 @@ import org.junit.jupiter.params.provider.MethodSource;
 
 import de.ozgcloud.common.errorhandling.TechnicalException;
 
-class Osi2PropertiesValidatorTest {
+class ApiClientConfigurationTest {
 
 	private static final Validator VALIDATOR;
 
@@ -34,27 +34,27 @@ class Osi2PropertiesValidatorTest {
 		@DisplayName("should return if is valid")
 		@Test
 		void shouldReturnIfIsValid() {
-			var validator = new Osi2PropertiesValidator(
+			var validator = new ApiClientConfiguration(
 					createAuthConfiguration(),
 					createApiConfiguration(),
 					createProxyConfiguration(),
 					VALIDATOR
 			);
 
-			assertThatCode(validator::validateConfiguration).doesNotThrowAnyException();
+			assertThatCode(validator::getErrorMessageOfViolations).doesNotThrowAnyException();
 		}
 
 		@DisplayName("should return if is valid with disabled proxy")
 		@Test
 		void shouldReturnIfIsValidWithDisabledProxy() {
-			var validator = new Osi2PropertiesValidator(
+			var validator = new ApiClientConfiguration(
 					createAuthConfiguration(),
 					createApiConfiguration(),
 					createDisabledProxyConfiguration(),
 					VALIDATOR
 			);
 
-			assertThatCode(validator::validateConfiguration).doesNotThrowAnyException();
+			assertThatCode(validator::getErrorMessageOfViolations).doesNotThrowAnyException();
 		}
 
 		static Stream<Arguments> invalidValidatorConfigurations() {
@@ -85,14 +85,14 @@ class Osi2PropertiesValidatorTest {
 				Osi2PostfachProperties.ApiConfiguration apiConfiguration,
 				Osi2PostfachProperties.ProxyConfiguration proxyConfiguration
 		) {
-			var validator = new Osi2PropertiesValidator(
+			var validator = new ApiClientConfiguration(
 					authConfiguration,
 					apiConfiguration,
 					proxyConfiguration,
 					VALIDATOR
 			);
 
-			assertThatThrownBy(validator::validateConfiguration)
+			assertThatThrownBy(validator::getErrorMessageOfViolations)
 					.isInstanceOf(TechnicalException.class)
 					.hasMessageContaining("is invalid");
 		}
-- 
GitLab