Allow for RestTemplate customization

This commit reuses the `RestTemplateBuilder` infrastructure wherever
a `RestTemplate` is required. This allows to customize the rest template
if necessary.

Closes gh-481
This commit is contained in:
Stephane Nicoll 2017-08-14 11:26:34 +02:00
parent 70d224b072
commit 1bf0d0fcde
7 changed files with 263 additions and 40 deletions

View File

@ -23,9 +23,13 @@ import io.spring.initializr.actuate.stat.ProjectRequestDocumentFactory;
import io.spring.initializr.actuate.stat.StatsProperties; import io.spring.initializr.actuate.stat.StatsProperties;
import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.metadata.InitializrMetadataProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.backoff.ExponentialBackOffPolicy;
@ -41,6 +45,7 @@ import org.springframework.retry.support.RetryTemplate;
@Configuration @Configuration
@EnableConfigurationProperties(StatsProperties.class) @EnableConfigurationProperties(StatsProperties.class)
@ConditionalOnProperty("initializr.stats.elastic.uri") @ConditionalOnProperty("initializr.stats.elastic.uri")
@AutoConfigureAfter(WebClientAutoConfiguration.class)
class InitializrStatsAutoConfiguration { class InitializrStatsAutoConfiguration {
private final StatsProperties statsProperties; private final StatsProperties statsProperties;
@ -50,11 +55,13 @@ class InitializrStatsAutoConfiguration {
} }
@Bean @Bean
@ConditionalOnBean(InitializrMetadataProvider.class)
public ProjectGenerationStatPublisher projectRequestStatHandler( public ProjectGenerationStatPublisher projectRequestStatHandler(
InitializrMetadataProvider provider) { InitializrMetadataProvider provider,
RestTemplateBuilder restTemplateBuilder) {
return new ProjectGenerationStatPublisher( return new ProjectGenerationStatPublisher(
new ProjectRequestDocumentFactory(provider), statsProperties, new ProjectRequestDocumentFactory(provider), statsProperties,
statsRetryTemplate()); restTemplateBuilder, statsRetryTemplate());
} }
@Bean @Bean

View File

@ -30,6 +30,7 @@ import org.springframework.http.RequestEntity;
import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryCallback;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
/** /**
@ -49,14 +50,20 @@ public class ProjectGenerationStatPublisher {
private final RetryTemplate retryTemplate; private final RetryTemplate retryTemplate;
public ProjectGenerationStatPublisher(ProjectRequestDocumentFactory documentFactory, public ProjectGenerationStatPublisher(ProjectRequestDocumentFactory documentFactory,
StatsProperties statsProperties, RetryTemplate retryTemplate) { StatsProperties statsProperties, RestTemplateBuilder restTemplateBuilder,
RetryTemplate retryTemplate) {
this.documentFactory = documentFactory; this.documentFactory = documentFactory;
this.statsProperties = statsProperties; this.statsProperties = statsProperties;
this.objectMapper = createObjectMapper(); this.objectMapper = createObjectMapper();
this.restTemplate = new RestTemplateBuilder() StatsProperties.Elastic elastic = statsProperties.getElastic();
.basicAuthorization(statsProperties.getElastic().getUsername(), if (StringUtils.hasText(elastic.getUsername())) {
statsProperties.getElastic().getPassword()) this.restTemplate = restTemplateBuilder
.basicAuthorization(elastic.getUsername(),
elastic.getPassword())
.build(); .build();
} else {
this.restTemplate = restTemplateBuilder.build();
}
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
} }

View File

@ -0,0 +1,103 @@
/*
* Copyright 2012-2017 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
*
* http://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 io.spring.initializr.actuate.autoconfigure;
import io.spring.initializr.actuate.stat.ProjectGenerationStatPublisher;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.web.autoconfigure.InitializrAutoConfiguration;
import io.spring.initializr.web.autoconfigure.InitializrAutoConfigurationTests;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link InitializrStatsAutoConfiguration}.
*
* @author Stephane Nicoll
*/
public class InitializrStatsAutoConfigurationTests {
private ConfigurableApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void customRestTemplateBuilderIsUsed() {
load(CustomRestTemplateConfiguration.class,
"initializr.stats.elastic.uri=http://localhost:9200");
assertThat(this.context.getBeansOfType(ProjectGenerationStatPublisher.class))
.hasSize(1);
RestTemplate restTemplate = (RestTemplate) new DirectFieldAccessor(
this.context.getBean(ProjectGenerationStatPublisher.class))
.getPropertyValue("restTemplate");
assertThat(restTemplate.getErrorHandler()).isSameAs(
CustomRestTemplateConfiguration.errorHandler);
}
private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(ctx, environment);
if (config != null) {
ctx.register(config);
}
ctx.register(WebClientAutoConfiguration.class,
InitializrStatsAutoConfiguration.class);
ctx.refresh();
this.context = ctx;
}
@Configuration
static class InfrastructureConfiguration {
@Bean
public InitializrMetadataProvider initializrMetadataProvider() {
return mock(InitializrMetadataProvider.class);
}
}
@Configuration
@Import(InfrastructureConfiguration.class)
static class CustomRestTemplateConfiguration {
private static final ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
@Bean
public RestTemplateCustomizer testRestTemplateCustomizer() {
return b -> b.setErrorHandler(errorHandler);
}
}
}

