001 /** 002 * 003 * Copyright 2003-2006 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package javax.mail; 019 020 import java.net.InetAddress; 021 import java.net.UnknownHostException; 022 import java.util.Vector; 023 024 import javax.mail.event.ConnectionEvent; 025 import javax.mail.event.ConnectionListener; 026 import javax.mail.event.MailEvent; 027 028 /** 029 * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $ 030 */ 031 public abstract class Service { 032 /** 033 * The session from which this service was created. 034 */ 035 protected Session session; 036 /** 037 * The URLName of this service 038 */ 039 protected URLName url; 040 /** 041 * Debug flag for this service, set from the Session's debug flag. 042 */ 043 protected boolean debug; 044 045 private boolean connected; 046 private final Vector connectionListeners = new Vector(2); 047 private final EventQueue queue = new EventQueue(); 048 049 /** 050 * Construct a new Service. 051 * @param session the session from which this service was created 052 * @param url the URLName of this service 053 */ 054 protected Service(Session session, URLName url) { 055 this.session = session; 056 this.url = url; 057 this.debug = session.getDebug(); 058 } 059 060 /** 061 * A generic connect method that takes no parameters allowing subclasses 062 * to implement an appropriate authentication scheme. 063 * The default implementation calls <code>connect(null, null, null)</code> 064 * @throws AuthenticationFailedException if authentication fails 065 * @throws MessagingException for other failures 066 */ 067 public void connect() throws MessagingException { 068 connect(null, null, null); 069 } 070 071 /** 072 * Connect to the specified host using a simple username/password authenticaion scheme 073 * and the default port. 074 * The default implementation calls <code>connect(host, -1, user, password)</code> 075 * 076 * @param host the host to connect to 077 * @param user the user name 078 * @param password the user's password 079 * @throws AuthenticationFailedException if authentication fails 080 * @throws MessagingException for other failures 081 */ 082 public void connect(String host, String user, String password) throws MessagingException { 083 connect(host, -1, user, password); 084 } 085 086 /** 087 * Connect to the specified host using a simple username/password authenticaion scheme 088 * and the default host and port. 089 * The default implementation calls <code>connect(host, -1, user, password)</code> 090 * 091 * @param user the user name 092 * @param password the user's password 093 * @throws AuthenticationFailedException if authentication fails 094 * @throws MessagingException for other failures 095 */ 096 public void connect(String user, String password) throws MessagingException { 097 connect(null, -1, user, password); 098 } 099 100 /** 101 * Connect to the specified host at the specified port using a simple username/password authenticaion scheme. 102 * 103 * If this Service is already connected, an IllegalStateException is thrown. 104 * 105 * @param host the host to connect to 106 * @param port the port to connect to; pass -1 to use the default for the protocol 107 * @param user the user name 108 * @param password the user's password 109 * @throws AuthenticationFailedException if authentication fails 110 * @throws MessagingException for other failures 111 * @throws IllegalStateException if this service is already connected 112 */ 113 public void connect(String host, int port, String user, String password) throws MessagingException { 114 115 if (isConnected()) { 116 throw new IllegalStateException("Already connected"); 117 } 118 119 // before we try to connect, we need to derive values for some parameters that may not have 120 // been explicitly specified. For example, the normal connect() method leaves us to derive all 121 // of these from other sources. Some of the values are derived from our URLName value, others 122 // from session parameters. We need to go through all of these to develop a set of values we 123 // can connect with. 124 125 // this is the protocol we're connecting with. We use this largely to derive configured values from 126 // session properties. 127 String protocol = null; 128 129 // if we're working with the URL form, then we can retrieve the protocol from the URL. 130 if (url != null) { 131 protocol = url.getProtocol(); 132 } 133 134 // now try to derive values for any of the arguments we've been given as defaults 135 if (host == null) { 136 // first choice is from the url, if we have 137 if (url != null) { 138 host = url.getHost(); 139 // it is possible that this could return null (rare). If it does, try to get a 140 // value from a protocol specific session variable. 141 if (host == null) { 142 host = session.getProperty("mail." + protocol + ".host"); 143 } 144 } 145 // this may still be null...get the global mail property 146 if (host == null) { 147 host = session.getProperty("mail.host"); 148 } 149 } 150 151 // ok, go after userid information next. 152 if (user == null) { 153 // first choice is from the url, if we have 154 if (url != null) { 155 user = url.getUsername(); 156 // make sure we get the password from the url, if we can. 157 if (password == null) { 158 password = url.getPassword(); 159 } 160 // user still null? We have several levels of properties to try yet 161 if (user == null) { 162 user = session.getProperty("mail." + protocol + ".user"); 163 } 164 } 165 166 // this may still be null...get the global mail property 167 if (user == null) { 168 user = session.getProperty("mail.user"); 169 } 170 171 // finally, we try getting the system defined user name 172 try { 173 user = System.getProperty("user.name"); 174 } catch (SecurityException e) { 175 // we ignore this, and just us a null username. 176 } 177 } 178 // if we have an explicitly given user name, we need to see if this matches the url one and 179 // grab the password from there. 180 else { 181 if (url != null && user.equals(url.getUsername())) { 182 password = url.getPassword(); 183 } 184 } 185 186 // we need to update the URLName associated with this connection once we have all of the information, 187 // which means we also need to propogate the file portion of the URLName if we have this form when 188 // we start. 189 String file = null; 190 if (url != null) { 191 file = url.getFile(); 192 } 193 194 // see if we have cached security information to use. If this is not cached, we'll save it 195 // after we successfully connect. 196 boolean cachePassword = false; 197 198 199 // still have a null password to this point, and using a url form? 200 if (password == null && url != null) { 201 // construct a new URL, filling in any pieces that may have been explicitly specified. 202 setURLName(new URLName(protocol, host, port, file, user, password)); 203 // now see if we have a saved password from a previous request. 204 PasswordAuthentication cachedPassword = session.getPasswordAuthentication(getURLName()); 205 206 // if we found a saved one, see if we need to get any the pieces from here. 207 if (cachedPassword != null) { 208 // not even a resolved userid? Then use both bits. 209 if (user == null) { 210 user = cachedPassword.getUserName(); 211 password = cachedPassword.getPassword(); 212 } 213 // our user name must match the cached name to be valid. 214 else if (user.equals(cachedPassword.getUserName())) { 215 password = cachedPassword.getPassword(); 216 } 217 } 218 else 219 { 220 // nothing found in the cache, so we need to save this if we can connect successfully. 221 cachePassword = true; 222 } 223 } 224 225 // we've done our best up to this point to obtain all of the information needed to make the 226 // connection. Now we pass this off to the protocol handler to see if it works. If we get a 227 // connection failure, we may need to prompt for a password before continuing. 228 try { 229 connected = protocolConnect(host, port, user, password); 230 } 231 catch (AuthenticationFailedException e) { 232 } 233 234 if (!connected) { 235 InetAddress ipAddress = null; 236 237 try { 238 ipAddress = InetAddress.getByName(host); 239 } catch (UnknownHostException e) { 240 } 241 242 // now ask the session to try prompting for a password. 243 PasswordAuthentication promptPassword = session.requestPasswordAuthentication(ipAddress, port, protocol, null, user); 244 245 // if we were able to obtain new information from the session, then try again using the 246 // provided information . 247 if (promptPassword != null) { 248 user = promptPassword.getUserName(); 249 password = promptPassword.getPassword(); 250 } 251 252 connected = protocolConnect(host, port, user, password); 253 } 254 255 256 // if we're still not connected, then this is an exception. 257 if (!connected) { 258 throw new AuthenticationFailedException(); 259 } 260 261 // the URL name needs to reflect the most recent information. 262 setURLName(new URLName(protocol, host, port, file, user, password)); 263 264 // we need to update the global password cache with this information. 265 if (cachePassword) { 266 session.setPasswordAuthentication(getURLName(), new PasswordAuthentication(user, password)); 267 } 268 269 // we're now connected....broadcast this to any interested parties. 270 setConnected(connected); 271 notifyConnectionListeners(ConnectionEvent.OPENED); 272 } 273 274 /** 275 * Attempt the protocol-specific connection; subclasses should override this to establish 276 * a connection in the appropriate manner. 277 * 278 * This method should return true if the connection was established. 279 * It may return false to cause the {@link #connect(String, int, String, String)} method to 280 * reattempt the connection after trying to obtain user and password information from the user. 281 * Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt. 282 * 283 * @param host 284 * @param port 285 * @param user 286 * @param password 287 * @return 288 * @throws AuthenticationFailedException if authentication fails 289 * @throws MessagingException for other failures 290 */ 291 protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException { 292 return false; 293 } 294 295 /** 296 * Check if this service is currently connected. 297 * The default implementation simply returns the value of a private boolean field; 298 * subclasses may wish to override this method to verify the physical connection. 299 * 300 * @return true if this service is connected 301 */ 302 public boolean isConnected() { 303 return connected; 304 } 305 306 /** 307 * Notification to subclasses that the connection state has changed. 308 * This method is called by the connect() and close() methods to indicate state change; 309 * subclasses should also call this method if the connection is automatically closed 310 * for some reason. 311 * 312 * @param connected the connection state 313 */ 314 protected void setConnected(boolean connected) { 315 this.connected = connected; 316 } 317 318 /** 319 * Close this service and terminate its physical connection. 320 * The default implementation simply calls setConnected(false) and then 321 * sends a CLOSED event to all registered ConnectionListeners. 322 * Subclasses overriding this method should still ensure it is closed; they should 323 * also ensure that it is called if the connection is closed automatically, for 324 * for example in a finalizer. 325 * 326 *@throws MessagingException if there were errors closing; the connection is still closed 327 */ 328 public void close() throws MessagingException { 329 setConnected(false); 330 notifyConnectionListeners(ConnectionEvent.CLOSED); 331 } 332 333 /** 334 * Return a copy of the URLName representing this service with the password and file information removed. 335 * 336 * @return the URLName for this service 337 */ 338 public URLName getURLName() { 339 340 return url == null ? null : new URLName(url.getProtocol(), url.getHost(), url.getPort(), null, url.getUsername(), null); 341 } 342 343 /** 344 * Set the url field. 345 * @param url the new value 346 */ 347 protected void setURLName(URLName url) { 348 this.url = url; 349 } 350 351 public void addConnectionListener(ConnectionListener listener) { 352 connectionListeners.add(listener); 353 } 354 355 public void removeConnectionListener(ConnectionListener listener) { 356 connectionListeners.remove(listener); 357 } 358 359 protected void notifyConnectionListeners(int type) { 360 queue.queueEvent(new ConnectionEvent(this, type), connectionListeners); 361 } 362 363 public String toString() { 364 return url == null ? super.toString() : url.toString(); 365 } 366 367 protected void queueEvent(MailEvent event, Vector listeners) { 368 queue.queueEvent(event, listeners); 369 } 370 371 protected void finalize() throws Throwable { 372 queue.stop(); 373 connectionListeners.clear(); 374 super.finalize(); 375 } 376 377 378 /** 379 * Package scope utility method to allow Message instances 380 * access to the Service's session. 381 * 382 * @return The Session the service is associated with. 383 */ 384 Session getSession() { 385 return session; 386 } 387 }