View Javadoc

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 }