diff --git a/spring-context-support/src/main/java/org/springframework/mail/MailSender.java b/spring-context-support/src/main/java/org/springframework/mail/MailSender.java index 6fd8265f59c2..3beee8e78588 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/MailSender.java +++ b/spring-context-support/src/main/java/org/springframework/mail/MailSender.java @@ -38,7 +38,9 @@ public interface MailSender { * @throws MailAuthenticationException in case of authentication failure * @throws MailSendException in case of failure when sending the message */ - void send(SimpleMailMessage simpleMessage) throws MailException; + default void send(SimpleMailMessage simpleMessage) throws MailException { + send(new SimpleMailMessage[] {simpleMessage}); + } /** * Send the given array of simple mail messages in batch. diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSender.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSender.java index f810be606dd8..1369a8476166 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSender.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSender.java @@ -17,11 +17,17 @@ package org.springframework.mail.javamail; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import org.springframework.mail.MailException; +import org.springframework.mail.MailParseException; +import org.springframework.mail.MailPreparationException; import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; /** * Extended {@link org.springframework.mail.MailSender} interface for JavaMail, @@ -82,6 +88,26 @@ public interface JavaMailSender extends MailSender { */ MimeMessage createMimeMessage(InputStream contentStream) throws MailException; + + //--------------------------------------------------------------------- + // Implementation of MailSender + //--------------------------------------------------------------------- + + @Override + default void send(SimpleMailMessage... simpleMessages) throws MailException { + List mimeMessages = new ArrayList<>(simpleMessages.length); + for (SimpleMailMessage simpleMessage : simpleMessages) { + MimeMailMessage message = new MimeMailMessage(createMimeMessage()); + simpleMessage.copyTo(message); + MimeMessage mimeMessage = message.getMimeMessage(); + if (mimeMessage instanceof SmartMimeMessage) { + ((SmartMimeMessage) mimeMessage).setOriginalSimpleMailMessage(simpleMessage); + } + mimeMessages.add(mimeMessage); + } + send(mimeMessages.toArray(new MimeMessage[0])); + } + /** * Send the given JavaMail MIME message. * The message needs to have been created with {@link #createMimeMessage()}. @@ -92,7 +118,9 @@ public interface JavaMailSender extends MailSender { * in case of failure when sending the message * @see #createMimeMessage */ - void send(MimeMessage mimeMessage) throws MailException; + default void send(MimeMessage mimeMessage) throws MailException { + send(new MimeMessage[] {mimeMessage}); + } /** * Send the given array of JavaMail MIME messages in batch. @@ -121,7 +149,9 @@ public interface JavaMailSender extends MailSender { * @throws org.springframework.mail.MailSendException * in case of failure when sending the message */ - void send(MimeMessagePreparator mimeMessagePreparator) throws MailException; + default void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { + send(new MimeMessagePreparator[] {mimeMessagePreparator}); + } /** * Send the JavaMail MIME messages prepared by the given MimeMessagePreparators. @@ -138,6 +168,25 @@ public interface JavaMailSender extends MailSender { * @throws org.springframework.mail.MailSendException * in case of failure when sending a message */ - void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException; + default void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException { + try { + List mimeMessages = new ArrayList<>(mimeMessagePreparators.length); + for (MimeMessagePreparator preparator : mimeMessagePreparators) { + MimeMessage mimeMessage = createMimeMessage(); + preparator.prepare(mimeMessage); + mimeMessages.add(mimeMessage); + } + send(mimeMessages.toArray(new MimeMessage[0])); + } + catch (MailException ex) { + throw ex; + } + catch (MessagingException ex) { + throw new MailParseException(ex); + } + catch (Exception ex) { + throw new MailPreparationException(ex); + } + } } diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderDecorator.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderDecorator.java new file mode 100644 index 000000000000..d0b61b78b7a4 --- /dev/null +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderDecorator.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2019 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.mail.javamail; + +import java.io.InputStream; + +import javax.mail.internet.MimeMessage; + +import org.springframework.mail.MailException; +import org.springframework.mail.SimpleMailMessage; + + +/** + * A decorator pattern facility for a {@link JavaMailSender}, where the sending + * operation can be customized (e.g. implement a whitelist, feature-toggle, etc) + * by overriding {@link JavaMailSenderDecorator#send(MimeMessage...)}. + * Any deriving class may also override the methods to + * {@link #createMimeMessage() create messages}, if applicable, but this is not + * a requirement. + * + *

Implementation note

+ * When overriding {@link #send(MimeMessage...)}, the decorated + * {@code JavaMailSender} can be accessed as {@code super.}{@link #decoratedMailSender}. + * + * @author Rune Flobakk + */ +public abstract class JavaMailSenderDecorator implements JavaMailSender { + + /** + * The decorated {@link JavaMailSender} which may (or not) be delegated to + * by {@link #send(MimeMessage...)}. + */ + protected final JavaMailSender decoratedMailSender; + + public JavaMailSenderDecorator(JavaMailSender decoratedMailSender) { + this.decoratedMailSender = decoratedMailSender; + } + + @Override + public MimeMessage createMimeMessage() { + return this.decoratedMailSender.createMimeMessage(); + } + + @Override + public MimeMessage createMimeMessage(InputStream contentStream) throws MailException { + return this.decoratedMailSender.createMimeMessage(contentStream); + } + + @Override + public final void send(SimpleMailMessage simpleMessage) throws MailException { + JavaMailSender.super.send(simpleMessage); + } + + @Override + public final void send(SimpleMailMessage... simpleMessages) throws MailException { + JavaMailSender.super.send(simpleMessages); + } + + @Override + public final void send(MimeMessage mimeMessage) throws MailException { + JavaMailSender.super.send(mimeMessage); + } + + @Override + public final void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { + JavaMailSender.super.send(mimeMessagePreparator); + } + + @Override + public final void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException { + JavaMailSender.super.send(mimeMessagePreparators); + } + +} diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderImpl.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderImpl.java index 9286d0ef665a..75c06e372bd7 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderImpl.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/JavaMailSenderImpl.java @@ -17,10 +17,8 @@ package org.springframework.mail.javamail; import java.io.InputStream; -import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -37,7 +35,6 @@ import org.springframework.mail.MailAuthenticationException; import org.springframework.mail.MailException; import org.springframework.mail.MailParseException; -import org.springframework.mail.MailPreparationException; import org.springframework.mail.MailSendException; import org.springframework.mail.SimpleMailMessage; import org.springframework.util.Assert; @@ -303,27 +300,6 @@ public FileTypeMap getDefaultFileTypeMap() { } - //--------------------------------------------------------------------- - // Implementation of MailSender - //--------------------------------------------------------------------- - - @Override - public void send(SimpleMailMessage simpleMessage) throws MailException { - send(new SimpleMailMessage[] {simpleMessage}); - } - - @Override - public void send(SimpleMailMessage... simpleMessages) throws MailException { - List mimeMessages = new ArrayList<>(simpleMessages.length); - for (SimpleMailMessage simpleMessage : simpleMessages) { - MimeMailMessage message = new MimeMailMessage(createMimeMessage()); - simpleMessage.copyTo(message); - mimeMessages.add(message.getMimeMessage()); - } - doSend(mimeMessages.toArray(new MimeMessage[0]), simpleMessages); - } - - //--------------------------------------------------------------------- // Implementation of JavaMailSender //--------------------------------------------------------------------- @@ -351,43 +327,11 @@ public MimeMessage createMimeMessage(InputStream contentStream) throws MailExcep } } - @Override - public void send(MimeMessage mimeMessage) throws MailException { - send(new MimeMessage[] {mimeMessage}); - } - @Override public void send(MimeMessage... mimeMessages) throws MailException { doSend(mimeMessages, null); } - @Override - public void send(MimeMessagePreparator mimeMessagePreparator) throws MailException { - send(new MimeMessagePreparator[] {mimeMessagePreparator}); - } - - @Override - public void send(MimeMessagePreparator... mimeMessagePreparators) throws MailException { - try { - List mimeMessages = new ArrayList<>(mimeMessagePreparators.length); - for (MimeMessagePreparator preparator : mimeMessagePreparators) { - MimeMessage mimeMessage = createMimeMessage(); - preparator.prepare(mimeMessage); - mimeMessages.add(mimeMessage); - } - send(mimeMessages.toArray(new MimeMessage[0])); - } - catch (MailException ex) { - throw ex; - } - catch (MessagingException ex) { - throw new MailParseException(ex); - } - catch (Exception ex) { - throw new MailPreparationException(ex); - } - } - /** * Validate that this instance can connect to the server that it is configured * for. Throws a {@link MessagingException} if the connection attempt failed. @@ -442,7 +386,7 @@ protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMes catch (Exception ex) { // Effectively, all remaining messages failed... for (int j = i; j < mimeMessages.length; j++) { - Object original = (originalMessages != null ? originalMessages[j] : mimeMessages[j]); + Object original = (originalMessages != null ? originalMessages[j] : resolveOriginal(mimeMessages[j])); failedMessages.put(original, ex); } throw new MailSendException("Mail server connection failed", ex, failedMessages); @@ -465,7 +409,7 @@ protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMes transport.sendMessage(mimeMessage, (addresses != null ? addresses : new Address[0])); } catch (Exception ex) { - Object original = (originalMessages != null ? originalMessages[i] : mimeMessage); + Object original = (originalMessages != null ? originalMessages[i] : resolveOriginal(mimeMessage)); failedMessages.put(original, ex); } } @@ -492,6 +436,10 @@ protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMes } } + private static Object resolveOriginal(MimeMessage mimeMessage) { + return mimeMessage instanceof SmartMimeMessage ? ((SmartMimeMessage) mimeMessage).getOriginalOrElseThis() : mimeMessage; + } + /** * Obtain and connect a Transport from the underlying JavaMail Session, * passing in the specified host, port, username, and password. diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/SmartMimeMessage.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/SmartMimeMessage.java index 2fa44c910af9..b1c9074735fd 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/SmartMimeMessage.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/SmartMimeMessage.java @@ -21,6 +21,7 @@ import javax.mail.internet.MimeMessage; import org.springframework.lang.Nullable; +import org.springframework.mail.SimpleMailMessage; /** * Special subclass of the standard JavaMail {@link MimeMessage}, carrying a @@ -45,6 +46,9 @@ class SmartMimeMessage extends MimeMessage { @Nullable private final FileTypeMap defaultFileTypeMap; + @Nullable + private SimpleMailMessage originalMessage; + /** * Create a new SmartMimeMessage. @@ -60,6 +64,13 @@ public SmartMimeMessage( this.defaultFileTypeMap = defaultFileTypeMap; } + void setOriginalSimpleMailMessage(@Nullable SimpleMailMessage originalMessage) { + this.originalMessage = originalMessage; + } + + Object getOriginalOrElseThis() { + return this.originalMessage != null ? this.originalMessage : this; + } /** * Return the default encoding of this message, or {@code null} if none. diff --git a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java index 3bd467b9106e..e607f314d2a9 100644 --- a/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java +++ b/spring-context-support/src/test/java/org/springframework/mail/javamail/JavaMailSenderTests.java @@ -38,14 +38,22 @@ import org.junit.jupiter.api.Test; +import org.springframework.mail.MailException; import org.springframework.mail.MailParseException; import org.springframework.mail.MailSendException; import org.springframework.mail.SimpleMailMessage; import org.springframework.util.ObjectUtils; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Answers.RETURNS_DEFAULTS; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.withSettings; /** * @author Juergen Hoeller @@ -486,6 +494,55 @@ public void failedMimeMessage() throws MessagingException { } } + @Test + public void decorateAnotherJavaMailSenderPipesEverySendInvocationToMimeMessageVarargMethod() { + JavaMailSender mailSender = mock(JavaMailSenderDecorator.class, withSettings().lenient() + .defaultAnswer(i -> MimeMessage.class.isAssignableFrom(i.getMethod().getReturnType()) ? new MimeMessage((Session) null) : RETURNS_DEFAULTS.answer(i))); + + mailSender.send(new SimpleMailMessage()); + verify(mailSender, times(1)).send((MimeMessage[]) notNull()); + + mailSender.send(new SimpleMailMessage(), new SimpleMailMessage()); + verify(mailSender, times(1)).send((MimeMessage) notNull(), notNull()); + + MimeMessage mimeMessage = mailSender.createMimeMessage(); + mailSender.send(mimeMessage); + verify(mailSender, times(1)).send(new MimeMessage[] {mimeMessage}); + + MimeMessage[] preparedMessage = new MimeMessage[1]; + mailSender.send(m -> preparedMessage[0] = m); + verify(mailSender, times(1)).send(preparedMessage); + + MimeMessage[] preparedMessages = new MimeMessage[2]; + mailSender.send(m1 -> preparedMessages[0] = m1, m2 -> preparedMessages[1] = m2); + verify(mailSender, times(1)).send(preparedMessages); + } + + @Test + public void decorateExistingMailSender() { + MockJavaMailSender mockSender = new MockJavaMailSender(); + mockSender.setHost("host"); + + List sent = new ArrayList<>(); + JavaMailSenderDecorator decoratedMailSender = new JavaMailSenderDecorator(mockSender) { + @Override + public void send(MimeMessage ... mimeMessages) throws MailException { + sent.addAll(asList(mimeMessages)); + decoratedMailSender.send(mimeMessages); + } + }; + + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo("you@mail.org"); + decoratedMailSender.send(message); + assertThat(sent) + .hasSize(1) + .hasOnlyOneElementSatisfying(m -> + assertThat(message.getTo()).hasOnlyOneElementSatisfying(to -> assertThat(to).isEqualTo("you@mail.org"))); + } + + + @Test public void testConnection() throws MessagingException { MockJavaMailSender sender = new MockJavaMailSender();