From e66745a98a3b42b3f4631a7cf3de6dbd4a5aa7a1 Mon Sep 17 00:00:00 2001 From: Patryk Kostrzewa Date: Wed, 7 Feb 2018 17:49:35 +0100 Subject: [PATCH 1/2] Improve FailureAnalyzer for embedded datasource See gh-11953 --- ...DataSourceBeanCreationFailureAnalyzer.java | 56 +++++++++++++++-- .../jdbc/DataSourceProperties.java | 61 ++++++------------- ...ourceBeanCreationFailureAnalyzerTests.java | 8 +-- .../jdbc/DataSourcePropertiesTests.java | 2 +- 4 files changed, 71 insertions(+), 56 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java index 072ea93810..ac38b0da13 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java @@ -16,26 +16,72 @@ package org.springframework.boot.autoconfigure.jdbc; +import java.util.Objects; + import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.DataSourceBeanCreationException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * An {@link AbstractFailureAnalyzer} for failures caused by a * {@link DataSourceBeanCreationException}. * * @author Andy Wilkinson + * @author Patryk Kostrzewa */ class DataSourceBeanCreationFailureAnalyzer - extends AbstractFailureAnalyzer { + extends AbstractFailureAnalyzer + implements EnvironmentAware { + + private Environment environment; @Override protected FailureAnalysis analyze(Throwable rootFailure, DataSourceBeanCreationException cause) { - String message = cause.getMessage(); - String description = message.substring(0, message.indexOf('.')).trim(); - String action = message.substring(message.indexOf('.') + 1).trim(); - return new FailureAnalysis(description, action, cause); + return getFailureAnalysis(cause); } + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + private FailureAnalysis getFailureAnalysis(DataSourceBeanCreationException cause) { + + final EmbeddedDatabaseConnection connection = cause.getConnection(); + final String action; + + if (EmbeddedDatabaseConnection.NONE == connection) { + action = "If you want an embedded database " + + "please put a supported one on the classpath."; + } + else { + action = "If you have database settings to be loaded " + + "from a particular profile you may need to activate it" + + getActiveProfiles(); + } + return new FailureAnalysis(cause.getMessage(), action, cause); + } + + private String getActiveProfiles() { + + final StringBuilder message = new StringBuilder(); + if (Objects.nonNull(this.environment)) { + String[] profiles = this.environment.getActiveProfiles(); + if (ObjectUtils.isEmpty(profiles)) { + message.append(" (no profiles are currently active)."); + } + else { + message.append(" (the profiles "); + message.append(StringUtils.arrayToCommaDelimitedString(profiles)); + message.append(" are currently active)."); + } + } + return message.toString(); + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 63ff671b9f..81651f3d30 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -32,11 +32,8 @@ import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DataSourceInitializationMode; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -47,16 +44,15 @@ import org.springframework.util.StringUtils; * @author Stephane Nicoll * @author Benedikt Ritter * @author Eddú Meléndez + * @author Patryk Kostrzewa * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.datasource") public class DataSourceProperties - implements BeanClassLoaderAware, EnvironmentAware, InitializingBean { + implements BeanClassLoaderAware, InitializingBean { private ClassLoader classLoader; - private Environment environment; - /** * Name of the datasource. Default to "testdb" when using an embedded database. */ @@ -166,11 +162,6 @@ public class DataSourceProperties this.classLoader = classLoader; } - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - @Override public void afterPropertiesSet() throws Exception { this.embeddedDatabaseConnection = EmbeddedDatabaseConnection @@ -244,8 +235,9 @@ public class DataSourceProperties driverClassName = this.embeddedDatabaseConnection.getDriverClassName(); } if (!StringUtils.hasText(driverClassName)) { - throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection, - this.environment, "driver class"); + throw new DataSourceBeanCreationException( + "Failed to determine a suitable driver class", + this.embeddedDatabaseConnection); } return driverClassName; } @@ -290,8 +282,9 @@ public class DataSourceProperties String url = (databaseName == null ? null : this.embeddedDatabaseConnection.getUrl(databaseName)); if (!StringUtils.hasText(url)) { - throw new DataSourceBeanCreationException(this.embeddedDatabaseConnection, - this.environment, "url"); + throw new DataSourceBeanCreationException( + "Failed to determine suitable jdbc url", + this.embeddedDatabaseConnection); } return url; } @@ -522,37 +515,17 @@ public class DataSourceProperties static class DataSourceBeanCreationException extends BeanCreationException { - DataSourceBeanCreationException(EmbeddedDatabaseConnection connection, - Environment environment, String property) { - super(getMessage(connection, environment, property)); + private final EmbeddedDatabaseConnection connection; + + DataSourceBeanCreationException(String message, + EmbeddedDatabaseConnection connection) { + + super(message); + this.connection = connection; } - private static String getMessage(EmbeddedDatabaseConnection connection, - Environment environment, String property) { - StringBuilder message = new StringBuilder(); - message.append("Cannot determine embedded database " + property - + " for database type " + connection + ". "); - message.append("If you want an embedded database please put a supported " - + "one on the classpath. "); - message.append("If you have database settings to be loaded from a " - + "particular profile you may need to active it"); - if (environment != null) { - String[] profiles = environment.getActiveProfiles(); - if (ObjectUtils.isEmpty(profiles)) { - message.append(" (no profiles are currently active)"); - } - else { - message.append(" (the profiles \"" - + StringUtils.arrayToCommaDelimitedString( - environment.getActiveProfiles()) - + "\" are currently active)"); - - } - } - message.append("."); - return message.toString(); + public EmbeddedDatabaseConnection getConnection() { + return this.connection; } - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java index 081d53ef68..a3bce95f31 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java @@ -41,12 +41,8 @@ public class DataSourceBeanCreationFailureAnalyzerTests { @Test public void failureAnalysisIsPerformed() { FailureAnalysis failureAnalysis = performAnalysis(TestConfiguration.class); - assertThat(failureAnalysis.getDescription()).isEqualTo( - "Cannot determine embedded database driver class for database type NONE"); - assertThat(failureAnalysis.getAction()).isEqualTo("If you want an embedded " - + "database please put a supported one on the classpath. If you have " - + "database settings to be loaded from a particular profile you may " - + "need to active it (no profiles are currently active)."); + assertThat(failureAnalysis.getDescription()).isEqualTo("Failed to determine a suitable driver class"); + assertThat(failureAnalysis.getAction()).isEqualTo("If you want an embedded database please put a supported one on the classpath."); } private FailureAnalysis performAnalysis(Class configuration) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java index f0d8dcbb9c..d49dc76dd3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourcePropertiesTests.java @@ -72,7 +72,7 @@ public class DataSourcePropertiesTests { new FilteredClassLoader("org.h2", "org.apache.derby", "org.hsqldb")); properties.afterPropertiesSet(); this.thrown.expect(DataSourceProperties.DataSourceBeanCreationException.class); - this.thrown.expectMessage("Cannot determine embedded database url"); + this.thrown.expectMessage("Failed to determine suitable jdbc url"); properties.determineUrl(); } From 2f13449b261828a32d2b4a3c217aa225dde44340 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 12 Feb 2018 11:09:18 +0100 Subject: [PATCH 2/2] Polish "Improve FailureAnalyzer for embedded datasource" Closes gh-11953 --- ...DataSourceBeanCreationFailureAnalyzer.java | 64 ++++++++++--------- .../jdbc/DataSourceProperties.java | 4 +- ...ourceBeanCreationFailureAnalyzerTests.java | 33 ++++++++-- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java index ac38b0da13..c6121e12f3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.jdbc; -import java.util.Objects; - import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.DataSourceBeanCreationException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; @@ -33,6 +31,7 @@ import org.springframework.util.StringUtils; * * @author Andy Wilkinson * @author Patryk Kostrzewa + * @author Stephane Nicoll */ class DataSourceBeanCreationFailureAnalyzer extends AbstractFailureAnalyzer @@ -40,48 +39,55 @@ class DataSourceBeanCreationFailureAnalyzer private Environment environment; + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + @Override protected FailureAnalysis analyze(Throwable rootFailure, DataSourceBeanCreationException cause) { return getFailureAnalysis(cause); } - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - private FailureAnalysis getFailureAnalysis(DataSourceBeanCreationException cause) { + StringBuilder description = new StringBuilder(); + boolean datasourceUrlSpecified = this.environment.containsProperty( + "spring.datasource.url"); + description.append("Failed to auto-configure a DataSource: "); + if (!datasourceUrlSpecified) { + description.append("'spring.datasource.url' is not specified and "); + } + description.append(String.format( + "no embedded datasource could be auto-configured.%n")); + description.append(String.format("%nReason: %s%n", cause.getMessage())); - final EmbeddedDatabaseConnection connection = cause.getConnection(); - final String action; - - if (EmbeddedDatabaseConnection.NONE == connection) { - action = "If you want an embedded database " - + "please put a supported one on the classpath."; + StringBuilder action = new StringBuilder(); + action.append(String.format("Consider the following:%n")); + if (EmbeddedDatabaseConnection.NONE == cause.getConnection()) { + action.append(String.format("\tIf you want an embedded database (H2, HSQL or " + + "Derby), please put it on the classpath.%n")); } else { - action = "If you have database settings to be loaded " - + "from a particular profile you may need to activate it" - + getActiveProfiles(); + action.append(String.format("\tReview the configuration of %s%n.", cause.getConnection())); } - return new FailureAnalysis(cause.getMessage(), action, cause); + action.append("\tIf you have database settings to be loaded from a particular " + + "profile you may need to activate it").append(getActiveProfiles()); + return new FailureAnalysis(description.toString(), action.toString(), cause); } private String getActiveProfiles() { - - final StringBuilder message = new StringBuilder(); - if (Objects.nonNull(this.environment)) { - String[] profiles = this.environment.getActiveProfiles(); - if (ObjectUtils.isEmpty(profiles)) { - message.append(" (no profiles are currently active)."); - } - else { - message.append(" (the profiles "); - message.append(StringUtils.arrayToCommaDelimitedString(profiles)); - message.append(" are currently active)."); - } + StringBuilder message = new StringBuilder(); + String[] profiles = this.environment.getActiveProfiles(); + if (ObjectUtils.isEmpty(profiles)) { + message.append(" (no profiles are currently active)."); + } + else { + message.append(" (the profiles "); + message.append(StringUtils.arrayToCommaDelimitedString(profiles)); + message.append(" are currently active)."); } return message.toString(); } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 81651f3d30..b09c421e6f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -44,7 +44,6 @@ import org.springframework.util.StringUtils; * @author Stephane Nicoll * @author Benedikt Ritter * @author Eddú Meléndez - * @author Patryk Kostrzewa * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.datasource") @@ -519,7 +518,6 @@ public class DataSourceProperties DataSourceBeanCreationException(String message, EmbeddedDatabaseConnection connection) { - super(message); this.connection = connection; } @@ -527,5 +525,7 @@ public class DataSourceProperties public EmbeddedDatabaseConnection getConnection() { return this.connection; } + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java index a3bce95f31..a8d382b256 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -26,6 +26,7 @@ import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -33,27 +34,49 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link DataSourceBeanCreationFailureAnalyzer}. * * @author Andy Wilkinson + * @author Stephane Nicoll */ @RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions({ "h2-*.jar", "hsqldb-*.jar" }) public class DataSourceBeanCreationFailureAnalyzerTests { + private final MockEnvironment environment = new MockEnvironment(); + @Test public void failureAnalysisIsPerformed() { FailureAnalysis failureAnalysis = performAnalysis(TestConfiguration.class); - assertThat(failureAnalysis.getDescription()).isEqualTo("Failed to determine a suitable driver class"); - assertThat(failureAnalysis.getAction()).isEqualTo("If you want an embedded database please put a supported one on the classpath."); + assertThat(failureAnalysis.getDescription()).contains( + "'spring.datasource.url' is not specified", + "no embedded datasource could be auto-configured", + "Failed to determine a suitable driver class"); + assertThat(failureAnalysis.getAction()).contains( + "If you want an embedded database (H2, HSQL or Derby), please put it on the classpath", + "If you have database settings to be loaded from a particular profile you may need to activate it", + "(no profiles are currently active)"); + } + + @Test + public void failureAnalysisIsPerformedWithActiveProfiles() { + this.environment.setActiveProfiles("first", "second"); + FailureAnalysis failureAnalysis = performAnalysis(TestConfiguration.class); + assertThat(failureAnalysis.getAction()).contains( + "(the profiles first,¬second are currently active)"); } private FailureAnalysis performAnalysis(Class configuration) { BeanCreationException failure = createFailure(configuration); assertThat(failure).isNotNull(); - return new DataSourceBeanCreationFailureAnalyzer().analyze(failure); + DataSourceBeanCreationFailureAnalyzer failureAnalyzer = new DataSourceBeanCreationFailureAnalyzer(); + failureAnalyzer.setEnvironment(this.environment); + return failureAnalyzer.analyze(failure); } private BeanCreationException createFailure(Class configuration) { try { - new AnnotationConfigApplicationContext(configuration).close(); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.setEnvironment(this.environment); + context.register(configuration); + context.refresh(); return null; } catch (BeanCreationException ex) {