11/*
2- * Copyright 2012-2023 the original author or authors.
2+ * Copyright 2012-2024 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package org .springframework .boot .actuate .autoconfigure .jdbc ;
1818
19+ import java .sql .SQLException ;
1920import java .util .HashMap ;
2021import java .util .Map ;
2122
2223import javax .sql .DataSource ;
2324
2425import org .junit .jupiter .api .Test ;
2526
27+ import org .springframework .beans .BeansException ;
28+ import org .springframework .beans .factory .config .BeanPostProcessor ;
2629import org .springframework .boot .actuate .autoconfigure .health .HealthContributorAutoConfiguration ;
2730import org .springframework .boot .actuate .autoconfigure .jdbc .DataSourceHealthContributorAutoConfiguration .RoutingDataSourceHealthContributor ;
2831import org .springframework .boot .actuate .health .CompositeHealthContributor ;
@@ -87,6 +90,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource() {
8790 });
8891 }
8992
93+ @ Test
94+ void runWithProxyBeanPostProcessorRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource () {
95+ this .contextRunner
96+ .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , EmbeddedDataSourceConfiguration .class ,
97+ RoutingDataSourceConfig .class )
98+ .run ((context ) -> {
99+ CompositeHealthContributor composite = context .getBean (CompositeHealthContributor .class );
100+ assertThat (composite .getContributor ("dataSource" )).isInstanceOf (DataSourceHealthIndicator .class );
101+ assertThat (composite .getContributor ("routingDataSource" ))
102+ .isInstanceOf (RoutingDataSourceHealthContributor .class );
103+ });
104+ }
105+
90106 @ Test
91107 void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
92108 this .contextRunner .withUserConfiguration (EmbeddedDataSourceConfiguration .class , RoutingDataSourceConfig .class )
@@ -98,6 +114,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgn
98114 });
99115 }
100116
117+ @ Test
118+ void runWithProxyBeanPostProcessorAndRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
119+ this .contextRunner
120+ .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , EmbeddedDataSourceConfiguration .class ,
121+ RoutingDataSourceConfig .class )
122+ .withPropertyValues ("management.health.db.ignore-routing-datasources:true" )
123+ .run ((context ) -> {
124+ assertThat (context ).doesNotHaveBean (CompositeHealthContributor .class );
125+ assertThat (context ).hasSingleBean (DataSourceHealthIndicator .class );
126+ assertThat (context ).doesNotHaveBean (RoutingDataSourceHealthContributor .class );
127+ });
128+ }
129+
101130 @ Test
102131 void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
103132 this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class ).run ((context ) -> {
@@ -112,6 +141,23 @@ void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndic
112141 });
113142 }
114143
144+ @ Test
145+ void runWithProxyBeanPostProcessorAndRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
146+ this .contextRunner .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , RoutingDataSourceConfig .class )
147+ .run ((context ) -> {
148+ assertThat (context ).hasSingleBean (RoutingDataSourceHealthContributor .class );
149+ RoutingDataSourceHealthContributor routingHealthContributor = context
150+ .getBean (RoutingDataSourceHealthContributor .class );
151+ assertThat (routingHealthContributor .getContributor ("one" ))
152+ .isInstanceOf (DataSourceHealthIndicator .class );
153+ assertThat (routingHealthContributor .getContributor ("two" ))
154+ .isInstanceOf (DataSourceHealthIndicator .class );
155+ assertThat (routingHealthContributor .iterator ()).toIterable ()
156+ .extracting ("name" )
157+ .containsExactlyInAnyOrder ("one" , "two" );
158+ });
159+ }
160+
115161 @ Test
116162 void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored () {
117163 this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class )
@@ -121,6 +167,15 @@ void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored() {
121167 .hasRootCauseInstanceOf (IllegalArgumentException .class ));
122168 }
123169
170+ @ Test
171+ void runWithProxyBeanPostProcessorAndOnlyRoutingDataSourceShouldCrashWhenIgnored () {
172+ this .contextRunner .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , RoutingDataSourceConfig .class )
173+ .withPropertyValues ("management.health.db.ignore-routing-datasources:true" )
174+ .run ((context ) -> assertThat (context ).hasFailed ()
175+ .getFailure ()
176+ .hasRootCauseInstanceOf (IllegalArgumentException .class ));
177+ }
178+
124179 @ Test
125180 void runWithValidationQueryPropertyShouldUseCustomQuery () {
126181 this .contextRunner
@@ -177,26 +232,55 @@ DataSource testDataSource() {
177232 static class RoutingDataSourceConfig {
178233
179234 @ Bean
180- AbstractRoutingDataSource routingDataSource () {
235+ AbstractRoutingDataSource routingDataSource () throws SQLException {
181236 Map <Object , DataSource > dataSources = new HashMap <>();
182237 dataSources .put ("one" , mock (DataSource .class ));
183238 dataSources .put ("two" , mock (DataSource .class ));
184239 AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
240+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
241+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
185242 given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
186243 return routingDataSource ;
187244 }
188245
189246 }
190247
248+ static class ProxyDataSourceBeanPostProcessor implements BeanPostProcessor {
249+
250+ @ Override
251+ public Object postProcessBeforeInitialization (Object bean , String beanName ) throws BeansException {
252+ if (bean instanceof DataSource dataSource ) {
253+ return proxyDataSource (dataSource );
254+ }
255+ return bean ;
256+ }
257+
258+ private static DataSource proxyDataSource (DataSource dataSource ) {
259+ try {
260+ DataSource mock = mock (DataSource .class );
261+ given (mock .isWrapperFor (AbstractRoutingDataSource .class ))
262+ .willReturn (dataSource instanceof AbstractRoutingDataSource );
263+ given (mock .unwrap (AbstractRoutingDataSource .class )).willAnswer ((invocation ) -> dataSource );
264+ return mock ;
265+ }
266+ catch (SQLException ex ) {
267+ throw new IllegalStateException (ex );
268+ }
269+ }
270+
271+ }
272+
191273 @ Configuration (proxyBeanMethods = false )
192274 static class NullKeyRoutingDataSourceConfig {
193275
194276 @ Bean
195- AbstractRoutingDataSource routingDataSource () {
277+ AbstractRoutingDataSource routingDataSource () throws Exception {
196278 Map <Object , DataSource > dataSources = new HashMap <>();
197279 dataSources .put (null , mock (DataSource .class ));
198280 dataSources .put ("one" , mock (DataSource .class ));
199281 AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
282+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
283+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
200284 given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
201285 return routingDataSource ;
202286 }
0 commit comments