001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: /*
038: * @(#)Service.java 1.33 07/05/14
039: */
040:
041: package javax.mail;
042:
043: import java.io.*;
044: import java.net.*;
045: import java.util.*;
046: import javax.mail.event.*;
047:
048: /**
049: * An abstract class that contains the functionality
050: * common to messaging services, such as stores and transports. <p>
051: * A messaging service is created from a <code>Session</code> and is
052: * named using a <code>URLName</code>. A service must be connected
053: * before it can be used. Connection events are sent to reflect
054: * its connection status.
055: *
056: * @author Christopher Cotton
057: * @author Bill Shannon
058: * @author Kanwar Oberoi
059: * @version 1.33, 07/05/14
060: */
061:
062: public abstract class Service {
063:
064: /**
065: * The session from which this service was created.
066: */
067: protected Session session;
068:
069: /**
070: * The <code>URLName</code> of this service.
071: */
072: protected URLName url = null;
073:
074: /**
075: * Debug flag for this service. Set from the session's debug
076: * flag when this service is created.
077: */
078: protected boolean debug = false;
079:
080: private boolean connected = false;
081: private Vector connectionListeners = null;
082:
083: /**
084: * Constructor.
085: *
086: * @param session Session object for this service
087: * @param urlname URLName object to be used for this service
088: */
089: protected Service(Session session, URLName urlname) {
090: this .session = session;
091: url = urlname;
092: debug = session.getDebug();
093: }
094:
095: /**
096: * A generic connect method that takes no parameters. Subclasses
097: * can implement the appropriate authentication schemes. Subclasses
098: * that need additional information might want to use some properties
099: * 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)
189: throws MessagingException {
190: connect(null, user, password);
191: }
192:
193: /**
194: * Similar to connect(host, user, password) except a specific port
195: * can be specified.
196: *
197: * @param host the host to connect to
198: * @param port the port to connect to (-1 means the default port)
199: * @param user the user name
200: * @param password this user's password
201: * @exception AuthenticationFailedException for authentication failures
202: * @exception MessagingException for other failures
203: * @exception IllegalStateException if the service is already connected
204: * @see #connect(java.lang.String, java.lang.String, java.lang.String)
205: * @see javax.mail.event.ConnectionEvent
206: */
207: public synchronized void connect(String host, int port,
208: String user, String password) throws MessagingException {
209:
210: // see if the service is already connected
211: if (isConnected())
212: throw new IllegalStateException("already connected");
213:
214: PasswordAuthentication pw;
215: boolean connected = false;
216: boolean save = false;
217: String protocol = null;
218: String file = null;
219:
220: // get whatever information we can from the URL
221: // XXX - url should always be non-null here, Session
222: // passes it into the constructor
223: if (url != null) {
224: protocol = url.getProtocol();
225: if (host == null)
226: host = url.getHost();
227: if (port == -1)
228: port = url.getPort();
229:
230: if (user == null) {
231: user = url.getUsername();
232: if (password == null) // get password too if we need it
233: password = url.getPassword();
234: } else {
235: if (password == null && user.equals(url.getUsername()))
236: // only get the password if it matches the username
237: password = url.getPassword();
238: }
239:
240: file = url.getFile();
241: }
242:
243: // try to get protocol-specific default properties
244: if (protocol != null) {
245: if (host == null)
246: host = session
247: .getProperty("mail." + protocol + ".host");
248: if (user == null)
249: user = session
250: .getProperty("mail." + protocol + ".user");
251: }
252:
253: // try to get mail-wide default properties
254: if (host == null)
255: host = session.getProperty("mail.host");
256:
257: if (user == null)
258: user = session.getProperty("mail.user");
259:
260: // try using the system username
261: if (user == null) {
262: try {
263: user = System.getProperty("user.name");
264: } catch (SecurityException sex) {
265: if (debug)
266: sex.printStackTrace(session.getDebugOut());
267: }
268: }
269:
270: // if we don't have a password, look for saved authentication info
271: if (password == null && url != null) {
272: // canonicalize the URLName
273: setURLName(new URLName(protocol, host, port, file, user,
274: null));
275: pw = session.getPasswordAuthentication(getURLName());
276: if (pw != null) {
277: if (user == null) {
278: user = pw.getUserName();
279: password = pw.getPassword();
280: } else if (user.equals(pw.getUserName())) {
281: password = pw.getPassword();
282: }
283: } else
284: save = true;
285: }
286:
287: // try connecting, if the protocol needs some missing
288: // information (user, password) it will not connect.
289: // if it tries to connect and fails, remember why for later.
290: AuthenticationFailedException authEx = null;
291: try {
292: connected = protocolConnect(host, port, user, password);
293: } catch (AuthenticationFailedException ex) {
294: authEx = ex;
295: }
296:
297: // if not connected, ask the user and try again
298: if (!connected) {
299: InetAddress addr;
300: try {
301: addr = InetAddress.getByName(host);
302: } catch (UnknownHostException e) {
303: addr = null;
304: }
305: pw = session.requestPasswordAuthentication(addr, port,
306: protocol, null, user);
307: if (pw != null) {
308: user = pw.getUserName();
309: password = pw.getPassword();
310:
311: // have the service connect again
312: connected = protocolConnect(host, port, user, password);
313: }
314: }
315:
316: // if we're not connected by now, we give up
317: if (!connected) {
318: if (authEx != null)
319: throw authEx;
320: else
321: throw new AuthenticationFailedException();
322: }
323:
324: setURLName(new URLName(protocol, host, port, file, user,
325: password));
326:
327: if (save)
328: session.setPasswordAuthentication(getURLName(),
329: new PasswordAuthentication(user, password));
330:
331: // set our connected state
332: setConnected(true);
333:
334: // finally, deliver the connection event
335: notifyConnectionListeners(ConnectionEvent.OPENED);
336: }
337:
338: /**
339: * The service implementation should override this method to
340: * perform the actual protocol-specific connection attempt.
341: * The default implementation of the <code>connect</code> method
342: * calls this method as needed. <p>
343: *
344: * The <code>protocolConnect</code> method should return
345: * <code>false</code> if a user name or password is required
346: * for authentication but the corresponding parameter is null;
347: * the <code>connect</code> method will prompt the user when
348: * needed to supply missing information. This method may
349: * also return <code>false</code> if authentication fails for
350: * the supplied user name or password. Alternatively, this method
351: * may throw an AuthenticationFailedException when authentication
352: * fails. This exception may include a String message with more
353: * detail about the failure. <p>
354: *
355: * The <code>protocolConnect</code> method should throw an
356: * exception to report failures not related to authentication,
357: * such as an invalid host name or port number, loss of a
358: * connection during the authentication process, unavailability
359: * of the server, etc.
360: *
361: * @param host the name of the host to connect to
362: * @param port the port to use (-1 means use default port)
363: * @param user the name of the user to login as
364: * @param password the user's password
365: * @return true if connection successful, false if authentication failed
366: * @exception AuthenticationFailedException for authentication failures
367: * @exception MessagingException for non-authentication failures
368: */
369: protected boolean protocolConnect(String host, int port,
370: String user, String password) throws MessagingException {
371: return false;
372: }
373:
374: /**
375: * Is this service currently connected? <p>
376: *
377: * This implementation uses a private boolean field to
378: * store the connection state. This method returns the value
379: * of that field. <p>
380: *
381: * Subclasses may want to override this method to verify that any
382: * connection to the message store is still alive.
383: *
384: * @return true if the service is connected, false if it is not connected
385: */
386: public synchronized boolean isConnected() {
387: return connected;
388: }
389:
390: /**
391: * Set the connection state of this service. The connection state
392: * will automatically be set by the service implementation during the
393: * <code>connect</code> and <code>close</code> methods.
394: * Subclasses will need to call this method to set the state
395: * if the service was automatically disconnected. <p>
396: *
397: * The implementation in this class merely sets the private field
398: * returned by the <code>isConnected</code> method.
399: *
400: * @param connected true if the service is connected,
401: * false if it is not connected
402: */
403: protected synchronized void setConnected(boolean connected) {
404: this .connected = connected;
405: }
406:
407: /**
408: * Close this service and terminate its connection. A close
409: * ConnectionEvent is delivered to any ConnectionListeners. Any
410: * Messaging components (Folders, Messages, etc.) belonging to this
411: * service are invalid after this service is closed. Note that the service
412: * is closed even if this method terminates abnormally by throwing
413: * a MessagingException. <p>
414: *
415: * This implementation uses <code>setConnected(false)</code> to set
416: * this service's connected state to <code>false</code>. It will then
417: * send a close ConnectionEvent to any registered ConnectionListeners.
418: * Subclasses overriding this method to do implementation specific
419: * cleanup should call this method as a last step to insure event
420: * notification, probably by including a call to <code>super.close()</code>
421: * in a <code>finally</code> clause.
422: *
423: * @see javax.mail.event.ConnectionEvent
424: * @throws MessagingException for errors while closing
425: */
426: public synchronized void close() throws MessagingException {
427: setConnected(false);
428: notifyConnectionListeners(ConnectionEvent.CLOSED);
429: }
430:
431: /**
432: * Return a URLName representing this service. The returned URLName
433: * does <em>not</em> include the password field. <p>
434: *
435: * Subclasses should only override this method if their
436: * URLName does not follow the standard format. <p>
437: *
438: * The implementation in the Service class returns (usually a copy of)
439: * the <code>url</code> field with the password and file information
440: * stripped out.
441: *
442: * @return the URLName representing this service
443: * @see URLName
444: */
445: public synchronized URLName getURLName() {
446: if (url != null
447: && (url.getPassword() != null || url.getFile() != null))
448: return new URLName(url.getProtocol(), url.getHost(), url
449: .getPort(), null /* no file */, url.getUsername(),
450: null /* no password */);
451: else
452: return url;
453: }
454:
455: /**
456: * Set the URLName representing this service.
457: * Normally used to update the <code>url</code> field
458: * after a service has successfully connected. <p>
459: *
460: * Subclasses should only override this method if their
461: * URL does not follow the standard format. In particular,
462: * subclasses should override this method if their URL
463: * does not require all the possible fields supported by
464: * <code>URLName</code>; a new <code>URLName</code> should
465: * be constructed with any unneeded fields removed. <p>
466: *
467: * The implementation in the Service class simply sets the
468: * <code>url</code> field.
469: *
470: * @see URLName
471: */
472: protected synchronized void setURLName(URLName url) {
473: this .url = url;
474: }
475:
476: /**
477: * Add a listener for Connection events on this service. <p>
478: *
479: * The default implementation provided here adds this listener
480: * to an internal list of ConnectionListeners.
481: *
482: * @param l the Listener for Connection events
483: * @see javax.mail.event.ConnectionEvent
484: */
485: public synchronized void addConnectionListener(ConnectionListener l) {
486: if (connectionListeners == null)
487: connectionListeners = new Vector();
488: connectionListeners.addElement(l);
489: }
490:
491: /**
492: * Remove a Connection event listener. <p>
493: *
494: * The default implementation provided here removes this listener
495: * from the internal list of ConnectionListeners.
496: *
497: * @param l the listener
498: * @see #addConnectionListener
499: */
500: public synchronized void removeConnectionListener(
501: ConnectionListener l) {
502: if (connectionListeners != null)
503: connectionListeners.removeElement(l);
504: }
505:
506: /**
507: * Notify all ConnectionListeners. Service implementations are
508: * expected to use this method to broadcast connection events. <p>
509: *
510: * The provided default implementation queues the event into
511: * an internal event queue. An event dispatcher thread dequeues
512: * events from the queue and dispatches them to the registered
513: * ConnectionListeners. Note that the event dispatching occurs
514: * in a separate thread, thus avoiding potential deadlock problems.
515: */
516: protected synchronized void notifyConnectionListeners(int type) {
517: if (connectionListeners != null) {
518: ConnectionEvent e = new ConnectionEvent(this , type);
519: queueEvent(e, connectionListeners);
520: }
521:
522: /* Fix for broken JDK1.1.x Garbage collector :
523: * The 'conservative' GC in JDK1.1.x occasionally fails to
524: * garbage-collect Threads which are in the wait state.
525: * This would result in thread (and consequently memory) leaks.
526: *
527: * We attempt to fix this by sending a 'terminator' event
528: * to the queue, after we've sent the CLOSED event. The
529: * terminator event causes the event-dispatching thread to
530: * self destruct.
531: */
532: if (type == ConnectionEvent.CLOSED)
533: terminateQueue();
534: }
535:
536: /**
537: * Return <code>getURLName.toString()</code> if this service has a URLName,
538: * otherwise it will return the default <code>toString</code>.
539: */
540: public String toString() {
541: URLName url = getURLName();
542: if (url != null)
543: return url.toString();
544: else
545: return super .toString();
546: }
547:
548: /*
549: * The queue of events to be delivered.
550: */
551: private EventQueue q;
552:
553: /*
554: * A lock for creating the EventQueue object. Only one thread should
555: * create an EventQueue for this service. We can't synchronize on the
556: * service's lock because that might violate the locking hierarchy in
557: * some cases.
558: */
559: private Object qLock = new Object();
560:
561: /**
562: * Add the event and vector of listeners to the queue to be delivered.
563: */
564: protected void queueEvent(MailEvent event, Vector vector) {
565: // synchronize creation of the event queue
566: synchronized (qLock) {
567: if (q == null)
568: q = new EventQueue();
569: }
570:
571: /*
572: * Copy the vector in order to freeze the state of the set
573: * of EventListeners the event should be delivered to prior
574: * to delivery. This ensures that any changes made to the
575: * Vector from a target listener's method during the delivery
576: * of this event will not take effect until after the event is
577: * delivered.
578: */
579: Vector v = (Vector) vector.clone();
580: q.enqueue(event, v);
581: }
582:
583: static class TerminatorEvent extends MailEvent {
584: private static final long serialVersionUID = 5542172141759168416L;
585:
586: TerminatorEvent() {
587: super (new Object());
588: }
589:
590: public void dispatch(Object listener) {
591: // Kill the event dispatching thread.
592: Thread.currentThread().interrupt();
593: }
594: }
595:
596: // Dispatch the terminator
597: private void terminateQueue() {
598: synchronized (qLock) {
599: if (q != null) {
600: Vector dummyListeners = new Vector();
601: dummyListeners.setSize(1); // need atleast one listener
602: q.enqueue(new TerminatorEvent(), dummyListeners);
603: q = null;
604: }
605: }
606: }
607:
608: /**
609: * Stop the event dispatcher thread so the queue can be garbage collected.
610: */
611: protected void finalize() throws Throwable {
612: super.finalize();
613: terminateQueue();
614: }
615: }
|