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    }