View File

@ -25,6 +25,7 @@ import io.spring.initializr.generator.ProjectRequest;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -54,7 +55,7 @@ public class ProjectGenerationStatPublisherTests extends AbstractInitializrStatT
new ProjectRequestDocumentFactory(createProvider(getMetadata())); new ProjectRequestDocumentFactory(createProvider(getMetadata()));
this.retryTemplate = new RetryTemplate(); this.retryTemplate = new RetryTemplate();
this.statPublisher = new ProjectGenerationStatPublisher(documentFactory, this.statPublisher = new ProjectGenerationStatPublisher(documentFactory,
properties, retryTemplate); properties, new RestTemplateBuilder(), retryTemplate);
mockServer = MockRestServiceServer.createServer( mockServer = MockRestServiceServer.createServer(
this.statPublisher.getRestTemplate()); this.statPublisher.getRestTemplate());
} }

View File

@ -115,9 +115,15 @@ reason for that is that Spring Initializr calls an API on spring.io to retrieve
latest versions automatically. This makes sure that you always get the latest available latest versions automatically. This makes sure that you always get the latest available
versions. versions.
If that's not what you want, you need to override the `InitializrMetadataProvider` bean to If you are behind a proxy, or need to customize the `RestTemplate` that is used behind the
provide your own metadata for the service. For instance, you could swap to an scenes, you can define a `RestTemplateCustomizer` bean in your configuration. For more
implementation that always returns the contents of static `application.yml`: details, {spring-boot-reference}/#boot-features-restclient-customization[check the
documentation].
If you don't want the version to be upgraded automatically, you need to override the
`InitializrMetadataProvider` bean to provide your own metadata for the service. For
instance, you could swap to an implementation that always returns the contents of static
`application.yml`:
[source,java,indent=0] [source,java,indent=0]
---- ----

View File

