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 .io .PrintWriter ;
20+ import java .sql .Connection ;
21+ import java .sql .ConnectionBuilder ;
22+ import java .sql .SQLException ;
23+ import java .sql .SQLFeatureNotSupportedException ;
24+ import java .sql .ShardingKeyBuilder ;
1925import java .util .HashMap ;
2026import java .util .Map ;
27+ import java .util .logging .Logger ;
2128
2229import javax .sql .DataSource ;
2330
2431import org .junit .jupiter .api .Test ;
2532
33+ import org .springframework .beans .BeansException ;
34+ import org .springframework .beans .factory .config .BeanPostProcessor ;
2635import org .springframework .boot .actuate .autoconfigure .health .HealthContributorAutoConfiguration ;
2736import org .springframework .boot .actuate .autoconfigure .jdbc .DataSourceHealthContributorAutoConfiguration .RoutingDataSourceHealthContributor ;
2837import org .springframework .boot .actuate .health .CompositeHealthContributor ;
@@ -87,6 +96,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource() {
8796 });
8897 }
8998
99+ @ Test
100+ void runWithProxyBeanPostProcessorRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource () {
101+ this .contextRunner
102+ .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , EmbeddedDataSourceConfiguration .class ,
103+ RoutingDataSourceConfig .class )
104+ .run ((context ) -> {
105+ CompositeHealthContributor composite = context .getBean (CompositeHealthContributor .class );
106+ assertThat (composite .getContributor ("dataSource" )).isInstanceOf (DataSourceHealthIndicator .class );
107+ assertThat (composite .getContributor ("routingDataSource" ))
108+ .isInstanceOf (RoutingDataSourceHealthContributor .class );
109+ });
110+ }
111+
90112 @ Test
91113 void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
92114 this .contextRunner .withUserConfiguration (EmbeddedDataSourceConfiguration .class , RoutingDataSourceConfig .class )
@@ -98,6 +120,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgn
98120 });
99121 }
100122
123+ @ Test
124+ void runWithProxyBeanPostProcessorAndRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
125+ this .contextRunner
126+ .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , EmbeddedDataSourceConfiguration .class ,
127+ RoutingDataSourceConfig .class )
128+ .withPropertyValues ("management.health.db.ignore-routing-datasources:true" )
129+ .run ((context ) -> {
130+ assertThat (context ).doesNotHaveBean (CompositeHealthContributor .class );
131+ assertThat (context ).hasSingleBean (DataSourceHealthIndicator .class );
132+ assertThat (context ).doesNotHaveBean (RoutingDataSourceHealthContributor .class );
133+ });
134+ }
135+
101136 @ Test
102137 void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
103138 this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class ).run ((context ) -> {
@@ -112,6 +147,23 @@ void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndic
112147 });
113148 }
114149
150+ @ Test
151+ void runWithProxyBeanPostProcessorAndRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
152+ this .contextRunner .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , RoutingDataSourceConfig .class )
153+ .run ((context ) -> {
154+ assertThat (context ).hasSingleBean (RoutingDataSourceHealthContributor .class );
155+ RoutingDataSourceHealthContributor routingHealthContributor = context
156+ .getBean (RoutingDataSourceHealthContributor .class );
157+ assertThat (routingHealthContributor .getContributor ("one" ))
158+ .isInstanceOf (DataSourceHealthIndicator .class );
159+ assertThat (routingHealthContributor .getContributor ("two" ))
160+ .isInstanceOf (DataSourceHealthIndicator .class );
161+ assertThat (routingHealthContributor .iterator ()).toIterable ()
162+ .extracting ("name" )
163+ .containsExactlyInAnyOrder ("one" , "two" );
164+ });
165+ }
166+
115167 @ Test
116168 void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored () {
117169 this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class )
@@ -121,6 +173,15 @@ void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored() {
121173 .hasRootCauseInstanceOf (IllegalArgumentException .class ));
122174 }
123175
176+ @ Test
177+ void runWithProxyBeanPostProcessorAndOnlyRoutingDataSourceShouldCrashWhenIgnored () {
178+ this .contextRunner .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , RoutingDataSourceConfig .class )
179+ .withPropertyValues ("management.health.db.ignore-routing-datasources:true" )
180+ .run ((context ) -> assertThat (context ).hasFailed ()
181+ .getFailure ()
182+ .hasRootCauseInstanceOf (IllegalArgumentException .class ));
183+ }
184+
124185 @ Test
125186 void runWithValidationQueryPropertyShouldUseCustomQuery () {
126187 this .contextRunner
@@ -177,30 +238,112 @@ DataSource testDataSource() {
177238 static class RoutingDataSourceConfig {
178239
179240 @ Bean
180- AbstractRoutingDataSource routingDataSource () {
241+ AbstractRoutingDataSource routingDataSource () throws SQLException {
181242 Map <Object , DataSource > dataSources = new HashMap <>();
182243 dataSources .put ("one" , mock (DataSource .class ));
183244 dataSources .put ("two" , mock (DataSource .class ));
184245 AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
246+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
247+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
185248 given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
186249 return routingDataSource ;
187250 }
188251
189252 }
190253
254+ static class ProxyDataSourceBeanPostProcessor implements BeanPostProcessor {
255+
256+ @ Override
257+ public Object postProcessBeforeInitialization (Object bean , String beanName ) throws BeansException {
258+ if (bean instanceof DataSource dataSource ) {
259+ return new ProxyDataSource (dataSource );
260+ }
261+ return bean ;
262+ }
263+
264+ }
265+
191266 @ Configuration (proxyBeanMethods = false )
192267 static class NullKeyRoutingDataSourceConfig {
193268
194269 @ Bean
195- AbstractRoutingDataSource routingDataSource () {
270+ AbstractRoutingDataSource routingDataSource () throws Exception {
196271 Map <Object , DataSource > dataSources = new HashMap <>();
197272 dataSources .put (null , mock (DataSource .class ));
198273 dataSources .put ("one" , mock (DataSource .class ));
199274 AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
275+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
276+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
200277 given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
201278 return routingDataSource ;
202279 }
203280
204281 }
205282
283+ static class ProxyDataSource implements DataSource {
284+
285+ private final DataSource dataSource ;
286+
287+ ProxyDataSource (DataSource dataSource ) {
288+ this .dataSource = dataSource ;
289+ }
290+
291+ @ Override
292+ public void setLogWriter (PrintWriter out ) throws SQLException {
293+ this .dataSource .setLogWriter (out );
294+ }
295+
296+ @ Override
297+ public Connection getConnection () throws SQLException {
298+ return this .dataSource .getConnection ();
299+ }
300+
301+ @ Override
302+ public Connection getConnection (String username , String password ) throws SQLException {
303+ return this .dataSource .getConnection (username , password );
304+ }
305+
306+ @ Override
307+ public PrintWriter getLogWriter () throws SQLException {
308+ return this .dataSource .getLogWriter ();
309+ }
310+
311+ @ Override
312+ public void setLoginTimeout (int seconds ) throws SQLException {
313+ this .dataSource .setLoginTimeout (seconds );
314+ }
315+
316+ @ Override
317+ public int getLoginTimeout () throws SQLException {
318+ return this .dataSource .getLoginTimeout ();
319+ }
320+
321+ @ Override
322+ public ConnectionBuilder createConnectionBuilder () throws SQLException {
323+ return this .dataSource .createConnectionBuilder ();
324+ }
325+
326+ @ Override
327+ public Logger getParentLogger () throws SQLFeatureNotSupportedException {
328+ return this .dataSource .getParentLogger ();
329+ }
330+
331+ @ Override
332+ public ShardingKeyBuilder createShardingKeyBuilder () throws SQLException {
333+ return this .dataSource .createShardingKeyBuilder ();
334+ }
335+
336+ @ Override
337+ @ SuppressWarnings ("unchecked" )
338+ public <T > T unwrap (Class <T > iface ) throws SQLException {
339+ return iface .isInstance (this ) ? (T ) this : this .dataSource .unwrap (iface );
340+ }
341+
342+ @ Override
343+ public boolean isWrapperFor (Class <?> iface ) throws SQLException {
344+ return (iface .isInstance (this ) || this .dataSource .isWrapperFor (iface ));
345+ }
346+
347+ }
348+
206349}
0 commit comments