1 /**
2 *
3 * Copyright 2003-2004 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: 390529 $ $Date: 2006-03-31 14:40:02 -0800 (Fri, 31 Mar 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 at the specified port using a simple username/password authenticaion scheme.
88 *
89 * If this Service is already connected, an IllegalStateException is thrown.
90 *
91 * @param host the host to connect to
92 * @param port the port to connect to; pass -1 to use the default for the protocol
93 * @param user the user name
94 * @param password the user's password
95 * @throws AuthenticationFailedException if authentication fails
96 * @throws MessagingException for other failures
97 * @throws IllegalStateException if this service is already connected
98 */
99 public void connect(String host, int port, String user, String password) throws MessagingException {
100
101 if (isConnected()) {
102 throw new IllegalStateException("Already connected");
103 }
104
105
106
107
108
109
110
111
112
113 String protocol = null;
114
115
116 if (url != null) {
117 protocol = url.getProtocol();
118 }
119
120
121 if (host == null) {
122
123 if (url != null) {
124 host = url.getHost();
125
126
127 if (host == null) {
128 host = session.getProperty("mail." + protocol + ".host");
129 }
130 }
131
132 if (host == null) {
133 host = session.getProperty("mail.host");
134 }
135 }
136
137
138 if (user == null) {
139
140 if (url != null) {
141 user = url.getUsername();
142
143 if (password == null) {
144 password = url.getPassword();
145 }
146
147 if (user == null) {
148 user = session.getProperty("mail." + protocol + ".user");
149 }
150 }
151
152
153 if (user == null) {
154 user = session.getProperty("mail.user");
155 }
156
157
158 try {
159 user = System.getProperty("user.name");
160 } catch (SecurityException e) {
161
162 }
163 }
164
165
166 else {
167 if (url != null && user.equals(url.getUsername())) {
168 password = url.getPassword();
169 }
170 }
171
172
173
174
175 String file = null;
176 if (url != null) {
177 file = url.getFile();
178 }
179
180
181
182 boolean cachePassword = false;
183
184
185
186 if (password == null && url != null) {
187
188 setURLName(new URLName(protocol, host, port, file, user, password));
189
190 PasswordAuthentication cachedPassword = session.getPasswordAuthentication(getURLName());
191
192
193 if (cachedPassword != null) {
194
195 if (user == null) {
196 user = cachedPassword.getUserName();
197 password = cachedPassword.getPassword();
198 }
199
200 else if (user.equals(cachedPassword.getUserName())) {
201 password = cachedPassword.getPassword();
202 }
203 }
204 else
205 {
206
207 cachePassword = true;
208 }
209 }
210
211
212
213
214 try {
215 connected = protocolConnect(host, port, user, password);
216 }
217 catch (AuthenticationFailedException e) {
218 }
219
220 if (!connected) {
221 InetAddress ipAddress = null;
222
223 try {
224 ipAddress = InetAddress.getByName(host);
225 } catch (UnknownHostException e) {
226 }
227
228
229 PasswordAuthentication promptPassword = session.requestPasswordAuthentication(ipAddress, port, protocol, null, user);
230
231
232
233 if (promptPassword != null) {
234 user = promptPassword.getUserName();
235 password = promptPassword.getPassword();
236 }
237
238 connected = protocolConnect(host, port, user, password);
239 }
240
241
242
243 if (!connected) {
244 throw new AuthenticationFailedException();
245 }
246
247
248 setURLName(new URLName(protocol, host, port, file, user, password));
249
250
251 if (cachePassword) {
252 session.setPasswordAuthentication(getURLName(), new PasswordAuthentication(user, password));
253 }
254
255
256 setConnected(connected);
257 notifyConnectionListeners(ConnectionEvent.OPENED);
258 }
259
260 /**
261 * Attempt the protocol-specific connection; subclasses should override this to establish
262 * a connection in the appropriate manner.
263 *
264 * This method should return true if the connection was established.
265 * It may return false to cause the {@link #connect(String, int, String, String)} method to
266 * reattempt the connection after trying to obtain user and password information from the user.
267 * Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt.
268 *
269 * @param host
270 * @param port
271 * @param user
272 * @param password
273 * @return
274 * @throws AuthenticationFailedException if authentication fails
275 * @throws MessagingException for other failures
276 */
277 protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException {
278 return false;
279 }
280
281 /**
282 * Check if this service is currently connected.
283 * The default implementation simply returns the value of a private boolean field;
284 * subclasses may wish to override this method to verify the physical connection.
285 *
286 * @return true if this service is connected
287 */
288 public boolean isConnected() {
289 return connected;
290 }
291
292 /**
293 * Notification to subclasses that the connection state has changed.
294 * This method is called by the connect() and close() methods to indicate state change;
295 * subclasses should also call this method if the connection is automatically closed
296 * for some reason.
297 *
298 * @param connected the connection state
299 */
300 protected void setConnected(boolean connected) {
301 this.connected = connected;
302 }
303
304 /**
305 * Close this service and terminate its physical connection.
306 * The default implementation simply calls setConnected(false) and then
307 * sends a CLOSED event to all registered ConnectionListeners.
308 * Subclasses overriding this method should still ensure it is closed; they should
309 * also ensure that it is called if the connection is closed automatically, for
310 * for example in a finalizer.
311 *
312 *@throws MessagingException if there were errors closing; the connection is still closed
313 */
314 public void close() throws MessagingException {
315 setConnected(false);
316 notifyConnectionListeners(ConnectionEvent.CLOSED);
317 }
318
319 /**
320 * Return a copy of the URLName representing this service with the password and file information removed.
321 *
322 * @return the URLName for this service
323 */
324 public URLName getURLName() {
325
326 return url == null ? null : new URLName(url.getProtocol(), url.getHost(), url.getPort(), null, url.getUsername(), null);
327 }
328
329 /**
330 * Set the url field.
331 * @param url the new value
332 */
333 protected void setURLName(URLName url) {
334 this.url = url;
335 }
336
337 public void addConnectionListener(ConnectionListener listener) {
338 connectionListeners.add(listener);
339 }
340
341 public void removeConnectionListener(ConnectionListener listener) {
342 connectionListeners.remove(listener);
343 }
344
345 protected void notifyConnectionListeners(int type) {
346 queue.queueEvent(new ConnectionEvent(this, type), connectionListeners);
347 }
348
349 public String toString() {
350 return url == null ? super.toString() : url.toString();
351 }
352
353 protected void queueEvent(MailEvent event, Vector listeners) {
354 queue.queueEvent(event, listeners);
355 }
356
357 protected void finalize() throws Throwable {
358 queue.stop();
359 connectionListeners.clear();
360 super.finalize();
361 }
362
363
364 /**
365 * Package scope utility method to allow Message instances
366 * access to the Service's session.
367 *
368 * @return The Session the service is associated with.
369 */
370 Session getSession() {
371 return session;
372 }
373 }