1 /**
2 *
3 * Copyright 2003-2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package javax.mail;
19
20 import java.net.InetAddress;
21 import java.net.UnknownHostException;
22 import java.util.Vector;
23
24 import javax.mail.event.ConnectionEvent;
25 import javax.mail.event.ConnectionListener;
26 import javax.mail.event.MailEvent;
27
28 /**
29 * @version $Rev: 421852 $ $Date: 2006-07-14 03:02:19 -0700 (Fri, 14 Jul 2006) $
30 */
31 public abstract class Service {
32 /**
33 * The session from which this service was created.
34 */
35 protected Session session;
36 /**
37 * The URLName of this service
38 */
39 protected URLName url;
40 /**
41 * Debug flag for this service, set from the Session's debug flag.
42 */
43 protected boolean debug;
44
45 private boolean connected;
46 private final Vector connectionListeners = new Vector(2);
47 private final EventQueue queue = new EventQueue();
48
49 /**
50 * Construct a new Service.
51 * @param session the session from which this service was created
52 * @param url the URLName of this service
53 */
54 protected Service(Session session, URLName url) {
55 this.session = session;
56 this.url = url;
57 this.debug = session.getDebug();
58 }
59
60 /**
61 * A generic connect method that takes no parameters allowing subclasses
62 * to implement an appropriate authentication scheme.
63 * The default implementation calls <code>connect(null, null, null)</code>
64 * @throws AuthenticationFailedException if authentication fails
65 * @throws MessagingException for other failures
66 */
67 public void connect() throws MessagingException {
68 connect(null, null, null);
69 }
70
71 /**
72 * Connect to the specified host using a simple username/password authenticaion scheme
73 * and the default port.
74 * The default implementation calls <code>connect(host, -1, user, password)</code>
75 *
76 * @param host the host to connect to
77 * @param user the user name
78 * @param password the user's password
79 * @throws AuthenticationFailedException if authentication fails
80 * @throws MessagingException for other failures
81 */
82 public void connect(String host, String user, String password) throws MessagingException {
83 connect(host, -1, user, password);
84 }
85
86 /**
87 * Connect to the specified host using a simple username/password authenticaion scheme
88 * and the default host and port.
89 * The default implementation calls <code>connect(host, -1, user, password)</code>
90 *
91 * @param user the user name
92 * @param password the user's password
93 * @throws AuthenticationFailedException if authentication fails
94 * @throws MessagingException for other failures
95 */
96 public void connect(String user, String password) throws MessagingException {
97 connect(null, -1, user, password);
98 }
99
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
120
121
122
123
124
125
126
127 String protocol = null;
128
129
130 if (url != null) {
131 protocol = url.getProtocol();
132 }
133
134
135 if (host == null) {
136
137 if (url != null) {
138 host = url.getHost();
139
140
141 if (host == null) {
142 host = session.getProperty("mail." + protocol + ".host");
143 }
144 }
145
146 if (host == null) {
147 host = session.getProperty("mail.host");
148 }
149 }
150
151
152 if (user == null) {
153
154 if (url != null) {
155 user = url.getUsername();
156
157 if (password == null) {
158 password = url.getPassword();
159 }
160
161 if (user == null) {
162 user = session.getProperty("mail." + protocol + ".user");
163 }
164 }
165
166
167 if (user == null) {
168 user = session.getProperty("mail.user");
169 }
170
171
172 try {
173 user = System.getProperty("user.name");
174 } catch (SecurityException e) {
175
176 }
177 }
178
179
180 else {
181 if (url != null && user.equals(url.getUsername())) {
182 password = url.getPassword();
183 }
184 }
185
186
187
188
189 String file = null;
190 if (url != null) {
191 file = url.getFile();
192 }
193
194
195
196 boolean cachePassword = false;
197
198
199
200 if (password == null && url != null) {
201
202 setURLName(new URLName(protocol, host, port, file, user, password));
203
204 PasswordAuthentication cachedPassword = session.getPasswordAuthentication(getURLName());
205
206
207 if (cachedPassword != null) {
208
209 if (user == null) {
210 user = cachedPassword.getUserName();
211 password = cachedPassword.getPassword();
212 }
213
214 else if (user.equals(cachedPassword.getUserName())) {
215 password = cachedPassword.getPassword();
216 }
217 }
218 else
219 {
220
221 cachePassword = true;
222 }
223 }
224
225
226
227
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
243 PasswordAuthentication promptPassword = session.requestPasswordAuthentication(ipAddress, port, protocol, null, user);
244
245
246
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
257 if (!connected) {
258 throw new AuthenticationFailedException();
259 }
260
261
262 setURLName(new URLName(protocol, host, port, file, user, password));
263
264
265 if (cachePassword) {
266 session.setPasswordAuthentication(getURLName(), new PasswordAuthentication(user, password));
267 }
268
269
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 }