diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 94e4f24333..861567a67f 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -75,6 +75,11 @@ jmustache true + + com.sun.mail + javax.mail + true + javax.cache cache-api diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/JndiSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/JndiSessionConfiguration.java new file mode 100644 index 0000000000..fda10ceb8e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/JndiSessionConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2015 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 + * + * http://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.autoconfigure.mail; + +import javax.mail.Session; +import javax.naming.NamingException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jndi.JndiLocatorDelegate; + +/** + * Auto-configure a {@link Session} available on JNDI. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + * @since 1.3.0 + */ +@Configuration +@ConditionalOnClass(Session.class) +@ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name") +@ConditionalOnJndi +class JndiSessionConfiguration { + + @Autowired + private MailProperties properties; + + @Bean + @ConditionalOnMissingBean + public Session session() { + try { + return new JndiLocatorDelegate() + .lookup(this.properties.getJndiName(), Session.class); + } + catch (NamingException e) { + throw new IllegalStateException(String.format( + "Unable to find Session in JNDI location %s", this.properties.getJndiName())); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java index 62928d78d7..7245af925d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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.context.properties.ConfigurationProperties; * * @author Oliver Gierke * @author Stephane Nicoll + * @author Eddú Meléndez * @since 1.2.0 */ @ConfigurationProperties(prefix = "spring.mail") @@ -61,6 +62,11 @@ public class MailProperties { */ private Map properties = new HashMap(); + /** + * Session JNDI name. When set, takes precedence to others mail settings. + */ + private String jndiName; + public String getHost() { return this.host; } @@ -105,4 +111,11 @@ public class MailProperties { return this.properties; } + public void setJndiName(String jndiName) { + this.jndiName = jndiName; + } + + public String getJndiName() { + return this.jndiName; + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfiguration.java index bc9ad71139..72f39e38f0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfiguration.java @@ -17,18 +17,22 @@ package org.springframework.boot.autoconfigure.mail; import java.util.Properties; - import javax.activation.MimeType; +import javax.mail.Session; import javax.mail.internet.MimeMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration.MailSenderCondition; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.mail.MailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; @@ -37,34 +41,64 @@ import org.springframework.mail.javamail.JavaMailSenderImpl; * * @author Oliver Gierke * @author Stephane Nicoll + * @author Eddú Meléndez * @since 1.2.0 */ @Configuration @ConditionalOnClass({ MimeMessage.class, MimeType.class }) -@ConditionalOnProperty(prefix = "spring.mail", value = "host") @ConditionalOnMissingBean(MailSender.class) +@Conditional(MailSenderCondition.class) @EnableConfigurationProperties(MailProperties.class) +@Import(JndiSessionConfiguration.class) public class MailSenderAutoConfiguration { + @Autowired(required = false) + private Session session; + @Autowired private MailProperties properties; @Bean public JavaMailSenderImpl mailSender() { JavaMailSenderImpl sender = new JavaMailSenderImpl(); - sender.setHost(this.properties.getHost()); - if (this.properties.getPort() != null) { - sender.setPort(this.properties.getPort()); + if (this.session != null) { + sender.setSession(this.session); } - sender.setUsername(this.properties.getUsername()); - sender.setPassword(this.properties.getPassword()); - sender.setDefaultEncoding(this.properties.getDefaultEncoding()); - if (!this.properties.getProperties().isEmpty()) { - Properties properties = new Properties(); - properties.putAll(this.properties.getProperties()); - sender.setJavaMailProperties(properties); + else { + sender.setHost(this.properties.getHost()); + if (this.properties.getPort() != null) { + sender.setPort(this.properties.getPort()); + } + sender.setUsername(this.properties.getUsername()); + sender.setPassword(this.properties.getPassword()); + sender.setDefaultEncoding(this.properties.getDefaultEncoding()); + if (!this.properties.getProperties().isEmpty()) { + Properties properties = new Properties(); + properties.putAll(this.properties.getProperties()); + sender.setJavaMailProperties(properties); + } } return sender; } + /** + * Condition to trigger the creation of a {@link JavaMailSenderImpl}. This kicks in + * if either the host or jndi name property is set. + */ + static class MailSenderCondition extends AnyNestedCondition { + + public MailSenderCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnProperty(prefix = "spring.mail", name = "host") + static class HostProperty { + } + + @ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name") + static class JndiNameProperty { + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java index 29bc020a8b..3ab252b8a2 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mail/MailSenderAutoConfigurationTests.java @@ -17,9 +17,13 @@ package org.springframework.boot.autoconfigure.mail; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.autoconfigure.jndi.JndiPropertiesHidingClassLoader; +import org.springframework.boot.autoconfigure.jndi.TestableInitialContextFactory; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -27,26 +31,60 @@ import org.springframework.context.annotation.Configuration; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; +import javax.mail.Session; +import javax.naming.Context; +import javax.naming.NamingException; + +import java.util.Properties; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; /** * Tests for {@link MailSenderAutoConfiguration}. * * @author Stephane Nicoll + * @author Eddú Meléndez */ public class MailSenderAutoConfigurationTests { @Rule public ExpectedException thrown = ExpectedException.none(); + private ClassLoader threadContextClassLoader; + + private String initialContextFactory; + private AnnotationConfigApplicationContext context; + @Before + public void setupJndi() { + this.initialContextFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY); + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, + TestableInitialContextFactory.class.getName()); + } + + @Before + public void setupThreadContextClassLoader() { + this.threadContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader( + new JndiPropertiesHidingClassLoader(getClass().getClassLoader())); + } + @After public void close() { + TestableInitialContextFactory.clearAll(); + if (this.initialContextFactory != null) { + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, + this.initialContextFactory); + } else { + System.clearProperty(Context.INITIAL_CONTEXT_FACTORY); + } if (this.context != null) { this.context.close(); } + Thread.currentThread().setContextClassLoader(this.threadContextClassLoader); } @Test @@ -98,6 +136,52 @@ public class MailSenderAutoConfigurationTests { assertNull(bean.getPassword()); } + @Test + public void jndiSessionAvailable() throws NamingException { + Session session = configureJndiSession("foo"); + load(EmptyConfig.class, "spring.mail.jndi-name:foo"); + + Session sessionBean = this.context.getBean(Session.class); + assertEquals(session, sessionBean); + assertEquals(sessionBean, this.context.getBean(JavaMailSenderImpl.class).getSession()); + } + + @Test + public void jndiSessionIgnoredIfJndiNameNotSet() throws NamingException { + configureJndiSession("foo"); + + load(EmptyConfig.class, "spring.mail.host:smtp.acme.org"); + + assertEquals(0, this.context.getBeanNamesForType(Session.class).length); + assertNotNull(this.context.getBean(JavaMailSender.class)); + } + + @Test + public void jndiSessionNotUsedIfJndiNameNotSet() throws NamingException { + configureJndiSession("foo"); + + load(EmptyConfig.class); + + assertEquals(0, this.context.getBeanNamesForType(Session.class).length); + assertEquals(0, this.context.getBeanNamesForType(JavaMailSender.class).length); + } + + @Test + public void jndiSessionNotAvailableWithJndiName() throws NamingException { + thrown.expect(BeanCreationException.class); + thrown.expectMessage("Unable to find Session in JNDI location foo"); + + load(EmptyConfig.class, "spring.mail.jndi-name:foo"); + } + + private Session configureJndiSession(String name) + throws IllegalStateException, NamingException { + Properties properties = new Properties(); + Session session = Session.getDefaultInstance(properties); + TestableInitialContextFactory.bind(name, session); + return session; + } + private void load(Class config, String... environment) { this.context = doLoad(new Class[] { config }, environment); } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index e76d577cac..375a0c79b8 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -486,6 +486,7 @@ content into your application; rather pick only the properties that you need. spring.mail.password= spring.mail.default-encoding=UTF-8 # encoding to use for MimeMessages spring.mail.properties.*= # properties to set on the JavaMail session + spring.mail.jndi-name= # JNDI location of a Mail Session # SPRING BATCH ({sc-spring-boot-autoconfigure}/batch/BatchProperties.{sc-ext}[BatchProperties]) spring.batch.job.names=job1,job2