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    }