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.james.fetchmail;
021    
022    import javax.mail.Flags;
023    import javax.mail.Folder;
024    import javax.mail.MessagingException;
025    import javax.mail.internet.MimeMessage;
026    
027    /**
028     * <p>
029     * Class <code>FolderProcessor</code> opens a Folder and iterates over all of
030     * the Messages, delegating their processing to <code>MessageProcessor</code>.
031     * </p>
032     * 
033     * <p>
034     * If isRecurse(), all subfolders are fetched recursively.
035     * </p>
036     */
037    public class FolderProcessor extends ProcessorAbstract {
038        /**
039         * The fetched folder
040         */
041        private Folder fieldFolder;
042    
043        private Boolean fieldMarkSeenPermanent;
044    
045        /**
046         * Constructor for FolderProcessor.
047         * 
048         * @param folder
049         *            The folder to be fetched
050         * @param account
051         *            The account being processed
052         */
053        protected FolderProcessor(Folder folder, Account account) {
054            super(account);
055            setFolder(folder);
056        }
057    
058        /**
059         * Method process opens a Folder, fetches the Envelopes for all of its
060         * Messages, creates a <code>MessageProcessor</code> and runs it to process
061         * each message.
062         * 
063         * @see org.apache.james.fetchmail.ProcessorAbstract#process()
064         */
065        public void process() throws MessagingException {
066            int messagesProcessed = 0;
067            int messageCount = 0;
068            try {
069                // open the folder
070                try {
071                    open();
072                } catch (MessagingException ex) {
073                    getLogger().error(getFetchTaskName() + " Failed to open folder!");
074                    throw ex;
075                }
076    
077                // Lock the folder while processing each message
078                synchronized (getFolder()) {
079                    messageCount = getFolder().getMessageCount();
080                    for (int i = 1; i <= messageCount; i++) {
081                        MimeMessage message = (MimeMessage) getFolder().getMessage(i);
082                        if (isFetchAll() || !isSeen(message)) {
083                            try {
084                                new MessageProcessor(message, getAccount()).process();
085                                messagesProcessed++;
086                            }
087                            // Catch and report an exception but don't rethrow it,
088                            // allowing subsequent messages to be processed.
089                            catch (Exception ex) {
090                                StringBuilder logMessageBuffer = new StringBuilder("Exception processing message ID: ");
091                                logMessageBuffer.append(message.getMessageID());
092                                getLogger().error(logMessageBuffer.toString(), ex);
093                            }
094                        }
095                    }
096                }
097            } catch (MessagingException mex) {
098                getLogger().error("A MessagingException has terminated fetching messages for this folder", mex);
099            } finally {
100                // Close the folder
101                try {
102                    close();
103                } catch (MessagingException ex) {
104                    // No-op
105                }
106                StringBuilder logMessageBuffer = new StringBuilder("Processed ");
107                logMessageBuffer.append(messagesProcessed);
108                logMessageBuffer.append(" messages of ");
109                logMessageBuffer.append(messageCount);
110                logMessageBuffer.append(" in folder '");
111                logMessageBuffer.append(getFolder().getName());
112                logMessageBuffer.append("'");
113                getLogger().info(logMessageBuffer.toString());
114            }
115    
116            // Recurse through sub-folders if required
117            try {
118                if (isRecurse())
119                    recurse();
120            } catch (MessagingException mex) {
121                getLogger().error("A MessagingException has terminated recursing through sub-folders", mex);
122            }
123        }
124    
125        /**
126         * Method close.
127         * 
128         * @throws MessagingException
129         */
130        protected void close() throws MessagingException {
131            if (null != getFolder() && getFolder().isOpen())
132                getFolder().close(true);
133        }
134    
135        /**
136         * Method recurse.
137         * 
138         * @throws MessagingException
139         */
140        protected void recurse() throws MessagingException {
141            if ((getFolder().getType() & Folder.HOLDS_FOLDERS) == Folder.HOLDS_FOLDERS) {
142                // folder contains subfolders...
143                Folder folders[] = getFolder().list();
144    
145                for (int i = 0; i < folders.length; i++) {
146                    new FolderProcessor(folders[i], getAccount()).process();
147                }
148    
149            }
150        }
151    
152        /**
153         * Method open.
154         * 
155         * @throws MessagingException
156         */
157        protected void open() throws MessagingException {
158            int openFlag = Folder.READ_WRITE;
159    
160            if (isOpenReadOnly())
161                openFlag = Folder.READ_ONLY;
162    
163            getFolder().open(openFlag);
164        }
165    
166        /**
167         * Returns the folder.
168         * 
169         * @return Folder
170         */
171        protected Folder getFolder() {
172            return fieldFolder;
173        }
174    
175        /**
176         * Answer if <code>aMessage</code> has been SEEN.
177         * 
178         * @param aMessage
179         * @return boolean
180         * @throws MessagingException
181         */
182        protected boolean isSeen(MimeMessage aMessage) throws MessagingException {
183            boolean isSeen = false;
184            if (isMarkSeenPermanent().booleanValue())
185                isSeen = aMessage.isSet(Flags.Flag.SEEN);
186            else
187                isSeen = handleMarkSeenNotPermanent(aMessage);
188            return isSeen;
189        }
190    
191        /**
192         * Answer the result of computing markSeenPermanent.
193         * 
194         * @return Boolean
195         */
196        protected Boolean computeMarkSeenPermanent() {
197            return Boolean.valueOf(getFolder().getPermanentFlags().contains(Flags.Flag.SEEN));
198        }
199    
200        /**
201         * <p>
202         * Handler for when the folder does not support the SEEN flag. The default
203         * behaviour implemented here is to answer the value of the SEEN flag
204         * anyway.
205         * </p>
206         * 
207         * <p>
208         * Subclasses may choose to override this method and implement their own
209         * solutions.
210         * </p>
211         * 
212         * @param aMessage
213         * @return boolean
214         * @throws MessagingException
215         */
216        protected boolean handleMarkSeenNotPermanent(MimeMessage aMessage) throws MessagingException {
217            return aMessage.isSet(Flags.Flag.SEEN);
218        }
219    
220        /**
221         * Sets the folder.
222         * 
223         * @param folder
224         *            The folder to set
225         */
226        protected void setFolder(Folder folder) {
227            fieldFolder = folder;
228        }
229    
230        /**
231         * Returns the isMarkSeenPermanent.
232         * 
233         * @return Boolean
234         */
235        protected Boolean isMarkSeenPermanent() {
236            Boolean markSeenPermanent = null;
237            if (null == (markSeenPermanent = isMarkSeenPermanentBasic())) {
238                updateMarkSeenPermanent();
239                return isMarkSeenPermanent();
240            }
241            return markSeenPermanent;
242        }
243    
244        /**
245         * Returns the markSeenPermanent.
246         * 
247         * @return Boolean
248         */
249        private Boolean isMarkSeenPermanentBasic() {
250            return fieldMarkSeenPermanent;
251        }
252    
253        /**
254         * Sets the markSeenPermanent.
255         * 
256         * @param markSeenPermanent
257         *            The isMarkSeenPermanent to set
258         */
259        protected void setMarkSeenPermanent(Boolean markSeenPermanent) {
260            fieldMarkSeenPermanent = markSeenPermanent;
261        }
262    
263        /**
264         * Updates the markSeenPermanent.
265         */
266        protected void updateMarkSeenPermanent() {
267            setMarkSeenPermanent(computeMarkSeenPermanent());
268        }
269    
270    }