001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.mail;
018
019import java.io.PrintStream;
020import java.io.PrintWriter;
021import java.util.LinkedHashMap;
022import java.util.Map;
023
024import org.springframework.lang.Nullable;
025import org.springframework.util.ObjectUtils;
026
027/**
028 * Exception thrown when a mail sending error is encountered.
029 * Can register failed messages with their exceptions.
030 *
031 * @author Dmitriy Kopylenko
032 * @author Juergen Hoeller
033 */
034@SuppressWarnings("serial")
035public class MailSendException extends MailException {
036
037        private final transient Map<Object, Exception> failedMessages;
038
039        @Nullable
040        private final Exception[] messageExceptions;
041
042
043        /**
044         * Constructor for MailSendException.
045         * @param msg the detail message
046         */
047        public MailSendException(String msg) {
048                this(msg, null);
049        }
050
051        /**
052         * Constructor for MailSendException.
053         * @param msg the detail message
054         * @param cause the root cause from the mail API in use
055         */
056        public MailSendException(String msg, @Nullable Throwable cause) {
057                super(msg, cause);
058                this.failedMessages = new LinkedHashMap<>();
059                this.messageExceptions = null;
060        }
061
062        /**
063         * Constructor for registration of failed messages, with the
064         * messages that failed as keys, and the thrown exceptions as values.
065         * <p>The messages should be the same that were originally passed
066         * to the invoked send method.
067         * @param msg the detail message
068         * @param cause the root cause from the mail API in use
069         * @param failedMessages a Map of failed messages as keys and thrown
070         * exceptions as values
071         */
072        public MailSendException(@Nullable String msg, @Nullable Throwable cause, Map<Object, Exception> failedMessages) {
073                super(msg, cause);
074                this.failedMessages = new LinkedHashMap<>(failedMessages);
075                this.messageExceptions = failedMessages.values().toArray(new Exception[0]);
076        }
077
078        /**
079         * Constructor for registration of failed messages, with the
080         * messages that failed as keys, and the thrown exceptions as values.
081         * <p>The messages should be the same that were originally passed
082         * to the invoked send method.
083         * @param failedMessages a Map of failed messages as keys and thrown
084         * exceptions as values
085         */
086        public MailSendException(Map<Object, Exception> failedMessages) {
087                this(null, null, failedMessages);
088        }
089
090
091        /**
092         * Return a Map with the failed messages as keys, and the thrown exceptions
093         * as values.
094         * <p>Note that a general mail server connection failure will not result
095         * in failed messages being returned here: A message will only be
096         * contained here if actually sending it was attempted but failed.
097         * <p>The messages will be the same that were originally passed to the
098         * invoked send method, that is, SimpleMailMessages in case of using
099         * the generic MailSender interface.
100         * <p>In case of sending MimeMessage instances via JavaMailSender,
101         * the messages will be of type MimeMessage.
102         * <p><b>NOTE:</b> This Map will not be available after serialization.
103         * Use {@link #getMessageExceptions()} in such a scenario, which will
104         * be available after serialization as well.
105         * @return the Map of failed messages as keys and thrown exceptions as values
106         * @see SimpleMailMessage
107         * @see javax.mail.internet.MimeMessage
108         */
109        public final Map<Object, Exception> getFailedMessages() {
110                return this.failedMessages;
111        }
112
113        /**
114         * Return an array with thrown message exceptions.
115         * <p>Note that a general mail server connection failure will not result
116         * in failed messages being returned here: A message will only be
117         * contained here if actually sending it was attempted but failed.
118         * @return the array of thrown message exceptions,
119         * or an empty array if no failed messages
120         */
121        public final Exception[] getMessageExceptions() {
122                return (this.messageExceptions != null ? this.messageExceptions : new Exception[0]);
123        }
124
125
126        @Override
127        @Nullable
128        public String getMessage() {
129                if (ObjectUtils.isEmpty(this.messageExceptions)) {
130                        return super.getMessage();
131                }
132                else {
133                        StringBuilder sb = new StringBuilder();
134                        String baseMessage = super.getMessage();
135                        if (baseMessage != null) {
136                                sb.append(baseMessage).append(". ");
137                        }
138                        sb.append("Failed messages: ");
139                        for (int i = 0; i < this.messageExceptions.length; i++) {
140                                Exception subEx = this.messageExceptions[i];
141                                sb.append(subEx.toString());
142                                if (i < this.messageExceptions.length - 1) {
143                                        sb.append("; ");
144                                }
145                        }
146                        return sb.toString();
147                }
148        }
149
150        @Override
151        public String toString() {
152                if (ObjectUtils.isEmpty(this.messageExceptions)) {
153                        return super.toString();
154                }
155                else {
156                        StringBuilder sb = new StringBuilder(super.toString());
157                        sb.append("; message exceptions (").append(this.messageExceptions.length).append(") are:");
158                        for (int i = 0; i < this.messageExceptions.length; i++) {
159                                Exception subEx = this.messageExceptions[i];
160                                sb.append('\n').append("Failed message ").append(i + 1).append(": ");
161                                sb.append(subEx);
162                        }
163                        return sb.toString();
164                }
165        }
166
167        @Override
168        public void printStackTrace(PrintStream ps) {
169                if (ObjectUtils.isEmpty(this.messageExceptions)) {
170                        super.printStackTrace(ps);
171                }
172                else {
173                        ps.println(super.toString() + "; message exception details (" +
174                                        this.messageExceptions.length + ") are:");
175                        for (int i = 0; i < this.messageExceptions.length; i++) {
176                                Exception subEx = this.messageExceptions[i];
177                                ps.println("Failed message " + (i + 1) + ":");
178                                subEx.printStackTrace(ps);
179                        }
180                }
181        }
182
183        @Override
184        public void printStackTrace(PrintWriter pw) {
185                if (ObjectUtils.isEmpty(this.messageExceptions)) {
186                        super.printStackTrace(pw);
187                }
188                else {
189                        pw.println(super.toString() + "; message exception details (" +
190                                        this.messageExceptions.length + ") are:");
191                        for (int i = 0; i < this.messageExceptions.length; i++) {
192                                Exception subEx = this.messageExceptions[i];
193                                pw.println("Failed message " + (i + 1) + ":");
194                                subEx.printStackTrace(pw);
195                        }
196                }
197        }
198
199}