View Javadoc
1   /***********************************************************************************************************************
2    *
3    * JavaMail Mock2 Provider - open source mock classes for mock up JavaMail
4    * =======================================================================
5    *
6    * Copyright (C) 2014 by Hendrik Saly (http://saly.de)
7    * 
8    * Based on ideas from Kohsuke Kawaguchi's Mock-javamail (https://java.net/projects/mock-javamail)
9    *
10   ***********************************************************************************************************************
11   *
12   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
13   * the License. You may obtain a copy of the License at
14   *
15   *     http://www.apache.org/licenses/LICENSE-2.0
16   *
17   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
18   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
19   * specific language governing permissions and limitations under the License.
20   *
21   ***********************************************************************************************************************
22   *
23   * $Id:$
24   *
25   **********************************************************************************************************************/
26  package de.saly.javamail.mock2;
27  
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.TreeSet;
35  
36  import javax.mail.Flags;
37  import javax.mail.Flags.Flag;
38  import javax.mail.Message;
39  import javax.mail.MessagingException;
40  import javax.mail.UIDFolder;
41  import javax.mail.internet.MimeMessage;
42  import javax.mail.search.SearchTerm;
43  
44  public class MailboxFolder implements MockMessage.FlagChangeListener {
45  
46      public static interface MailboxEventListener {
47  
48          void folderCreated(MailboxFolder mf);
49  
50          void folderDeleted(MailboxFolder mf);
51  
52          void folderRenamed(String from, MailboxFolder to);
53  
54          void messageAdded(MailboxFolder mf, MockMessage msg);
55  
56          void messageChanged(MailboxFolder mf, MockMessage msg, boolean headerChanged, boolean flagsChanged); // TODO
57                                                                                                               // header
58                                                                                                               // change
59                                                                                                               // can
60                                                                                                               // not
61                                                                                                               // happen
62                                                                                                               // because
63                                                                                                               // MockMessage
64                                                                                                               // is
65                                                                                                               // readonly?
66  
67          void messageExpunged(MailboxFolder mf, MockMessage msg, boolean removed);
68  
69          void uidInvalidated();
70  
71      }
72  
73      public static final char SEPARATOR = '/';
74      private final List<MailboxFolder> children = new ArrayList<MailboxFolder>();
75      private boolean exists = true;
76      protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());
77  
78      private final MockMailbox mailbox;
79  
80      private volatile List<MailboxEventListener> mailboxEventListeners = Collections.synchronizedList(new ArrayList<MailboxEventListener>());
81  
82      private final Map<Long, MockMessage> messages = new HashMap<Long, MockMessage>();
83      private String name;
84      private MailboxFolder parent;
85      private boolean simulateError = false;
86      private boolean subscribed;
87  
88      private long uidValidity = 50;
89  
90      private long uniqueMessageId = 10;
91  
92      protected MailboxFolder(final String name, final MockMailbox mb, final boolean exists) {
93          super();
94  
95          if (name == null) {
96              this.name = "";
97          } else {
98              this.name = name;
99          }
100 
101         this.mailbox = mb;
102         this.exists = exists;
103 
104         logger.debug("Created " + name + " (exists: " + exists + ")");
105     }
106 
107     public synchronized Message add(final MimeMessage e) throws MessagingException {
108         checkExists();
109 
110         uniqueMessageId++;
111 
112         final MockMessage mockMessage = new MockMessage(e, uniqueMessageId, this, this);
113 
114         mockMessage.setSpecialHeader("Message-ID", String.valueOf(uniqueMessageId));
115         mockMessage.setSpecialHeader("X-Mock-Folder", getFullName());
116         mockMessage.setFlags(new Flags(Flag.RECENT), true);
117         // unread.add(e);
118 
119         messages.put(uniqueMessageId, mockMessage);
120 
121         for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
122             mailboxEventListener.messageAdded(this, mockMessage);
123         }
124 
125         logger.debug("Message ID " + uniqueMessageId + " to " + getFullName() + " added for user " + mailbox.getAddress());
126 
127         return mockMessage;
128     }
129 
130     public synchronized void addMailboxEventListener(final MailboxEventListener l) {
131         if (l != null) {
132             mailboxEventListeners.add(l);
133         }
134     }
135 
136     protected MailboxFolder addSpecialSubFolder(final String name) {
137         final MailboxFolder mbt = new MailboxFolder(name, mailbox, true);
138         mbt.parent = this;
139         children.add(mbt);
140         return mbt;
141     }
142 
143     protected void checkExists() {
144         if (!exists) {
145             throw new IllegalStateException("folder does not exist");
146         }
147     }
148 
149     protected void checkFolderName(final String name) {
150         checkFolderName(name, true);
151     }
152 
153     protected void checkFolderName(final String name, final boolean checkSeparator) {
154         // TODO regex for valid folder names?
155 
156         if (name == null || name.trim().equals("") || name.equalsIgnoreCase("inbox") || checkSeparator
157                 && name.contains(String.valueOf(SEPARATOR))) {
158             throw new IllegalArgumentException("name '" + name + "' is not valid");
159         }
160     }
161 
162     public synchronized MailboxFolder create() {
163         if (isExists()) {
164             throw new IllegalStateException("already exists");
165         }
166         checkFolderName(this.name);
167 
168         exists = true;
169 
170         // TODO set parent and/or children to exists?
171 
172         if (parent != null && !parent.isExists()) {
173             parent.create();
174         }
175 
176         /*children.clear();
177 
178         if (parent != null) {
179             parent.children.add(this);
180         }
181 
182         if (mailboxEventListener != null) {
183             mailboxEventListener.folderCreated(this);
184         }*/
185 
186         for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
187             mailboxEventListener.folderCreated(this);
188         }
189 
190         logger.debug("Folder " + this.getFullName() + " created");
191         return this;
192 
193     }
194 
195     public synchronized void deleteFolder(final boolean recurse) {
196         checkExists();
197         checkFolderName(this.name);
198 
199         if (isRoot()) {
200             throw new IllegalArgumentException("root cannot be deleted");
201         }
202 
203         messages.clear();
204         // unread.clear();
205 
206         if (recurse) {
207             for (final MailboxFolder mf : getChildren()) {
208                 mf.deleteFolder(recurse);
209             }
210         }
211 
212         parent.children.remove(this);
213         this.exists = false;
214 
215         for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
216             mailboxEventListener.folderDeleted(this);
217         }
218         logger.debug("Folder " + this.getFullName() + " deleted");
219 
220     }
221 
222     @Override
223     public boolean equals(final Object obj) {
224         if (this == obj) {
225             return true;
226         }
227         if (obj == null) {
228             return false;
229         }
230         if (getClass() != obj.getClass()) {
231             return false;
232         }
233         final MailboxFolder other = (MailboxFolder) obj;
234         if (name == null) {
235             if (other.name != null) {
236                 return false;
237             }
238         } else if (!name.equals(other.name)) {
239             return false;
240         }
241         if (parent == null) {
242             if (other.parent != null) {
243                 return false;
244             }
245         } else if (!parent.equals(other.parent)) {
246             return false;
247         }
248         return true;
249     }
250 
251     public synchronized Message[] expunge() throws MessagingException {
252         checkExists();
253         final List<Message> expunged = new ArrayList<Message>();
254         for (final Message msg : getByFlags(new Flags(Flag.DELETED), true)) {
255 
256             expunged.add(messages.remove(((MockMessage) msg).getMockid()));
257             ((MockMessage) msg).setExpunged(true);
258 
259             for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
260                 mailboxEventListener.messageExpunged(this, (MockMessage) msg, true);
261             }
262         }
263 
264         logger.debug(expunged.size() + " messages expunged (deleted) from" + getFullName());
265         return expunged.toArray(new Message[expunged.size()]);
266 
267     }
268 
269     public synchronized Message[] expunge(final Message[] msgs) throws MessagingException {
270         checkExists();
271         final List<Long> toExpunge = new ArrayList<Long>();
272 
273         for (final Message msg : msgs) {
274             toExpunge.add(((MockMessage) msg).getMockid());
275         }
276 
277         final List<Message> expunged = new ArrayList<Message>();
278         for (final Message msg : getByFlags(new Flags(Flag.DELETED), true)) {
279 
280             if (!toExpunge.contains(new Long(((MockMessage) msg).getMockid()))) {
281                 continue;
282             }
283 
284             expunged.add(messages.remove(((MockMessage) msg).getMockid()));
285             ((MockMessage) msg).setExpunged(true);
286 
287             for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
288                 mailboxEventListener.messageExpunged(this, (MockMessage) msg, true);
289             }
290         }
291 
292         logger.debug(expunged.size() + " messages expunged (deleted) from " + getFullName());
293         return expunged.toArray(new Message[expunged.size()]);
294 
295     }
296 
297     public synchronized Message[] getByFlags(final Flags flags, final boolean mustSet /*final Folder folder*/) throws MessagingException {
298         checkExists();
299         final List<MockMessage> sms = new ArrayList<MockMessage>();
300         int num = 0;
301 
302         for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
303 
304             if (mustSet && mockMessage.getFlags().contains(flags) || !mustSet && !mockMessage.getFlags().contains(flags)) {
305                 mockMessage.setMessageNumber(++num);
306                 // mockMessage.setFolder(folder);
307                 sms.add(mockMessage);
308             }
309 
310         }
311         logger.debug("getByFlags() for " + getFullName() + " returns " + sms.size());
312         return sms.toArray(new Message[sms.size()]);
313     }
314 
315     public synchronized Message getById(final long id /*final Folder folder*/) {
316         checkExists();
317         final Message m = messages.get(id);
318 
319         if (m == null) {
320 
321             logger.debug("No message with id " + id + ", return null");
322             return null;
323 
324         }
325 
326         // ((MockMessage) m).setFolder(folder);
327         logger.debug("getById(" + id + ") for " + getFullName() + " returns successful");
328 
329         return m;
330     }
331 
332     // private List<Message> unread = new ArrayList<Message>();
333 
334     public synchronized Message[] getByIds(final long start, final long end/* final Folder folder*/) {
335         checkExists();
336         final List<MockMessage> sms = new ArrayList<MockMessage>();
337         int num = 0;
338 
339         MockMessage lastMsg = null;
340 
341         for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
342 
343             lastMsg = mockMessage;
344 
345             if (end == UIDFolder.LASTUID) {
346                 if (getMessageCount() != 1 && mockMessage.getMockid() < start) { // TODO
347                                                                                  // check?
348                     continue;
349                 }
350             } else {
351                 if (mockMessage.getMockid() < start || mockMessage.getMockid() > end) {
352                     continue;
353                 }
354             }
355 
356             mockMessage.setMessageNumber(++num);
357             // mockMessage.setFolder(folder);
358             sms.add(mockMessage);
359         }
360 
361         if (end == UIDFolder.LASTUID && sms.size() == 0) {
362             lastMsg.setMessageNumber(++num);
363             // lastMsg.setFolder(folder);
364             sms.add(lastMsg);
365         }
366 
367         logger.debug("getByIds(" + start + "," + end + " for " + getFullName() + " returns " + sms.size());
368         return sms.toArray(new Message[sms.size()]);
369     }
370 
371     public synchronized Message[] getByIds(final long[] id /*final Folder folder*/) {
372         checkExists();
373         final List<Long> idlist = new ArrayList<Long>();
374         for (final long value : id) {
375             idlist.add(value);
376         }
377         final List<MockMessage> sms = new ArrayList<MockMessage>();
378         int num = 0;
379 
380         for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
381 
382             if (!idlist.contains(mockMessage.getMockid())) {
383                 continue;
384             }
385 
386             mockMessage.setMessageNumber(++num);
387             // mockMessage.setFolder(folder);
388             sms.add(mockMessage);
389         }
390 
391         logger.debug("getByIds(" + Arrays.toString(id) + ") for " + getFullName() + " returns " + sms.size());
392         return sms.toArray(new Message[sms.size()]);
393     }
394 
395     public synchronized Message getByMsgNum(final int msgnum/*, final Folder folder*/) {
396         checkExists();
397         final List<MockMessage> sms = new ArrayList<MockMessage>();
398 
399         int num = 0;
400 
401         for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
402 
403             mockMessage.setMessageNumber(++num);
404             // mockMessage.setFolder(folder);
405             sms.add(mockMessage);
406         }
407 
408         logger.debug("getByMsgNum(" + msgnum + "), size is " + sms.size());
409 
410         if (msgnum - 1 < 0 || msgnum > sms.size()) {
411             throw new ArrayIndexOutOfBoundsException("message number (" + msgnum + ") out of bounds (" + sms.size() + ") for "
412                     + getFullName());
413         }
414 
415         final Message m = sms.get(msgnum - 1);
416         return m;
417     }
418 
419     /**
420      * 
421      * @return Unmodifieable new list copy
422      */
423     public synchronized List<MailboxFolder> getChildren() {
424         checkExists();
425         return Collections.unmodifiableList(new ArrayList<MailboxFolder>(children));
426     }
427 
428     public synchronized String getFullName() {
429         // checkExists();
430         if (isRoot()) {
431             return "";
432         }
433 
434         return parent.isRoot() ? name : parent.getFullName() + SEPARATOR + name;
435 
436     }
437 
438     /**
439      * @return the mailbox
440      */
441     public MockMailbox getMailbox() {
442         return mailbox;
443     }
444 
445     public synchronized int getMessageCount() {
446         checkExists();
447         logger.debug("getMessageCount() for " + getFullName() + " returns " + messages.size());
448         return messages.size();
449     }
450 
451     public synchronized Message[] getMessages(/*final Folder folder*/) {
452         checkExists();
453         final List<MockMessage> sms = new ArrayList<MockMessage>();
454         int num = 0;
455 
456         for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
457 
458             mockMessage.setMessageNumber(++num);
459             // mockMessage.setFolder(folder);
460             sms.add(mockMessage);
461         }
462         logger.debug("getMessages() for " + getFullName() + " returns " + sms.size());
463         return sms.toArray(new Message[sms.size()]);
464     }
465 
466     public String getName() {
467         return name;
468     }
469 
470     public MailboxFolder getOrAddSubFolder(final String name) throws MessagingException {
471         // checkExists();
472 
473         if (name == null || "".equals(name.trim())) {
474             throw new MessagingException("cannot get or add root folder");
475         }
476 
477         logger.debug("getOrAddSubFolder(" + name + ") on " + getFullName());
478 
479         final String[] path = name.split(String.valueOf(SEPARATOR));
480 
481         MailboxFolder last = this;
482         for (int i = 0; i < path.length; i++) {
483 
484             final String element = path[i];
485 
486             if ("inbox".equalsIgnoreCase(element)) {
487                 last = mailbox.getInbox();
488             } else {
489                 checkFolderName(element);
490                 final MailboxFolder mbt = new MailboxFolder(element, mailbox, false);
491                 mbt.parent = last;
492 
493                 int index = -1;
494                 if ((index = last.children.indexOf(mbt)) != -1) {
495 
496                     final MailboxFolder tmp = last.children.get(index);
497                     if (tmp.isExists()) {
498                         last = tmp;
499                         continue;
500                     }
501                 }
502 
503                 last.children.add(mbt);
504 
505                 logger.debug("Subfolder " + mbt.getFullName() + " added");
506                 last = mbt;
507             }
508 
509         }
510 
511         return last;
512 
513     }
514 
515     public synchronized MailboxFolder getParent() {
516         checkExists();
517         return parent;
518     }
519 
520     public synchronized int getSizeInBytes() throws MessagingException {
521         checkExists();
522         int size = 0;
523 
524         for (final MockMessage mockMessage : new TreeSet<MockMessage>(messages.values())) {
525 
526             if (mockMessage.getSize() > 0) {
527                 size += mockMessage.getSize();
528             }
529 
530         }
531 
532         return size;
533     }
534 
535     public synchronized long getUID(final Message msg) {
536         checkExists();
537         return ((MockMessage) msg).getMockid();
538     }
539 
540     /**
541      * @return the uidValidity
542      */
543     public synchronized long getUidValidity() {
544         checkExists();
545         return uidValidity;
546     }
547 
548     /**
549      * @return the uniqueMessageId
550      */
551     protected long getUniqueMessageId() {
552         return uniqueMessageId;
553     }
554 
555     @Override
556     public int hashCode() {
557         final int prime = 31;
558         int result = 1;
559         result = prime * result + (name == null ? 0 : name.hashCode());
560         result = prime * result + (parent == null ? 0 : parent.hashCode());
561         return result;
562     }
563 
564     public synchronized boolean hasMessages() {
565         checkExists();
566         return messages.isEmpty();
567     }
568 
569     public synchronized void invalidateUid() {
570         checkExists();
571         uidValidity += 10;
572 
573         for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
574             mailboxEventListener.uidInvalidated();
575         }
576         logger.debug("UidValidity invalidated, new UidValidity is " + uidValidity);
577     }
578 
579     /**
580      * @return the exists
581      */
582     public boolean isExists() {
583         return exists;
584     }
585 
586     public boolean isInbox() {
587         return name != null && name.equalsIgnoreCase("inbox");
588     }
589 
590     public boolean isRoot() {
591         return name == null || name.equals("") || parent == null;
592     }
593 
594     /**
595      * @return the simulateError
596      */
597     public boolean isSimulateError() {
598         return simulateError;
599     }
600 
601     protected boolean isSubscribed() {
602         return subscribed;
603     }
604 
605     public synchronized void markMessageAsDeleted(final Message e) throws MessagingException {
606         checkExists();
607         ((MockMessage) e).setFlag(Flag.DELETED, true);
608         // if(mailboxEventListener!=null)
609         // mailboxEventListener.messageRemoved(this, ((MockMessage)e), false);
610         logger.debug("Mark message " + ((MockMessage) e).getMockid() + " as deleted (Flag DELETED set)");
611     }
612 
613     public synchronized void markMessageAsSeen(final Message e) throws MessagingException {
614         checkExists();
615         ((MockMessage) e).setFlag(Flag.SEEN, true);
616         // if(mailboxEventListener!=null)
617         // mailboxEventListener.messageRemoved(this, ((MockMessage)e), false);
618         logger.debug("Mark message " + ((MockMessage) e).getMockid() + " as seen (Flag SEEN set)");
619     }
620 
621     @Override
622     public void onFlagChange(final MockMessage msg, final Flags flags, final boolean set) {
623 
624         for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
625             mailboxEventListener.messageChanged(this, msg, false, true);
626         }
627 
628         logger.debug("Flags of message " + msg.getMockid() + " change");
629 
630         if (messages.size() > 0 && messages.get(msg.getMockid()) != null) {
631             try {
632                 if (set && messages.get(msg.getMockid()).getFlags().contains(flags)) {
633                     return;
634 
635                 }
636 
637                 if (set && !messages.get(msg.getMockid()).getFlags().contains(flags)) {
638                     messages.get(msg.getMockid()).setFlags(flags, set);
639 
640                 }
641 
642                 if (!set && messages.get(msg.getMockid()).getFlags().contains(flags)) {
643                     messages.get(msg.getMockid()).setFlags(flags, set);
644 
645                 }
646 
647                 if (!set && !messages.get(msg.getMockid()).getFlags().contains(flags)) {
648                     return;
649 
650                 }
651             } catch (final Exception e) {
652                 logger.error("Error while changing flags " + e.toString(), e);
653             }
654         }
655 
656     }
657 
658     public synchronized void removeMailboxEventListener(final MailboxEventListener l) {
659         if (l != null) {
660             mailboxEventListeners.remove(l);
661         }
662     }
663 
664     public synchronized void renameFolder(final String newName) {
665         checkExists();
666         checkFolderName(this.name);
667         checkFolderName(newName);
668         final String tmpOldName = name;
669 
670         name = newName;
671 
672         for (final MailboxEventListener mailboxEventListener : mailboxEventListeners) {
673             mailboxEventListener.folderRenamed(tmpOldName, this);
674         }
675 
676         // TODO purge old folders, exists =false
677 
678         // TODO notify children?
679         /*for (MailboxFolder mf: children) {
680         	renameFolder(mf.name); //do not really change name of children, just notify because parent changes
681         }*/
682 
683         logger.debug("Folder " + tmpOldName + " renamed to " + newName + newName + " - New Fullname is " + this.getFullName());
684 
685     }
686 
687     public Message[] search(final SearchTerm term, final Message[] msgsToSearch) {
688         final List<MockMessage> sms = new ArrayList<MockMessage>();
689         final List<Message> msgsToSearchL = new ArrayList<Message>();
690 
691         if (msgsToSearch != null) {
692             msgsToSearchL.addAll(Arrays.asList(msgsToSearch));
693         }
694 
695         for (final Message msg : getMessages()) {
696             if (term != null && term.match(msg)) {
697 
698                 if (msgsToSearch == null || msgsToSearchL.contains(msg)) {
699                     sms.add((MockMessage) msg);
700                 }
701             }
702         }
703         return sms.toArray(new Message[sms.size()]);
704     }
705 
706     /**
707      * @param simulateError
708      *            the simulateError to set
709      */
710     public void setSimulateError(final boolean simulateError) {
711         this.simulateError = simulateError;
712     }
713 
714     protected void setSubscribed(final boolean subscribed) {
715         this.subscribed = subscribed;
716     }
717 }