Skip to content

Commit

Permalink
Merge branch '3.3.x'
Browse files Browse the repository at this point in the history
Closes gh-43359
  • Loading branch information
philwebb committed Dec 3, 2024
2 parents dd64b06 + b340c85 commit 91778e9
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2024 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.
Expand Down Expand Up @@ -46,6 +46,7 @@
* @author Andy Wilkinson
* @author Marten Deinum
* @author Stephane Nicoll
* @author Phillip Webb
* @since 1.3.0
*/
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
Expand All @@ -57,46 +58,25 @@ public class H2ConsoleAutoConfiguration {

private static final Log logger = LogFactory.getLog(H2ConsoleAutoConfiguration.class);

private final H2ConsoleProperties properties;

H2ConsoleAutoConfiguration(H2ConsoleProperties properties) {
this.properties = properties;
}

@Bean
public ServletRegistrationBean<JakartaWebServlet> h2Console(H2ConsoleProperties properties,
ObjectProvider<DataSource> dataSource) {
String path = properties.getPath();
public ServletRegistrationBean<JakartaWebServlet> h2Console() {
String path = this.properties.getPath();
String urlMapping = path + (path.endsWith("/") ? "*" : "/*");
ServletRegistrationBean<JakartaWebServlet> registration = new ServletRegistrationBean<>(new JakartaWebServlet(),
urlMapping);
configureH2ConsoleSettings(registration, properties.getSettings());
if (logger.isInfoEnabled()) {
withThreadContextClassLoader(getClass().getClassLoader(), () -> logDataSources(dataSource, path));
}
configureH2ConsoleSettings(registration, this.properties.getSettings());
return registration;
}

private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) {
ClassLoader previous = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
action.run();
}
finally {
Thread.currentThread().setContextClassLoader(previous);
}
}

private void logDataSources(ObjectProvider<DataSource> dataSource, String path) {
List<String> urls = dataSource.orderedStream().map(this::getConnectionUrl).filter(Objects::nonNull).toList();
if (!urls.isEmpty()) {
logger.info(LogMessage.format("H2 console available at '%s'. %s available at %s", path,
(urls.size() > 1) ? "Databases" : "Database", String.join(", ", urls)));
}
}

private String getConnectionUrl(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
return "'" + connection.getMetaData().getURL() + "'";
}
catch (Exception ex) {
return null;
}
@Bean
H2ConsoleLogger h2ConsoleLogger(ObjectProvider<DataSource> dataSource) {
return new H2ConsoleLogger(dataSource, this.properties.getPath());
}

private void configureH2ConsoleSettings(ServletRegistrationBean<JakartaWebServlet> registration,
Expand All @@ -112,4 +92,46 @@ private void configureH2ConsoleSettings(ServletRegistrationBean<JakartaWebServle
}
}

static class H2ConsoleLogger {

H2ConsoleLogger(ObjectProvider<DataSource> dataSources, String path) {
if (logger.isInfoEnabled()) {
ClassLoader classLoader = getClass().getClassLoader();
withThreadContextClassLoader(classLoader, () -> log(getConnectionUrls(dataSources), path));
}
}

private void withThreadContextClassLoader(ClassLoader classLoader, Runnable action) {
ClassLoader previous = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classLoader);
action.run();
}
finally {
Thread.currentThread().setContextClassLoader(previous);
}
}

private List<String> getConnectionUrls(ObjectProvider<DataSource> dataSources) {
return dataSources.orderedStream().map(this::getConnectionUrl).filter(Objects::nonNull).toList();
}

private String getConnectionUrl(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
return "'" + connection.getMetaData().getURL() + "'";
}
catch (Exception ex) {
return null;
}
}

private void log(List<String> urls, String path) {
if (!urls.isEmpty()) {
logger.info(LogMessage.format("H2 console available at '%s'. %s available at %s", path,
(urls.size() > 1) ? "Databases" : "Database", String.join(", ", urls)));
}
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
Expand Down Expand Up @@ -30,15 +30,20 @@
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
Expand All @@ -51,6 +56,7 @@
* @author Marten Deinum
* @author Stephane Nicoll
* @author Shraddha Yeole
* @author Phillip Webb
*/
class H2ConsoleAutoConfigurationTests {

Expand Down Expand Up @@ -163,6 +169,22 @@ void h2ConsoleShouldNotFailIfDatabaseConnectionFails() {
.run((context) -> assertThat(context.isRunning()).isTrue());
}

@Test
@ExtendWith(OutputCaptureExtension.class)
void dataSourceIsNotInitializedEarly(CapturedOutput output) {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(H2ConsoleAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(EarlyInitializationConfiguration.class)
.withPropertyValues("spring.h2.console.enabled=true", "server.port=0")
.run((context) -> {
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
assertThat(output).contains("H2 console available at '/h2-console'. Database available at '"
+ connection.getMetaData().getURL() + "'");
}
});
}

@Configuration(proxyBeanMethods = false)
static class FailingDataSourceConfiguration {

Expand Down Expand Up @@ -206,4 +228,15 @@ private DataSource mockDataSource(String url) throws SQLException {

}

@Configuration(proxyBeanMethods = false)
static class EarlyInitializationConfiguration {

@Bean
DataSource dataSource(ConfigurableApplicationContext applicationContext) {
assertThat(applicationContext.getBeanFactory().isConfigurationFrozen()).isTrue();
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}

}

}

0 comments on commit 91778e9

Please sign in to comment.