1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.geronimo.javamail.store.pop3;
21
22 import java.util.List;
23
24 import javax.mail.FetchProfile;
25 import javax.mail.Flags;
26 import javax.mail.Folder;
27 import javax.mail.FolderClosedException;
28 import javax.mail.Message;
29 import javax.mail.MessagingException;
30 import javax.mail.MethodNotSupportedException;
31 import javax.mail.Session;
32 import javax.mail.Store;
33 import javax.mail.URLName;
34 import javax.mail.event.ConnectionEvent;
35
36 import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
37 import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse;
38
39 /**
40 * The POP3 implementation of the javax.mail.Folder Note that only INBOX is
41 * supported in POP3
42 * <p>
43 * <url>http://www.faqs.org/rfcs/rfc1939.html</url>
44 * </p>
45 *
46 * @see javax.mail.Folder
47 *
48 * @version $Rev: 689140 $ $Date: 2008-08-26 13:20:01 -0400 (Tue, 26 Aug 2008) $
49 */
50 public class POP3Folder extends Folder {
51
52 protected boolean isFolderOpen = false;
53
54 protected int mode;
55
56 protected int msgCount;
57
58 private POP3Message[] messageCache;
59 // The fully qualified name of the folder. For a POP3 folder, this is either "" for the root or
60 // "INPUT" for the in-basket. It is possible to create other folders, but they will report that
61 // they don't exist.
62 protected String fullName;
63 // indicates whether this folder exists or not
64 protected boolean exists = false;
65 // indicates the type of folder this is.
66 protected int folderType;
67
68 /**
69 * Create a new folder associate with a POP3 store instance.
70 *
71 * @param store The owning Store.
72 * @param name The name of the folder. Note that POP3 stores only
73 * have 2 real folders, the root ("") and the in-basket
74 * ("INBOX"). It is possible to create other instances
75 * of Folder associated with the Store, but they will
76 * be non-functional.
77 */
78 public POP3Folder(POP3Store store, String name) {
79 super(store);
80 this.fullName = name;
81 // if this is the input folder, this exists
82 if (name.equalsIgnoreCase("INPUT")) {
83 exists = true;
84 }
85 // by default, we're holding messages.
86 folderType = Folder.HOLDS_MESSAGES;
87 }
88
89
90 /**
91 * Retrieve the folder name. This is the simple folder
92 * name at the its hiearchy level. This can be invoked when the folder is closed.
93 *
94 * @return The folder's name.
95 */
96 public String getName() {
97 // the name and the full name are always the same
98 return fullName;
99 }
100
101 /**
102 * Retrieve the folder's full name (including hierarchy information).
103 * This can be invoked when the folder is closed.
104 *
105 * @return The full name value.
106 */
107 public String getFullName() {
108 return fullName;
109 }
110
111
112 /**
113 * Never return "this" as the parent folder. Somebody not familliar with
114 * POP3 may do something like while(getParent() != null) or something
115 * simmilar which will result in an infinte loop
116 */
117 public Folder getParent() throws MessagingException {
118 // the default folder returns null. We return the default
119 // folder
120 return store.getDefaultFolder();
121 }
122
123 /**
124 * Indicate whether a folder exists. Only the root
125 * folder and "INBOX" will ever return true.
126 *
127 * @return true for real POP3 folders, false for any other
128 * instances that have been created.
129 * @exception MessagingException
130 */
131 public boolean exists() throws MessagingException {
132 // only one folder truely exists...this might be it.
133 return exists;
134 }
135
136 public Folder[] list(String pattern) throws MessagingException {
137 throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
138 }
139
140 /**
141 * No sub folders, hence there is no notion of a seperator. This is always a null character.
142 */
143 public char getSeparator() throws MessagingException {
144 return '\0';
145 }
146
147 /**
148 * There's no hierarchy in POP3, so the only type
149 * is HOLDS_MESSAGES (and only one of those exists).
150 *
151 * @return Always returns HOLDS_MESSAGES.
152 * @exception MessagingException
153 */
154 public int getType() throws MessagingException {
155 return folderType;
156 }
157
158 /**
159 * Always returns false as any creation operation must
160 * fail.
161 *
162 * @param type The type of folder to create. This is ignored.
163 *
164 * @return Always returns false.
165 * @exception MessagingException
166 */
167 public boolean create(int type) throws MessagingException {
168 return false;
169 }
170
171 /**
172 * No way to detect new messages, so always return false.
173 *
174 * @return Always returns false.
175 * @exception MessagingException
176 */
177 public boolean hasNewMessages() throws MessagingException {
178 return false;
179 }
180
181 public Folder getFolder(String name) throws MessagingException {
182 throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
183 }
184
185 public boolean delete(boolean recurse) throws MessagingException {
186 throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be deleted");
187 }
188
189 public boolean renameTo(Folder f) throws MessagingException {
190 throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be renamed");
191 }
192
193 /**
194 * @see javax.mail.Folder#open(int)
195 */
196 public void open(int mode) throws MessagingException {
197 // Can only be performed on a closed folder
198 checkClosed();
199
200 // get a connection object
201 POP3Connection connection = getConnection();
202
203 try {
204 POP3StatusResponse res = connection.retrieveMailboxStatus();
205 this.mode = mode;
206 this.isFolderOpen = true;
207 this.msgCount = res.getNumMessages();
208 // JavaMail API has no method in Folder to expose the total
209 // size (no of bytes) of the mail drop;
210
211 // NB: We use the actual message number to access the messages from
212 // the cache, which is origin 1. Vectors are origin 0, so we have to subtract each time
213 // we access a messagge.
214 messageCache = new POP3Message[msgCount];
215 } catch (Exception e) {
216 throw new MessagingException("Unable to execute STAT command", e);
217 }
218 finally {
219 // return the connection when finished
220 releaseConnection(connection);
221 }
222
223 notifyConnectionListeners(ConnectionEvent.OPENED);
224 }
225
226 /**
227 * Close a POP3 folder.
228 *
229 * @param expunge The expunge flag (ignored for POP3).
230 *
231 * @exception MessagingException
232 */
233 public void close(boolean expunge) throws MessagingException {
234 // Can only be performed on an open folder
235 checkOpen();
236
237 // get a connection object
238 POP3Connection connection = getConnection();
239 try {
240 // we might need to reset the connection before we
241 // process deleted messages and send the QUIT. The
242 // connection knows if we need to do this.
243 connection.reset();
244 // clean up any messages marked for deletion
245 expungeDeletedMessages(connection);
246 } finally {
247 // return the connection when finished
248 releaseConnection(connection);
249 // cleanup the the state even if exceptions occur when deleting the
250 // messages.
251 cleanupFolder(false);
252 }
253 }
254
255 /**
256 * Mark any messages we've flagged as deleted from the
257 * POP3 server before closing.
258 *
259 * @exception MessagingException
260 */
261 protected void expungeDeletedMessages(POP3Connection connection) throws MessagingException {
262 if (mode == READ_WRITE) {
263 for (int i = 0; i < messageCache.length; i++) {
264 POP3Message msg = messageCache[i];
265 if (msg != null) {
266 // if the deleted flag is set, go delete this
267 // message. NB: We adjust the index back to an
268 // origin 1 value
269 if (msg.isSet(Flags.Flag.DELETED)) {
270 try {
271 connection.deleteMessage(i + 1);
272 } catch (MessagingException e) {
273 throw new MessagingException("Exception deleting message number " + (i + 1), e);
274 }
275 }
276 }
277 }
278 }
279 }
280
281
282 /**
283 * Do folder cleanup. This is used both for normal
284 * close operations, and adnormal closes where the
285 * server has sent us a BYE message.
286 *
287 * @param expunge Indicates whether open messages should be expunged.
288 * @param disconnected
289 * The disconnected flag. If true, the server has cut
290 * us off, which means our connection can not be returned
291 * to the connection pool.
292 *
293 * @exception MessagingException
294 */
295 protected void cleanupFolder(boolean disconnected) throws MessagingException {
296 messageCache = null;
297 isFolderOpen = false;
298 notifyConnectionListeners(ConnectionEvent.CLOSED);
299 }
300
301
302 /**
303 * Obtain a connection object for a Message attached to this Folder. This
304 * will be the Folder's connection, which is only available if the Folder
305 * is currently open.
306 *
307 * @return The connection object for the Message instance to use.
308 * @exception MessagingException
309 */
310 synchronized POP3Connection getMessageConnection() throws MessagingException {
311 // we always get one from the store. If we're fully single threaded, then
312 // we can get away with just a single one.
313 return getConnection();
314 }
315
316
317 /**
318 * Release the connection object back to the Folder instance.
319 *
320 * @param connection The connection being released.
321 *
322 * @exception MessagingException
323 */
324 void releaseMessageConnection(POP3Connection connection) throws MessagingException {
325 // give this back to the store
326 releaseConnection(connection);
327 }
328
329 public boolean isOpen() {
330 // if we're not open, we're not open
331 if (!isFolderOpen) {
332 return false;
333 }
334
335 try {
336 // we might be open, but the Store has been closed. In which case, we're not any more
337 // closing also changes the isFolderOpen flag.
338 if (!((POP3Store)store).isConnected()) {
339 close(false);
340 }
341 } catch (MessagingException e) {
342 }
343 return isFolderOpen;
344 }
345
346 public Flags getPermanentFlags() {
347 // unfortunately doesn't have a throws clause for this method
348 // throw new MethodNotSupportedException("POP3 doesn't support permanent
349 // flags");
350
351 // Better than returning null, save the extra condition from a user to
352 // check for null
353 // and avoids a NullPointerException for the careless.
354 return new Flags();
355 }
356
357 /**
358 * Get the folder message count.
359 *
360 * @return The number of messages in the folder.
361 * @exception MessagingException
362 */
363 public int getMessageCount() throws MessagingException {
364 // NB: returns -1 if the folder isn't open.
365 return msgCount;
366 }
367
368 /**
369 * Checks wether the message is in cache, if not will create a new message
370 * object and return it.
371 *
372 * @see javax.mail.Folder#getMessage(int)
373 */
374 public Message getMessage(int msgNum) throws MessagingException {
375 // Can only be performed on an Open folder
376 checkOpen();
377 if (msgNum < 1 || msgNum > getMessageCount()) {
378 throw new MessagingException("Invalid Message number");
379 }
380
381 Message msg = messageCache[msgNum - 1];
382 if (msg == null) {
383 msg = new POP3Message(this, msgNum);
384 messageCache[msgNum - 1] = (POP3Message)msg;
385 }
386
387 return msg;
388 }
389
390 public void appendMessages(Message[] msgs) throws MessagingException {
391 throw new MethodNotSupportedException("Message appending is not supported in POP3");
392
393 }
394
395 public Message[] expunge() throws MessagingException {
396 throw new MethodNotSupportedException("Expunge is not supported in POP3");
397 }
398
399 public int getMode() throws IllegalStateException {
400 // Can only be performed on an Open folder
401 checkOpen();
402 return mode;
403 }
404
405 /**
406 * @see javax.mail.Folder#fetch(javax.mail.Message[],
407 * javax.mail.FetchProfile)
408 *
409 * The JavaMail API recommends that this method be overrident to provide a
410 * meaningfull implementation.
411 */
412 public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
413 // Can only be performed on an Open folder
414 checkOpen();
415 for (int i = 0; i < msgs.length; i++) {
416 Message msg = msgs[i];
417
418 if (fp.contains(FetchProfile.Item.ENVELOPE)) {
419 // fetching the size and the subject will force all of the
420 // envelope information to load
421 msg.getHeader("Subject");
422 msg.getSize();
423 }
424 if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
425 // force the content to load...this also fetches the header information.
426 // C'est la vie.
427 ((POP3Message)msg).loadContent();
428 msg.getSize();
429 }
430 // force flag loading for this message
431 if (fp.contains(FetchProfile.Item.FLAGS)) {
432 msg.getFlags();
433 }
434
435 if (fp.getHeaderNames().length > 0) {
436 // loading any header loads all headers, so just grab the header set.
437 msg.getHeader("Subject");
438 }
439 }
440 }
441
442 /**
443 * Retrieve the UID for a given message.
444 *
445 * @param msg The message of interest.
446 *
447 * @return The String UID value for this message.
448 * @exception MessagingException
449 */
450 public synchronized String getUID(Message msg) throws MessagingException {
451 checkOpen();
452 // the Message knows how to do this
453 return ((POP3Message)msg).getUID();
454 }
455
456
457 /**
458 * Below is a list of covinience methods that avoid repeated checking for a
459 * value and throwing an exception
460 */
461
462 /** Ensure the folder is open */
463 private void checkOpen() throws IllegalStateException {
464 if (!isFolderOpen) {
465 throw new IllegalStateException("Folder is not Open");
466 }
467 }
468
469 /** Ensure the folder is not open */
470 private void checkClosed() throws IllegalStateException {
471 if (isFolderOpen) {
472 throw new IllegalStateException("Folder is Open");
473 }
474 }
475
476 /**
477 * @see javax.mail.Folder#notifyMessageChangedListeners(int,
478 * javax.mail.Message)
479 *
480 * this method is protected and cannot be used outside of Folder, therefore
481 * had to explicitly expose it via a method in POP3Folder, so that
482 * POP3Message has access to it
483 *
484 * Bad design on the part of the Java Mail API.
485 */
486 public void notifyMessageChangedListeners(int type, Message m) {
487 super.notifyMessageChangedListeners(type, m);
488 }
489
490
491 /**
492 * Retrieve the connection attached to this folder. Throws an
493 * exception if we don't have an active connection.
494 *
495 * @return The current connection object.
496 * @exception MessagingException
497 */
498 protected synchronized POP3Connection getConnection() throws MessagingException {
499 // request a connection from the central store.
500 return ((POP3Store)store).getFolderConnection(this);
501 }
502
503
504 /**
505 * Release our connection back to the Store.
506 *
507 * @param connection The connection to release.
508 *
509 * @exception MessagingException
510 */
511 protected void releaseConnection(POP3Connection connection) throws MessagingException {
512 // we need to release the connection to the Store once we're finished with it
513 ((POP3Store)store).releaseFolderConnection(this, connection);
514 }
515 }