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 org.apache.geronimo.javamail.transport.nntp;
021    
022    import java.io.PrintStream;
023    import java.util.ArrayList;
024    
025    import javax.mail.Address;
026    import javax.mail.Message;
027    import javax.mail.MessagingException;
028    import javax.mail.Session;
029    import javax.mail.Transport;
030    import javax.mail.URLName;
031    import javax.mail.event.TransportEvent;
032    import javax.mail.internet.InternetAddress;
033    import javax.mail.internet.MimeMessage;
034    import javax.mail.internet.NewsAddress;
035    
036    import org.apache.geronimo.javamail.util.ProtocolProperties; 
037    
038    /**
039     * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
040     * <p/> There is no way to indicate failure for a given recipient (it's possible
041     * to have a recipient address rejected). The sun impl throws exceptions even if
042     * others successful), but maybe we do a different way... <p/>
043     * 
044     * @version $Rev: 673649 $ $Date: 2008-07-03 06:37:56 -0400 (Thu, 03 Jul 2008) $
045     */
046    public class NNTPTransport extends Transport {
047        /**
048         * property keys for protocol properties.
049         */
050        protected static final String NNTP_FROM = "from";
051    
052        protected static final int DEFAULT_NNTP_PORT = 119;
053        protected static final int DEFAULT_NNTP_SSL_PORT = 563;
054    
055        // our accessor for protocol properties and the holder of 
056        // protocol-specific information 
057        protected ProtocolProperties props; 
058        // our active connection object (shared code with the NNTPStore).
059        protected NNTPConnection connection;
060    
061        /**
062         * Normal constructor for an NNTPTransport() object. This constructor is
063         * used to build a transport instance for the "smtp" protocol.
064         * 
065         * @param session
066         *            The attached session.
067         * @param name
068         *            An optional URLName object containing target information.
069         */
070        public NNTPTransport(Session session, URLName name) {
071            this(session, name, "nntp-post", DEFAULT_NNTP_PORT, false);
072        }
073    
074        /**
075         * Common constructor used by the POP3Store and POP3SSLStore classes
076         * to do common initialization of defaults.
077         *
078         * @param session
079         *            The host session instance.
080         * @param name
081         *            The URLName of the target.
082         * @param protocol
083         *            The protocol type ("pop3"). This helps us in
084         *            retrieving protocol-specific session properties.
085         * @param defaultPort
086         *            The default port used by this protocol. For pop3, this will
087         *            be 110. The default for pop3 with ssl is 995.
088         * @param sslConnection
089         *            Indicates whether an SSL connection should be used to initial
090         *            contact the server. This is different from the STARTTLS
091         *            support, which switches the connection to SSL after the
092         *            initial startup.
093         */
094        protected NNTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
095            super(session, name);
096            
097            // create the protocol property holder.  This gives an abstraction over the different 
098            // flavors of the protocol. 
099            props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
100            // the connection manages connection for the transport 
101            connection = new NNTPConnection(props); 
102        }
103    
104        /**
105         * Do the protocol connection for an NNTP transport. This handles server
106         * authentication, if possible. Returns false if unable to connect to the
107         * server.
108         * 
109         * @param host
110         *            The target host name.
111         * @param port
112         *            The server port number.
113         * @param user
114         *            The authentication user (if any).
115         * @param password
116         *            The server password. Might not be sent directly if more
117         *            sophisticated authentication is used.
118         * 
119         * @return true if we were able to connect to the server properly, false for
120         *         any failures.
121         * @exception MessagingException
122         */
123        protected boolean protocolConnect(String host, int port, String username, String password)
124                throws MessagingException {
125            // the connection pool handles all of the details here. 
126            return connection.protocolConnect(host, port, username, password);
127        }
128        
129    
130        /**
131         * Send a message to multiple addressees.
132         * 
133         * @param message
134         *            The message we're sending.
135         * @param addresses
136         *            An array of addresses to send to.
137         * 
138         * @exception MessagingException
139         */
140        public void sendMessage(Message message, Address[] addresses) throws MessagingException {
141            if (!isConnected()) {
142                throw new IllegalStateException("Not connected");
143            }
144    
145            if (!connection.isPostingAllowed()) {
146                throw new MessagingException("Posting disabled for host server");
147            }
148            // don't bother me w/ null messages or no addreses
149            if (message == null) {
150                throw new MessagingException("Null message");
151            }
152    
153            // NNTP only handles instances of MimeMessage, not the more general
154            // message case.
155            if (!(message instanceof MimeMessage)) {
156                throw new MessagingException("NNTP can only send MimeMessages");
157            }
158    
159            // need to sort the from value out from a variety of sources.
160            InternetAddress from = null;
161    
162            Address[] fromAddresses = message.getFrom();
163    
164            // If the message has a From address set, we just use that. Otherwise,
165            // we set a From using
166            // the property version, if available.
167            if (fromAddresses == null || fromAddresses.length == 0) {
168                // the from value can be set explicitly as a property
169                String defaultFrom = props.getProperty(NNTP_FROM);
170                if (defaultFrom == null) {
171                    message.setFrom(new InternetAddress(defaultFrom));
172                }
173            }
174    
175            // we must have a message list.
176            if (addresses == null || addresses.length == 0) {
177                throw new MessagingException("Null or empty address array");
178            }
179    
180            boolean haveGroup = false;
181    
182            // enforce the requirement that all of the targets are NewsAddress
183            // instances.
184            for (int i = 0; i < addresses.length; i++) {
185                if (!(addresses[i] instanceof NewsAddress)) {
186                    throw new MessagingException("Illegal NewsAddress " + addresses[i]);
187                }
188            }
189    
190            // event notifcation requires we send lists of successes and failures
191            // broken down by category.
192            // The categories are:
193            //
194            // 1) addresses successfully processed.
195            // 2) addresses deemed valid, but had a processing failure that
196            // prevented sending.
197            // 3) addressed deemed invalid (basically all other processing
198            // failures).
199            ArrayList sentAddresses = new ArrayList();
200            ArrayList unsentAddresses = new ArrayList();
201            ArrayList invalidAddresses = new ArrayList();
202    
203            boolean sendFailure = false;
204    
205            // now try to post this message to the different news groups.
206            for (int i = 0; i < addresses.length; i++) {
207                try {
208                    // select the target news group
209                    NNTPReply reply = connection.selectGroup(((NewsAddress) addresses[i]).getNewsgroup());
210    
211                    if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
212                        invalidAddresses.add(addresses[i]);
213                        sendFailure = true;
214                    } else {
215                        // send data
216                        connection.sendPost(message);
217                        sentAddresses.add(addresses[i]);
218                    }
219                } catch (MessagingException e) {
220                    unsentAddresses.add(addresses[i]);
221                    sendFailure = true;
222                }
223            }
224    
225            // create our lists for notification and exception reporting from this
226            // point on.
227            Address[] sent = (Address[]) sentAddresses.toArray(new Address[0]);
228            Address[] unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
229            Address[] invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
230    
231            if (sendFailure) {
232                // did we deliver anything at all?
233                if (sent.length == 0) {
234                    // notify of the error.
235                    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
236                } else {
237                    // notify that we delivered at least part of this
238                    notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
239                }
240    
241                throw new MessagingException("Error posting NNTP message");
242            }
243    
244            // notify our listeners of successful delivery.
245            notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
246        }
247    
248        /**
249         * Close the connection. On completion, we'll be disconnected from the
250         * server and unable to send more data.
251         * 
252         * @exception MessagingException
253         */
254        public void close() throws MessagingException {
255            // This is done to ensure proper event notification.
256            super.close();
257            // NB:  We reuse the connection if asked to reconnect 
258            connection.close();
259        }
260    }