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 }