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