001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package javax.mail;
021
022 import java.util.ArrayList;
023 import java.util.HashMap;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Vector;
028 import javax.mail.event.TransportEvent;
029 import javax.mail.event.TransportListener;
030
031 /**
032 * Abstract class modeling a message transport.
033 *
034 * @version $Rev: 582780 $ $Date: 2007-10-08 07:17:15 -0400 (Mon, 08 Oct 2007) $
035 */
036 public abstract class Transport extends Service {
037 /**
038 * Send a message to all recipient addresses the message contains (as returned by {@link Message#getAllRecipients()})
039 * using message transports appropriate for each address. Message addresses are checked during submission,
040 * but there is no guarantee that the ultimate address is valid or that the message will ever be delivered.
041 * <p/>
042 * {@link Message#saveChanges()} will be called before the message is actually sent.
043 *
044 * @param message the message to send
045 * @throws MessagingException if there was a problem sending the message
046 */
047 public static void send(Message message) throws MessagingException {
048 send(message, message.getAllRecipients());
049 }
050
051 /**
052 * Send a message to all addresses provided irrespective of any recipients contained in the message,
053 * using message transports appropriate for each address. Message addresses are checked during submission,
054 * but there is no guarantee that the ultimate address is valid or that the message will ever be delivered.
055 * <p/>
056 * {@link Message#saveChanges()} will be called before the message is actually sent.
057 *
058 * @param message the message to send
059 * @param addresses the addesses to send to
060 * @throws MessagingException if there was a problem sending the message
061 */
062 public static void send(Message message, Address[] addresses) throws MessagingException {
063 Session session = message.session;
064 Map msgsByTransport = new HashMap();
065 for (int i = 0; i < addresses.length; i++) {
066 Address address = addresses[i];
067 Transport transport = session.getTransport(address);
068 List addrs = (List) msgsByTransport.get(transport);
069 if (addrs == null) {
070 addrs = new ArrayList();
071 msgsByTransport.put(transport, addrs);
072 }
073 addrs.add(address);
074 }
075
076 message.saveChanges();
077
078 // Since we might be sending to multiple protocols, we need to catch and process each exception
079 // when we send and then throw a new SendFailedException when everything is done. Unfortunately, this
080 // also means unwrapping the information in any SendFailedExceptions we receive and building
081 // composite failed list.
082 MessagingException chainedException = null;
083 ArrayList sentAddresses = new ArrayList();
084 ArrayList unsentAddresses = new ArrayList();
085 ArrayList invalidAddresses = new ArrayList();
086
087
088 for (Iterator i = msgsByTransport.entrySet().iterator(); i.hasNext();) {
089 Map.Entry entry = (Map.Entry) i.next();
090 Transport transport = (Transport) entry.getKey();
091 List addrs = (List) entry.getValue();
092 try {
093 // we MUST connect to the transport before attempting to send.
094 transport.connect();
095 transport.sendMessage(message, (Address[]) addrs.toArray(new Address[addrs.size()]));
096 // if we have to throw an exception because of another failure, these addresses need to
097 // be in the valid list. Since we succeeded here, we can add these now.
098 sentAddresses.addAll(addrs);
099 } catch (SendFailedException e) {
100 // a true send failure. The exception contains a wealth of information about
101 // the failures, including a potential chain of exceptions explaining what went wrong. We're
102 // going to send a new one of these, so we need to merge the information.
103
104 // add this to our exception chain
105 if (chainedException == null) {
106 chainedException = e;
107 }
108 else {
109 chainedException.setNextException(e);
110 }
111
112 // now extract each of the address categories from
113 Address[] exAddrs = e.getValidSentAddresses();
114 if (exAddrs != null) {
115 for (int j = 0; j < exAddrs.length; j++) {
116 sentAddresses.add(exAddrs[j]);
117 }
118 }
119
120 exAddrs = e.getValidUnsentAddresses();
121 if (exAddrs != null) {
122 for (int j = 0; j < exAddrs.length; j++) {
123 unsentAddresses.add(exAddrs[j]);
124 }
125 }
126
127 exAddrs = e.getInvalidAddresses();
128 if (exAddrs != null) {
129 for (int j = 0; j < exAddrs.length; j++) {
130 invalidAddresses.add(exAddrs[j]);
131 }
132 }
133
134 } catch (MessagingException e) {
135 // add this to our exception chain
136 if (chainedException == null) {
137 chainedException = e;
138 }
139 else {
140 chainedException.setNextException(e);
141 }
142 }
143 finally {
144 transport.close();
145 }
146 }
147
148 // if we have an exception chain then we need to throw a new exception giving the failure
149 // information.
150 if (chainedException != null) {
151 // if we're only sending to a single transport (common), and we received a SendFailedException
152 // as a result, then we have a fully formed exception already. Rather than wrap this in another
153 // exception, we can just rethrow the one we have.
154 if (msgsByTransport.size() == 1 && chainedException instanceof SendFailedException) {
155 throw chainedException;
156 }
157
158 // create our lists for notification and exception reporting from this point on.
159 Address[] sent = (Address[])sentAddresses.toArray(new Address[0]);
160 Address[] unsent = (Address[])unsentAddresses.toArray(new Address[0]);
161 Address[] invalid = (Address[])invalidAddresses.toArray(new Address[0]);
162
163 throw new SendFailedException("Send failure", chainedException, sent, unsent, invalid);
164 }
165 }
166
167 /**
168 * Constructor taking Session and URLName parameters required for {@link Service#Service(Session, URLName)}.
169 *
170 * @param session the Session this transport is for
171 * @param name the location this transport is for
172 */
173 public Transport(Session session, URLName name) {
174 super(session, name);
175 }
176
177 /**
178 * Send a message to the supplied addresses using this transport; if any of the addresses are
179 * invalid then a {@link SendFailedException} is thrown. Whether the message is actually sent
180 * to any of the addresses is undefined.
181 * <p/>
182 * Unlike the static {@link #send(Message, Address[])} method, {@link Message#saveChanges()} is
183 * not called. A {@link TransportEvent} will be sent to registered listeners once the delivery
184 * attempt has been made.
185 *
186 * @param message the message to send
187 * @param addresses list of addresses to send it to
188 * @throws SendFailedException if the send failed
189 * @throws MessagingException if there was a problem sending the message
190 */
191 public abstract void sendMessage(Message message, Address[] addresses) throws MessagingException;
192
193 private Vector transportListeners = new Vector();
194
195 public void addTransportListener(TransportListener listener) {
196 transportListeners.add(listener);
197 }
198
199 public void removeTransportListener(TransportListener listener) {
200 transportListeners.remove(listener);
201 }
202
203 protected void notifyTransportListeners(int type, Address[] validSent, Address[] validUnsent, Address[] invalid, Message message) {
204 queueEvent(new TransportEvent(this, type, validSent, validUnsent, invalid, message), transportListeners);
205 }
206 }