diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java index ecd2e99ef00..a53bc9db451 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfiguration.java @@ -18,10 +18,12 @@ package org.springframework.boot.actuate.autoconfigure; import com.codahale.metrics.MetricRegistry; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.actuate.metrics.GaugeService; import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices; +import org.springframework.boot.actuate.metrics.dropwizard.ReservoirFactory; import org.springframework.boot.actuate.metrics.reader.MetricRegistryMetricReader; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -41,6 +43,13 @@ import org.springframework.context.annotation.Configuration; @AutoConfigureBefore(MetricRepositoryAutoConfiguration.class) public class MetricsDropwizardAutoConfiguration { + private final ReservoirFactory reservoirFactory; + + public MetricsDropwizardAutoConfiguration( + ObjectProvider reservoirFactory) { + this.reservoirFactory = reservoirFactory.getIfAvailable(); + } + @Bean @ConditionalOnMissingBean public MetricRegistry metricRegistry() { @@ -52,7 +61,12 @@ public class MetricsDropwizardAutoConfiguration { GaugeService.class }) public DropwizardMetricServices dropwizardMetricServices( MetricRegistry metricRegistry) { - return new DropwizardMetricServices(metricRegistry); + if (this.reservoirFactory == null) { + return new DropwizardMetricServices(metricRegistry); + } + else { + return new DropwizardMetricServices(metricRegistry, this.reservoirFactory); + } } @Bean diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java index 9df978a816f..460891dec26 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; @@ -83,10 +84,14 @@ public class CloudFoundryActuatorAutoConfiguration { private CloudFoundrySecurityService getCloudFoundrySecurityService( RestTemplateBuilder restTemplateBuilder, Environment environment) { + RelaxedPropertyResolver cloudFoundryProperties = new RelaxedPropertyResolver( + environment, "management.cloudfoundry."); String cloudControllerUrl = environment.getProperty("vcap.application.cf_api"); + boolean skipSslValidation = cloudFoundryProperties + .getProperty("skip-ssl-validation", Boolean.class, false); return cloudControllerUrl == null ? null - : new CloudFoundrySecurityService(restTemplateBuilder, - cloudControllerUrl); + : new CloudFoundrySecurityService(restTemplateBuilder, cloudControllerUrl, + skipSslValidation); } private CorsConfiguration getCorsConfiguration() { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java index ab13a0e1484..38d0e474334 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java @@ -38,7 +38,8 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; */ class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter { - protected final Log logger = LogFactory.getLog(getClass()); + private static final Log logger = LogFactory + .getLog(CloudFoundrySecurityInterceptor.class); private final TokenValidator tokenValidator; @@ -74,7 +75,7 @@ class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter { check(request, mvcEndpoint); } catch (CloudFoundryAuthorizationException ex) { - this.logger.error(ex); + logger.error(ex); response.setContentType(MediaType.APPLICATION_JSON.toString()); response.getWriter() .write("{\"security_error\":\"" + ex.getMessage() + "\"}"); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java index ce6126ba883..723b53a7e95 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java @@ -46,11 +46,14 @@ class CloudFoundrySecurityService { private String uaaUrl; CloudFoundrySecurityService(RestTemplateBuilder restTemplateBuilder, - String cloudControllerUrl) { + String cloudControllerUrl, boolean skipSslValidation) { Assert.notNull(restTemplateBuilder, "RestTemplateBuilder must not be null"); Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null"); - this.restTemplate = restTemplateBuilder - .requestFactory(SkipSslVerificationHttpRequestFactory.class).build(); + if (skipSslValidation) { + restTemplateBuilder = restTemplateBuilder + .requestFactory(SkipSslVerificationHttpRequestFactory.class); + } + this.restTemplate = restTemplateBuilder.build(); this.cloudControllerUrl = cloudControllerUrl; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java index 08d4dba5c03..e9c6d3b7852 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java @@ -17,11 +17,16 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.http.HttpStatus; +import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -34,10 +39,15 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; */ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { + private static final Log logger = LogFactory + .getLog(MvcEndpointSecurityInterceptor.class); + private final boolean secure; private final List roles; + private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean(); + public MvcEndpointSecurityInterceptor(boolean secure, List roles) { this.secure = secure; this.roles = roles; @@ -59,17 +69,30 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { return true; } } - setFailureResponseStatus(request, response); + sendFailureResponse(request, response); return false; } - private void setFailureResponseStatus(HttpServletRequest request, - HttpServletResponse response) { + private void sendFailureResponse(HttpServletRequest request, + HttpServletResponse response) throws Exception { if (request.getUserPrincipal() != null) { - response.setStatus(HttpStatus.FORBIDDEN.value()); + String roles = StringUtils.collectionToDelimitedString(this.roles, " "); + response.sendError(HttpStatus.FORBIDDEN.value(), + "Access is denied. User must have one of the these roles: " + roles); } else { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); + logUnauthorizedAttempt(); + response.sendError(HttpStatus.UNAUTHORIZED.value(), + "Full authentication is required to access this resource."); + } + } + + private void logUnauthorizedAttempt() { + if (this.loggedUnauthorizedAttempt.compareAndSet(false, true) + && logger.isInfoEnabled()) { + logger.info("Full authentication is required to access " + + "actuator endpoints. Consider adding Spring Security " + + "or set 'management.security.enabled' to false."); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServices.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServices.java index 93a6a16ee90..98a70ad8cce 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServices.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServices.java @@ -24,11 +24,15 @@ import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Reservoir; import com.codahale.metrics.Timer; import org.springframework.boot.actuate.metrics.CounterService; import org.springframework.boot.actuate.metrics.GaugeService; +import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; /** * A {@link GaugeService} and {@link CounterService} that sends data to a Dropwizard @@ -53,6 +57,8 @@ public class DropwizardMetricServices implements CounterService, GaugeService { private final MetricRegistry registry; + private final ReservoirFactory reservoirFactory; + private final ConcurrentMap gauges = new ConcurrentHashMap(); private final ConcurrentHashMap names = new ConcurrentHashMap(); @@ -62,7 +68,20 @@ public class DropwizardMetricServices implements CounterService, GaugeService { * @param registry the underlying metric registry */ public DropwizardMetricServices(MetricRegistry registry) { + this(registry, null); + } + + /** + * Create a new {@link DropwizardMetricServices} instance. + * @param registry the underlying metric registry + * @param reservoirFactory the factory that instantiates the {@link Reservoir} that + * will be used on Timers and Histograms + */ + public DropwizardMetricServices(MetricRegistry registry, + ReservoirFactory reservoirFactory) { this.registry = registry; + this.reservoirFactory = (reservoirFactory == null ? ReservoirFactory.NONE + : reservoirFactory); } @Override @@ -90,14 +109,10 @@ public class DropwizardMetricServices implements CounterService, GaugeService { @Override public void submit(String name, double value) { if (name.startsWith("histogram")) { - long longValue = (long) value; - Histogram metric = this.registry.histogram(name); - metric.update(longValue); + submitHistogram(name, value); } else if (name.startsWith("timer")) { - long longValue = (long) value; - Timer metric = this.registry.timer(name); - metric.update(longValue, TimeUnit.MILLISECONDS); + submitTimer(name, value); } else { name = wrapGaugeName(name); @@ -105,6 +120,39 @@ public class DropwizardMetricServices implements CounterService, GaugeService { } } + private void submitTimer(String name, double value) { + long longValue = (long) value; + Timer metric = register(name, new TimerMetricRegistrar()); + metric.update(longValue, TimeUnit.MILLISECONDS); + } + + private void submitHistogram(String name, double value) { + long longValue = (long) value; + Histogram metric = register(name, new HistogramMetricRegistrar()); + metric.update(longValue); + } + + @SuppressWarnings("unchecked") + private T register(String name, MetricRegistrar registrar) { + Reservoir reservoir = this.reservoirFactory.getReservoir(name); + if (reservoir == null) { + return registrar.register(this.registry, name); + } + Metric metric = this.registry.getMetrics().get(name); + if (metric != null) { + registrar.checkExisting(metric); + return (T) metric; + } + try { + return this.registry.register(name, registrar.createForReservoir(reservoir)); + } + catch (IllegalArgumentException ex) { + Metric added = this.registry.getMetrics().get(name); + registrar.checkExisting(metric); + return (T) added; + } + } + private void setGaugeValue(String name, double value) { // NOTE: Dropwizard provides no way to do this atomically SimpleGauge gauge = this.gauges.get(name); @@ -170,4 +218,62 @@ public class DropwizardMetricServices implements CounterService, GaugeService { } + /** + * Strategy used to register metrics. + */ + private static abstract class MetricRegistrar { + + private final Class type; + + @SuppressWarnings("unchecked") + MetricRegistrar() { + this.type = (Class) ResolvableType + .forClass(MetricRegistrar.class, getClass()).resolveGeneric(); + } + + public void checkExisting(Metric metric) { + Assert.isInstanceOf(this.type, metric, + "Different metric type already registered"); + } + + protected abstract T register(MetricRegistry registry, String name); + + protected abstract T createForReservoir(Reservoir reservoir); + + } + + /** + * {@link MetricRegistrar} for {@link Timer} metrics. + */ + private static class TimerMetricRegistrar extends MetricRegistrar { + + @Override + protected Timer register(MetricRegistry registry, String name) { + return registry.timer(name); + } + + @Override + protected Timer createForReservoir(Reservoir reservoir) { + return new Timer(reservoir); + } + + } + + /** + * {@link MetricRegistrar} for {@link Histogram} metrics. + */ + private static class HistogramMetricRegistrar extends MetricRegistrar { + + @Override + protected Histogram register(MetricRegistry registry, String name) { + return registry.histogram(name); + } + + @Override + protected Histogram createForReservoir(Reservoir reservoir) { + return new Histogram(reservoir); + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/ReservoirFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/ReservoirFactory.java new file mode 100644 index 00000000000..64396043c59 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/dropwizard/ReservoirFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2016 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 org.springframework.boot.actuate.metrics.dropwizard; + +import com.codahale.metrics.Reservoir; + +/** + * Factory interface that can be used by {@link DropwizardMetricServices} to create a + * custom {@link Reservoir}. + * + * @author Lucas Saldanha + * @author Phillip Webb + * @since 1.5.0 + */ +public interface ReservoirFactory { + + /** + * Default empty {@link ReservoirFactory} implementation. + */ + ReservoirFactory NONE = new ReservoirFactory() { + + @Override + public Reservoir getReservoir(String name) { + return null; + } + + }; + + /** + * Return the {@link Reservoir} instance to use or {@code null} if a custom reservoir + * is not needed. + * @param name the name of the metric + * @return a reservoir instance or {@code null} + */ + Reservoir getReservoir(String name); + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfigurationTests.java new file mode 100644 index 00000000000..f878306b97f --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricsDropwizardAutoConfigurationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2016 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 org.springframework.boot.actuate.autoconfigure; + +import com.codahale.metrics.Reservoir; +import com.codahale.metrics.UniformReservoir; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.actuate.metrics.dropwizard.DropwizardMetricServices; +import org.springframework.boot.actuate.metrics.dropwizard.ReservoirFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MetricsDropwizardAutoConfiguration}. + * + * @author Lucas Saldanha + */ +public class MetricsDropwizardAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void after() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void dropwizardWithoutCustomReservoirConfigured() { + this.context = new AnnotationConfigApplicationContext( + MetricsDropwizardAutoConfiguration.class); + DropwizardMetricServices dropwizardMetricServices = this.context + .getBean(DropwizardMetricServices.class); + ReservoirFactory reservoirFactory = (ReservoirFactory) ReflectionTestUtils + .getField(dropwizardMetricServices, "reservoirFactory"); + assertThat(reservoirFactory.getReservoir("test")).isNull(); + } + + @Test + public void dropwizardWithCustomReservoirConfigured() { + this.context = new AnnotationConfigApplicationContext( + MetricsDropwizardAutoConfiguration.class, Config.class); + DropwizardMetricServices dropwizardMetricServices = this.context + .getBean(DropwizardMetricServices.class); + ReservoirFactory reservoirFactory = (ReservoirFactory) ReflectionTestUtils + .getField(dropwizardMetricServices, "reservoirFactory"); + assertThat(reservoirFactory.getReservoir("test")) + .isInstanceOf(UniformReservoir.class); + } + + @Configuration + static class Config { + + @Bean + public ReservoirFactory reservoirFactory() { + return new UniformReservoirFactory(); + } + + } + + private static class UniformReservoirFactory implements ReservoirFactory { + + @Override + public Reservoir getReservoir(String name) { + return new UniformReservoir(); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java index 5de230a3533..374bdb977b3 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java @@ -42,6 +42,7 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.cors.CorsConfiguration; @@ -117,6 +118,22 @@ public class CloudFoundryActuatorAutoConfigurationTests { assertThat(cloudControllerUrl).isEqualTo("http://my-cloud-controller.com"); } + @Test + public void skipSslValidation() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "management.cloudfoundry.skipSslValidation:true"); + this.context.refresh(); + CloudFoundryEndpointHandlerMapping handlerMapping = getHandlerMapping(); + Object interceptor = ReflectionTestUtils.getField(handlerMapping, + "securityInterceptor"); + Object interceptorSecurityService = ReflectionTestUtils.getField(interceptor, + "cloudFoundrySecurityService"); + RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils + .getField(interceptorSecurityService, "restTemplate"); + assertThat(restTemplate.getRequestFactory()) + .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); + } + @Test public void cloudFoundryPlatformActiveAndCloudControllerUrlNotPresent() throws Exception { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityServiceTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityServiceTests.java index 585b69a759c..0f80c90fef2 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityServiceTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityServiceTests.java @@ -28,7 +28,9 @@ import org.springframework.boot.test.web.client.MockServerRestTemplateCustomizer import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; @@ -63,10 +65,33 @@ public class CloudFoundrySecurityServiceTests { public void setup() throws Exception { MockServerRestTemplateCustomizer mockServerCustomizer = new MockServerRestTemplateCustomizer(); RestTemplateBuilder builder = new RestTemplateBuilder(mockServerCustomizer); - this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER); + this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER, + false); this.server = mockServerCustomizer.getServer(); } + @Test + public void skipSslValidationWhenTrue() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder(); + this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER, + true); + RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils + .getField(this.securityService, "restTemplate"); + assertThat(restTemplate.getRequestFactory()) + .isInstanceOf(SkipSslVerificationHttpRequestFactory.class); + } + + @Test + public void doNotskipSslValidationWhenFalse() throws Exception { + RestTemplateBuilder builder = new RestTemplateBuilder(); + this.securityService = new CloudFoundrySecurityService(builder, CLOUD_CONTROLLER, + false); + RestTemplate restTemplate = (RestTemplate) ReflectionTestUtils + .getField(this.securityService, "restTemplate"); + assertThat(restTemplate.getRequestFactory()) + .isNotInstanceOf(SkipSslVerificationHttpRequestFactory.class); + } + @Test public void getAccessLevelWhenSpaceDeveloperShouldReturnFull() throws Exception { String responseBody = "{\"read_sensitive_data\": true,\"read_basic_data\": true}"; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java index 8586bdd7f70..cbfa6981332 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java @@ -16,19 +16,26 @@ package org.springframework.boot.actuate.endpoint.mvc; +import java.security.Principal; import java.util.Arrays; import java.util.List; +import javax.servlet.http.HttpServletResponse; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.web.method.HandlerMethod; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link MvcEndpointSecurityInterceptor}. @@ -37,6 +44,9 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class MvcEndpointSecurityInterceptorTests { + @Rule + public OutputCapture output = new OutputCapture(); + private MvcEndpointSecurityInterceptor securityInterceptor; private TestMvcEndpoint mvcEndpoint; @@ -47,7 +57,7 @@ public class MvcEndpointSecurityInterceptorTests { private MockHttpServletRequest request; - private MockHttpServletResponse response; + private HttpServletResponse response; private MockServletContext servletContext; @@ -62,7 +72,7 @@ public class MvcEndpointSecurityInterceptorTests { this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); this.servletContext = new MockServletContext(); this.request = new MockHttpServletRequest(this.servletContext); - this.response = new MockHttpServletResponse(); + this.response = mock(HttpServletResponse.class); } @Test @@ -87,11 +97,30 @@ public class MvcEndpointSecurityInterceptorTests { } @Test - public void sensitiveEndpointIfRoleIsNotPresentShouldNotAllowAccess() + public void sensitiveEndpointIfNotAuthenticatedShouldNotAllowAccess() throws Exception { + assertThat(this.securityInterceptor.preHandle(this.request, this.response, + this.handlerMethod)).isFalse(); + verify(this.response).sendError(HttpStatus.UNAUTHORIZED.value(), + "Full authentication is required to access this resource."); + assertThat(this.securityInterceptor.preHandle(this.request, this.response, + this.handlerMethod)).isFalse(); + assertThat(this.output.toString()) + .containsOnlyOnce("Full authentication is required to access actuator " + + "endpoints. Consider adding Spring Security or set " + + "'management.security.enabled' to false"); + } + + @Test + public void sensitiveEndpointIfRoleIsNotCorrectShouldNotAllowAccess() + throws Exception { + Principal principal = mock(Principal.class); + this.request.setUserPrincipal(principal); this.servletContext.declareRoles("HERO"); assertThat(this.securityInterceptor.preHandle(this.request, this.response, this.handlerMethod)).isFalse(); + verify(this.response).sendError(HttpStatus.FORBIDDEN.value(), + "Access is denied. User must have one of the these roles: SUPER_HERO"); } private static class TestEndpoint extends AbstractEndpoint { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServicesTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServicesTests.java index ec745b16461..535797bc5ca 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServicesTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/dropwizard/DropwizardMetricServicesTests.java @@ -20,22 +20,41 @@ import java.util.ArrayList; import java.util.List; import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.codahale.metrics.UniformReservoir; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyString; /** * Tests for {@link DropwizardMetricServices}. * * @author Dave Syer + * @author Lucas Saldanha */ public class DropwizardMetricServicesTests { - private final MetricRegistry registry = new MetricRegistry(); + private MetricRegistry registry = new MetricRegistry(); - private final DropwizardMetricServices writer = new DropwizardMetricServices( - this.registry); + @Mock + private ReservoirFactory reservoirFactory; + + private DropwizardMetricServices writer; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.writer = new DropwizardMetricServices(this.registry, this.reservoirFactory); + } @Test public void incrementCounter() { @@ -78,6 +97,20 @@ public class DropwizardMetricServicesTests { assertThat(this.registry.timer("timer.foo").getCount()).isEqualTo(2); } + @Test + public void setCustomReservoirTimer() { + given(this.reservoirFactory.getReservoir(anyString())) + .willReturn(new UniformReservoir()); + this.writer.submit("timer.foo", 200); + this.writer.submit("timer.foo", 300); + assertThat(this.registry.timer("timer.foo").getCount()).isEqualTo(2); + Timer timer = (Timer) this.registry.getMetrics().get("timer.foo"); + Histogram histogram = (Histogram) ReflectionTestUtils.getField(timer, + "histogram"); + assertThat(ReflectionTestUtils.getField(histogram, "reservoir").getClass() + .equals(UniformReservoir.class)).isTrue(); + } + @Test public void setPredefinedHistogram() { this.writer.submit("histogram.foo", 2.1); @@ -85,6 +118,18 @@ public class DropwizardMetricServicesTests { assertThat(this.registry.histogram("histogram.foo").getCount()).isEqualTo(2); } + @Test + public void setCustomReservoirHistogram() { + given(this.reservoirFactory.getReservoir(anyString())) + .willReturn(new UniformReservoir()); + this.writer.submit("histogram.foo", 2.1); + this.writer.submit("histogram.foo", 2.3); + assertThat(this.registry.histogram("histogram.foo").getCount()).isEqualTo(2); + assertThat(ReflectionTestUtils + .getField(this.registry.getMetrics().get("histogram.foo"), "reservoir") + .getClass().equals(UniformReservoir.class)).isTrue(); + } + /** * Test the case where a given writer is used amongst several threads where each * thread is updating the same set of metrics. This would be an example case of the diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java index 251601ba280..5437f5e4077 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java @@ -33,6 +33,7 @@ import org.springframework.batch.core.repository.support.JobRepositoryFactoryBea import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.util.StringUtils; /** @@ -40,6 +41,7 @@ import org.springframework.util.StringUtils; * * @author Dave Syer * @author Andy Wilkinson + * @author Kazuki Shimizu */ public class BasicBatchConfigurer implements BatchConfigurer { @@ -150,6 +152,12 @@ public class BasicBatchConfigurer implements BatchConfigurer { } protected PlatformTransactionManager createTransactionManager() { + AbstractPlatformTransactionManager transactionManager = createAppropriateTransactionManager(); + this.properties.getTransaction().applyTo(transactionManager); + return transactionManager; + } + + private AbstractPlatformTransactionManager createAppropriateTransactionManager() { if (this.entityManagerFactory != null) { return new JpaTransactionManager(this.entityManagerFactory); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java index cb9c3515c8e..2586c8621a2 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java @@ -42,6 +42,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.StringUtils; /** @@ -57,6 +58,7 @@ import org.springframework.util.StringUtils; * * @author Dave Syer * @author Eddú Meléndez + * @author Kazuki Shimizu */ @Configuration @ConditionalOnClass({ JobLauncher.class, DataSource.class, JdbcOperations.class }) @@ -133,7 +135,8 @@ public class BatchAutoConfiguration { return factory; } - @ConditionalOnClass(name = "javax.persistence.EntityManagerFactory") + @EnableConfigurationProperties(BatchProperties.class) + @ConditionalOnClass(value = PlatformTransactionManager.class, name = "javax.persistence.EntityManagerFactory") @ConditionalOnMissingBean(BatchConfigurer.class) @Configuration protected static class JpaBatchConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchProperties.java index 9582a05f347..998098416db 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchProperties.java @@ -16,7 +16,9 @@ package org.springframework.boot.autoconfigure.batch; +import org.springframework.boot.autoconfigure.transaction.TransactionProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; /** * Configuration properties for Spring Batch. @@ -46,6 +48,9 @@ public class BatchProperties { private final Job job = new Job(); + @NestedConfigurationProperty + private final TransactionProperties transaction = new TransactionProperties(); + public String getSchema() { return this.schema; } @@ -54,6 +59,14 @@ public class BatchProperties { this.schema = schema; } + public String getTablePrefix() { + return this.tablePrefix; + } + + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + public Initializer getInitializer() { return this.initializer; } @@ -62,12 +75,8 @@ public class BatchProperties { return this.job; } - public void setTablePrefix(String tablePrefix) { - this.tablePrefix = tablePrefix; - } - - public String getTablePrefix() { - return this.tablePrefix; + public TransactionProperties getTransaction() { + return this.transaction; } public class Initializer { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java index e5743e25959..f0a069b1b90 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java @@ -48,10 +48,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter * @author Josh Long * @author Vince Bickers * @author Stephane Nicoll + * @author Kazuki Shimizu * @since 1.4.0 */ @Configuration -@ConditionalOnClass(SessionFactory.class) +@ConditionalOnClass({ SessionFactory.class, PlatformTransactionManager.class }) @ConditionalOnMissingBean(SessionFactory.class) @EnableConfigurationProperties(Neo4jProperties.class) @SuppressWarnings("deprecation") @@ -87,8 +88,12 @@ public class Neo4jDataAutoConfiguration { @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) - public Neo4jTransactionManager transactionManager(SessionFactory sessionFactory) { - return new Neo4jTransactionManager(sessionFactory); + public Neo4jTransactionManager transactionManager(SessionFactory sessionFactory, + Neo4jProperties properties) { + Neo4jTransactionManager transactionManager = new Neo4jTransactionManager( + sessionFactory); + properties.getTransaction().applyTo(transactionManager); + return transactionManager; } private String[] getPackagesToScan(ApplicationContext applicationContext) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java index e58bda102b9..030b69cee67 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java @@ -23,7 +23,9 @@ import org.neo4j.ogm.config.Configuration; import org.neo4j.ogm.config.DriverConfiguration; import org.springframework.beans.BeansException; +import org.springframework.boot.autoconfigure.transaction.TransactionProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.ClassUtils; @@ -69,6 +71,9 @@ public class Neo4jProperties implements ApplicationContextAware { private final Embedded embedded = new Embedded(); + @NestedConfigurationProperty + private final TransactionProperties transaction = new TransactionProperties(); + private ClassLoader classLoader = Neo4jProperties.class.getClassLoader(); public String getUri() { @@ -107,6 +112,10 @@ public class Neo4jProperties implements ApplicationContextAware { return this.embedded; } + public TransactionProperties getTransaction() { + return this.transaction; + } + @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { this.classLoader = ctx.getClassLoader(); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index 618377f8892..1c770888d5b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.data.redis; +import java.net.URI; +import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; @@ -55,6 +57,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Eddú Meléndez * @author Stephane Nicoll + * @author Marco Aust */ @Configuration @ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }) @@ -91,10 +94,9 @@ public class RedisAutoConfiguration { protected final JedisConnectionFactory applyProperties( JedisConnectionFactory factory) { - factory.setHostName(this.properties.getHost()); - factory.setPort(this.properties.getPort()); - if (this.properties.getPassword() != null) { - factory.setPassword(this.properties.getPassword()); + configureConnection(factory); + if (this.properties.isSsl()) { + factory.setUseSsl(true); } factory.setDatabase(this.properties.getDatabase()); if (this.properties.getTimeout() > 0) { @@ -103,6 +105,43 @@ public class RedisAutoConfiguration { return factory; } + private void configureConnection(JedisConnectionFactory factory) { + if (StringUtils.hasText(this.properties.getUrl())) { + configureConnectionFromUrl(factory); + } + else { + factory.setHostName(this.properties.getHost()); + factory.setPort(this.properties.getPort()); + if (this.properties.getPassword() != null) { + factory.setPassword(this.properties.getPassword()); + } + } + } + + private void configureConnectionFromUrl(JedisConnectionFactory factory) { + String url = this.properties.getUrl(); + if (url.startsWith("rediss://")) { + factory.setUseSsl(true); + } + try { + URI uri = new URI(url); + factory.setHostName(uri.getHost()); + factory.setPort(uri.getPort()); + if (uri.getUserInfo() != null) { + String password = uri.getUserInfo(); + int index = password.lastIndexOf(":"); + if (index >= 0) { + password = password.substring(index + 1); + } + factory.setPassword(password); + } + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, + ex); + } + } + protected final RedisSentinelConfiguration getSentinelConfig() { if (this.sentinelConfiguration != null) { return this.sentinelConfiguration; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index 8c6d3ddfdfe..cd182a485d8 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -26,6 +26,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Dave Syer * @author Christoph Strobl * @author Eddú Meléndez + * @author Marco Aust */ @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { @@ -35,6 +36,11 @@ public class RedisProperties { */ private int database = 0; + /** + * Redis url, which will overrule host, port and password if set. + */ + private String url; + /** * Redis server host. */ @@ -50,6 +56,11 @@ public class RedisProperties { */ private int port = 6379; + /** + * Enable SSL. + */ + private boolean ssl; + /** * Connection timeout in milliseconds. */ @@ -69,6 +80,14 @@ public class RedisProperties { this.database = database; } + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + public String getHost() { return this.host; } @@ -93,6 +112,14 @@ public class RedisProperties { this.port = port; } + public boolean isSsl() { + return this.ssl; + } + + public void setSsl(boolean ssl) { + this.ssl = ssl; + } + public void setTimeout(int timeout) { this.timeout = timeout; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 6b79062f894..be1b5a130d1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -27,7 +27,9 @@ import javax.sql.DataSource; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.transaction.TransactionProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; @@ -157,6 +159,9 @@ public class DataSourceProperties private String uniqueName; + @NestedConfigurationProperty + private final TransactionProperties transaction = new TransactionProperties(); + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; @@ -473,6 +478,10 @@ public class DataSourceProperties this.xa = xa; } + public TransactionProperties getTransaction() { + return this.transaction; + } + /** * XA Specific datasource settings. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java index d5feaf1352c..931da19c9cf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -39,10 +40,12 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * @author Dave Syer * @author Stephane Nicoll * @author Andy Wilkinson + * @author Kazuki Shimizu */ @Configuration @ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class }) @AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) +@EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceTransactionManagerAutoConfiguration { @Configuration @@ -57,8 +60,12 @@ public class DataSourceTransactionManagerAutoConfiguration { @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) - public DataSourceTransactionManager transactionManager() { - return new DataSourceTransactionManager(this.dataSource); + public DataSourceTransactionManager transactionManager( + DataSourceProperties properties) { + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager( + this.dataSource); + properties.getTransaction().applyTo(transactionManager); + return transactionManager; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java index c6ed49f4903..d4e07744006 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java @@ -33,6 +33,7 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.io.Resource; import org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode; +import org.springframework.util.CollectionUtils; /** * Configuration properties for Spring for Apache Kafka. @@ -47,18 +48,6 @@ import org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMo @ConfigurationProperties(prefix = "spring.kafka") public class KafkaProperties { - private final Consumer consumer = new Consumer(); - - private final Producer producer = new Producer(); - - private final Listener listener = new Listener(); - - private final Template template = new Template(); - - private final Ssl ssl = new Ssl(); - - // Apache Kafka Common Properties - /** * Comma-delimited list of host:port pairs to use for establishing the initial * connection to the Kafka cluster. @@ -71,6 +60,45 @@ public class KafkaProperties { */ private String clientId; + /** + * Additional properties used to configure the client. + */ + private Map properties = new HashMap(); + + private final Consumer consumer = new Consumer(); + + private final Producer producer = new Producer(); + + private final Listener listener = new Listener(); + + private final Ssl ssl = new Ssl(); + + private final Template template = new Template(); + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + public Consumer getConsumer() { return this.consumer; } @@ -91,22 +119,6 @@ public class KafkaProperties { return this.template; } - public List getBootstrapServers() { - return this.bootstrapServers; - } - - public void setBootstrapServers(List bootstrapServers) { - this.bootstrapServers = bootstrapServers; - } - - public String getClientId() { - return this.clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - private Map buildCommonProperties() { Map properties = new HashMap(); if (this.bootstrapServers != null) { @@ -135,6 +147,9 @@ public class KafkaProperties { properties.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, this.ssl.getTruststorePassword()); } + if (!CollectionUtils.isEmpty(this.properties)) { + properties.putAll(this.properties); + } return properties; } @@ -147,9 +162,9 @@ public class KafkaProperties { * instance */ public Map buildConsumerProperties() { - Map props = buildCommonProperties(); - props.putAll(this.consumer.buildProperties()); - return props; + Map properties = buildCommonProperties(); + properties.putAll(this.consumer.buildProperties()); + return properties; } /** @@ -161,9 +176,9 @@ public class KafkaProperties { * instance */ public Map buildProducerProperties() { - Map props = buildCommonProperties(); - props.putAll(this.producer.buildProperties()); - return props; + Map properties = buildCommonProperties(); + properties.putAll(this.producer.buildProperties()); + return properties; } private static String resourceToPath(Resource resource) { @@ -240,6 +255,11 @@ public class KafkaProperties { */ private Class valueDeserializer = StringDeserializer.class; + /** + * Maximum number of records returned in a single call to poll(). + */ + private Integer maxPollRecords; + public Ssl getSsl() { return this.ssl; } @@ -332,6 +352,14 @@ public class KafkaProperties { this.valueDeserializer = valueDeserializer; } + public Integer getMaxPollRecords() { + return this.maxPollRecords; + } + + public void setMaxPollRecords(Integer maxPollRecords) { + this.maxPollRecords = maxPollRecords; + } + public Map buildProperties() { Map properties = new HashMap(); if (this.autoCommitInterval != null) { @@ -395,6 +423,10 @@ public class KafkaProperties { properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, this.valueDeserializer); } + if (this.maxPollRecords != null) { + properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, + this.maxPollRecords); + } return properties; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java index 67d1ccbd962..48f79d2ff0e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java @@ -59,6 +59,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter * @author Dave Syer * @author Oliver Gierke * @author Andy Wilkinson + * @author Kazuki Shimizu */ @EnableConfigurationProperties(JpaProperties.class) @Import(DataSourceInitializedPublisher.Registrar.class) @@ -82,7 +83,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { @Bean @ConditionalOnMissingBean(PlatformTransactionManager.class) public PlatformTransactionManager transactionManager() { - return new JpaTransactionManager(); + JpaTransactionManager transactionManager = new JpaTransactionManager(); + this.properties.getTransaction().applyTo(transactionManager); + return transactionManager; } @Bean diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java index d967e9cd274..9a81357a158 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java @@ -22,6 +22,7 @@ import java.util.Map; import javax.sql.DataSource; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.autoconfigure.transaction.TransactionProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.orm.jpa.vendor.Database; @@ -67,6 +68,9 @@ public class JpaProperties { private Hibernate hibernate = new Hibernate(); + @NestedConfigurationProperty + private final TransactionProperties transaction = new TransactionProperties(); + public Map getProperties() { return this.properties; } @@ -125,6 +129,10 @@ public class JpaProperties { return this.hibernate.getAdditionalProperties(this.properties, dataSource); } + public TransactionProperties getTransaction() { + return this.transaction; + } + public static class Hibernate { private static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id." diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java index 55e30f672e0..86866d662f4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2RestOperationsConfiguration.java @@ -16,12 +16,6 @@ package org.springframework.boot.autoconfigure.security.oauth2.client; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Qualifier; @@ -76,7 +70,7 @@ import org.springframework.util.StringUtils; public class OAuth2RestOperationsConfiguration { @Configuration - @ConditionalOnClientCredentials + @Conditional(ClientCredentialsCondition.class) protected static class SingletonScopedConfiguration { @Bean @@ -96,7 +90,7 @@ public class OAuth2RestOperationsConfiguration { @Configuration @ConditionalOnBean(OAuth2ClientConfiguration.class) - @ConditionalOnNotClientCredentials + @Conditional(NoClientCredentialsCondition.class) @Import(OAuth2ProtectedResourceDetailsConfiguration.class) protected static class SessionScopedConfiguration { @@ -126,15 +120,13 @@ public class OAuth2RestOperationsConfiguration { } - /* - * When the authentication is per cookie but the stored token is an oauth2 one, we can - * pass that on to a client that wants to call downstream. We don't even need an - * OAuth2ClientContextFilter until we need to refresh the access token. To handle - * refresh tokens you need to {@code @EnableOAuth2Client} - */ + // When the authentication is per cookie but the stored token is an oauth2 one, we can + // pass that on to a client that wants to call downstream. We don't even need an + // OAuth2ClientContextFilter until we need to refresh the access token. To handle + // refresh tokens you need to @EnableOAuth2Client @Configuration @ConditionalOnMissingBean(OAuth2ClientConfiguration.class) - @ConditionalOnNotClientCredentials + @Conditional(NoClientCredentialsCondition.class) @Import(OAuth2ProtectedResourceDetailsConfiguration.class) protected static class RequestScopedConfiguration { @@ -182,22 +174,24 @@ public class OAuth2RestOperationsConfiguration { } - @Conditional(ClientCredentialsCondition.class) - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - public static @interface ConditionalOnClientCredentials { - - } - - @Conditional(NotClientCredentialsCondition.class) - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - public static @interface ConditionalOnNotClientCredentials { + /** + * Condition to check for no client credentials. + */ + static class NoClientCredentialsCondition extends NoneNestedConditions { + + NoClientCredentialsCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @Conditional(ClientCredentialsCondition.class) + static class ClientCredentialsActivated { + } } + /** + * Condition to check for client credentials. + */ static class ClientCredentialsCondition extends AnyNestedCondition { ClientCredentialsCondition() { @@ -211,17 +205,6 @@ public class OAuth2RestOperationsConfiguration { @ConditionalOnNotWebApplication static class NoWebApplication { } - } - - static class NotClientCredentialsCondition extends NoneNestedConditions { - - NotClientCredentialsCondition() { - super(ConfigurationPhase.PARSE_CONFIGURATION); - } - - @ConditionalOnClientCredentials - static class ClientCredentialsActivated { - } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/SpringSocialTokenServices.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/SpringSocialTokenServices.java index 6cadf238c34..f4534936265 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/SpringSocialTokenServices.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/SpringSocialTokenServices.java @@ -18,9 +18,6 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource; import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; @@ -43,8 +40,6 @@ import org.springframework.social.oauth2.AccessGrant; */ public class SpringSocialTokenServices implements ResourceServerTokenServices { - protected final Log logger = LogFactory.getLog(getClass()); - private final OAuth2ConnectionFactory connectionFactory; private final String clientId; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java new file mode 100644 index 00000000000..619c368b841 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionProperties.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 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 org.springframework.boot.autoconfigure.transaction; + +import org.springframework.transaction.support.AbstractPlatformTransactionManager; + +/** + * Nested configuration properties that can be applied to an + * {@link AbstractPlatformTransactionManager}. + * + * @author Kazuki Shimizu + * @since 1.5.0 + */ +public class TransactionProperties { + + /** + * Default transaction timeout in seconds. + */ + private Integer defaultTimeout; + + /** + * Perform the rollback on commit failurures. + */ + private Boolean rollbackOnCommitFailure; + + public Integer getDefaultTimeout() { + return this.defaultTimeout; + } + + public void setDefaultTimeout(Integer defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + + public Boolean getRollbackOnCommitFailure() { + return this.rollbackOnCommitFailure; + } + + public void setRollbackOnCommitFailure(Boolean rollbackOnCommitFailure) { + this.rollbackOnCommitFailure = rollbackOnCommitFailure; + } + + /** + * Apply all transaction custom properties to the specified transaction manager. + * @param transactionManager the target transaction manager + */ + public void applyTo(AbstractPlatformTransactionManager transactionManager) { + if (this.defaultTimeout != null) { + transactionManager.setDefaultTimeout(this.defaultTimeout); + } + if (this.rollbackOnCommitFailure != null) { + transactionManager.setRollbackOnCommitFailure(this.rollbackOnCommitFailure); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/AtomikosJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/AtomikosJtaConfiguration.java index bde242ed0be..308fe36e7cf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/AtomikosJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/AtomikosJtaConfiguration.java @@ -50,10 +50,11 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll + * @author Kazuki Shimizu * @since 1.2.0 */ @Configuration -@EnableConfigurationProperties(AtomikosProperties.class) +@EnableConfigurationProperties({ AtomikosProperties.class, JtaProperties.class }) @ConditionalOnClass({ JtaTransactionManager.class, UserTransactionManager.class }) @ConditionalOnMissingBean(PlatformTransactionManager.class) class AtomikosJtaConfiguration { @@ -111,7 +112,10 @@ class AtomikosJtaConfiguration { @Bean public JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { - return new JtaTransactionManager(userTransaction, transactionManager); + JtaTransactionManager jtaTransactionManager = new JtaTransactionManager( + userTransaction, transactionManager); + this.jtaProperties.getTransaction().applyTo(jtaTransactionManager); + return jtaTransactionManager; } @Configuration diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/BitronixJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/BitronixJtaConfiguration.java index c10098ce14a..36e27bc7381 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/BitronixJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/BitronixJtaConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.boot.ApplicationHome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jta.XAConnectionFactoryWrapper; import org.springframework.boot.jta.XADataSourceWrapper; import org.springframework.boot.jta.bitronix.BitronixDependentBeanFactoryPostProcessor; @@ -46,9 +47,11 @@ import org.springframework.util.StringUtils; * @author Josh Long * @author Phillip Webb * @author Andy Wilkinson + * @author Kazuki Shimizu * @since 1.2.0 */ @Configuration +@EnableConfigurationProperties(JtaProperties.class) @ConditionalOnClass({ JtaTransactionManager.class, BitronixContext.class }) @ConditionalOnMissingBean(PlatformTransactionManager.class) class BitronixJtaConfiguration { @@ -105,7 +108,10 @@ class BitronixJtaConfiguration { @Bean public JtaTransactionManager transactionManager( TransactionManager transactionManager) { - return new JtaTransactionManager(transactionManager); + JtaTransactionManager jtaTransactionManager = new JtaTransactionManager( + transactionManager); + this.jtaProperties.getTransaction().applyTo(jtaTransactionManager); + return jtaTransactionManager; } @ConditionalOnClass(Message.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java index 12f20dd39fa..478a898a45b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JndiJtaConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.transaction.jta.JtaTransactionManager; * * @author Phillip Webb * @author Stephane Nicoll + * @author Kazuki Shimizu * @since 1.2.0 */ @Configuration @@ -40,9 +41,18 @@ import org.springframework.transaction.jta.JtaTransactionManager; @ConditionalOnMissingBean(PlatformTransactionManager.class) class JndiJtaConfiguration { + private final JtaProperties jtaProperties; + + JndiJtaConfiguration(JtaProperties jtaProperties) { + this.jtaProperties = jtaProperties; + } + @Bean public JtaTransactionManager transactionManager() { - return new JtaTransactionManagerFactoryBean().getObject(); + JtaTransactionManager transactionManager = new JtaTransactionManagerFactoryBean() + .getObject(); + this.jtaProperties.getTransaction().applyTo(transactionManager); + return transactionManager; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaProperties.java index 124bba94340..bbf63e4b53d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaProperties.java @@ -16,7 +16,9 @@ package org.springframework.boot.autoconfigure.transaction.jta; +import org.springframework.boot.autoconfigure.transaction.TransactionProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.transaction.jta.JtaTransactionManager; /** @@ -42,6 +44,9 @@ public class JtaProperties { */ private String transactionManagerId; + @NestedConfigurationProperty + private final TransactionProperties transaction = new TransactionProperties(); + public void setLogDir(String logDir) { this.logDir = logDir; } @@ -58,4 +63,8 @@ public class JtaProperties { this.transactionManagerId = transactionManagerId; } + public TransactionProperties getTransaction() { + return this.transaction; + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java index 31394d6ed93..ec971943746 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java @@ -28,6 +28,7 @@ import org.jboss.tm.XAResourceRecoveryRegistry; import org.springframework.boot.ApplicationHome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jta.XAConnectionFactoryWrapper; import org.springframework.boot.jta.XADataSourceWrapper; import org.springframework.boot.jta.narayana.NarayanaBeanFactoryPostProcessor; @@ -47,12 +48,14 @@ import org.springframework.util.StringUtils; * JTA Configuration for Narayana. * * @author Gytis Trikleris + * @author Kazuki Shimizu * @since 1.4.0 */ @Configuration @ConditionalOnClass({ JtaTransactionManager.class, com.arjuna.ats.jta.UserTransaction.class, XAResourceRecoveryRegistry.class }) @ConditionalOnMissingBean(PlatformTransactionManager.class) +@EnableConfigurationProperties(JtaProperties.class) public class NarayanaJtaConfiguration { private final JtaProperties jtaProperties; @@ -116,7 +119,10 @@ public class NarayanaJtaConfiguration { @Bean public JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { - return new JtaTransactionManager(userTransaction, transactionManager); + JtaTransactionManager jtaTransactionManager = new JtaTransactionManager( + userTransaction, transactionManager); + this.jtaProperties.getTransaction().applyTo(jtaTransactionManager); + return jtaTransactionManager; } @Bean diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java index c668df50198..327ee070ca0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java @@ -58,6 +58,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import static org.assertj.core.api.Assertions.assertThat; @@ -68,6 +70,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Dave Syer * @author Stephane Nicoll * @author Vedran Pavic + * @author Kazuki Shimizu */ public class BatchAutoConfigurationTests { @@ -267,6 +270,43 @@ public class BatchAutoConfigurationTests { .queryForList("select * from BATCH_JOB_EXECUTION"); } + @Test + public void testCustomizeJpaTransactionManagerUsingProperties() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.batch.transaction.default-timeout:30", + "spring.batch.transaction.rollback-on-commit-failure:true"); + this.context.register(TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, + HibernateJpaAutoConfiguration.class, BatchAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + this.context.getBean(BatchConfigurer.class); + JpaTransactionManager transactionManager = JpaTransactionManager.class.cast( + this.context.getBean(BatchConfigurer.class).getTransactionManager()); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + + @Test + public void testCustomizeDataSourceTransactionManagerUsingProperties() + throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.batch.transaction.default-timeout:30", + "spring.batch.transaction.rollback-on-commit-failure:true"); + this.context.register(TestConfiguration.class, + EmbeddedDataSourceConfiguration.class, BatchAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + this.context.getBean(BatchConfigurer.class); + DataSourceTransactionManager transactionManager = DataSourceTransactionManager.class + .cast(this.context.getBean(BatchConfigurer.class) + .getTransactionManager()); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + @Configuration protected static class EmptyConfiguration { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java index 25fc2b5662a..38044b74880 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.template.Neo4jOperations; +import org.springframework.data.neo4j.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -53,6 +54,7 @@ import static org.mockito.Mockito.verify; * @author Michael Hunger * @author Vince Bickers * @author Andy Wilkinson + * @author Kazuki Shimizu */ @SuppressWarnings("deprecation") public class Neo4jDataAutoConfigurationTests { @@ -73,10 +75,21 @@ public class Neo4jDataAutoConfigurationTests { .hasSize(1); assertThat(this.context.getBeansOfType(SessionFactory.class)).hasSize(1); assertThat(this.context.getBeansOfType(Neo4jOperations.class)).hasSize(1); + assertThat(this.context.getBeansOfType(Neo4jTransactionManager.class)).hasSize(1); assertThat(this.context.getBeansOfType(OpenSessionInViewInterceptor.class)) .isEmpty(); } + @Test + public void customNeo4jTransactionManagerUsingProperties() { + load(null, "spring.data.neo4j.transaction.default-timeout=30", + "spring.data.neo4j.transaction.rollback-on-commit-failure:true"); + Neo4jTransactionManager transactionManager = this.context + .getBean(Neo4jTransactionManager.class); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + @Test public void customSessionFactory() { load(CustomSessionFactory.class); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index e75f3e9e855..69be81f6dca 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Christian Dupuis * @author Christoph Strobl * @author Eddú Meléndez + * @author Marco Aust */ public class RedisAutoConfigurationTests { @@ -75,6 +76,21 @@ public class RedisAutoConfigurationTests { .isEqualTo(1); } + @Test + public void testOverrideUrlRedisConfiguration() throws Exception { + load("spring.redis.host:foo", "spring.redis.password:xyz", + "spring.redis.port:1000", "spring.redis.ssl:true", + "spring.redis.url:redis://user:password@example:33"); + assertThat(this.context.getBean(JedisConnectionFactory.class).getHostName()) + .isEqualTo("example"); + assertThat(this.context.getBean(JedisConnectionFactory.class).getPort()) + .isEqualTo(33); + assertThat(this.context.getBean(JedisConnectionFactory.class).getPassword()) + .isEqualTo("password"); + assertThat(this.context.getBean(JedisConnectionFactory.class).isUseSsl()) + .isEqualTo(true); + } + @Test public void testRedisConfigurationWithPool() throws Exception { load("spring.redis.host:foo", "spring.redis.pool.max-idle:1"); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java index 93b44a1414d..16c331edc78 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfigurationTests.java @@ -20,6 +20,7 @@ import javax.sql.DataSource; import org.junit.Test; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -106,6 +107,21 @@ public class DataSourceTransactionManagerAutoConfigurationTests { .isNotNull(); } + @Test + public void testCustomizeDataSourceTransactionManagerUsingProperties() + throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.transaction.default-timeout:30", + "spring.datasource.transaction.rollback-on-commit-failure:true"); + this.context.register(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class); + this.context.refresh(); + DataSourceTransactionManager transactionManager = this.context + .getBean(DataSourceTransactionManager.class); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + @EnableTransactionManagement protected static class SwitchTransactionsOn { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java index db3a8d8bae8..dc27e38da62 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfigurationTests.java @@ -60,13 +60,16 @@ public class KafkaAutoConfigurationTests { @Test public void consumerProperties() { - load("spring.kafka.bootstrap-servers=foo:1234", + load("spring.kafka.bootstrap-servers=foo:1234", "spring.kafka.properties.foo=bar", + "spring.kafka.properties.baz=qux", + "spring.kafka.properties.foo.bar.baz=qux.fiz.buz", "spring.kafka.ssl.key-password=p1", "spring.kafka.ssl.keystore-location=classpath:ksLoc", "spring.kafka.ssl.keystore-password=p2", "spring.kafka.ssl.truststore-location=classpath:tsLoc", "spring.kafka.ssl.truststore-password=p3", "spring.kafka.consumer.auto-commit-interval=123", + "spring.kafka.consumer.max-poll-records=42", "spring.kafka.consumer.auto-offset-reset=earliest", "spring.kafka.consumer.client-id=ccid", // test override common "spring.kafka.consumer.enable-auto-commit=false", @@ -109,6 +112,10 @@ public class KafkaAutoConfigurationTests { .isEqualTo(LongDeserializer.class); assertThat(configs.get(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG)) .isEqualTo(IntegerDeserializer.class); + assertThat(configs.get(ConsumerConfig.MAX_POLL_RECORDS_CONFIG)).isEqualTo(42); + assertThat(configs.get("foo")).isEqualTo("bar"); + assertThat(configs.get("baz")).isEqualTo("qux"); + assertThat(configs.get("foo.bar.baz")).isEqualTo("qux.fiz.buz"); } @Test diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java index 4e38cd9bba9..5727d6ceadd 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java @@ -81,20 +81,24 @@ public class AutoConfigurationReportLoggingInitializerTests { given(this.log.isDebugEnabled()).willReturn(debug); willAnswer(new Answer() { + @Override public Object answer(InvocationOnMock invocation) throws Throwable { return AutoConfigurationReportLoggingInitializerTests.this.debugLog .add(String.valueOf(invocation.getArguments()[0])); } + }).given(this.log).debug(anyObject()); given(this.log.isInfoEnabled()).willReturn(info); willAnswer(new Answer() { + @Override public Object answer(InvocationOnMock invocation) throws Throwable { return AutoConfigurationReportLoggingInitializerTests.this.infoLog .add(String.valueOf(invocation.getArguments()[0])); } + }).given(this.log).info(anyObject()); LogFactory.releaseAll(); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 1732de89d4c..be6d1c72e43 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -37,6 +37,7 @@ import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfigurati import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import static org.assertj.core.api.Assertions.assertThat; @@ -48,6 +49,7 @@ import static org.mockito.Mockito.mock; * @author Dave Syer * @author Phillip Webb * @author Andy Wilkinson + * @author Kazuki Shimizu */ public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTests { @@ -134,6 +136,19 @@ public class HibernateJpaAutoConfigurationTests .isEqualTo(TestJtaPlatform.class.getName()); } + @Test + public void testCustomJpaTransactionManagerUsingProperties() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jpa.transaction.default-timeout:30", + "spring.jpa.transaction.rollback-on-commit-failure:true"); + setupTestConfiguration(); + this.context.refresh(); + JpaTransactionManager transactionManager = this.context + .getBean(JpaTransactionManager.class); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + public static class TestJtaPlatform implements JtaPlatform { @Override diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java index 750f009b5f9..22bad3fbccf 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java @@ -21,7 +21,6 @@ import java.util.Arrays; import java.util.List; import com.fasterxml.jackson.databind.JsonNode; - import org.junit.Test; import org.springframework.aop.support.AopUtils; @@ -196,8 +195,8 @@ public class OAuth2AutoConfigurationTests { "security.oauth2.client.clientId=client", "security.oauth2.client.grantType=client_credentials"); this.context.refresh(); - assertThat(this.context.getBean(OAuth2ClientContext.class).getAccessTokenRequest()) - .isNotNull(); + OAuth2ClientContext bean = this.context.getBean(OAuth2ClientContext.class); + assertThat(bean.getAccessTokenRequest()).isNotNull(); assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1); assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(1); } @@ -211,17 +210,15 @@ public class OAuth2AutoConfigurationTests { "security.oauth2.client.clientId=client", "security.oauth2.client.grantType=client_credentials"); this.context.refresh(); - // Thr primary context is fine (not session scoped): - assertThat(this.context.getBean(OAuth2ClientContext.class).getAccessTokenRequest()) - .isNotNull(); + // The primary context is fine (not session scoped): + OAuth2ClientContext bean = this.context.getBean(OAuth2ClientContext.class); + assertThat(bean.getAccessTokenRequest()).isNotNull(); assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1); - /* - * Kind of a bug (should ideally be 1), but the cause is in Spring OAuth2 (there - * is no need for the extra session-scoped bean). What this test proves is that - * even if the user screws up and does @EnableOAuth2Client for client credentials, - * it will still just about work (because of the @Primary annotation on the - * Boot-created instance of OAuth2ClientContext). - */ + // Kind of a bug (should ideally be 1), but the cause is in Spring OAuth2 (there + // is no need for the extra session-scoped bean). What this test proves is that + // even if the user screws up and does @EnableOAuth2Client for client credentials, + // it will still just about work (because of the @Primary annotation on the + // Boot-created instance of OAuth2ClientContext). assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java index f57b84e2fa2..70239874a6c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java @@ -69,6 +69,7 @@ import static org.mockito.Mockito.mock; * @author Josh Long * @author Phillip Webb * @author Andy Wilkinson + * @author Kazuki Shimizu */ public class JtaAutoConfigurationTests { @@ -245,6 +246,34 @@ public class JtaAutoConfigurationTests { assertThat(dataSource.getMaxPoolSize()).isEqualTo(10); } + @Test + public void atomikosCustomizeJtaTransactionManagerUsingProperties() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jta.transaction.default-timeout:30", + "spring.jta.transaction.rollback-on-commit-failure:true"); + this.context.register(AtomikosJtaConfiguration.class); + this.context.refresh(); + JtaTransactionManager transactionManager = this.context + .getBean(JtaTransactionManager.class); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + + @Test + public void bitronixCustomizeJtaTransactionManagerUsingProperties() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jta.transaction.default-timeout:30", + "spring.jta.transaction.rollback-on-commit-failure:true"); + this.context.register(BitronixJtaConfiguration.class); + this.context.refresh(); + JtaTransactionManager transactionManager = this.context + .getBean(JtaTransactionManager.class); + assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30); + assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue(); + } + @Configuration @EnableConfigurationProperties(JtaProperties.class) public static class JtaPropertiesConfiguration { diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java index 3b43a450ab3..7d68419769d 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java @@ -30,6 +30,9 @@ import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.boot.devtools.settings.DevToolsSettings; import org.springframework.util.StringUtils; @@ -41,6 +44,8 @@ import org.springframework.util.StringUtils; */ final class ChangeableUrls implements Iterable { + private static final Log logger = LogFactory.getLog(ChangeableUrls.class); + private final List urls; private ChangeableUrls(URL... urls) { @@ -52,6 +57,9 @@ final class ChangeableUrls implements Iterable { reloadableUrls.add(url); } } + if (logger.isDebugEnabled()) { + logger.debug("Matching URLs for reloading : " + reloadableUrls); + } this.urls = Collections.unmodifiableList(reloadableUrls); } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java index 525ab6cd600..332bfeb7569 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/settings/DevToolsSettings.java @@ -24,6 +24,9 @@ import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.io.UrlResource; import org.springframework.core.io.support.PropertiesLoaderUtils; @@ -40,6 +43,8 @@ public class DevToolsSettings { */ public static final String SETTINGS_RESOURCE_LOCATION = "META-INF/spring-devtools.properties"; + private static final Log logger = LogFactory.getLog(DevToolsSettings.class); + private static DevToolsSettings settings; private final List restartIncludePatterns = new ArrayList(); @@ -105,6 +110,12 @@ public class DevToolsSettings { settings.add(PropertiesLoaderUtils .loadProperties(new UrlResource(urls.nextElement()))); } + if (logger.isDebugEnabled()) { + logger.debug("Included patterns for restart : " + + settings.restartIncludePatterns); + logger.debug("Excluded patterns for restart : " + + settings.restartExcludePatterns); + } return settings; } catch (Exception ex) { diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 14ffa4ca9c5..e4f96391eab 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -581,6 +581,7 @@ content into your application; rather pick only the properties that you need. spring.data.neo4j.open-in-view=false # Register OpenSessionInViewInterceptor. Binds a Neo4j Session to the thread for the entire processing of the request. spring.data.neo4j.password= # Login password of the server. spring.data.neo4j.repositories.enabled=true # Enable Neo4j repositories. + spring.data.neo4j.transaction.*= # Transaction manager settings spring.data.neo4j.uri= # URI used by the driver. Auto-detected by default. spring.data.neo4j.username= # Login user of the server. @@ -622,6 +623,7 @@ content into your application; rather pick only the properties that you need. spring.datasource.separator=; # Statement separator in SQL initialization scripts. spring.datasource.sql-script-encoding= # SQL scripts encoding. spring.datasource.tomcat.*= # Tomcat datasource specific settings + spring.datasource.transaction.*= # Transaction manager settings spring.datasource.type= # Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath. spring.datasource.url= # JDBC url of the database. spring.datasource.username= @@ -656,10 +658,12 @@ content into your application; rather pick only the properties that you need. spring.jpa.open-in-view=true # Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request. spring.jpa.properties.*= # Additional native properties to set on the JPA provider. spring.jpa.show-sql=false # Enable logging of SQL statements. + spring.jpa.transaction.*= # Transaction manager settings # JTA ({sc-spring-boot-autoconfigure}/transaction/jta/JtaAutoConfiguration.{sc-ext}[JtaAutoConfiguration]) spring.jta.enabled=true # Enable JTA support. spring.jta.log-dir= # Transaction logs directory. + spring.jta.transaction.*= # Transaction manager settings spring.jta.transaction-manager-id= # Transaction manager unique identifier. # ATOMIKOS ({sc-spring-boot}/jta/atomikos/AtomikosProperties.{sc-ext}[AtomikosProperties]) @@ -793,8 +797,10 @@ content into your application; rather pick only the properties that you need. spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster. spring.redis.cluster.nodes= # Comma-separated list of "host:port" pairs to bootstrap from. spring.redis.database=0 # Database index used by the connection factory. + spring.redis.url= # Connection URL, will override host, port and password (user will be ignored), e.g. redis://user:password@example.com:6379 spring.redis.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server. + spring.redis.ssl=false # Enable SSL support. spring.redis.pool.max-active=8 # Max number of connections that can be allocated by the pool at a given time. Use a negative value for no limit. spring.redis.pool.max-idle=8 # Max number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections. spring.redis.pool.max-wait=-1 # Maximum amount of time (in milliseconds) a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely. @@ -842,6 +848,7 @@ content into your application; rather pick only the properties that you need. spring.batch.job.names= # Comma-separated list of job names to execute on startup (For instance `job1,job2`). By default, all Jobs found in the context are executed. spring.batch.schema=classpath:org/springframework/batch/core/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema. spring.batch.table-prefix= # Table prefix for all the batch meta-data tables. + spring.batch.transaction.*= # Transaction manager settings # JMS ({sc-spring-boot-autoconfigure}/jms/JmsProperties.{sc-ext}[JmsProperties]) spring.jms.jndi-name= # Connection factory JNDI name. When set, takes precedence to others connection factory auto-configurations. @@ -871,6 +878,7 @@ content into your application; rather pick only the properties that you need. spring.kafka.consumer.group-id= # Unique string that identifies the consumer group this consumer belongs to. spring.kafka.consumer.heartbeat-interval= # Expected time in milliseconds between heartbeats to the consumer coordinator. spring.kafka.consumer.key-deserializer= # Deserializer class for keys. + spring.kafka.consumer.max-poll-messages= # Maximum number of records returned in a single call to poll(). spring.kafka.consumer.value-deserializer= # Deserializer class for values. spring.kafka.listener.ack-count= # Number of records between offset commits when ackMode is "COUNT" or "COUNT_TIME". spring.kafka.listener.ack-mode= # Listener AckMode; see the spring-kafka documentation. @@ -886,6 +894,7 @@ content into your application; rather pick only the properties that you need. spring.kafka.producer.key-serializer= # Serializer class for keys. spring.kafka.producer.retries= # When greater than zero, enables retrying of failed sends. spring.kafka.producer.value-serializer= # Serializer class for values. + spring.kafka.properties.*= # Additional properties used to configure the client. spring.kafka.ssl.key-password= # Password of the private key in the key store file. spring.kafka.ssl.keystore-location= # Location of the key store file. spring.kafka.ssl.keystore-password= # Store password for the key store file. diff --git a/spring-boot-docs/src/main/asciidoc/deployment.adoc b/spring-boot-docs/src/main/asciidoc/deployment.adoc index 714efaf4ceb..c8aa0fc6819 100644 --- a/spring-boot-docs/src/main/asciidoc/deployment.adoc +++ b/spring-boot-docs/src/main/asciidoc/deployment.adoc @@ -634,6 +634,10 @@ for Gradle and to `${project.name}` for Maven. |`useStartStopDaemon` |If the `start-stop-daemon` command, when it's available, should be used to control the process. Defaults to `true`. + +|`stopWaitTime` +|The default value for `STOP_WAIT_TIME`. Only valid for an `init.d` service. + Defaults to 60 seconds. |=== @@ -694,6 +698,10 @@ The following environment properties are supported with the default script: |`DEBUG` |if not empty will set the `-x` flag on the shell process, making it easy to see the logic in the script. + +|`STOP_WAIT_TIME` +|The time in seconds to wait when stopping the application before forcing a shutdown + (`60` by default). |=== NOTE: The `PID_FOLDER`, `LOG_FOLDER` and `LOG_FILENAME` variables are only valid for an diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 5a880451813..3973bce8f84 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4604,18 +4604,23 @@ auto configuration supports all HIGH importance properties, some selected MEDIUM and any that do not have a default value. Only a subset of the properties supported by Kafka are available via the `KafkaProperties` -class. If you wish to configure the producer or consumer with additional properties, you -can override the producer factory and/or consumer factory bean, adding additional -properties, for example: +class. If you wish to configure the producer or consumer with additional properties that +are not directly supported, use the following: + +[source,properties,indent=0] +---- + spring.kafka.properties.foo.bar=baz +---- + +This sets the common `foo.bar` Kafka property to `baz`. + +These properties will be shared by both the consumer and producer factory beans. +If you wish to customize these components with different properties, such as to use a +different metrics reader for each, you can override the bean definitions, as follows: [source,java,indent=0] ---- - @Bean - public ProducerFactory kafkaProducerFactory(KafkaProperties properties) { - Map producerProperties = properties.buildProducerProperties(); - producerProperties.put("some.property", "some.value"); - return new DefaultKafkaProducerFactory(producerProperties); - } +include::{code-examples}/kafka/KafkaSpecialProducerConsumerConfigExample.java[tag=configuration] ---- diff --git a/spring-boot-docs/src/main/java/org/springframework/boot/kafka/KafkaSpecialProducerConsumerConfigExample.java b/spring-boot-docs/src/main/java/org/springframework/boot/kafka/KafkaSpecialProducerConsumerConfigExample.java new file mode 100644 index 00000000000..23a7aa139d6 --- /dev/null +++ b/spring-boot-docs/src/main/java/org/springframework/boot/kafka/KafkaSpecialProducerConsumerConfigExample.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016-2016 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 org.springframework.boot.kafka; + +import java.util.List; +import java.util.Map; + +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.common.metrics.KafkaMetric; +import org.apache.kafka.common.metrics.MetricsReporter; + +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.ProducerFactory; + +/** + * Example custom kafka configuration beans used when the user wants to apply different + * common properties to the producer and consumer. + * + * @author Gary Russell + * @since 1.5 + */ +public class KafkaSpecialProducerConsumerConfigExample { + + // tag::configuration[] + @Configuration + public static class CustomKafkaBeans { + + /** + * Customized ProducerFactory bean. + * @param properties the kafka properties. + * @return the bean. + */ + @Bean + public ProducerFactory kafkaProducerFactory(KafkaProperties properties) { + Map producerProperties = properties.buildProducerProperties(); + producerProperties.put(CommonClientConfigs.METRIC_REPORTER_CLASSES_CONFIG, + MyProducerMetricsReporter.class); + return new DefaultKafkaProducerFactory(producerProperties); + } + + /** + * Customized ConsumerFactory bean. + * @param properties the kafka properties. + * @return the bean. + */ + @Bean + public ConsumerFactory kafkaConsumerFactory(KafkaProperties properties) { + Map consumererProperties = properties + .buildConsumerProperties(); + consumererProperties.put(CommonClientConfigs.METRIC_REPORTER_CLASSES_CONFIG, + MyConsumerMetricsReporter.class); + return new DefaultKafkaConsumerFactory(consumererProperties); + } + + } + // end::configuration[] + + public static class MyConsumerMetricsReporter implements MetricsReporter { + + @Override + public void configure(Map configs) { + } + + @Override + public void init(List metrics) { + } + + @Override + public void metricChange(KafkaMetric metric) { + } + + @Override + public void metricRemoval(KafkaMetric metric) { + } + + @Override + public void close() { + } + + } + + public static class MyProducerMetricsReporter implements MetricsReporter { + + @Override + public void configure(Map configs) { + } + + @Override + public void init(List metrics) { + } + + @Override + public void metricChange(KafkaMetric metric) { + } + + @Override + public void metricRemoval(KafkaMetric metric) { + } + + @Override + public void close() { + } + + } + +} diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script index 84cadd780af..feb9f538a70 100755 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script @@ -73,6 +73,9 @@ fi # Initialize log file name if not provided by the config file [[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="{{logFilename:${identity}.log}}" +# Initialize stop wait time if not provided by the config file +[[ -z "$STOP_WAIT_TIME" ]] && STOP_WAIT_TIME={{stopWaitTime:60}} + # ANSI Colors echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; } echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; } @@ -191,9 +194,9 @@ stop() { do_stop() { kill "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; } - for i in $(seq 1 60); do + for i in $(seq 1 $STOP_WAIT_TIME); do isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; } - [[ $i -eq 30 ]] && kill "$1" &> /dev/null + [[ $i -eq STOP_WAIT_TIME/2 ]] && kill "$1" &> /dev/null sleep 1 done echoRed "Unable to kill process $1"; diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java index 278acaa6f82..92c1012a523 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java @@ -111,6 +111,11 @@ public class DefaultLaunchScriptTests { assertThatPlaceholderCanBeReplaced("confFolder"); } + @Test + public void stopWaitTimeCanBeReplaced() throws Exception { + assertThatPlaceholderCanBeReplaced("stopWaitTime"); + } + @Test public void defaultForUseStartStopDaemonIsTrue() throws Exception { DefaultLaunchScript script = new DefaultLaunchScript(null, null); @@ -125,6 +130,13 @@ public class DefaultLaunchScriptTests { assertThat(content).contains("MODE=\"auto\""); } + @Test + public void defaultForStopWaitTimeIs60() throws Exception { + DefaultLaunchScript script = new DefaultLaunchScript(null, null); + String content = new String(script.toByteArray()); + assertThat(content).contains("STOP_WAIT_TIME=60"); + } + @Test public void loadFromFile() throws Exception { File file = this.temporaryFolder.newFile(); diff --git a/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java b/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java index c6b2133e0b6..0404b16ca33 100644 --- a/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java +++ b/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java @@ -49,7 +49,7 @@ import org.springframework.util.Assert; */ public class ImageBanner implements Banner { - private static final Log log = LogFactory.getLog(ImageBanner.class); + private static final Log logger = LogFactory.getLog(ImageBanner.class); private static final double[] RGB_WEIGHT = { 0.2126d, 0.7152d, 0.0722d }; @@ -76,9 +76,9 @@ public class ImageBanner implements Banner { printBanner(environment, out); } catch (Throwable ex) { - log.warn("Image banner not printable: " + this.image + " (" + ex.getClass() + logger.warn("Image banner not printable: " + this.image + " (" + ex.getClass() + ": '" + ex.getMessage() + "')"); - log.debug("Image banner printing failure", ex); + logger.debug("Image banner printing failure", ex); } finally { if (headless == null) { diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java index ecc7e8efd61..8dc84355ead 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java @@ -58,7 +58,8 @@ public class PropertiesConfigurationFactory private static final char[] TARGET_NAME_DELIMITERS = { '_', '.' }; - private final Log logger = LogFactory.getLog(getClass()); + private static final Log logger = LogFactory + .getLog(PropertiesConfigurationFactory.class); private boolean ignoreUnknownFields = true; @@ -228,8 +229,8 @@ public class PropertiesConfigurationFactory public void bindPropertiesToTarget() throws BindException { Assert.state(this.propertySources != null, "PropertySources should not be null"); try { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Property Sources: " + this.propertySources); + if (logger.isTraceEnabled()) { + logger.trace("Property Sources: " + this.propertySources); } this.hasBeenBound = true; @@ -239,8 +240,9 @@ public class PropertiesConfigurationFactory if (this.exceptionIfInvalid) { throw ex; } - this.logger.error("Failed to load Properties validation bean. " - + "Your Properties may be invalid.", ex); + PropertiesConfigurationFactory.logger + .error("Failed to load Properties validation bean. " + + "Your Properties may be invalid.", ex); } } @@ -340,10 +342,10 @@ public class PropertiesConfigurationFactory dataBinder.validate(); BindingResult errors = dataBinder.getBindingResult(); if (errors.hasErrors()) { - this.logger.error("Properties configuration failed validation"); + logger.error("Properties configuration failed validation"); for (ObjectError error : errors.getAllErrors()) { - this.logger - .error(this.messageSource != null + logger.error( + this.messageSource != null ? this.messageSource.getMessage(error, Locale.getDefault()) + " (" + error + ")" : error); diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/YamlConfigurationFactory.java b/spring-boot/src/main/java/org/springframework/boot/bind/YamlConfigurationFactory.java index 6ab2e551ea9..fd45de32967 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/YamlConfigurationFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/YamlConfigurationFactory.java @@ -52,7 +52,7 @@ import org.springframework.validation.Validator; public class YamlConfigurationFactory implements FactoryBean, MessageSourceAware, InitializingBean { - private final Log logger = LogFactory.getLog(getClass()); + private static final Log logger = LogFactory.getLog(YamlConfigurationFactory.class); private final Class type; @@ -137,8 +137,8 @@ public class YamlConfigurationFactory Assert.state(this.yaml != null, "Yaml document should not be null: " + "either set it directly or set the resource to load it from"); try { - if (this.logger.isTraceEnabled()) { - this.logger.trace(String.format("Yaml document is %n%s", this.yaml)); + if (logger.isTraceEnabled()) { + logger.trace(String.format("Yaml document is %n%s", this.yaml)); } Constructor constructor = new YamlJavaBeanPropertyConstructor(this.type, this.propertyAliases); @@ -151,7 +151,7 @@ public class YamlConfigurationFactory if (this.exceptionIfInvalid) { throw ex; } - this.logger.error("Failed to load YAML validation bean. " + logger.error("Failed to load YAML validation bean. " + "Your YAML file may be invalid.", ex); } } @@ -161,13 +161,9 @@ public class YamlConfigurationFactory "configuration"); this.validator.validate(this.configuration, errors); if (errors.hasErrors()) { - this.logger.error("YAML configuration failed validation"); + logger.error("YAML configuration failed validation"); for (ObjectError error : errors.getAllErrors()) { - this.logger - .error(this.messageSource != null - ? this.messageSource.getMessage(error, - Locale.getDefault()) + " (" + error + ")" - : error); + logger.error(getErrorMessage(error)); } if (this.exceptionIfInvalid) { BindException summary = new BindException(errors); @@ -176,6 +172,14 @@ public class YamlConfigurationFactory } } + private Object getErrorMessage(ObjectError error) { + if (this.messageSource != null) { + Locale locale = Locale.getDefault(); + return this.messageSource.getMessage(error, locale) + " (" + error + ")"; + } + return error; + } + @Override public Class getObjectType() { if (this.configuration == null) { diff --git a/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java b/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java index 75e19c9e075..2f9f800e910 100644 --- a/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java +++ b/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java @@ -48,7 +48,7 @@ import org.springframework.util.ReflectionUtils; */ public final class FailureAnalyzers { - private static final Log log = LogFactory.getLog(FailureAnalyzers.class); + private static final Log logger = LogFactory.getLog(FailureAnalyzers.class); private final ClassLoader classLoader; @@ -82,7 +82,7 @@ public final class FailureAnalyzers { analyzers.add((FailureAnalyzer) constructor.newInstance()); } catch (Throwable ex) { - log.trace("Failed to load " + analyzerName, ex); + logger.trace("Failed to load " + analyzerName, ex); } } AnnotationAwareOrderComparator.sort(analyzers); diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/ClasspathLoggingApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/logging/ClasspathLoggingApplicationListener.java index 3dcd654818f..e31eea40808 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/ClasspathLoggingApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/ClasspathLoggingApplicationListener.java @@ -42,17 +42,17 @@ public final class ClasspathLoggingApplicationListener private static final int ORDER = LoggingApplicationListener.DEFAULT_ORDER + 1; - private final Log logger = LogFactory.getLog(getClass()); + private static final Log logger = LogFactory + .getLog(ClasspathLoggingApplicationListener.class); @Override public void onApplicationEvent(ApplicationEvent event) { - if (this.logger.isDebugEnabled()) { + if (logger.isDebugEnabled()) { if (event instanceof ApplicationEnvironmentPreparedEvent) { - this.logger - .debug("Application started with classpath: " + getClasspath()); + logger.debug("Application started with classpath: " + getClasspath()); } else if (event instanceof ApplicationFailedEvent) { - this.logger.debug( + logger.debug( "Application failed to start with classpath: " + getClasspath()); } }