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