@ -18,6 +18,7 @@ package io.spring.initializr.web.autoconfigure;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.cache.configuration.MutableConfiguration; import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy; import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration; import javax.cache.expiry.Duration;
@ -43,12 +44,14 @@ import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.resource.ResourceUrlProvider; import org.springframework.web.servlet.resource.ResourceUrlProvider;
/** /**
@ -64,7 +67,7 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(InitializrProperties.class) @EnableConfigurationProperties(InitializrProperties.class)
@AutoConfigureAfter(CacheAutoConfiguration.class) @AutoConfigureAfter({ CacheAutoConfiguration.class, WebClientAutoConfiguration.class })
public class InitializrAutoConfiguration { public class InitializrAutoConfiguration {
private final List<ProjectRequestPostProcessor> postProcessors; private final List<ProjectRequestPostProcessor> postProcessors;
@ -75,30 +78,6 @@ public class InitializrAutoConfiguration {
this.postProcessors = list != null ? list : new ArrayList<>(); this.postProcessors = list != null ? list : new ArrayList<>();
} }
@Bean
public WebConfig webConfig() {
return new WebConfig();
}
@Bean
@ConditionalOnMissingBean
public MainController initializrMainController(
InitializrMetadataProvider metadataProvider,
TemplateRenderer templateRenderer,
ResourceUrlProvider resourceUrlProvider,
ProjectGenerator projectGenerator,
DependencyMetadataProvider dependencyMetadataProvider) {
return new MainController(metadataProvider, templateRenderer, resourceUrlProvider
, projectGenerator, dependencyMetadataProvider);
}
@Bean
@ConditionalOnMissingBean
public UiController initializrUiController(
InitializrMetadataProvider metadataProvider) {
return new UiController(metadataProvider);
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ProjectGenerator projectGenerator() { public ProjectGenerator projectGenerator() {
@ -130,10 +109,12 @@ public class InitializrAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(InitializrMetadataProvider.class) @ConditionalOnMissingBean(InitializrMetadataProvider.class)
public InitializrMetadataProvider initializrMetadataProvider( public InitializrMetadataProvider initializrMetadataProvider(
InitializrProperties properties) { InitializrProperties properties,
RestTemplateBuilder restTemplateBuilder) {
InitializrMetadata metadata = InitializrMetadataBuilder InitializrMetadata metadata = InitializrMetadataBuilder
.fromInitializrProperties(properties).build(); .fromInitializrProperties(properties).build();
return new DefaultInitializrMetadataProvider(metadata, new RestTemplate()); return new DefaultInitializrMetadataProvider(metadata,
restTemplateBuilder.build());
} }
@Bean @Bean
@ -142,9 +123,40 @@ public class InitializrAutoConfiguration {
return new DefaultDependencyMetadataProvider(); return new DefaultDependencyMetadataProvider();
} }
@Configuration
@ConditionalOnWebApplication
static class InitializrWebConfiguration {
@Bean
public WebConfig webConfig() {
return new WebConfig();
}
@Bean
@ConditionalOnMissingBean
public MainController initializrMainController(
InitializrMetadataProvider metadataProvider,
TemplateRenderer templateRenderer,
ResourceUrlProvider resourceUrlProvider,
ProjectGenerator projectGenerator,
DependencyMetadataProvider dependencyMetadataProvider) {
return new MainController(metadataProvider, templateRenderer, resourceUrlProvider
, projectGenerator, dependencyMetadataProvider);
}
@Bean
@ConditionalOnMissingBean
public UiController initializrUiController(
InitializrMetadataProvider metadataProvider) {
return new UiController(metadataProvider);
}
}
@Configuration @Configuration
@ConditionalOnClass(javax.cache.CacheManager.class) @ConditionalOnClass(javax.cache.CacheManager.class)
static class CacheConfiguration { static class InitializrCacheConfiguration {
@Bean @Bean
public JCacheManagerCustomizer initializrCacheManagerCustomizer() { public JCacheManagerCustomizer initializrCacheManagerCustomizer() {

View File

@ -0,0 +1,87 @@
/*
* Copyright 2012-2017 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
*
* http://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 io.spring.initializr.web.autoconfigure;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link InitializrAutoConfiguration}.
*
* @author Stephane Nicoll
*/
public class InitializrAutoConfigurationTests {
private ConfigurableApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void customRestTemplateBuilderIsUsed() {
load(CustomRestTemplateConfiguration.class);
assertThat(this.context.getBeansOfType(InitializrMetadataProvider.class))
.hasSize(1);
RestTemplate restTemplate = (RestTemplate) new DirectFieldAccessor(
this.context.getBean(InitializrMetadataProvider.class))
.getPropertyValue("restTemplate");
assertThat(restTemplate.getErrorHandler()).isSameAs(
CustomRestTemplateConfiguration.errorHandler);
}
private void load(Class<?> config, String... environment) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(ctx, environment);
if (config != null) {
ctx.register(config);
}
ctx.register(WebClientAutoConfiguration.class, InitializrAutoConfiguration.class);
ctx.refresh();
this.context = ctx;
}
@Configuration
static class CustomRestTemplateConfiguration {
private static final ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
@Bean
public RestTemplateCustomizer testRestTemplateCustomizer() {
return b -> b.setErrorHandler(errorHandler);
}
}
}