View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package javax.mail;
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Vector;
28  import javax.mail.event.TransportEvent;
29  import javax.mail.event.TransportListener;
30  
31  /**
32   * Abstract class modeling a message transport.
33   *
34   * @version $Rev: 582780 $ $Date: 2007-10-08 07:17:15 -0400 (Mon, 08 Oct 2007) $
35   */
36  public abstract class Transport extends Service {
37      /**
38       * Send a message to all recipient addresses the message contains (as returned by {@link Message#getAllRecipients()})
39       * using message transports appropriate for each address. Message addresses are checked during submission,
40       * but there is no guarantee that the ultimate address is valid or that the message will ever be delivered.
41       * <p/>
42       * {@link Message#saveChanges()} will be called before the message is actually sent.
43       *
44       * @param message the message to send
45       * @throws MessagingException if there was a problem sending the message
46       */
47      public static void send(Message message) throws MessagingException {
48          send(message, message.getAllRecipients());
49      }
50  
51      /**
52       * Send a message to all addresses provided irrespective of any recipients contained in the message, 
53       * using message transports appropriate for each address. Message addresses are checked during submission,
54       * but there is no guarantee that the ultimate address is valid or that the message will ever be delivered.
55       * <p/>
56       * {@link Message#saveChanges()} will be called before the message is actually sent.
57       *
58       * @param message   the message to send
59       * @param addresses the addesses to send to
60       * @throws MessagingException if there was a problem sending the message
61       */
62      public static void send(Message message, Address[] addresses) throws MessagingException {
63          Session session = message.session;
64          Map msgsByTransport = new HashMap();
65          for (int i = 0; i < addresses.length; i++) {
66              Address address = addresses[i];
67              Transport transport = session.getTransport(address);
68              List addrs = (List) msgsByTransport.get(transport);
69              if (addrs == null) {
70                  addrs = new ArrayList();
71                  msgsByTransport.put(transport, addrs);
72              }
73              addrs.add(address);
74          }
75  
76          message.saveChanges();
77  
78          // Since we might be sending to multiple protocols, we need to catch and process each exception
79          // when we send and then throw a new SendFailedException when everything is done.  Unfortunately, this
80          // also means unwrapping the information in any SendFailedExceptions we receive and building
81          // composite failed list.
82          MessagingException chainedException = null;
83          ArrayList sentAddresses = new ArrayList();
84          ArrayList unsentAddresses = new ArrayList();
85          ArrayList invalidAddresses = new ArrayList();
86  
87  
88          for (Iterator i = msgsByTransport.entrySet().iterator(); i.hasNext();) {
89              Map.Entry entry = (Map.Entry) i.next();
90              Transport transport = (Transport) entry.getKey();
91              List addrs = (List) entry.getValue();
92              try {
93                  // we MUST connect to the transport before attempting to send.
94                  transport.connect();
95                  transport.sendMessage(message, (Address[]) addrs.toArray(new Address[addrs.size()]));
96                  // if we have to throw an exception because of another failure, these addresses need to
97                  // be in the valid list.  Since we succeeded here, we can add these now.
98                  sentAddresses.addAll(addrs);
99              } 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 }