001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.javamail.store.pop3;
021    
022    import java.util.List;     
023    
024    import javax.mail.FetchProfile;
025    import javax.mail.Flags;
026    import javax.mail.Folder;
027    import javax.mail.FolderClosedException;
028    import javax.mail.Message;
029    import javax.mail.MessagingException;
030    import javax.mail.MethodNotSupportedException;
031    import javax.mail.Session;
032    import javax.mail.Store;
033    import javax.mail.URLName;
034    import javax.mail.event.ConnectionEvent;
035    
036    import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 
037    import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse; 
038    
039    /**
040     * The POP3 implementation of the javax.mail.Folder Note that only INBOX is
041     * supported in POP3
042     * <p>
043     * <url>http://www.faqs.org/rfcs/rfc1939.html</url>
044     * </p>
045     * 
046     * @see javax.mail.Folder
047     * 
048     * @version $Rev: 689140 $ $Date: 2008-08-26 13:20:01 -0400 (Tue, 26 Aug 2008) $
049     */
050    public class POP3Folder extends Folder {
051    
052        protected boolean isFolderOpen = false;
053    
054        protected int mode;
055    
056        protected int msgCount;
057    
058        private POP3Message[] messageCache; 
059        // The fully qualified name of the folder.  For a POP3 folder, this is either "" for the root or 
060        // "INPUT" for the in-basket.  It is possible to create other folders, but they will report that 
061        // they don't exist. 
062        protected String fullName;  
063        // indicates whether this folder exists or not 
064        protected boolean exists = false; 
065        // indicates the type of folder this is. 
066        protected int folderType; 
067        
068        /**
069         * Create a new folder associate with a POP3 store instance.
070         * 
071         * @param store  The owning Store.
072         * @param name   The name of the folder.  Note that POP3 stores only
073         *               have 2 real folders, the root ("") and the in-basket
074         *               ("INBOX").  It is possible to create other instances
075         *               of Folder associated with the Store, but they will
076         *               be non-functional.
077         */
078         public POP3Folder(POP3Store store, String name) {
079            super(store);
080            this.fullName = name; 
081            // if this is the input folder, this exists 
082            if (name.equalsIgnoreCase("INPUT")) {
083                exists = true; 
084            }
085            // by default, we're holding messages. 
086            folderType = Folder.HOLDS_MESSAGES; 
087        }
088        
089        
090        /**
091         * Retrieve the folder name.  This is the simple folder
092         * name at the its hiearchy level.  This can be invoked when the folder is closed.
093         * 
094         * @return The folder's name.
095         */
096            public String getName() {
097            // the name and the full name are always the same
098            return fullName; 
099            }
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    }