1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can obtain
10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
12 * language governing permissions and limitations under the License.
13 *
14 * When distributing the software, include this License Header Notice in each
15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
16 * Sun designates this particular file as subject to the "Classpath" exception
17 * as provided by Sun in the GPL Version 2 section of the License file that
18 * accompanied this code. If applicable, add the following below the License
19 * Header, with the fields enclosed by brackets [] replaced by your own
20 * identifying information: "Portions Copyrighted [year]
21 * [name of copyright owner]"
22 *
23 * Contributor(s):
24 *
25 * If you wish your version of this file to be governed by only the CDDL or
26 * only the GPL Version 2, indicate your decision by adding "[Contributor]
27 * elects to include this software in this distribution under the [CDDL or GPL
28 * Version 2] license." If you don't indicate a single choice of license, a
29 * recipient has the option to distribute your version of this file under
30 * either the CDDL, the GPL Version 2 or to extend the choice of license to
31 * its licensees as provided above. However, if you add GPL Version 2 code
32 * and therefore, elected the GPL Version 2 license, then the option applies
33 * only if the new code is made subject to such option by the copyright
34 * holder.
35 */
36
37 /*
38 * @(#)Service.java 1.33 07/05/14
39 */
40
41 package javax.mail;
42
43 import java.io;
44 import java.net;
45 import java.util;
46 import javax.mail.event;
47
48 /**
49 * An abstract class that contains the functionality
50 * common to messaging services, such as stores and transports. <p>
51 * A messaging service is created from a <code>Session</code> and is
52 * named using a <code>URLName</code>. A service must be connected
53 * before it can be used. Connection events are sent to reflect
54 * its connection status.
55 *
56 * @author Christopher Cotton
57 * @author Bill Shannon
58 * @author Kanwar Oberoi
59 * @version 1.33, 07/05/14
60 */
61
62 public abstract class Service {
63
64 /**
65 * The session from which this service was created.
66 */
67 protected Session session;
68
69 /**
70 * The <code>URLName</code> of this service.
71 */
72 protected URLName url = null;
73
74 /**
75 * Debug flag for this service. Set from the session's debug
76 * flag when this service is created.
77 */
78 protected boolean debug = false;
79
80 private boolean connected = false;
81 private Vector connectionListeners = null;
82
83 /**
84 * Constructor.
85 *
86 * @param session Session object for this service
87 * @param urlname URLName object to be used for this service
88 */
89 protected Service(Session session, URLName urlname) {
90 this.session = session;
91 url = urlname;
92 debug = session.getDebug();
93 }
94
95 /**
96 * A generic connect method that takes no parameters. Subclasses
97 * can implement the appropriate authentication schemes. Subclasses
98 * that need additional information might want to use some properties
99 * or might get it interactively using a popup window. <p>
100 *
101 * If the connection is successful, an "open" <code>ConnectionEvent</code>
102 * is delivered to any <code>ConnectionListeners</code> on this service. <p>
103 *
104 * Most clients should just call this method to connect to the service.<p>
105 *
106 * It is an error to connect to an already connected service. <p>
107 *
108 * The implementation provided here simply calls the following
109 * <code>connect(String, String, String)</code> method with nulls.
110 *
111 * @exception AuthenticationFailedException for authentication failures
112 * @exception MessagingException for other failures
113 * @exception IllegalStateException if the service is already connected
114 *
115 * @see javax.mail.event.ConnectionEvent
116 */
117 public void connect() throws MessagingException {
118 connect(null, null, null);
119 }
120
121 /**
122 * Connect to the specified address. This method provides a simple
123 * authentication scheme that requires a username and password. <p>
124 *
125 * If the connection is successful, an "open" <code>ConnectionEvent</code>
126 * is delivered to any <code>ConnectionListeners</code> on this service. <p>
127 *
128 * It is an error to connect to an already connected service. <p>
129 *
130 * The implementation in the Service class will collect defaults
131 * for the host, user, and password from the session, from the
132 * <code>URLName</code> for this service, and from the supplied
133 * parameters and then call the <code>protocolConnect</code> method.
134 * If the <code>protocolConnect</code> method returns <code>false</code>,
135 * the user will be prompted for any missing information and the
136 * <code>protocolConnect</code> method will be called again. The
137 * subclass should override the <code>protocolConnect</code> method.
138 * The subclass should also implement the <code>getURLName</code>
139 * method, or use the implementation in this class. <p>
140 *
141 * On a successful connection, the <code>setURLName</code> method is
142 * called with a URLName that includes the information used to make
143 * the connection, including the password. <p>
144 *
145 * If the username passed in is null, a default value will be chosen
146 * as described above.
147 *
148 * If the password passed in is null and this is the first successful
149 * connection to this service, the user name and the password
150 * collected from the user will be saved as defaults for subsequent
151 * connection attempts to this same service when using other Service object
152 * instances (the connection information is typically always saved within
153 * a particular Service object instance). The password is saved using the
154 * Session method <code>setPasswordAuthentication</code>. If the
155 * password passed in is not null, it is not saved, on the assumption
156 * that the application is managing passwords explicitly.
157 *
158 * @param host the host to connect to
159 * @param user the user name
160 * @param password this user's password
161 * @exception AuthenticationFailedException for authentication failures
162 * @exception MessagingException for other failures
163 * @exception IllegalStateException if the service is already connected
164 * @see javax.mail.event.ConnectionEvent
165 * @see javax.mail.Session#setPasswordAuthentication
166 */
167 public void connect(String host, String user, String password)
168 throws MessagingException {
169 connect(host, -1, user, password);
170 }
171
172 /**
173 * Connect to the current host using the specified username
174 * and password. This method is equivalent to calling the
175 * <code>connect(host, user, password)</code> method with null
176 * for the host name.
177 *
178 * @param user the user name
179 * @param password this user's password
180 * @exception AuthenticationFailedException for authentication failures
181 * @exception MessagingException for other failures
182 * @exception IllegalStateException if the service is already connected
183 * @see javax.mail.event.ConnectionEvent
184 * @see javax.mail.Session#setPasswordAuthentication
185 * @see #connect(java.lang.String, java.lang.String, java.lang.String)
186 * @since JavaMail 1.4
187 */
188 public void connect(String user, String password) throws MessagingException {
189 connect(null, user, password);
190 }
191
192 /**
193 * Similar to connect(host, user, password) except a specific port
194 * can be specified.
195 *
196 * @param host the host to connect to
197 * @param port the port to connect to (-1 means the default port)
198 * @param user the user name
199 * @param password this user's password
200 * @exception AuthenticationFailedException for authentication failures
201 * @exception MessagingException for other failures
202 * @exception IllegalStateException if the service is already connected
203 * @see #connect(java.lang.String, java.lang.String, java.lang.String)
204 * @see javax.mail.event.ConnectionEvent
205 */
206 public synchronized void connect(String host, int port,
207 String user, String password) throws MessagingException {
208
209 // see if the service is already connected
210 if (isConnected())
211 throw new IllegalStateException("already connected");
212
213 PasswordAuthentication pw;
214 boolean connected = false;
215 boolean save = false;
216 String protocol = null;
217 String file = null;
218
219 // get whatever information we can from the URL
220 // XXX - url should always be non-null here, Session
221 // passes it into the constructor
222 if (url != null) {
223 protocol = url.getProtocol();
224 if (host == null)
225 host = url.getHost();
226 if (port == -1)
227 port = url.getPort();
228
229 if (user == null) {
230 user = url.getUsername();
231 if (password == null) // get password too if we need it
232 password = url.getPassword();
233 } else {
234 if (password == null && user.equals(url.getUsername()))
235 // only get the password if it matches the username
236 password = url.getPassword();
237 }
238
239 file = url.getFile();
240 }
241
242 // try to get protocol-specific default properties
243 if (protocol != null) {
244 if (host == null)
245 host = session.getProperty("mail." + protocol + ".host");
246 if (user == null)
247 user = session.getProperty("mail." + protocol + ".user");
248 }
249
250 // try to get mail-wide default properties
251 if (host == null)
252 host = session.getProperty("mail.host");
253
254 if (user == null)
255 user = session.getProperty("mail.user");
256
257 // try using the system username
258 if (user == null) {
259 try {
260 user = System.getProperty("user.name");
261 } catch (SecurityException sex) {
262 if (debug)
263 sex.printStackTrace(session.getDebugOut());
264 }
265 }
266
267 // if we don't have a password, look for saved authentication info
268 if (password == null && url != null) {
269 // canonicalize the URLName
270 setURLName(new URLName(protocol, host, port, file, user, null));
271 pw = session.getPasswordAuthentication(getURLName());
272 if (pw != null) {
273 if (user == null) {
274 user = pw.getUserName();
275 password = pw.getPassword();
276 } else if (user.equals(pw.getUserName())) {
277 password = pw.getPassword();
278 }
279 } else
280 save = true;
281 }
282
283 // try connecting, if the protocol needs some missing
284 // information (user, password) it will not connect.
285 // if it tries to connect and fails, remember why for later.
286 AuthenticationFailedException authEx = null;
287 try {
288 connected = protocolConnect(host, port, user, password);
289 } catch (AuthenticationFailedException ex) {
290 authEx = ex;
291 }
292
293 // if not connected, ask the user and try again
294 if (!connected) {
295 InetAddress addr;
296 try {
297 addr = InetAddress.getByName(host);
298 } catch (UnknownHostException e) {
299 addr = null;
300 }
301 pw = session.requestPasswordAuthentication(
302 addr, port,
303 protocol,
304 null, user);
305 if (pw != null) {
306 user = pw.getUserName();
307 password = pw.getPassword();
308
309 // have the service connect again
310 connected = protocolConnect(host, port, user, password);
311 }
312 }
313
314 // if we're not connected by now, we give up
315 if (!connected) {
316 if (authEx != null)
317 throw authEx;
318 else
319 throw new AuthenticationFailedException();
320 }
321
322 setURLName(new URLName(protocol, host, port, file, user, password));
323
324 if (save)
325 session.setPasswordAuthentication(getURLName(),
326 new PasswordAuthentication(user, password));
327
328 // set our connected state
329 setConnected(true);
330
331 // finally, deliver the connection event
332 notifyConnectionListeners(ConnectionEvent.OPENED);
333 }
334
335
336 /**
337 * The service implementation should override this method to
338 * perform the actual protocol-specific connection attempt.
339 * The default implementation of the <code>connect</code> method
340 * calls this method as needed. <p>
341 *
342 * The <code>protocolConnect</code> method should return
343 * <code>false</code> if a user name or password is required
344 * for authentication but the corresponding parameter is null;
345 * the <code>connect</code> method will prompt the user when
346 * needed to supply missing information. This method may
347 * also return <code>false</code> if authentication fails for
348 * the supplied user name or password. Alternatively, this method
349 * may throw an AuthenticationFailedException when authentication
350 * fails. This exception may include a String message with more
351 * detail about the failure. <p>
352 *
353 * The <code>protocolConnect</code> method should throw an
354 * exception to report failures not related to authentication,
355 * such as an invalid host name or port number, loss of a
356 * connection during the authentication process, unavailability
357 * of the server, etc.
358 *
359 * @param host the name of the host to connect to
360 * @param port the port to use (-1 means use default port)
361 * @param user the name of the user to login as
362 * @param password the user's password
363 * @return true if connection successful, false if authentication failed
364 * @exception AuthenticationFailedException for authentication failures
365 * @exception MessagingException for non-authentication failures
366 */
367 protected boolean protocolConnect(String host, int port, String user,
368 String password) throws MessagingException {
369 return false;
370 }
371
372 /**
373 * Is this service currently connected? <p>
374 *
375 * This implementation uses a private boolean field to
376 * store the connection state. This method returns the value
377 * of that field. <p>
378 *
379 * Subclasses may want to override this method to verify that any
380 * connection to the message store is still alive.
381 *
382 * @return true if the service is connected, false if it is not connected
383 */
384 public synchronized boolean isConnected() {
385 return connected;
386 }
387
388 /**
389 * Set the connection state of this service. The connection state
390 * will automatically be set by the service implementation during the
391 * <code>connect</code> and <code>close</code> methods.
392 * Subclasses will need to call this method to set the state
393 * if the service was automatically disconnected. <p>
394 *
395 * The implementation in this class merely sets the private field
396 * returned by the <code>isConnected</code> method.
397 *
398 * @param connected true if the service is connected,
399 * false if it is not connected
400 */
401 protected synchronized void setConnected(boolean connected) {
402 this.connected = connected;
403 }
404
405 /**
406 * Close this service and terminate its connection. A close
407 * ConnectionEvent is delivered to any ConnectionListeners. Any
408 * Messaging components (Folders, Messages, etc.) belonging to this
409 * service are invalid after this service is closed. Note that the service
410 * is closed even if this method terminates abnormally by throwing
411 * a MessagingException. <p>
412 *
413 * This implementation uses <code>setConnected(false)</code> to set
414 * this service's connected state to <code>false</code>. It will then
415 * send a close ConnectionEvent to any registered ConnectionListeners.
416 * Subclasses overriding this method to do implementation specific
417 * cleanup should call this method as a last step to insure event
418 * notification, probably by including a call to <code>super.close()</code>
419 * in a <code>finally</code> clause.
420 *
421 * @see javax.mail.event.ConnectionEvent
422 * @throws MessagingException for errors while closing
423 */
424 public synchronized void close() throws MessagingException {
425 setConnected(false);
426 notifyConnectionListeners(ConnectionEvent.CLOSED);
427 }
428
429 /**
430 * Return a URLName representing this service. The returned URLName
431 * does <em>not</em> include the password field. <p>
432 *
433 * Subclasses should only override this method if their
434 * URLName does not follow the standard format. <p>
435 *
436 * The implementation in the Service class returns (usually a copy of)
437 * the <code>url</code> field with the password and file information
438 * stripped out.
439 *
440 * @return the URLName representing this service
441 * @see URLName
442 */
443 public synchronized URLName getURLName() {
444 if (url != null && (url.getPassword() != null || url.getFile() != null))
445 return new URLName(url.getProtocol(), url.getHost(),
446 url.getPort(), null /* no file */,
447 url.getUsername(), null /* no password */);
448 else
449 return url;
450 }
451
452 /**
453 * Set the URLName representing this service.
454 * Normally used to update the <code>url</code> field
455 * after a service has successfully connected. <p>
456 *
457 * Subclasses should only override this method if their
458 * URL does not follow the standard format. In particular,
459 * subclasses should override this method if their URL
460 * does not require all the possible fields supported by
461 * <code>URLName</code>; a new <code>URLName</code> should
462 * be constructed with any unneeded fields removed. <p>
463 *
464 * The implementation in the Service class simply sets the
465 * <code>url</code> field.
466 *
467 * @see URLName
468 */
469 protected synchronized void setURLName(URLName url) {
470 this.url = url;
471 }
472
473 /**
474 * Add a listener for Connection events on this service. <p>
475 *
476 * The default implementation provided here adds this listener
477 * to an internal list of ConnectionListeners.
478 *
479 * @param l the Listener for Connection events
480 * @see javax.mail.event.ConnectionEvent
481 */
482 public synchronized void addConnectionListener(ConnectionListener l) {
483 if (connectionListeners == null)
484 connectionListeners = new Vector();
485 connectionListeners.addElement(l);
486 }
487
488 /**
489 * Remove a Connection event listener. <p>
490 *
491 * The default implementation provided here removes this listener
492 * from the internal list of ConnectionListeners.
493 *
494 * @param l the listener
495 * @see #addConnectionListener
496 */
497 public synchronized void removeConnectionListener(ConnectionListener l) {
498 if (connectionListeners != null)
499 connectionListeners.removeElement(l);
500 }
501
502 /**
503 * Notify all ConnectionListeners. Service implementations are
504 * expected to use this method to broadcast connection events. <p>
505 *
506 * The provided default implementation queues the event into
507 * an internal event queue. An event dispatcher thread dequeues
508 * events from the queue and dispatches them to the registered
509 * ConnectionListeners. Note that the event dispatching occurs
510 * in a separate thread, thus avoiding potential deadlock problems.
511 */
512 protected synchronized void notifyConnectionListeners(int type) {
513 if (connectionListeners != null) {
514 ConnectionEvent e = new ConnectionEvent(this, type);
515 queueEvent(e, connectionListeners);
516 }
517
518 /* Fix for broken JDK1.1.x Garbage collector :
519 * The 'conservative' GC in JDK1.1.x occasionally fails to
520 * garbage-collect Threads which are in the wait state.
521 * This would result in thread (and consequently memory) leaks.
522 *
523 * We attempt to fix this by sending a 'terminator' event
524 * to the queue, after we've sent the CLOSED event. The
525 * terminator event causes the event-dispatching thread to
526 * self destruct.
527 */
528 if (type == ConnectionEvent.CLOSED)
529 terminateQueue();
530 }
531
532 /**
533 * Return <code>getURLName.toString()</code> if this service has a URLName,
534 * otherwise it will return the default <code>toString</code>.
535 */
536 public String toString() {
537 URLName url = getURLName();
538 if (url != null)
539 return url.toString();
540 else
541 return super.toString();
542 }
543
544 /*
545 * The queue of events to be delivered.
546 */
547 private EventQueue q;
548
549 /*
550 * A lock for creating the EventQueue object. Only one thread should
551 * create an EventQueue for this service. We can't synchronize on the
552 * service's lock because that might violate the locking hierarchy in
553 * some cases.
554 */
555 private Object qLock = new Object();
556
557 /**
558 * Add the event and vector of listeners to the queue to be delivered.
559 */
560 protected void queueEvent(MailEvent event, Vector vector) {
561 // synchronize creation of the event queue
562 synchronized (qLock) {
563 if (q == null)
564 q = new EventQueue();
565 }
566
567 /*
568 * Copy the vector in order to freeze the state of the set
569 * of EventListeners the event should be delivered to prior
570 * to delivery. This ensures that any changes made to the
571 * Vector from a target listener's method during the delivery
572 * of this event will not take effect until after the event is
573 * delivered.
574 */
575 Vector v = (Vector)vector.clone();
576 q.enqueue(event, v);
577 }
578
579 static class TerminatorEvent extends MailEvent {
580 private static final long serialVersionUID = 5542172141759168416L;
581
582 TerminatorEvent() {
583 super(new Object());
584 }
585
586 public void dispatch(Object listener) {
587 // Kill the event dispatching thread.
588 Thread.currentThread().interrupt();
589 }
590 }
591
592 // Dispatch the terminator
593 private void terminateQueue() {
594 synchronized (qLock) {
595 if (q != null) {
596 Vector dummyListeners = new Vector();
597 dummyListeners.setSize(1); // need atleast one listener
598 q.enqueue(new TerminatorEvent(), dummyListeners);
599 q = null;
600 }
601 }
602 }
603
604 /**
605 * Stop the event dispatcher thread so the queue can be garbage collected.
606 */
607 protected void finalize() throws Throwable {
608 super.finalize();
609 terminateQueue();
610 }
611 }