diff --git a/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java b/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java new file mode 100644 index 00000000000..985e9daf8db --- /dev/null +++ b/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.mail.autoconfigure; + +import org.jspecify.annotations.Nullable; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; +import org.springframework.mail.MailSender; + +/** + * An {@link AbstractFailureAnalyzer} that improves missing {@link MailSender} guidance + * when mail auto-configuration is present but not activated. + */ +class NoSuchMailSenderBeanFailureAnalyzer extends AbstractFailureAnalyzer + implements Ordered { + + private static final String MAIL_HOST_PROPERTY = "spring.mail.host"; + + private static final String MAIL_JNDI_NAME_PROPERTY = "spring.mail.jndi-name"; + + private final @Nullable Environment environment; + + NoSuchMailSenderBeanFailureAnalyzer(@Nullable Environment environment) { + this.environment = environment; + } + + @Override + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) { + if (!isMissingMailSenderBean(cause) || hasMailConfigurationProperty()) { + return null; + } + String description = "A MailSender bean could not be found because Spring Boot mail auto-configuration " + + "did not match. Neither '" + MAIL_HOST_PROPERTY + "' nor '" + MAIL_JNDI_NAME_PROPERTY + + "' is configured."; + String action = "Consider configuring '" + MAIL_HOST_PROPERTY + "' or '" + MAIL_JNDI_NAME_PROPERTY + + "' to enable auto-configuration. If you want to use a custom mail sender, define a MailSender " + + "bean in your configuration."; + return new FailureAnalysis(description, action, cause); + } + + private boolean isMissingMailSenderBean(NoSuchBeanDefinitionException cause) { + Class beanType = cause.getBeanType(); + if (beanType == null && cause.getResolvableType() != null) { + beanType = cause.getResolvableType().resolve(); + } + return (beanType != null) && MailSender.class.isAssignableFrom(beanType); + } + + private boolean hasMailConfigurationProperty() { + return this.environment != null && (this.environment.containsProperty(MAIL_HOST_PROPERTY) + || this.environment.containsProperty(MAIL_JNDI_NAME_PROPERTY)); + } + + @Override + public int getOrder() { + return 0; + } + +} diff --git a/module/spring-boot-mail/src/main/resources/META-INF/spring.factories b/module/spring-boot-mail/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..bfd0e1dd108 --- /dev/null +++ b/module/spring-boot-mail/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Failure Analyzers +org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.boot.mail.autoconfigure.NoSuchMailSenderBeanFailureAnalyzer diff --git a/module/spring-boot-mail/src/test/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzerTests.java b/module/spring-boot-mail/src/test/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzerTests.java new file mode 100644 index 00000000000..b3b03121fb1 --- /dev/null +++ b/module/spring-boot-mail/src/test/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzerTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.mail.autoconfigure; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.core.env.Environment; +import org.springframework.mail.MailSender; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NoSuchMailSenderBeanFailureAnalyzer}. + */ +class NoSuchMailSenderBeanFailureAnalyzerTests { + + @Test + void analyzeWhenNotNoSuchBeanDefinitionExceptionShouldReturnNull() { + assertThat(new NoSuchMailSenderBeanFailureAnalyzer(null).analyze(new Exception())).isNull(); + } + + @Test + void analyzeWhenNoSuchBeanDefinitionExceptionForDifferentTypeShouldReturnNull() { + assertThat( + new NoSuchMailSenderBeanFailureAnalyzer(null).analyze(new NoSuchBeanDefinitionException(String.class))) + .isNull(); + } + + @Test + void analyzeWhenMailHostPropertyIsConfiguredShouldReturnNull() { + Environment environment = new MockEnvironment().withProperty("spring.mail.host", "smtp.example.org"); + assertThat(new NoSuchMailSenderBeanFailureAnalyzer(environment) + .analyze(new NoSuchBeanDefinitionException(MailSender.class))).isNull(); + } + + @Test + void analyzeWhenMailJndiNamePropertyIsConfiguredShouldReturnNull() { + Environment environment = new MockEnvironment().withProperty("spring.mail.jndi-name", "mail/Session"); + assertThat(new NoSuchMailSenderBeanFailureAnalyzer(environment) + .analyze(new NoSuchBeanDefinitionException(MailSender.class))).isNull(); + } + + @Test + void analyzeWhenMailSenderBeanIsMissingAndNoMailPropertiesAreConfiguredShouldProvideGuidance() { + FailureAnalysis analysis = new NoSuchMailSenderBeanFailureAnalyzer(new MockEnvironment()) + .analyze(new NoSuchBeanDefinitionException(MailSender.class)); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()) + .contains("A MailSender bean could not be found") + .contains("spring.mail.host") + .contains("spring.mail.jndi-name"); + assertThat(analysis.getAction()) + .contains("spring.mail.host") + .contains("spring.mail.jndi-name") + .contains("MailSender bean"); + } + + @Test + void analyzeWhenJavaMailSenderBeanIsMissingAndNoMailPropertiesAreConfiguredShouldProvideGuidance() { + assertThat(new NoSuchMailSenderBeanFailureAnalyzer(new MockEnvironment()) + .analyze(new NoSuchBeanDefinitionException(JavaMailSender.class))).isNotNull(); + } + +}