Java tutorial
/**************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * * or more contributor license agreements. See the NOTICE file * * distributed with this work for additional information * * regarding copyright ownership. The ASF licenses this file * * to you under the Apache License, Version 2.0 (the * * "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, * * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * * KIND, either express or implied. See the License for the * * specific language governing permissions and limitations * * under the License. * ****************************************************************/ package org.apache.james.mailbox.maildir.mail; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import javax.mail.Flags; import javax.mail.Flags.Flag; import org.apache.commons.io.FileUtils; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.maildir.MaildirFolder; import org.apache.james.mailbox.maildir.MaildirMessageName; import org.apache.james.mailbox.maildir.MaildirStore; import org.apache.james.mailbox.maildir.mail.model.MaildirMailboxMessage; import org.apache.james.mailbox.model.MessageMetaData; import org.apache.james.mailbox.model.MessageRange; import org.apache.james.mailbox.model.MessageRange.Type; import org.apache.james.mailbox.model.UpdatedFlags; import org.apache.james.mailbox.store.FlagsUpdateCalculator; import org.apache.james.mailbox.store.SimpleMessageMetaData; import org.apache.james.mailbox.store.mail.AbstractMessageMapper; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; public class MaildirMessageMapper extends AbstractMessageMapper { private final MaildirStore maildirStore; private final static int BUF_SIZE = 2048; public MaildirMessageMapper(MailboxSession session, MaildirStore maildirStore) { super(session, maildirStore, maildirStore); this.maildirStore = maildirStore; } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#countMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) */ @Override public long countMessagesInMailbox(Mailbox mailbox) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); File newFolder = folder.getNewFolder(); File curFolder = folder.getCurFolder(); File[] newFiles = newFolder.listFiles(); File[] curFiles = curFolder.listFiles(); if (newFiles == null || curFiles == null) throw new MailboxException("Unable to count messages in Mailbox " + mailbox, new IOException("Not a valid Maildir folder: " + maildirStore.getFolderName(mailbox))); return newFiles.length + curFiles.length; } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#countUnseenMessagesInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) */ @Override public long countUnseenMessagesInMailbox(Mailbox mailbox) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); File newFolder = folder.getNewFolder(); File curFolder = folder.getCurFolder(); String[] unseenMessages = curFolder.list(MaildirMessageName.FILTER_UNSEEN_MESSAGES); String[] newUnseenMessages = newFolder.list(MaildirMessageName.FILTER_UNSEEN_MESSAGES); if (newUnseenMessages == null || unseenMessages == null) throw new MailboxException("Unable to count unseen messages in Mailbox " + mailbox, new IOException("Not a valid Maildir folder: " + maildirStore.getFolderName(mailbox))); return newUnseenMessages.length + unseenMessages.length; } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#delete(org.apache.james.mailbox.store.mail.model.Mailbox, * MailboxMessage) */ @Override public void delete(Mailbox mailbox, MailboxMessage message) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); try { folder.delete(mailboxSession, message.getUid()); } catch (MailboxException e) { throw new MailboxException("Unable to delete MailboxMessage " + message + " in Mailbox " + mailbox, e); } } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#findInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox, * org.apache.james.mailbox.model.MessageRange, * org.apache.james.mailbox.store.mail.MessageMapper.FetchType, int) */ @Override public Iterator<MailboxMessage> findInMailbox(Mailbox mailbox, MessageRange set, FetchType fType, int max) throws MailboxException { final List<MailboxMessage> results; final long from = set.getUidFrom(); final long to = set.getUidTo(); final Type type = set.getType(); switch (type) { default: case ALL: results = findMessagesInMailboxBetweenUIDs(mailbox, null, 0, -1, max); break; case FROM: results = findMessagesInMailboxBetweenUIDs(mailbox, null, from, -1, max); break; case ONE: results = findMessageInMailboxWithUID(mailbox, from); break; case RANGE: results = findMessagesInMailboxBetweenUIDs(mailbox, null, from, to, max); break; } return results.iterator(); } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#findRecentMessageUidsInMailbox(org.apache.james.mailbox.store.mail.model.Mailbox) */ @Override public List<Long> findRecentMessageUidsInMailbox(Mailbox mailbox) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); SortedMap<Long, MaildirMessageName> recentMessageNames = folder.getRecentMessages(mailboxSession); return new ArrayList<Long>(recentMessageNames.keySet()); } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#findFirstUnseenMessageUid(org.apache.james.mailbox.store.mail.model.Mailbox) */ @Override public Long findFirstUnseenMessageUid(Mailbox mailbox) throws MailboxException { List<MailboxMessage> result = findMessagesInMailbox(mailbox, MaildirMessageName.FILTER_UNSEEN_MESSAGES, 1); if (result.isEmpty()) { return null; } else { return result.get(0).getUid(); } } /** * @see org.apache.james.mailbox.store.mail.MessageMapper#updateFlags(org.apache.james.mailbox.store.mail.model.Mailbox, * javax.mail.Flags, boolean, boolean, * org.apache.james.mailbox.model.MessageRange) */ @Override public Iterator<UpdatedFlags> updateFlags(Mailbox mailbox, FlagsUpdateCalculator flagsUpdateCalculator, MessageRange set) throws MailboxException { final List<UpdatedFlags> updatedFlags = new ArrayList<UpdatedFlags>(); final MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); Iterator<MailboxMessage> it = findInMailbox(mailbox, set, FetchType.Metadata, -1); while (it.hasNext()) { final MailboxMessage member = it.next(); Flags originalFlags = member.createFlags(); member.setFlags(flagsUpdateCalculator.buildNewFlags(originalFlags)); Flags newFlags = member.createFlags(); try { MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, member.getUid()); if (messageName != null) { File messageFile = messageName.getFile(); // System.out.println("save existing " + message + // " as " + messageFile.getName()); messageName.setFlags(member.createFlags()); // this automatically moves messages from new to cur if // needed String newMessageName = messageName.getFullName(); File newMessageFile; // See MAILBOX-57 if (newFlags.contains(Flag.RECENT)) { // message is recent so save it in the new folder newMessageFile = new File(folder.getNewFolder(), newMessageName); } else { newMessageFile = new File(folder.getCurFolder(), newMessageName); } long modSeq; // if the flags don't have change we should not try to move // the file if (newMessageFile.equals(messageFile) == false) { FileUtils.moveFile(messageFile, newMessageFile); modSeq = newMessageFile.lastModified(); } else { modSeq = messageFile.lastModified(); } member.setModSeq(modSeq); updatedFlags.add(new UpdatedFlags(member.getUid(), modSeq, originalFlags, newFlags)); long uid = member.getUid(); folder.update(mailboxSession, uid, newMessageName); } } catch (IOException e) { throw new MailboxException("Failure while save MailboxMessage " + member + " in Mailbox " + mailbox, e); } } return updatedFlags.iterator(); } @Override public Map<Long, MessageMetaData> expungeMarkedForDeletionInMailbox(Mailbox mailbox, MessageRange set) throws MailboxException { List<MailboxMessage> results = new ArrayList<MailboxMessage>(); final long from = set.getUidFrom(); final long to = set.getUidTo(); final Type type = set.getType(); switch (type) { default: case ALL: results = findMessagesInMailbox(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, -1); break; case FROM: results = findMessagesInMailboxBetweenUIDs(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, from, -1, -1); break; case ONE: results = findDeletedMessageInMailboxWithUID(mailbox, from); break; case RANGE: results = findMessagesInMailboxBetweenUIDs(mailbox, MaildirMessageName.FILTER_DELETED_MESSAGES, from, to, -1); break; } Map<Long, MessageMetaData> uids = new HashMap<Long, MessageMetaData>(); for (MailboxMessage m : results) { long uid = m.getUid(); uids.put(uid, new SimpleMessageMetaData(m)); delete(mailbox, m); } return uids; } /** * (non-Javadoc) * * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, * MailboxMessage) */ @Override public MessageMetaData move(Mailbox mailbox, MailboxMessage original) throws MailboxException { throw new UnsupportedOperationException( "Not implemented - see https://issues.apache.org/jira/browse/IMAP-370"); } /** * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#copy(org.apache * .james.mailbox.store.mail.model.Mailbox, long, long, * MailboxMessage) */ @Override protected MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, MailboxMessage original) throws MailboxException { SimpleMailboxMessage theCopy = SimpleMailboxMessage.copy(mailbox.getMailboxId(), original); Flags flags = theCopy.createFlags(); flags.add(Flag.RECENT); theCopy.setFlags(flags); return save(mailbox, theCopy); } /** * @see org.apache.james.mailbox.store.mail.AbstractMessageMapper#save(org.apache.james.mailbox.store.mail.model.Mailbox, * MailboxMessage) */ @Override protected MessageMetaData save(Mailbox mailbox, MailboxMessage message) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); long uid = 0; // a new message // save file to "tmp" folder File tmpFolder = folder.getTmpFolder(); // The only case in which we could get problems with clashing names // is if the system clock // has been set backwards, then the server is restarted with the // same pid, delivers the same // number of messages since its start in the exact same millisecond // as done before and the // random number generator returns the same number. // In order to prevent this case we would need to check ALL files in // all folders and compare // them to this message name. We rather let this happen once in a // billion years... MaildirMessageName messageName = MaildirMessageName.createUniqueName(folder, message.getFullContentOctets()); File messageFile = new File(tmpFolder, messageName.getFullName()); FileOutputStream fos = null; InputStream input = null; try { if (!messageFile.createNewFile()) throw new IOException("Could not create file " + messageFile); fos = new FileOutputStream(messageFile); input = message.getFullContent(); byte[] b = new byte[BUF_SIZE]; int len = 0; while ((len = input.read(b)) != -1) fos.write(b, 0, len); } catch (IOException ioe) { throw new MailboxException("Failure while save MailboxMessage " + message + " in Mailbox " + mailbox, ioe); } finally { try { if (fos != null) fos.close(); } catch (IOException e) { } try { if (input != null) input.close(); } catch (IOException e) { } } File newMessageFile = null; // delivered via SMTP, goes to ./new without flags if (message.isRecent()) { messageName.setFlags(message.createFlags()); newMessageFile = new File(folder.getNewFolder(), messageName.getFullName()); // System.out.println("save new recent " + message + " as " + // newMessageFile.getName()); } // appended via IMAP (might already have flags etc, goes to ./cur // directly) else { messageName.setFlags(message.createFlags()); newMessageFile = new File(folder.getCurFolder(), messageName.getFullName()); // System.out.println("save new not recent " + message + " as " // + newMessageFile.getName()); } try { FileUtils.moveFile(messageFile, newMessageFile); } catch (IOException e) { // TODO: Try copy and delete throw new MailboxException("Failure while save MailboxMessage " + message + " in Mailbox " + mailbox, e); } try { uid = folder.appendMessage(mailboxSession, newMessageFile.getName()); message.setUid(uid); message.setModSeq(newMessageFile.lastModified()); return new SimpleMessageMetaData(message); } catch (MailboxException e) { throw new MailboxException("Failure while save MailboxMessage " + message + " in Mailbox " + mailbox, e); } } /** * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#endRequest() */ @Override public void endRequest() { // not used } private List<MailboxMessage> findMessageInMailboxWithUID(Mailbox mailbox, long uid) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); try { MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, uid); ArrayList<MailboxMessage> messages = new ArrayList<MailboxMessage>(); if (messageName != null && messageName.getFile().exists()) { messages.add(new MaildirMailboxMessage(mailbox, uid, messageName)); } return messages; } catch (IOException e) { throw new MailboxException( "Failure while search for MailboxMessage with uid " + uid + " in Mailbox " + mailbox, e); } } private List<MailboxMessage> findMessagesInMailboxBetweenUIDs(Mailbox mailbox, FilenameFilter filter, long from, long to, int max) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); int cur = 0; SortedMap<Long, MaildirMessageName> uidMap = null; try { if (filter != null) uidMap = folder.getUidMap(mailboxSession, filter, from, to); else uidMap = folder.getUidMap(mailboxSession, from, to); ArrayList<MailboxMessage> messages = new ArrayList<MailboxMessage>(); for (Entry<Long, MaildirMessageName> entry : uidMap.entrySet()) { messages.add(new MaildirMailboxMessage(mailbox, entry.getKey(), entry.getValue())); if (max != -1) { cur++; if (cur >= max) break; } } return messages; } catch (IOException e) { throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); } } private List<MailboxMessage> findMessagesInMailbox(Mailbox mailbox, FilenameFilter filter, int limit) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); try { SortedMap<Long, MaildirMessageName> uidMap = folder.getUidMap(mailboxSession, filter, limit); ArrayList<MailboxMessage> filtered = new ArrayList<MailboxMessage>(uidMap.size()); for (Entry<Long, MaildirMessageName> entry : uidMap.entrySet()) filtered.add(new MaildirMailboxMessage(mailbox, entry.getKey(), entry.getValue())); return filtered; } catch (IOException e) { throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); } } private List<MailboxMessage> findDeletedMessageInMailboxWithUID(Mailbox mailbox, long uid) throws MailboxException { MaildirFolder folder = maildirStore.createMaildirFolder(mailbox); try { MaildirMessageName messageName = folder.getMessageNameByUid(mailboxSession, uid); ArrayList<MailboxMessage> messages = new ArrayList<MailboxMessage>(); if (MaildirMessageName.FILTER_DELETED_MESSAGES.accept(null, messageName.getFullName())) { messages.add(new MaildirMailboxMessage(mailbox, uid, messageName)); } return messages; } catch (IOException e) { throw new MailboxException("Failure while search for Messages in Mailbox " + mailbox, e); } } /** * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#begin() */ @Override protected void begin() throws MailboxException { // nothing todo } /** * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#commit() */ @Override protected void commit() throws MailboxException { // nothing todo } /** * @see org.apache.james.mailbox.store.transaction.TransactionalMapper#rollback() */ @Override protected void rollback() throws MailboxException { // nothing todo } }