1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package de.saly.javamail.mock2;
27
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.UUID;
32 import java.util.concurrent.Semaphore;
33
34 import javax.mail.FetchProfile;
35 import javax.mail.Flags;
36 import javax.mail.Flags.Flag;
37 import javax.mail.Folder;
38 import javax.mail.FolderClosedException;
39 import javax.mail.FolderNotFoundException;
40 import javax.mail.Message;
41 import javax.mail.MessagingException;
42 import javax.mail.Quota;
43 import javax.mail.event.ConnectionEvent;
44 import javax.mail.event.FolderEvent;
45 import javax.mail.event.MailEvent;
46 import javax.mail.event.MessageChangedEvent;
47 import javax.mail.internet.MimeMessage;
48 import javax.mail.search.SearchTerm;
49
50 import com.sun.mail.iap.ProtocolException;
51 import com.sun.mail.iap.Response;
52 import com.sun.mail.imap.AppendUID;
53 import com.sun.mail.imap.IMAPFolder;
54 import com.sun.mail.imap.ResyncData;
55 import com.sun.mail.imap.SortTerm;
56
57 import de.saly.javamail.mock2.MailboxFolder.MailboxEventListener;
58
59 public class IMAPMockFolder extends IMAPFolder implements MailboxEventListener {
60
61 private static final int ABORTING = 2;
62 private static final int IDLE = 1;
63 private static final int RUNNING = 0;
64 private final Semaphore idleLock = new Semaphore(0, true);
65
66 private int idleState = RUNNING;
67
68 protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
69
70 private final MailboxFolder mailboxFolder;
71
72 private final UUID objectId = UUID.randomUUID();
73
74 private volatile boolean opened = false;
75
76 private int openMode;
77
78 private final IMAPMockStore store;
79
80 protected IMAPMockFolder(final IMAPMockStore store, final MailboxFolder mailboxFolder) {
81 super("DUMMY_NAME_WHICH_MUST_NOT_BE_VISIBLE", MailboxFolder.SEPARATOR, store, false);
82 this.mailboxFolder = mailboxFolder;
83 this.mailboxFolder.addMailboxEventListener(this);
84 this.store = store;
85 logger.debug("Folder created " + objectId);
86 }
87
88 protected synchronized void abortIdle() {
89 if (idleState == IDLE) {
90 logger.trace("Abort idle");
91
92 if (logger.isTraceEnabled()) {
93 try {
94 throw new RuntimeException();
95 } catch (final Exception e) {
96 logger.trace("TRACE stacktrace ", e);
97 }
98
99 }
100
101 idleState = ABORTING;
102 idleLock.release();
103
104 }
105 }
106
107 @Override
108 public void appendMessages(final Message[] msgs) throws MessagingException {
109 abortIdle();
110 checkExists();
111
112
113 for (final Message m : msgs) {
114 mailboxFolder.add((MimeMessage) m);
115 }
116
117 logger.debug("Append " + msgs.length + " to " + getFullName());
118 }
119
120 @Override
121 public synchronized AppendUID[] appendUIDMessages(final Message[] msgs) throws MessagingException {
122 abortIdle();
123 checkExists();
124
125
126 final AppendUID[] uids = new AppendUID[msgs.length];
127 int i = 0;
128 for (final Message m : msgs) {
129 final MockMessage mockMessage = (MockMessage) mailboxFolder.add((MimeMessage) m);
130 uids[i++] = new AppendUID(mailboxFolder.getUidValidity(), mockMessage.getMockid());
131 }
132
133 logger.debug("Append " + msgs.length + " to " + getFullName());
134
135 return uids;
136 }
137
138 @Override
139 protected void checkClosed() {
140 if (opened) {
141 throw new IllegalStateException("This operation is not allowed on an open folder:" + getFullName() + " (" + objectId + ")");
142 }
143 }
144
145 @Override
146 protected void checkExists() throws MessagingException {
147 if (!exists()) {
148 throw new FolderNotFoundException(this, getFullName() + " not found");
149 }
150 }
151
152 @Override
153 protected void checkOpened() throws FolderClosedException {
154
155 if (!opened) {
156
157 throw new IllegalStateException("This operation is not allowed on a closed folder: " + getFullName() + " (" + objectId + ")");
158
159 }
160 }
161
162 protected void checkWriteMode() {
163 if (openMode != Folder.READ_WRITE) {
164 throw new IllegalStateException("Folder " + getFullName() + " is readonly" + " (" + objectId + ")");
165 }
166 }
167
168 @Override
169 public synchronized void close(final boolean expunge) throws MessagingException {
170 abortIdle();
171 checkOpened();
172 checkExists();
173
174 if (expunge) {
175 expunge();
176 }
177
178 opened = false;
179 logger.debug("Folder " + getFullName() + " closed (" + objectId + ")");
180 notifyConnectionListeners(ConnectionEvent.CLOSED);
181 }
182
183 @Override
184 public synchronized void copyMessages(final Message[] msgs, final Folder folder) throws MessagingException {
185 abortIdle();
186 checkOpened();
187 checkExists();
188 if (msgs == null || folder == null || msgs.length == 0) {
189 return;
190 }
191
192 if (!folder.exists()) {
193 throw new FolderNotFoundException(folder.getFullName() + " does not exist", folder);
194 }
195
196 folder.appendMessages(msgs);
197 }
198
199 @Override
200 public synchronized AppendUID[] copyUIDMessages(final Message[] msgs, final Folder folder) throws MessagingException {
201
202 abortIdle();
203 checkExists();
204 checkOpened();
205 if (msgs == null || folder == null || msgs.length == 0) {
206 return null;
207 }
208
209 final AppendUID[] uids = new AppendUID[msgs.length];
210
211 int i = 0;
212 for (final Message m : msgs) {
213 final MockMessage mockMessage = (MockMessage) mailboxFolder.add((MimeMessage) m);
214 uids[i++] = new AppendUID(mailboxFolder.getUidValidity(), mockMessage.getMockid());
215 }
216
217 logger.debug("Copied " + msgs.length + " to " + getFullName());
218
219 return uids;
220 }
221
222 @Override
223 public synchronized boolean create(final int type) throws MessagingException {
224 abortIdle();
225 if (exists()) {
226 return true;
227 }
228
229 mailboxFolder.create();
230 notifyFolderListeners(FolderEvent.CREATED);
231 return mailboxFolder.isExists();
232
233
234
235 }
236
237 @Override
238 public synchronized boolean delete(final boolean recurse) throws MessagingException {
239 abortIdle();
240 checkExists();
241 checkClosed();
242 mailboxFolder.deleteFolder(recurse);
243 notifyFolderListeners(FolderEvent.DELETED);
244 return true;
245 }
246
247 @Override
248 public synchronized Object doCommand(final ProtocolCommand cmd) throws MessagingException {
249 throw new MessagingException(
250 "no protocol for mock class - you should never see this exception. Please file a bugrfeport and include stacktrace");
251
252 }
253
254 @Override
255 public synchronized Object doCommandIgnoreFailure(final ProtocolCommand cmd) throws MessagingException {
256 throw new MessagingException(
257 "no protocol for mock class - you should never see this exception. Please file a bugrfeport and include stacktrace");
258
259 }
260
261 @Override
262 public synchronized Object doOptionalCommand(final String err, final ProtocolCommand cmd) throws MessagingException {
263 throw new MessagingException("Optional command not supported: " + err);
264 }
265
266 @Override
267 protected synchronized Object doProtocolCommand(final ProtocolCommand cmd) throws ProtocolException {
268
269 throw new ProtocolException(
270 "no protocol for mock class - you should never see this exception. Please file a bugrfeport and include stacktrace");
271
272 }
273
274 @Override
275 public synchronized boolean exists() throws MessagingException {
276 abortIdle();
277 return mailboxFolder.isExists();
278 }
279
280 @Override
281 public synchronized Message[] expunge() throws MessagingException {
282 abortIdle();
283 checkExists();
284 checkOpened();
285 checkWriteMode();
286
287 final Message[] removed = wrap(mailboxFolder.expunge());
288
289 if (removed.length > 0) {
290 notifyMessageRemovedListeners(true, removed);
291 }
292
293 return removed;
294
295 }
296
297 @Override
298 public synchronized Message[] expunge(final Message[] msgs) throws MessagingException {
299 abortIdle();
300 checkExists();
301 checkOpened();
302 checkWriteMode();
303 final Message[] removed = wrap(mailboxFolder.expunge(msgs));
304
305 if (removed.length > 0) {
306 notifyMessageRemovedListeners(true, removed);
307 }
308
309 return removed;
310
311 }
312
313 @Override
314 public synchronized void fetch(final Message[] msgs, final FetchProfile fp) throws MessagingException {
315 abortIdle();
316
317 }
318
319 @Override
320 public void folderCreated(final MailboxFolder mf) {
321
322
323 }
324
325 @Override
326 public void folderDeleted(final MailboxFolder mf) {
327
328
329 }
330
331 @Override
332 public void folderRenamed(final String from, final MailboxFolder to) {
333
334
335 }
336
337 @Override
338 public synchronized void forceClose() throws MessagingException {
339 close(false);
340 }
341
342 @Override
343 public synchronized String[] getAttributes() throws MessagingException {
344 checkExists();
345
346 return new String[0];
347 }
348
349 @Override
350 public synchronized int getDeletedMessageCount() throws MessagingException {
351 abortIdle();
352 checkExists();
353 if (!opened) {
354 return -1;
355 }
356
357 return mailboxFolder.getByFlags(new Flags(Flags.Flag.DELETED), false).length;
358 }
359
360 @Override
361 public synchronized Folder getFolder(final String name) throws MessagingException {
362 abortIdle();
363
364
365 logger.debug("getFolder(" + name + ") on " + getFullName());
366
367 if ("inbox".equalsIgnoreCase(name)) {
368 return new IMAPMockFolder(store, mailboxFolder.getMailbox().getInbox());
369 }
370
371 return new IMAPMockFolder(store, mailboxFolder.getOrAddSubFolder(name));
372
373 }
374
375 @Override
376 public synchronized String getFullName() {
377
378 return mailboxFolder.getFullName();
379 }
380
381 @Override
382 public synchronized long getHighestModSeq() throws MessagingException {
383 throw new MessagingException("CONDSTORE not supported");
384 }
385
386 @Override
387 public Message getMessage(final int msgnum) throws MessagingException {
388 abortIdle();
389 checkExists();
390 checkOpened();
391 return new MockMessage(mailboxFolder.getByMsgNum(msgnum), this);
392 }
393
394 @Override
395 public synchronized Message getMessageByUID(final long uid) throws MessagingException {
396 abortIdle();
397 checkExists();
398 checkOpened();
399 return new MockMessage(mailboxFolder.getById(uid), this);
400 }
401
402 @Override
403 public int getMessageCount() throws MessagingException {
404 abortIdle();
405 checkExists();
406 return mailboxFolder.getMessageCount();
407 }
408
409 @Override
410 public Message[] getMessages(final int low, final int high) throws MessagingException {
411 abortIdle();
412 checkExists();
413 checkOpened();
414 final List<Message> messages = new ArrayList<Message>();
415 for (int i = low; i <= high; i++) {
416 final Message m = mailboxFolder.getByMsgNum(i);
417 messages.add(new MockMessage(m, this));
418 }
419 return messages.toArray(new Message[messages.size()]);
420 }
421
422 @Override
423 public synchronized Message[] getMessagesByUID(final long start, final long end) throws MessagingException {
424 abortIdle();
425 checkExists();
426 checkOpened();
427 return wrap(mailboxFolder.getByIds(start, end));
428
429 }
430
431 @Override
432 public synchronized Message[] getMessagesByUID(final long[] uids) throws MessagingException {
433 abortIdle();
434 checkExists();
435 checkOpened();
436 return wrap(mailboxFolder.getByIds(uids));
437 }
438
439 @Override
440 public synchronized Message[] getMessagesByUIDChangedSince(final long start, final long end, final long modseq)
441 throws MessagingException {
442 throw new MessagingException("CONDSTORE not supported");
443 }
444
445 @Override
446 public synchronized String getName() {
447
448 return mailboxFolder.getName();
449 }
450
451 @Override
452 public int getNewMessageCount() throws MessagingException {
453 abortIdle();
454 checkExists();
455 return mailboxFolder.getByFlags(new Flags(Flag.RECENT), true).length;
456
457
458
459
460
461 }
462
463 @Override
464 public Folder getParent() throws MessagingException {
465 checkExists();
466 if (mailboxFolder.getParent() == null) {
467 throw new MessagingException("no parent, is already default root");
468 }
469
470 return new IMAPMockFolder(store, mailboxFolder.getParent());
471 }
472
473 @Override
474 public Flags getPermanentFlags() {
475 return null;
476 }
477
478 @Override
479 public Quota[] getQuota() throws MessagingException {
480 throw new MessagingException("QUOTA not supported");
481 }
482
483 @Override
484 public char getSeparator() throws MessagingException {
485 abortIdle();
486 return MailboxFolder.SEPARATOR;
487 }
488
489 @Override
490 public synchronized Message[] getSortedMessages(final SortTerm[] term) throws MessagingException {
491 throw new MessagingException("SORT not supported");
492 }
493
494 @Override
495 public synchronized Message[] getSortedMessages(final SortTerm[] term, final SearchTerm sterm) throws MessagingException {
496 throw new MessagingException("SORT not supported");
497 }
498
499 @Override
500 public int getType() throws MessagingException {
501
502 return mailboxFolder.isRoot() ? HOLDS_FOLDERS : HOLDS_MESSAGES | HOLDS_FOLDERS;
503 }
504
505
506
507
508 @Override
509 public synchronized long getUID(final Message message) throws MessagingException {
510 abortIdle();
511 return mailboxFolder.getUID(message);
512 }
513
514
515
516
517 @Override
518 public synchronized long getUIDNext() throws MessagingException {
519 abortIdle();
520 return mailboxFolder.getUniqueMessageId() + 10;
521 }
522
523
524
525
526 @Override
527 public synchronized long getUIDValidity() throws MessagingException {
528 abortIdle();
529 return mailboxFolder.getUidValidity();
530 }
531
532 @Override
533 public synchronized int getUnreadMessageCount() throws MessagingException {
534 abortIdle();
535 checkExists();
536 return mailboxFolder.getByFlags(new Flags(Flags.Flag.SEEN), false).length;
537 }
538
539 @Override
540 public void handleResponse(final Response r) {
541 throw new RuntimeException("not implemented/should not happen");
542 }
543
544 @Override
545 public boolean hasNewMessages() throws MessagingException {
546 checkExists();
547 return getNewMessageCount() > 0;
548 }
549
550 @Override
551 public Map<String, String> id(final Map<String, String> clientParams) throws MessagingException {
552 return store.id(clientParams);
553 }
554
555 @Override
556 public void idle(final boolean once) throws MessagingException {
557
558 if (Thread.holdsLock(this)) {
559 logger.error("Thread already hold folder lock, thats not supposed to be the case");
560 }
561
562 synchronized (this) {
563 checkOpened();
564 if (idleState == RUNNING) {
565
566 idleState = IDLE;
567
568
569 } else {
570
571 logger.trace("Another thread is idle, return from idle()");
572 return;
573 }
574 }
575
576
577
578 logger.trace("Now idle ...");
579 try {
580
581 while (idleState != ABORTING && opened && mailboxFolder.isExists()) {
582 logger.trace("wait for folder actions");
583 idleLock.acquire();
584 logger.trace("folder action happend");
585
586 if (once) {
587 logger.trace("once =0 true, so return from idle()");
588 break;
589 }
590 }
591
592 logger.trace("while loop end with idle state " + idleState);
593
594 } catch (final InterruptedException e) {
595 Thread.currentThread().interrupt();
596
597 } finally {
598 logger.trace("set idle state to: running");
599 idleState = RUNNING;
600 }
601
602 logger.trace("return from idle()");
603 }
604
605 @Override
606 public boolean isOpen() {
607 return opened;
608 }
609
610 @Override
611 public synchronized boolean isSubscribed() {
612 abortIdle();
613 return mailboxFolder.isSubscribed();
614 }
615
616 @Override
617 public Folder[] list(final String pattern) throws MessagingException {
618 abortIdle();
619 checkExists();
620
621 final List<MailboxFolder> children = mailboxFolder.getChildren();
622 final List<Folder> ret = new ArrayList<Folder>();
623
624 for (final MailboxFolder mf : children) {
625 if (mf.isExists()) {
626 ret.add(new IMAPMockFolder(store, mf));
627 }
628 }
629
630 logger.debug("Folder (" + getFullName() + ") list return " + ret);
631
632 return ret.toArray(new Folder[ret.size()]);
633
634 }
635
636 @Override
637 public Folder[] listSubscribed(final String pattern) throws MessagingException {
638 abortIdle();
639 checkExists();
640
641
642 final List<MailboxFolder> children = mailboxFolder.getChildren();
643 final List<Folder> ret = new ArrayList<Folder>();
644
645 for (final MailboxFolder mf : children) {
646 if (mf.isExists() && mf.isSubscribed()) {
647 ret.add(new IMAPMockFolder(store, mf));
648 }
649 }
650
651 logger.debug("Folder (" + getFullName() + ") list subscribed return " + ret);
652
653 return ret.toArray(new Folder[ret.size()]);
654 }
655
656 @Override
657 public void messageAdded(final MailboxFolder mf, final MockMessage msg) {
658 notifyMessageAddedListeners(new Message[] { msg });
659 idleLock.release();
660
661 }
662
663 @Override
664 public void messageChanged(final MailboxFolder mf, final MockMessage msg, final boolean headerChanged, final boolean flagsChanged) {
665 notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, msg);
666 idleLock.release();
667 }
668
669 @Override
670 public void messageExpunged(final MailboxFolder mf, final MockMessage msg, final boolean removed) {
671 idleLock.release();
672
673 }
674
675 @Override
676 public void open(final int mode) throws MessagingException {
677 checkClosed();
678 checkExists();
679 opened = true;
680 openMode = mode;
681 logger.debug("Open folder " + getFullName() + " (" + objectId + ")");
682 notifyConnectionListeners(ConnectionEvent.OPENED);
683 }
684
685 @Override
686 public synchronized List<MailEvent> open(final int mode, final ResyncData rd) throws MessagingException {
687
688 if (rd == null) {
689 open(mode);
690 return null;
691 }
692
693 throw new MessagingException("CONDSTORE and QRESYNC not supported");
694
695 }
696
697 @Override
698 public synchronized boolean renameTo(final Folder f) throws MessagingException {
699 abortIdle();
700 checkClosed();
701 checkExists();
702 if (f.getStore() != store) {
703 throw new MessagingException("Can't rename across Stores");
704 }
705
706 mailboxFolder.renameFolder(f.getName());
707 notifyFolderRenamedListeners(f);
708 return true;
709 }
710
711 @Override
712 public Message[] search(final SearchTerm term) throws MessagingException {
713 abortIdle();
714 checkOpened();
715 return mailboxFolder.search(term, null);
716 }
717
718 @Override
719 public Message[] search(final SearchTerm term, final Message[] msgs) throws MessagingException {
720 abortIdle();
721 checkOpened();
722 return mailboxFolder.search(term, msgs);
723 }
724
725 @Override
726 public synchronized void setFlags(final Message[] msgs, final Flags flag, final boolean value) throws MessagingException {
727 abortIdle();
728 checkOpened();
729
730 for (final Message message : msgs) {
731
732 final Message m = mailboxFolder.getById(((MockMessage) message).getMockid());
733
734 if (m != null) {
735 m.setFlags(flag, value);
736 }
737 }
738
739 }
740
741 @Override
742 public void setQuota(final Quota quota) throws MessagingException {
743 throw new MessagingException("QUOTA not supported");
744 }
745
746 @Override
747 public synchronized void setSubscribed(final boolean subscribe) throws MessagingException {
748 abortIdle();
749 mailboxFolder.setSubscribed(subscribe);
750 }
751
752 @Override
753 public void uidInvalidated() {
754
755
756 }
757
758 private Message[] wrap(final Message[] msgs) throws MessagingException {
759 final Message[] ret = new Message[msgs.length];
760 int i = 0;
761 for (final Message message : msgs) {
762 ret[i++] = new MockMessage(message, this);
763 }
764 return ret;
765 }
766
767 }