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.Arrays; 030 import java.util.Collections; 031 import java.util.HashMap; 032 import java.util.List; 033 import java.util.Map; 034 import java.util.TreeSet; 035 036 import javax.mail.Flags; 037 import javax.mail.Flags.Flag; 038 import javax.mail.Message; 039 import javax.mail.MessagingException; 040 import javax.mail.UIDFolder; 041 import javax.mail.internet.MimeMessage; 042 import javax.mail.search.SearchTerm; 043 044 public class MailboxFolder implements MockMessage.FlagChangeListener { 045 046 public static interface MailboxEventListener { 047 048 void folderCreated(MailboxFolder mf); 049 050 void folderDeleted(MailboxFolder mf); 051 052 void folderRenamed(String from, MailboxFolder to); 053 054 void messageAdded(MailboxFolder mf, MockMessage msg); 055 056 void messageChanged(MailboxFolder mf, MockMessage msg, boolean headerChanged, boolean flagsChanged); // TODO 057 // header 058 // change 059 // can 060 // not 061 // happen 062 // because 063 // MockMessage 064 // is 065 // readonly? 066 067 void messageExpunged(MailboxFolder mf, MockMessage msg, boolean removed); 068 069 void uidInvalidated(); 070 071 } 072 073 public static final char SEPARATOR = '/'; 074 private final List<MailboxFolder> children = new ArrayList<MailboxFolder>(); 075 private boolean exists = true; 076 protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass()); 077 078 private final MockMailbox mailbox; 079 080 private volatile List<MailboxEventListener> mailboxEventListeners = Collections.synchronizedList(new ArrayList<MailboxEventListener>()); 081 082 private final Map<Long, MockMessage> messages = new HashMap<Long, MockMessage>(); 083 private String name; 084 private MailboxFolder parent; 085 private boolean simulateError = false; 086 private boolean subscribed; 087 088 private long uidValidity = 50; 089 090 private long uniqueMessageId = 10; 091 092 protected MailboxFolder(final String name, final MockMailbox mb, final boolean exists) { 093 super(); 094 095 if (name == null) { 096 this.name = ""; 097 } else { 098 this.name = name; 099 } 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 }