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