1
1
/*
2
- * Copyright 2012-2023 the original author or authors.
2
+ * Copyright 2012-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
17
17
package org .springframework .boot .actuate .autoconfigure .jdbc ;
18
18
19
+ import java .sql .SQLException ;
19
20
import java .util .HashMap ;
20
21
import java .util .Map ;
21
22
22
23
import javax .sql .DataSource ;
23
24
24
25
import org .junit .jupiter .api .Test ;
25
26
27
+ import org .springframework .beans .BeansException ;
28
+ import org .springframework .beans .factory .config .BeanPostProcessor ;
26
29
import org .springframework .boot .actuate .autoconfigure .health .HealthContributorAutoConfiguration ;
27
30
import org .springframework .boot .actuate .autoconfigure .jdbc .DataSourceHealthContributorAutoConfiguration .RoutingDataSourceHealthContributor ;
28
31
import org .springframework .boot .actuate .health .CompositeHealthContributor ;
@@ -87,6 +90,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource() {
87
90
});
88
91
}
89
92
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
+
90
106
@ Test
91
107
void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
92
108
this .contextRunner .withUserConfiguration (EmbeddedDataSourceConfiguration .class , RoutingDataSourceConfig .class )
@@ -98,6 +114,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgn
98
114
});
99
115
}
100
116
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
+
101
130
@ Test
102
131
void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
103
132
this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class ).run ((context ) -> {
@@ -112,6 +141,23 @@ void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndic
112
141
});
113
142
}
114
143
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
+
115
161
@ Test
116
162
void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored () {
117
163
this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class )
@@ -121,6 +167,15 @@ void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored() {
121
167
.hasRootCauseInstanceOf (IllegalArgumentException .class ));
122
168
}
123
169
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
+
124
179
@ Test
125
180
void runWithValidationQueryPropertyShouldUseCustomQuery () {
126
181
this .contextRunner
@@ -177,26 +232,55 @@ DataSource testDataSource() {
177
232
static class RoutingDataSourceConfig {
178
233
179
234
@ Bean
180
- AbstractRoutingDataSource routingDataSource () {
235
+ AbstractRoutingDataSource routingDataSource () throws SQLException {
181
236
Map <Object , DataSource > dataSources = new HashMap <>();
182
237
dataSources .put ("one" , mock (DataSource .class ));
183
238
dataSources .put ("two" , mock (DataSource .class ));
184
239
AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
240
+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
241
+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
185
242
given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
186
243
return routingDataSource ;
187
244
}
188
245
189
246
}
190
247
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
+
191
273
@ Configuration (proxyBeanMethods = false )
192
274
static class NullKeyRoutingDataSourceConfig {
193
275
194
276
@ Bean
195
- AbstractRoutingDataSource routingDataSource () {
277
+ AbstractRoutingDataSource routingDataSource () throws Exception {
196
278
Map <Object , DataSource > dataSources = new HashMap <>();
197
279
dataSources .put (null , mock (DataSource .class ));
198
280
dataSources .put ("one" , mock (DataSource .class ));
199
281
AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
282
+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
283
+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
200
284
given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
201
285
return routingDataSource ;
202
286
}
0 commit comments