001 /***********************************************************************************************************************
002 *
003 * JavaMail Mock2 Provider - open source mock classes for mock up JavaMail
004 * =======================================================================
005 *
006 * Copyright (C) 2014 by Hendrik Saly (http://saly.de)
007 *
008 * Based on ideas from Kohsuke Kawaguchi's Mock-javamail (https://java.net/projects/mock-javamail)
009 *
010 ***********************************************************************************************************************
011 *
012 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
013 * the License. You may obtain a copy of the License at
014 *
015 * http://www.apache.org/licenses/LICENSE-2.0
016 *
017 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
018 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
019 * specific language governing permissions and limitations under the License.
020 *
021 ***********************************************************************************************************************
022 *
023 * $Id:$
024 *
025 **********************************************************************************************************************/
026 package de.saly.javamail.mock2;
027
028 import java.util.ArrayList;
029 import java.util.List;
030 import java.util.UUID;
031
032 import javax.mail.FetchProfile;
033 import javax.mail.Flags;
034 import javax.mail.Flags.Flag;
035 import javax.mail.Folder;
036 import javax.mail.FolderClosedException;
037 import javax.mail.FolderNotFoundException;
038 import javax.mail.Message;
039 import javax.mail.MessagingException;
040 import javax.mail.event.ConnectionEvent;
041 import javax.mail.event.FolderEvent;
042 import javax.mail.event.MessageChangedEvent;
043 import javax.mail.internet.MimeMessage;
044
045 import com.sun.mail.iap.Response;
046 import com.sun.mail.imap.AppendUID;
047 import com.sun.mail.imap.IMAPFolder;
048
049 import de.saly.javamail.mock2.MailboxFolder.MailboxEventListener;
050
051 public class IMAPMockFolder extends IMAPFolder implements MailboxEventListener {
052
053 private final MailboxFolder mailboxFolder;
054
055 private final UUID objectId = UUID.randomUUID();
056
057 private volatile boolean opened = false;
058
059 private int openMode;
060
061 private final IMAPMockStore store;
062
063 protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
064
065 protected IMAPMockFolder(final IMAPMockStore store, final MailboxFolder mailboxFolder) {
066 super("DUMMY_NAME_WHICH_MUST_NOT_BE_VISIBLE", MailboxFolder.SEPARATOR, store, false);
067 this.mailboxFolder = mailboxFolder;
068 this.mailboxFolder.addMailboxEventListener(this);
069 this.store = store;
070 logger.debug("Folder created " + objectId);
071 }
072
073 @Override
074 public void appendMessages(final Message[] msgs) throws MessagingException {
075 checkExists();
076 // checkOpened();
077 // checkWriteMode();
078 for (final Message m : msgs) {
079 mailboxFolder.add((MimeMessage) m);
080 }
081
082 logger.debug("Append " + msgs.length + " to " + getFullName());
083 }
084
085 @Override
086 public AppendUID[] appendUIDMessages(final Message[] msgs) throws MessagingException {
087 final AppendUID[] uids = new AppendUID[msgs.length];
088 checkExists();
089 // checkOpened();
090 // checkWriteMode();
091 int i = 0;
092 for (final Message m : msgs) {
093 final MockMessage mockMessage = (MockMessage) mailboxFolder.add((MimeMessage) m);
094 uids[i++] = new AppendUID(mailboxFolder.getUidValidity(), mockMessage.getMockid());
095 }
096
097 logger.debug("Append " + msgs.length + " to " + getFullName());
098
099 return uids;
100 }
101
102 @Override
103 public void close(final boolean expunge) throws MessagingException {
104 checkOpened();
105 checkExists();
106
107 if (expunge) {
108 expunge();
109 }
110
111 opened = false;
112 logger.debug("Folder " + getFullName() + " closed (" + objectId + ")");
113 notifyConnectionListeners(ConnectionEvent.CLOSED);
114 }
115
116 @Override
117 public boolean create(final int type) throws MessagingException {
118
119 if (exists()) {
120 return true;
121 }
122
123 mailboxFolder.create();
124 notifyFolderListeners(FolderEvent.CREATED);
125 return mailboxFolder.isExists();
126
127 // return mailboxFolder.reCreate().isExists();
128
129 }
130
131 @Override
132 public boolean delete(final boolean recurse) throws MessagingException {
133 checkExists();
134 checkClosed();
135 mailboxFolder.deleteFolder(recurse);
136 notifyFolderListeners(FolderEvent.DELETED);
137 return true;
138 }
139
140 @Override
141 public Object doOptionalCommand(final String err, final ProtocolCommand cmd) throws MessagingException {
142 throw new MessagingException("Optional command not supported: " + err);
143 }
144
145 @Override
146 public boolean exists() throws MessagingException {
147 return mailboxFolder.isExists();
148 }
149
150 @Override
151 public Message[] expunge() throws MessagingException {
152 checkExists();
153 checkOpened();
154 checkWriteMode();
155
156 final Message[] removed = wrap(mailboxFolder.expunge());
157
158 if (removed.length > 0) {
159 notifyMessageRemovedListeners(true, removed);
160 }
161
162 return removed;
163
164 }
165
166 @Override
167 public synchronized Message[] expunge(final Message[] msgs) throws MessagingException {
168 checkExists();
169 checkOpened();
170 checkWriteMode();
171 final Message[] removed = wrap(mailboxFolder.expunge(msgs));
172
173 if (removed.length > 0) {
174 notifyMessageRemovedListeners(true, removed);
175 }
176
177 return removed;
178
179 }
180
181 @Override
182 public synchronized void fetch(final Message[] msgs, final FetchProfile fp) throws MessagingException {
183 // do nothing
184 }
185
186 @Override
187 public void folderCreated(final MailboxFolder mf) {
188 // ignore
189
190 }
191
192 @Override
193 public void folderDeleted(final MailboxFolder mf) {
194 // ignore
195
196 }
197
198 @Override
199 public void folderRenamed(final String from, final MailboxFolder to) {
200 // ignore
201
202 }
203
204 @Override
205 public Folder getFolder(final String name) throws MessagingException {
206 // checkExists();
207
208 logger.debug("getFolder(" + name + ") on " + getFullName());
209
210 if ("inbox".equalsIgnoreCase(name)) {
211 return new IMAPMockFolder(store, mailboxFolder.getMailbox().getInbox());
212 }
213
214 return new IMAPMockFolder(store, mailboxFolder.getOrAddSubFolder(name));
215
216 }
217
218 @Override
219 public synchronized String getFullName() {
220
221 return mailboxFolder.getFullName();
222 }
223
224 @Override
225 public Message getMessage(final int msgnum) throws MessagingException {
226 checkExists();
227 checkOpened();
228 return new MockMessage(mailboxFolder.getByMsgNum(msgnum), this);
229 }
230
231 @Override
232 public synchronized Message getMessageByUID(final long uid) throws MessagingException {
233 checkExists();
234 checkOpened();
235 return new MockMessage(mailboxFolder.getById(uid), this);
236 }
237
238 @Override
239 public int getMessageCount() throws MessagingException {
240 checkExists();
241 return mailboxFolder.getMessageCount();
242 }
243
244 @Override
245 public Message[] getMessages(final int low, final int high) throws MessagingException {
246 checkExists();
247 checkOpened();
248 final List<Message> messages = new ArrayList<Message>();
249 for (int i = low; i <= high; i++) {
250 final Message m = mailboxFolder.getByMsgNum(i);
251 messages.add(new MockMessage(m, this));
252 }
253 return messages.toArray(new Message[messages.size()]);
254 }
255
256 @Override
257 public synchronized Message[] getMessagesByUID(final long start, final long end) throws MessagingException {
258 checkExists();
259 checkOpened();
260 return wrap(mailboxFolder.getByIds(start, end));
261
262 }
263
264 @Override
265 public synchronized Message[] getMessagesByUID(final long[] uids) throws MessagingException {
266 checkExists();
267 checkOpened();
268 return wrap(mailboxFolder.getByIds(uids));
269 }
270
271 @Override
272 public synchronized Message[] getMessagesByUIDChangedSince(final long start, final long end, final long modseq)
273 throws MessagingException {
274 throw new MessagingException("CONDSTORE not supported");
275 }
276
277 @Override
278 public synchronized String getName() {
279
280 return mailboxFolder.getName();
281 }
282
283 @Override
284 public int getNewMessageCount() throws MessagingException {
285 checkExists();
286 return mailboxFolder.getByFlags(new Flags(Flag.RECENT), true).length; // TODO
287 // or
288 // is
289 // it
290 // SEEN
291 // false?
292 }
293
294 @Override
295 public Folder getParent() throws MessagingException {
296 checkExists();
297 if (mailboxFolder.getParent() == null) {
298 throw new MessagingException("no parent, is already default root");
299 }
300
301 return new IMAPMockFolder(store, mailboxFolder.getParent());
302 }
303
304 @Override
305 public Flags getPermanentFlags() {
306 return null;
307 }
308
309 @Override
310 public char getSeparator() throws MessagingException {
311 return MailboxFolder.SEPARATOR;
312 }
313
314 @Override
315 public int getType() throws MessagingException {
316 // checkExists();
317 return mailboxFolder.isRoot() ? HOLDS_FOLDERS : HOLDS_MESSAGES | HOLDS_FOLDERS;
318 }
319
320 /* (non-Javadoc)
321 * @see com.sun.mail.imap.IMAPFolder#getUID(javax.mail.Message)
322 */
323 @Override
324 public synchronized long getUID(final Message message) throws MessagingException {
325
326 return mailboxFolder.getUID(message);
327 }
328
329 /* (non-Javadoc)
330 * @see com.sun.mail.imap.IMAPFolder#getUIDNext()
331 */
332 @Override
333 public synchronized long getUIDNext() throws MessagingException {
334
335 return mailboxFolder.getUniqueMessageId() + 10; // TODO +10 magic number
336 }
337
338 /* (non-Javadoc)
339 * @see com.sun.mail.imap.IMAPFolder#getUIDValidity()
340 */
341 @Override
342 public synchronized long getUIDValidity() throws MessagingException {
343
344 return mailboxFolder.getUidValidity();
345 }
346
347 @Override
348 public void handleResponse(final Response r) {
349 throw new RuntimeException("not implemented/should not happen");
350 }
351
352 @Override
353 public boolean hasNewMessages() throws MessagingException {
354 checkExists();
355 return getNewMessageCount() > 0;
356 }
357
358 @Override
359 public boolean isOpen() {
360 return opened;
361 }
362
363 @Override
364 public Folder[] list(final String pattern) throws MessagingException {
365 checkExists();
366
367 final List<MailboxFolder> children = mailboxFolder.getChildren();
368 final List<Folder> ret = new ArrayList<Folder>();
369
370 for (final MailboxFolder mf : children) {
371 if (mf.isExists()) {
372 ret.add(new IMAPMockFolder(store, mf));
373 }
374 }
375
376 logger.debug("Folder (" + getFullName() + ") list return " + ret);
377
378 return ret.toArray(new Folder[ret.size()]);
379
380 }
381
382 @Override
383 public void messageAdded(final MailboxFolder mf, final MockMessage msg) {
384 notifyMessageAddedListeners(new Message[] { msg });
385
386 }
387
388 @Override
389 public void messageChanged(final MailboxFolder mf, final MockMessage msg, final boolean headerChanged, final boolean flagsChanged) {
390
391 notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, msg);
392
393 }
394
395 @Override
396 public void messageExpunged(final MailboxFolder mf, final MockMessage msg, final boolean removed) {
397 // ignore
398
399 }
400
401 @Override
402 public void open(final int mode) throws MessagingException {
403 checkClosed();
404 checkExists();
405 opened = true;
406 openMode = mode;
407 logger.debug("Open folder " + getFullName() + " (" + objectId + ")");
408 notifyConnectionListeners(ConnectionEvent.OPENED);
409 }
410
411 @Override
412 public synchronized boolean renameTo(final Folder f) throws MessagingException {
413 checkClosed(); // insure that we are closed.
414 checkExists();
415 if (f.getStore() != store) {
416 throw new MessagingException("Can't rename across Stores");
417 }
418
419 mailboxFolder.renameFolder(f.getName());
420 notifyFolderRenamedListeners(f);
421 return true;
422 }
423
424 @Override
425 public void uidInvalidated() {
426
427 // ignore
428 }
429
430 private Message[] wrap(final Message[] msgs) throws MessagingException {
431 final Message[] ret = new Message[msgs.length];
432 int i = 0;
433 for (final Message message : msgs) {
434 ret[i++] = new MockMessage(message, this);
435 }
436 return ret;
437 }
438
439 @Override
440 protected void checkClosed() {
441 if (opened) {
442 throw new IllegalStateException("This operation is not allowed on an open folder:" + getFullName() + " (" + objectId + ")");
443 }
444 }
445
446 @Override
447 protected void checkExists() throws MessagingException {
448 if (!exists()) {
449 throw new FolderNotFoundException(this, getFullName() + " not found");
450 }
451 }
452
453 @Override
454 protected void checkOpened() throws FolderClosedException {
455
456 if (!opened) {
457
458 throw new IllegalStateException("This operation is not allowed on a closed folder: " + getFullName() + " (" + objectId + ")");
459
460 }
461 }
462
463 protected void checkWriteMode() {
464 if (openMode != Folder.READ_WRITE) {
465 throw new IllegalStateException("Folder " + getFullName() + " is readonly" + " (" + objectId + ")");
466 }
467 }
468 }