From f607a679e56d8a1797213630a43f6463a747dfff Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 29 Apr 2020 15:10:15 +0200 Subject: [PATCH] Introduce WebClientFactory and RestTemplateFactory We now encapsulate WebClient and RestTemplate creation through WebClientFactory and RestTemplateFactory beans. These interfaces provide safe access through the related builders to HTTP client creation without the risk of using a mutable builder whose state might have changed in the meantime. Those reusable components reduce the need of code duplication when calling code wants to participate in HTTP clients created by the configuration infrastructure. Closes gh-554. --- .../vault/client/RestTemplateCustomizer.java | 6 +- .../vault/client/RestTemplateFactory.java | 53 ++++++++++ .../vault/client/WebClientCustomizer.java | 6 +- .../vault/client/WebClientFactory.java | 52 ++++++++++ .../AbstractReactiveVaultConfiguration.java | 57 +++++++++-- .../config/AbstractVaultConfiguration.java | 57 +++++++++-- .../config/DefaultRestTemplateFactory.java | 54 ++++++++++ .../vault/config/DefaultWebClientFactory.java | 55 +++++++++++ ...ctReactiveVaultConfigurationUnitTests.java | 98 ++++++++++++++++++ .../AbstractVaultConfigurationUnitTests.java | 99 +++++++++++++++++++ 10 files changed, 516 insertions(+), 21 deletions(-) create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateFactory.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/client/WebClientFactory.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/config/DefaultRestTemplateFactory.java create mode 100644 spring-vault-core/src/main/java/org/springframework/vault/config/DefaultWebClientFactory.java create mode 100644 spring-vault-core/src/test/java/org/springframework/vault/config/AbstractReactiveVaultConfigurationUnitTests.java create mode 100644 spring-vault-core/src/test/java/org/springframework/vault/config/AbstractVaultConfigurationUnitTests.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java index ac62e167..d271e566 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateCustomizer.java @@ -18,11 +18,13 @@ package org.springframework.vault.client; import org.springframework.web.client.RestTemplate; /** - * Callback interface that can be used to customize a {@link RestTemplate}. + * Callback interface that can be used to customize a {@link RestTemplate}. Beans + * implementing this interface are applied to + * {@link org.springframework.vault.client.RestTemplateBuilder}. * * @author Mark Paluch * @since 2.2 - * @see RestTemplateBuilder + * @see org.springframework.vault.client.RestTemplateBuilder#customizers(RestTemplateCustomizer...) */ @FunctionalInterface public interface RestTemplateCustomizer { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateFactory.java new file mode 100644 index 00000000..20373119 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.client; + +import java.util.function.Consumer; + +import org.springframework.web.client.RestTemplate; + +/** + * Factory interface that produces a {@link RestTemplate} object. Factory implementations + * are expected to create a new {@link RestTemplate} object when calling + * {@link #create()}. + * + * @author Mark Paluch + * @since 2.3 + * @see RestTemplateBuilder + * @see RestTemplate + */ +@FunctionalInterface +public interface RestTemplateFactory { + + /** + * Create a {@link RestTemplate} instance. + * + * @return a {@link RestTemplate} instance. + */ + default RestTemplate create() { + return create(builder -> { + }); + } + + /** + * Create a {@link RestTemplate} instance by applying {@code customizer} to the + * underlying {@link RestTemplateBuilder}. + * + * @param customizer builder customizer. + * @return a {@link RestTemplate} instance. + */ + RestTemplate create(Consumer customizer); +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java index 0e1e3db1..74fc943c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientCustomizer.java @@ -18,11 +18,13 @@ package org.springframework.vault.client; import org.springframework.web.reactive.function.client.WebClient; /** - * Callback interface that can be used to customize a {@link WebClient.Builder}. + * Callback interface that can be used to customize a {@link WebClient.Builder}. Beans + * implementing this interface are applied to + * {@link org.springframework.vault.client.WebClientBuilder}. * * @author Mark Paluch * @since 2.2 - * @see WebClientBuilder + * @see org.springframework.vault.client.WebClientBuilder#customizers(WebClientCustomizer...) */ @FunctionalInterface public interface WebClientCustomizer { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientFactory.java new file mode 100644 index 00000000..205dd4da --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/WebClientFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.client; + +import java.util.function.Consumer; + +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Factory interface that produces a {@link WebClient} object. Factory implementations are + * expected to create a new {@link WebClient} object when calling {@link #create()}. + * + * @author Mark Paluch + * @since 2.3 + * @see WebClientBuilder + * @see WebClient + */ +@FunctionalInterface +public interface WebClientFactory { + + /** + * Create a {@link WebClient} instance. + * + * @return a {@link WebClient} instance. + */ + default WebClient create() { + return create(builder -> { + }); + } + + /** + * Create a {@link WebClient} instance by applying {@code customizer} to the + * underlying {@link WebClientBuilder}. + * + * @param customizer builder customizer. + * @return a {@link WebClient} instance. + */ + WebClient create(Consumer customizer); +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java index 1a0fd40b..3abe5e81 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java @@ -19,6 +19,7 @@ import java.time.Duration; import reactor.core.publisher.Mono; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.reactive.ClientHttpConnector; @@ -33,9 +34,11 @@ import org.springframework.vault.authentication.SessionManager; import org.springframework.vault.authentication.TokenAuthentication; import org.springframework.vault.authentication.VaultTokenSupplier; import org.springframework.vault.client.ClientHttpConnectorFactory; -import org.springframework.vault.client.ReactiveVaultClients; +import org.springframework.vault.client.VaultEndpoint; import org.springframework.vault.client.VaultEndpointProvider; import org.springframework.vault.client.WebClientBuilder; +import org.springframework.vault.client.WebClientCustomizer; +import org.springframework.vault.client.WebClientFactory; import org.springframework.vault.core.ReactiveVaultTemplate; import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.VaultToken; @@ -75,8 +78,33 @@ public abstract class AbstractReactiveVaultConfiguration */ protected WebClientBuilder webClientBuilder(VaultEndpointProvider endpointProvider, ClientHttpConnector httpConnector) { - return WebClientBuilder.builder().endpointProvider(endpointProvider) - .httpConnector(httpConnector); + + ObjectProvider customizers = getBeanFactory() + .getBeanProvider(WebClientCustomizer.class); + + WebClientBuilder builder = WebClientBuilder.builder() + .endpointProvider(endpointProvider).httpConnector(httpConnector); + + builder.customizers(customizers.stream().toArray(WebClientCustomizer[]::new)); + + return builder; + } + + /** + * Create a {@link WebClientFactory} bean that is used to produce a {@link WebClient}. + * + * @return the {@link WebClientFactory}. + * @see #clientHttpConnector() + * @since 2.3 + */ + @Bean + public WebClientFactory webClientFactory() { + + ClientHttpConnector httpConnector = clientHttpConnector(); + + return new DefaultWebClientFactory(httpConnector, clientHttpConnector -> { + return webClientBuilder(vaultEndpointProvider(), clientHttpConnector); + }); } /** @@ -89,8 +117,12 @@ public abstract class AbstractReactiveVaultConfiguration */ @Bean public ReactiveVaultTemplate reactiveVaultTemplate() { + + VaultEndpointProvider provider = vaultEndpointProvider(); + VaultEndpoint vaultEndpoint = provider.getVaultEndpoint(); + return new ReactiveVaultTemplate( - webClientBuilder(vaultEndpointProvider(), clientHttpConnector()), + webClientBuilder(() -> vaultEndpoint, clientHttpConnector()), getReactiveSessionManager()); } @@ -118,8 +150,8 @@ public abstract class AbstractReactiveVaultConfiguration @Bean public ReactiveSessionManager reactiveSessionManager() { - WebClient webClient = ReactiveVaultClients.createWebClient(vaultEndpoint(), - clientHttpConnector()); + WebClient webClient = getWebClientFactory().create(); + return new ReactiveLifecycleAwareSessionManager(vaultTokenSupplier(), getVaultThreadPoolTaskScheduler(), webClient); } @@ -147,8 +179,7 @@ public abstract class AbstractReactiveVaultConfiguration AuthenticationStepsFactory factory = (AuthenticationStepsFactory) clientAuthentication; - WebClient webClient = ReactiveVaultClients.createWebClient(vaultEndpoint(), - clientHttpConnector()); + WebClient webClient = getWebClientFactory().create(); AuthenticationStepsOperator stepsOperator = new AuthenticationStepsOperator( factory.getAuthenticationSteps(), webClient); @@ -173,6 +204,16 @@ public abstract class AbstractReactiveVaultConfiguration return ClientHttpConnectorFactory.create(clientOptions(), sslConfiguration()); } + /** + * Return the {@link WebClientFactory}. + * + * @return the {@link WebClientFactory} bean. + * @since 2.3 + */ + protected WebClientFactory getWebClientFactory() { + return getBeanFactory().getBean(WebClientFactory.class); + } + private ReactiveSessionManager getReactiveSessionManager() { return getBeanFactory().getBean("reactiveSessionManager", ReactiveSessionManager.class); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java index 722eab73..bf9b7022 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractVaultConfiguration.java @@ -19,6 +19,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; @@ -33,6 +34,8 @@ import org.springframework.vault.authentication.LifecycleAwareSessionManager; import org.springframework.vault.authentication.SessionManager; import org.springframework.vault.client.ClientHttpRequestFactoryFactory; import org.springframework.vault.client.RestTemplateBuilder; +import org.springframework.vault.client.RestTemplateCustomizer; +import org.springframework.vault.client.RestTemplateFactory; import org.springframework.vault.client.SimpleVaultEndpointProvider; import org.springframework.vault.client.VaultEndpoint; import org.springframework.vault.client.VaultEndpointProvider; @@ -41,6 +44,7 @@ import org.springframework.vault.core.lease.SecretLeaseContainer; import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.SslConfiguration; import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; /** * Base class for Spring Vault configuration using JavaConfig. @@ -84,13 +88,40 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw * @return the {@link RestTemplateBuilder}. * @see #vaultEndpointProvider() * @see #clientHttpRequestFactoryWrapper() - * @since 2.2 + * @since 2.3 */ protected RestTemplateBuilder restTemplateBuilder( VaultEndpointProvider endpointProvider, ClientHttpRequestFactory requestFactory) { - return RestTemplateBuilder.builder().endpointProvider(endpointProvider) - .requestFactory(requestFactory); + + ObjectProvider customizers = getBeanFactory() + .getBeanProvider(RestTemplateCustomizer.class); + + RestTemplateBuilder builder = RestTemplateBuilder.builder() + .endpointProvider(endpointProvider).requestFactory(requestFactory); + + builder.customizers(customizers.stream().toArray(RestTemplateCustomizer[]::new)); + + return builder; + } + + /** + * Create a {@link RestTemplateFactory} bean that is used to produce + * {@link RestTemplate}. + * + * @return the {@link RestTemplateFactory}. + * @see #vaultEndpointProvider() + * @see #clientHttpRequestFactoryWrapper() + * @since 2.3 + */ + @Bean + public RestTemplateFactory restTemplateFactory( + ClientFactoryWrapper requestFactoryWrapper) { + + return new DefaultRestTemplateFactory( + requestFactoryWrapper.getClientHttpRequestFactory(), it -> { + return restTemplateBuilder(vaultEndpointProvider(), it); + }); } /** @@ -179,16 +210,14 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw /** * Construct a {@link RestOperations} object configured for Vault session management - * and authentication usage. Can be customized by overriding - * {@link #restTemplateBuilder(VaultEndpointProvider, ClientHttpRequestFactory)}. + * and authentication usage. Can be customized by providing a + * {@link RestTemplateFactory} bean. * * @return the {@link RestOperations} to be used for Vault access. - * @see #vaultEndpointProvider() - * @see #clientHttpRequestFactoryWrapper() + * @see #restTemplateFactory(ClientFactoryWrapper) */ public RestOperations restOperations() { - return restTemplateBuilder(vaultEndpointProvider(), - getClientFactoryWrapper().getClientHttpRequestFactory()).build(); + return getRestTemplateFactory().create(); } /** @@ -249,6 +278,16 @@ public abstract class AbstractVaultConfiguration implements ApplicationContextAw this.applicationContext = applicationContext; } + /** + * Return the {@link RestTemplateFactory}. + * + * @return the {@link RestTemplateFactory} bean. + * @since 2.3 + */ + protected RestTemplateFactory getRestTemplateFactory() { + return getBeanFactory().getBean(RestTemplateFactory.class); + } + BeanFactory getBeanFactory() { Assert.state(applicationContext != null, diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/DefaultRestTemplateFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/config/DefaultRestTemplateFactory.java new file mode 100644 index 00000000..2fa92361 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/DefaultRestTemplateFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.config; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.lang.Nullable; +import org.springframework.vault.client.RestTemplateBuilder; +import org.springframework.vault.client.RestTemplateFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Default {@link RestTemplateFactory} implementation. + * + * @author Mark Paluch + */ +class DefaultRestTemplateFactory implements RestTemplateFactory { + + private final ClientHttpRequestFactory requestFactory; + private final Function builderFunction; + + DefaultRestTemplateFactory(ClientHttpRequestFactory requestFactory, + Function builderFunction) { + this.requestFactory = requestFactory; + this.builderFunction = builderFunction; + } + + @Override + public RestTemplate create(@Nullable Consumer customizer) { + + RestTemplateBuilder builder = builderFunction.apply(requestFactory); + + if (customizer != null) { + customizer.accept(builder); + } + + return builder.build(); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/DefaultWebClientFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/config/DefaultWebClientFactory.java new file mode 100644 index 00000000..e7051e69 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/DefaultWebClientFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.config; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.lang.Nullable; +import org.springframework.vault.client.WebClientBuilder; +import org.springframework.vault.client.WebClientFactory; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Default implementation of {@link WebClientFactory}. + * + * @author Mark Paluch + * @since 2.3 + */ +class DefaultWebClientFactory implements WebClientFactory { + + private final ClientHttpConnector connector; + private final Function builderFunction; + + DefaultWebClientFactory(ClientHttpConnector connector, + Function builderFunction) { + this.connector = connector; + this.builderFunction = builderFunction; + } + + @Override + public WebClient create(@Nullable Consumer customizer) { + + WebClientBuilder builder = builderFunction.apply(connector); + + if (customizer != null) { + customizer.accept(builder); + } + + return builder.build(); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/config/AbstractReactiveVaultConfigurationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/config/AbstractReactiveVaultConfigurationUnitTests.java new file mode 100644 index 00000000..c2c1e86d --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/config/AbstractReactiveVaultConfigurationUnitTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.config; + +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.vault.authentication.ClientAuthentication; +import org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.client.WebClientCustomizer; +import org.springframework.vault.client.WebClientFactory; +import org.springframework.vault.core.ReactiveVaultOperations; +import org.springframework.vault.support.SslConfiguration; +import org.springframework.vault.util.Settings; +import org.springframework.vault.util.TestRestTemplateFactory; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Integration tests for {@link AbstractReactiveVaultConfiguration}. + * + * @author Mark Paluch + */ +class AbstractReactiveVaultConfigurationUnitTests { + + @Test + void shouldApplyCustomizerToWebClientFactory() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + WebClientCustomizerConfiguration.class); + + WebClientFactory factory = context.getBean(WebClientFactory.class); + WebClient webClient = factory.create(); + + webClient.get().uri("/foo").exchange().as(StepVerifier::create) + .verifyError(CustomizedSignal.class); + } + + @Test + void shouldApplyCustomizerToTemplate() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + WebClientCustomizerConfiguration.class); + + ReactiveVaultOperations operations = context + .getBean(ReactiveVaultOperations.class); + + operations.read("/foo").as(StepVerifier::create) + .verifyError(CustomizedSignal.class); + } + + @Configuration(proxyBeanMethods = false) + static class WebClientCustomizerConfiguration + extends AbstractReactiveVaultConfiguration { + + @Override + public VaultEndpoint vaultEndpoint() { + return TestRestTemplateFactory.TEST_VAULT_ENDPOINT; + } + + @Override + public ClientAuthentication clientAuthentication() { + return new TokenAuthentication(Settings.token()); + } + + @Override + public SslConfiguration sslConfiguration() { + return Settings.createSslConfiguration(); + } + + @Bean + public WebClientCustomizer customizer() { + return builder -> builder.exchangeFunction(request -> { + throw new CustomizedSignal(); + }); + } + } + + static class CustomizedSignal extends RuntimeException { + + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/config/AbstractVaultConfigurationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/config/AbstractVaultConfigurationUnitTests.java new file mode 100644 index 00000000..f0fad430 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/config/AbstractVaultConfigurationUnitTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.vault.config; + +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.vault.authentication.ClientAuthentication; +import org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.RestTemplateCustomizer; +import org.springframework.vault.client.RestTemplateFactory; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.core.VaultOperations; +import org.springframework.vault.support.SslConfiguration; +import org.springframework.vault.util.Settings; +import org.springframework.vault.util.TestRestTemplateFactory; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Integration tests for {@link AbstractVaultConfiguration}. + * + * @author Mark Paluch + */ +class AbstractVaultConfigurationUnitTests { + + @Test + void shouldApplyCustomizerToRestTemplateFactory() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + RestTemplateCustomizerConfiguration.class); + + RestTemplateFactory factory = context.getBean(RestTemplateFactory.class); + RestTemplate restTemplate = factory.create(); + + assertThatExceptionOfType(CustomizedSignal.class) + .isThrownBy(() -> restTemplate.delete("/foo")); + } + + @Test + void shouldApplyCustomizerToTemplate() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + RestTemplateCustomizerConfiguration.class); + + VaultOperations operations = context.getBean(VaultOperations.class); + + assertThatExceptionOfType(CustomizedSignal.class) + .isThrownBy(() -> operations.opsForSys().health()); + } + + @Configuration(proxyBeanMethods = false) + static class RestTemplateCustomizerConfiguration extends AbstractVaultConfiguration { + + @Override + public VaultEndpoint vaultEndpoint() { + return TestRestTemplateFactory.TEST_VAULT_ENDPOINT; + } + + @Override + public ClientAuthentication clientAuthentication() { + return new TokenAuthentication(Settings.token()); + } + + @Override + public SslConfiguration sslConfiguration() { + return Settings.createSslConfiguration(); + } + + @Bean + public RestTemplateCustomizer customizer() { + return restTemplate -> restTemplate + .setRequestFactory((ClientHttpRequestFactory) (uri, httpMethod) -> { + throw new CustomizedSignal(); + }); + } + } + + static class CustomizedSignal extends RuntimeException { + + } +}