View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 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 javax.mail;
19  
20  import java.net.InetAddress;
21  import java.net.UnknownHostException;
22  import java.util.Vector;
23  
24  import javax.mail.event.ConnectionEvent;
25  import javax.mail.event.ConnectionListener;
26  import javax.mail.event.MailEvent;
27  
28  /**
29   * @version $Rev: 390529 $ $Date: 2006-03-31 14:40:02 -0800 (Fri, 31 Mar 2006) $
30   */
31  public abstract class Service {
32      /**
33       * The session from which this service was created.
34       */
35      protected Session session;
36      /**
37       * The URLName of this service
38       */
39      protected URLName url;
40      /**
41       * Debug flag for this service, set from the Session's debug flag.
42       */
43      protected boolean debug;
44  
45      private boolean connected;
46      private final Vector connectionListeners = new Vector(2);
47      private final EventQueue queue = new EventQueue();
48  
49      /**
50       * Construct a new Service.
51       * @param session the session from which this service was created
52       * @param url the URLName of this service
53       */
54      protected Service(Session session, URLName url) {
55          this.session = session;
56          this.url = url;
57          this.debug = session.getDebug();
58      }
59  
60      /**
61       * A generic connect method that takes no parameters allowing subclasses
62       * to implement an appropriate authentication scheme.
63       * The default implementation calls <code>connect(null, null, null)</code>
64       * @throws AuthenticationFailedException if authentication fails
65       * @throws MessagingException for other failures
66       */
67      public void connect() throws MessagingException {
68          connect(null, null, null);
69      }
70  
71      /**
72       * Connect to the specified host using a simple username/password authenticaion scheme
73       * and the default port.
74       * The default implementation calls <code>connect(host, -1, user, password)</code>
75       *
76       * @param host the host to connect to
77       * @param user the user name
78       * @param password the user's password
79       * @throws AuthenticationFailedException if authentication fails
80       * @throws MessagingException for other failures
81       */
82      public void connect(String host, String user, String password) throws MessagingException {
83          connect(host, -1, user, password);
84      }
85  
86      /**
87       * Connect to the specified host at the specified port using a simple username/password authenticaion scheme.
88       *
89       * If this Service is already connected, an IllegalStateException is thrown.
90       *
91       * @param host the host to connect to
92       * @param port the port to connect to; pass -1 to use the default for the protocol
93       * @param user the user name
94       * @param password the user's password
95       * @throws AuthenticationFailedException if authentication fails
96       * @throws MessagingException for other failures
97       * @throws IllegalStateException if this service is already connected
98       */
99      public void connect(String host, int port, String user, String password) throws MessagingException {
100 
101         if (isConnected()) {
102             throw new IllegalStateException("Already connected");
103         }
104 
105         // before we try to connect, we need to derive values for some parameters that may not have
106         // been explicitly specified.  For example, the normal connect() method leaves us to derive all
107         // of these from other sources.  Some of the values are derived from our URLName value, others
108         // from session parameters.  We need to go through all of these to develop a set of values we
109         // can connect with.
110 
111         // this is the protocol we're connecting with.  We use this largely to derive configured values from
112         // session properties.
113         String protocol = null;
114 
115         // if we're working with the URL form, then we can retrieve the protocol from the URL.
116         if (url != null) {
117             protocol = url.getProtocol();
118         }
119 
120         // now try to derive values for any of the arguments we've been given as defaults
121         if (host == null) {
122             // first choice is from the url, if we have
123             if (url != null) {
124                 host = url.getHost();
125                 // it is possible that this could return null (rare).  If it does, try to get a
126                 // value from a protocol specific session variable.
127                 if (host == null) {
128                     host = session.getProperty("mail." + protocol + ".host");
129                 }
130             }
131             // this may still be null...get the global mail property
132             if (host == null) {
133                 host = session.getProperty("mail.host");
134             }
135         }
136 
137         // ok, go after userid information next.
138         if (user == null) {
139             // first choice is from the url, if we have
140             if (url != null) {
141                 user = url.getUsername();
142                 // make sure we get the password from the url, if we can.
143                 if (password == null) {
144                     password = url.getPassword();
145                 }
146                 // user still null?  We have several levels of properties to try yet
147                 if (user == null) {
148                     user = session.getProperty("mail." + protocol + ".user");
149                 }
150             }
151 
152             // this may still be null...get the global mail property
153             if (user == null) {
154                 user = session.getProperty("mail.user");
155             }
156 
157             // finally, we try getting the system defined user name
158             try {
159                 user = System.getProperty("user.name");
160             } catch (SecurityException e) {
161                 // we ignore this, and just us a null username.
162             }
163         }
164         // if we have an explicitly given user name, we need to see if this matches the url one and
165         // grab the password from there.
166         else {
167             if (url != null && user.equals(url.getUsername())) {
168                 password = url.getPassword();
169             }
170         }
171 
172         // we need to update the URLName associated with this connection once we have all of the information,
173         // which means we also need to propogate the file portion of the URLName if we have this form when
174         // we start.
175         String file = null;
176         if (url != null) {
177             file = url.getFile();
178         }
179 
180         // see if we have cached security information to use.  If this is not cached, we'll save it
181         // after we successfully connect.
182         boolean cachePassword = false;
183 
184 
185         // still have a null password to this point, and using a url form?
186         if (password == null && url != null) {
187             // construct a new URL, filling in any pieces that may have been explicitly specified.
188             setURLName(new URLName(protocol, host, port, file, user, password));
189             // now see if we have a saved password from a previous request.
190             PasswordAuthentication cachedPassword = session.getPasswordAuthentication(getURLName());
191 
192             // if we found a saved one, see if we need to get any the pieces from here.
193             if (cachedPassword != null) {
194                 // not even a resolved userid?  Then use both bits.
195                 if (user == null) {
196                     user = cachedPassword.getUserName();
197                     password = cachedPassword.getPassword();
198                 }
199                 // our user name must match the cached name to be valid.
200                 else if (user.equals(cachedPassword.getUserName())) {
201                     password = cachedPassword.getPassword();
202                 }
203             }
204             else
205             {
206                 // nothing found in the cache, so we need to save this if we can connect successfully.
207                 cachePassword = true;
208             }
209         }
210 
211         // we've done our best up to this point to obtain all of the information needed to make the
212         // connection.  Now we pass this off to the protocol handler to see if it works.  If we get a
213         // connection failure, we may need to prompt for a password before continuing.
214         try {
215             connected = protocolConnect(host, port, user, password);
216         }
217         catch (AuthenticationFailedException e) {
218         }
219 
220         if (!connected) {
221             InetAddress ipAddress = null;
222 
223             try {
224                 ipAddress = InetAddress.getByName(host);
225             } catch (UnknownHostException e) {
226             }
227 
228             // now ask the session to try prompting for a password.
229             PasswordAuthentication promptPassword = session.requestPasswordAuthentication(ipAddress, port, protocol, null, user);
230 
231             // if we were able to obtain new information from the session, then try again using the
232             // provided information .
233             if (promptPassword != null) {
234                 user = promptPassword.getUserName();
235                 password = promptPassword.getPassword();
236             }
237 
238             connected = protocolConnect(host, port, user, password);
239         }
240 
241 
242         // if we're still not connected, then this is an exception.
243         if (!connected) {
244             throw new AuthenticationFailedException();
245         }
246 
247         // the URL name needs to reflect the most recent information.
248         setURLName(new URLName(protocol, host, port, file, user, password));
249 
250         // we need to update the global password cache with this information.
251         if (cachePassword) {
252             session.setPasswordAuthentication(getURLName(), new PasswordAuthentication(user, password));
253         }
254 
255         // we're now connected....broadcast this to any interested parties.
256         setConnected(connected);
257         notifyConnectionListeners(ConnectionEvent.OPENED);
258     }
259 
260     /**
261      * Attempt the protocol-specific connection; subclasses should override this to establish
262      * a connection in the appropriate manner.
263      *
264      * This method should return true if the connection was established.
265      * It may return false to cause the {@link #connect(String, int, String, String)} method to
266      * reattempt the connection after trying to obtain user and password information from the user.
267      * Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt.
268      *
269      * @param host
270      * @param port
271      * @param user
272      * @param password
273      * @return
274      * @throws AuthenticationFailedException if authentication fails
275      * @throws MessagingException for other failures
276      */
277     protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException {
278         return false;
279     }
280 
281     /**
282      * Check if this service is currently connected.
283      * The default implementation simply returns the value of a private boolean field;
284      * subclasses may wish to override this method to verify the physical connection.
285      *
286      * @return true if this service is connected
287      */
288     public boolean isConnected() {
289         return connected;
290     }
291 
292     /**
293      * Notification to subclasses that the connection state has changed.
294      * This method is called by the connect() and close() methods to indicate state change;
295      * subclasses should also call this method if the connection is automatically closed
296      * for some reason.
297      *
298      * @param connected the connection state
299      */
300     protected void setConnected(boolean connected) {
301         this.connected = connected;
302     }
303 
304     /**
305      * Close this service and terminate its physical connection.
306      * The default implementation simply calls setConnected(false) and then
307      * sends a CLOSED event to all registered ConnectionListeners.
308      * Subclasses overriding this method should still ensure it is closed; they should
309      * also ensure that it is called if the connection is closed automatically, for
310      * for example in a finalizer.
311      *
312      *@throws MessagingException if there were errors closing; the connection is still closed
313      */
314     public void close() throws MessagingException {
315         setConnected(false);
316         notifyConnectionListeners(ConnectionEvent.CLOSED);
317     }
318 
319     /**
320      * Return a copy of the URLName representing this service with the password and file information removed.
321      *
322      * @return the URLName for this service
323      */
324     public URLName getURLName() {
325 
326         return url == null ? null : new URLName(url.getProtocol(), url.getHost(), url.getPort(), null, url.getUsername(), null);
327     }
328 
329     /**
330      * Set the url field.
331      * @param url the new value
332      */
333     protected void setURLName(URLName url) {
334         this.url = url;
335     }
336 
337     public void addConnectionListener(ConnectionListener listener) {
338         connectionListeners.add(listener);
339     }
340 
341     public void removeConnectionListener(ConnectionListener listener) {
342         connectionListeners.remove(listener);
343     }
344 
345     protected void notifyConnectionListeners(int type) {
346         queue.queueEvent(new ConnectionEvent(this, type), connectionListeners);
347     }
348 
349     public String toString() {
350         return url == null ? super.toString() : url.toString();
351     }
352 
353     protected void queueEvent(MailEvent event, Vector listeners) {
354         queue.queueEvent(event, listeners);
355     }
356 
357     protected void finalize() throws Throwable {
358         queue.stop();
359         connectionListeners.clear();
360         super.finalize();
361     }
362 
363 
364     /**
365      * Package scope utility method to allow Message instances
366      * access to the Service's session.
367      *
368      * @return The Session the service is associated with.
369      */
370     Session getSession() {
371         return session;
372     }
373 }