View Javadoc

1   /**
2    *
3    * Copyright 2003-2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.transport.nntp;
19  
20  import java.io.PrintStream;
21  import java.util.ArrayList;
22  
23  import javax.mail.Address;
24  import javax.mail.Message;
25  import javax.mail.MessagingException;
26  import javax.mail.Session;
27  import javax.mail.Transport;
28  import javax.mail.URLName;
29  import javax.mail.event.TransportEvent;
30  import javax.mail.internet.InternetAddress;
31  import javax.mail.internet.MimeMessage;
32  import javax.mail.internet.NewsAddress;
33  
34  import org.apache.geronimo.mail.util.SessionUtil;
35  
36  /**
37   * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
38   * <p/> There is no way to indicate failure for a given recipient (it's possible
39   * to have a recipient address rejected). The sun impl throws exceptions even if
40   * others successful), but maybe we do a different way... <p/>
41   * 
42   * @version $Rev: 432884 $ $Date: 2006-08-19 14:53:20 -0700 (Sat, 19 Aug 2006) $
43   */
44  public class NNTPTransport extends Transport {
45  
46      /**
47       * property keys for protocol properties.
48       */
49      protected static final String NNTP_AUTH = "auth";
50  
51      protected static final String NNTP_PORT = "port";
52  
53      protected static final String NNTP_FROM = "from";
54  
55      protected static final String protocol = "nntp-post";
56  
57      protected static final int DEFAULT_NNTP_PORT = 119;
58  
59      // our active connection object (shared code with the NNTPStore).
60      protected NNTPConnection connection;
61  
62      // our session provided debug output stream.
63      protected PrintStream debugStream;
64  
65      /**
66       * Normal constructor for an NNTPTransport() object. This constructor is
67       * used to build a transport instance for the "smtp" protocol.
68       * 
69       * @param session
70       *            The attached session.
71       * @param name
72       *            An optional URLName object containing target information.
73       */
74      public NNTPTransport(Session session, URLName name) {
75          super(session, name);
76  
77          // get our debug output.
78          debugStream = session.getDebugOut();
79      }
80  
81      /**
82       * Do the protocol connection for an NNTP transport. This handles server
83       * authentication, if possible. Returns false if unable to connect to the
84       * server.
85       * 
86       * @param host
87       *            The target host name.
88       * @param port
89       *            The server port number.
90       * @param user
91       *            The authentication user (if any).
92       * @param password
93       *            The server password. Might not be sent directly if more
94       *            sophisticated authentication is used.
95       * 
96       * @return true if we were able to connect to the server properly, false for
97       *         any failures.
98       * @exception MessagingException
99       */
100     protected boolean protocolConnect(String host, int port, String username, String password)
101             throws MessagingException {
102         if (debug) {
103             debugOut("Connecting to server " + host + ":" + port + " for user " + username);
104         }
105 
106         // first check to see if we need to authenticate. If we need this, then
107         // we must have a username and
108         // password specified. Failing this may result in a user prompt to
109         // collect the information.
110         boolean mustAuthenticate = SessionUtil.getBooleanProperty(session, NNTP_AUTH, false);
111 
112         // if we need to authenticate, and we don't have both a userid and
113         // password, then we fail this
114         // immediately. The Service.connect() method will try to obtain the user
115         // information and retry the
116         // connection one time.
117         if (mustAuthenticate && (username == null || password == null)) {
118             return false;
119         }
120 
121         // if the port is defaulted, then see if we have something configured in
122         // the session.
123         // if not configured, we just use the default default.
124         if (port == -1) {
125             // check for a property and fall back on the default if it's not
126             // set.
127             port = SessionUtil.getIntProperty(session, NNTP_PORT, DEFAULT_NNTP_PORT);
128         }
129 
130         // create socket and connect to server.
131         connection = new NNTPConnection(protocol, session, host, port, username, password, debug);
132         connection.connect();
133 
134         // we're going to return success here, but in truth, the server may end
135         // up asking for our
136         // bonafides at any time, and we'll be expected to authenticate then.
137         return true;
138     }
139 
140     /**
141      * Send a message to multiple addressees.
142      * 
143      * @param message
144      *            The message we're sending.
145      * @param addresses
146      *            An array of addresses to send to.
147      * 
148      * @exception MessagingException
149      */
150     public void sendMessage(Message message, Address[] addresses) throws MessagingException {
151         if (!isConnected()) {
152             throw new IllegalStateException("Not connected");
153         }
154 
155         if (!connection.isPostingAllowed()) {
156             throw new MessagingException("Posting disabled for host server");
157         }
158         // don't bother me w/ null messages or no addreses
159         if (message == null) {
160             throw new MessagingException("Null message");
161         }
162 
163         // NNTP only handles instances of MimeMessage, not the more general
164         // message case.
165         if (!(message instanceof MimeMessage)) {
166             throw new MessagingException("NNTP can only send MimeMessages");
167         }
168 
169         // need to sort the from value out from a variety of sources.
170         InternetAddress from = null;
171 
172         Address[] fromAddresses = message.getFrom();
173 
174         // If the message has a From address set, we just use that. Otherwise,
175         // we set a From using
176         // the property version, if available.
177         if (fromAddresses == null || fromAddresses.length == 0) {
178             // the from value can be set explicitly as a property
179             String defaultFrom = session.getProperty(NNTP_FROM);
180             if (defaultFrom == null) {
181                 message.setFrom(new InternetAddress(defaultFrom));
182             }
183         }
184 
185         // we must have a message list.
186         if (addresses == null || addresses.length == 0) {
187             throw new MessagingException("Null or empty address array");
188         }
189 
190         boolean haveGroup = false;
191 
192         // enforce the requirement that all of the targets are NewsAddress
193         // instances.
194         for (int i = 0; i < addresses.length; i++) {
195             if (!(addresses[i] instanceof NewsAddress)) {
196                 System.out.println("Illegal address is of class " + addresses[i].getClass());
197                 throw new MessagingException("Illegal NewsAddress " + addresses[i]);
198             }
199         }
200 
201         // event notifcation requires we send lists of successes and failures
202         // broken down by category.
203         // The categories are:
204         //
205         // 1) addresses successfully processed.
206         // 2) addresses deemed valid, but had a processing failure that
207         // prevented sending.
208         // 3) addressed deemed invalid (basically all other processing
209         // failures).
210         ArrayList sentAddresses = new ArrayList();
211         ArrayList unsentAddresses = new ArrayList();
212         ArrayList invalidAddresses = new ArrayList();
213 
214         boolean sendFailure = false;
215 
216         // now try to post this message to the different news groups.
217         for (int i = 0; i < addresses.length; i++) {
218             try {
219                 // select the target news group
220                 NNTPReply reply = connection.selectGroup(((NewsAddress) addresses[i]).getNewsgroup());
221 
222                 if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
223                     invalidAddresses.add(addresses[i]);
224                     sendFailure = true;
225                 } else {
226                     // send data
227                     connection.sendPost(message);
228                     sentAddresses.add(addresses[i]);
229                 }
230             } catch (MessagingException e) {
231                 unsentAddresses.add(addresses[i]);
232                 sendFailure = true;
233             }
234         }
235 
236         // create our lists for notification and exception reporting from this
237         // point on.
238         Address[] sent = (Address[]) sentAddresses.toArray(new Address[0]);
239         Address[] unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
240         Address[] invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
241 
242         if (sendFailure) {
243             // did we deliver anything at all?
244             if (sent.length == 0) {
245                 // notify of the error.
246                 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
247             } else {
248                 // notify that we delivered at least part of this
249                 notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
250             }
251 
252             throw new MessagingException("Error posting NNTP message");
253         }
254 
255         // notify our listeners of successful delivery.
256         notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
257     }
258 
259     /**
260      * Close the connection. On completion, we'll be disconnected from the
261      * server and unable to send more data.
262      * 
263      * @exception MessagingException
264      */
265     public void close() throws MessagingException {
266         // This is done to ensure proper event notification.
267         super.close();
268         connection.close();
269         connection = null;
270     }
271 
272     /**
273      * Internal debug output routine.
274      * 
275      * @param value
276      *            The string value to output.
277      */
278     protected void debugOut(String message) {
279         debugStream.println("NNTPTransport DEBUG: " + message);
280     }
281 
282     /**
283      * Internal debugging routine for reporting exceptions.
284      * 
285      * @param message
286      *            A message associated with the exception context.
287      * @param e
288      *            The received exception.
289      */
290     protected void debugOut(String message, Throwable e) {
291         debugOut("Received exception -> " + message);
292         debugOut("Exception message -> " + e.getMessage());
293         e.printStackTrace(debugStream);
294     }
295 
296     /**
297      * Get a property associated with this mail protocol.
298      * 
299      * @param name
300      *            The name of the property.
301      * 
302      * @return The property value (returns null if the property has not been
303      *         set).
304      */
305     String getProperty(String name) {
306         // the name we're given is the least qualified part of the name. We
307         // construct the full property name
308         // using the protocol (either "nntp" or "nntp-post").
309         String fullName = "mail." + protocol + "." + name;
310         return session.getProperty(fullName);
311     }
312 
313     /**
314      * Get a property associated with this mail session. Returns the provided
315      * default if it doesn't exist.
316      * 
317      * @param name
318      *            The name of the property.
319      * @param defaultValue
320      *            The default value to return if the property doesn't exist.
321      * 
322      * @return The property value (returns defaultValue if the property has not
323      *         been set).
324      */
325     String getProperty(String name, String defaultValue) {
326         // the name we're given is the least qualified part of the name. We
327         // construct the full property name
328         // using the protocol (either "nntp" or "nntp-post").
329         String fullName = "mail." + protocol + "." + name;
330         return SessionUtil.getProperty(session, fullName, defaultValue);
331     }
332 
333     /**
334      * Get a property associated with this mail session as an integer value.
335      * Returns the default value if the property doesn't exist or it doesn't
336      * have a valid int value.
337      * 
338      * @param name
339      *            The name of the property.
340      * @param defaultValue
341      *            The default value to return if the property doesn't exist.
342      * 
343      * @return The property value converted to an int.
344      */
345     int getIntProperty(String name, int defaultValue) {
346         // the name we're given is the least qualified part of the name. We
347         // construct the full property name
348         // using the protocol (either "nntp" or "nntp-post").
349         String fullName = "mail." + protocol + "." + name;
350         return SessionUtil.getIntProperty(session, fullName, defaultValue);
351     }
352 
353     /**
354      * Get a property associated with this mail session as an boolean value.
355      * Returns the default value if the property doesn't exist or it doesn't
356      * have a valid int value.
357      * 
358      * @param name
359      *            The name of the property.
360      * @param defaultValue
361      *            The default value to return if the property doesn't exist.
362      * 
363      * @return The property value converted to a boolean
364      */
365     boolean getBooleanProperty(String name, boolean defaultValue) {
366         // the name we're given is the least qualified part of the name. We
367         // construct the full property name
368         // using the protocol (either "nntp" or "nntp-post").
369         String fullName = "mail." + protocol + "." + name;
370         return SessionUtil.getBooleanProperty(session, fullName, defaultValue);
371     }
372 }