Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JAMES-1887: AbstractMessageIdManager, make it pass the AbstractMessag…
…eIdManagerSideEffectTest
- Loading branch information
Quynh Nguyen
committed
Dec 15, 2016
1 parent
67cdbd6
commit 951a8ac
Showing
8 changed files
with
869 additions
and
48 deletions.
There are no files selected for viewing
276 changes: 276 additions & 0 deletions
276
mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
/**************************************************************** | ||
* 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.store; | ||
|
||
import java.util.Collection; | ||
import java.util.Date; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import javax.mail.Flags; | ||
import javax.mail.internet.SharedInputStream; | ||
|
||
import org.apache.james.mailbox.MailboxSession; | ||
import org.apache.james.mailbox.MessageIdManager; | ||
import org.apache.james.mailbox.MessageManager; | ||
import org.apache.james.mailbox.MessageUid; | ||
import org.apache.james.mailbox.exception.MailboxException; | ||
import org.apache.james.mailbox.model.MailboxId; | ||
import org.apache.james.mailbox.model.MailboxPath; | ||
import org.apache.james.mailbox.model.MessageAttachment; | ||
import org.apache.james.mailbox.model.MessageId; | ||
import org.apache.james.mailbox.model.MessageMetaData; | ||
import org.apache.james.mailbox.model.MessageResult; | ||
import org.apache.james.mailbox.model.QuotaRoot; | ||
import org.apache.james.mailbox.model.UpdatedFlags; | ||
import org.apache.james.mailbox.quota.QuotaManager; | ||
import org.apache.james.mailbox.quota.QuotaRootResolver; | ||
import org.apache.james.mailbox.store.event.MailboxEventDispatcher; | ||
import org.apache.james.mailbox.store.mail.MailboxMapper; | ||
import org.apache.james.mailbox.store.mail.MessageIdMapper; | ||
import org.apache.james.mailbox.store.mail.MessageMapper; | ||
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.PropertyBuilder; | ||
import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; | ||
import org.apache.james.mailbox.store.quota.QuotaChecker; | ||
|
||
import com.google.common.base.Function; | ||
import com.google.common.base.Optional; | ||
import com.google.common.base.Predicate; | ||
import com.google.common.collect.FluentIterable; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableSet; | ||
import com.google.common.collect.Sets; | ||
import com.google.common.collect.Sets.SetView; | ||
|
||
public class StoreMessageIdManager implements MessageIdManager { | ||
|
||
private static final Function<MailboxMessage, MetadataWithMailboxId> EXTRACT_METADATA_FUNCTION = new Function<MailboxMessage, MetadataWithMailboxId>() { | ||
@Override | ||
public MetadataWithMailboxId apply(MailboxMessage mailboxMessage) { | ||
return new MetadataWithMailboxId(new SimpleMessageMetaData(mailboxMessage), mailboxMessage.getMailboxId()); | ||
} | ||
}; | ||
|
||
private static final Function<MailboxMessage, MailboxId> EXTRACT_MAILBOX_ID_FUNCTION = new Function<MailboxMessage, MailboxId>() { | ||
@Override | ||
public MailboxId apply(MailboxMessage input) { | ||
return input.getMailboxId(); | ||
} | ||
}; | ||
|
||
private final MailboxSessionMapperFactory mailboxSessionMapperFactory; | ||
private final MailboxEventDispatcher dispatcher; | ||
private final MessageId.Factory messageIdFactory; | ||
private final QuotaManager quotaManager; | ||
private final QuotaRootResolver quotaRootResolver; | ||
|
||
public StoreMessageIdManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, MailboxEventDispatcher dispatcher, | ||
MessageId.Factory messageIdFactory, | ||
QuotaManager quotaManager, QuotaRootResolver quotaRootResolver) { | ||
this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; | ||
this.dispatcher = dispatcher; | ||
this.messageIdFactory = messageIdFactory; | ||
this.quotaManager = quotaManager; | ||
this.quotaRootResolver = quotaRootResolver; | ||
} | ||
|
||
@Override | ||
public void setFlags(Flags newState, MessageManager.FlagsUpdateMode replace, MessageId messageId, List<MailboxId> mailboxIds, MailboxSession mailboxSession) throws MailboxException { | ||
MessageIdMapper messageIdMapper = mailboxSessionMapperFactory.getMessageIdMapper(mailboxSession); | ||
Map<MailboxId, UpdatedFlags> updatedFlags = messageIdMapper.setFlags(messageId, mailboxIds, newState, replace); | ||
for (Map.Entry<MailboxId, UpdatedFlags> entry : updatedFlags.entrySet()) { | ||
dispatchFlagsChange(mailboxSession, entry.getKey(), entry.getValue()); | ||
} | ||
} | ||
|
||
@Override | ||
public List<MessageResult> getMessages(List<MessageId> messageIds, final MessageResult.FetchGroup fetchGroup, MailboxSession mailboxSession) throws MailboxException { | ||
try { | ||
MessageIdMapper messageIdMapper = mailboxSessionMapperFactory.getMessageIdMapper(mailboxSession); | ||
return FluentIterable.from(messageIdMapper.find(messageIds, MessageMapper.FetchType.Full)) | ||
.transform(messageResultConverter(fetchGroup)) | ||
.toList(); | ||
} catch (WrappedException wrappedException) { | ||
throw wrappedException.unwrap(); | ||
} | ||
} | ||
|
||
@Override | ||
public void delete(MessageId messageId, final List<MailboxId> mailboxIds, MailboxSession mailboxSession) throws MailboxException { | ||
MessageIdMapper messageIdMapper = mailboxSessionMapperFactory.getMessageIdMapper(mailboxSession); | ||
MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession); | ||
|
||
Iterable<MetadataWithMailboxId> metadatasWithMailbox = FluentIterable | ||
.from(messageIdMapper.find(ImmutableList.of(messageId), MessageMapper.FetchType.Metadata)) | ||
.filter(inMailbox(mailboxIds)) | ||
.transform(EXTRACT_METADATA_FUNCTION); | ||
|
||
messageIdMapper.delete(messageId, mailboxIds); | ||
|
||
for (MetadataWithMailboxId metadataWithMailboxId : metadatasWithMailbox) { | ||
dispatcher.expunged(mailboxSession, metadataWithMailboxId.messageMetaData, mailboxMapper.findMailboxById(metadataWithMailboxId.mailboxId)); | ||
} | ||
} | ||
|
||
@Override | ||
public void setInMailboxes(MessageId messageId, List<MailboxId> mailboxIds, MailboxSession mailboxSession) throws MailboxException { | ||
MessageIdMapper messageIdMapper = mailboxSessionMapperFactory.getMessageIdMapper(mailboxSession); | ||
|
||
List<MailboxMessage> mailboxMessages = messageIdMapper.find(ImmutableList.of(messageId), MessageMapper.FetchType.Full); | ||
|
||
if (!mailboxMessages.isEmpty()) { | ||
ImmutableSet<MailboxId> currentMailboxes = FluentIterable.from(mailboxMessages) | ||
.transform(EXTRACT_MAILBOX_ID_FUNCTION) | ||
.toSet(); | ||
HashSet<MailboxId> targetMailboxes = Sets.newHashSet(mailboxIds); | ||
List<MailboxId> mailboxesToRemove = ImmutableList.copyOf(Sets.difference(currentMailboxes, targetMailboxes)); | ||
SetView<MailboxId> mailboxesToAdd = Sets.difference(targetMailboxes, currentMailboxes); | ||
|
||
MailboxMessage mailboxMessage = mailboxMessages.get(0); | ||
validateQuota(mailboxesToAdd, mailboxesToRemove, mailboxSession, mailboxMessage); | ||
|
||
if (!mailboxesToAdd.isEmpty()) { | ||
addMessageToMailboxes(messageIdMapper, mailboxMessage, mailboxesToAdd, mailboxSession); | ||
} | ||
if (!mailboxesToRemove.isEmpty()) { | ||
delete(messageId, mailboxesToRemove, mailboxSession); | ||
} | ||
} | ||
} | ||
|
||
protected MailboxMessage createMessage(Date internalDate, int size, int bodyStartOctet, SharedInputStream content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachment> attachments, MailboxId mailboxId) throws MailboxException { | ||
return new SimpleMailboxMessage(messageIdFactory.generate(), internalDate, size, bodyStartOctet, content, flags, propertyBuilder, mailboxId, attachments); | ||
} | ||
|
||
private void dispatchFlagsChange(MailboxSession mailboxSession, MailboxId mailboxId, UpdatedFlags updatedFlags) throws MailboxException { | ||
if (updatedFlags.flagsChanged()) { | ||
Mailbox mailbox = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession).findMailboxById(mailboxId); | ||
dispatcher.flagsUpdated(mailboxSession, updatedFlags.getUid(), mailbox, updatedFlags); | ||
} | ||
} | ||
|
||
private void validateQuota(Collection<MailboxId> mailboxIdsToBeAdded, Collection<MailboxId> mailboxIdsToBeRemove, MailboxSession mailboxSession, MailboxMessage mailboxMessage) throws MailboxException { | ||
MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession); | ||
|
||
Map<QuotaRoot, Integer> messageCountByQuotaRoot = buildMapQuotaRoot(mailboxIdsToBeAdded, mailboxIdsToBeRemove, mailboxMapper); | ||
for (Map.Entry<QuotaRoot, Integer> entry : messageCountByQuotaRoot.entrySet()) { | ||
if (entry.getValue() > 0) { | ||
new QuotaChecker(quotaManager.getMessageQuota(entry.getKey()), quotaManager.getStorageQuota(entry.getKey()), entry.getKey()) | ||
.tryAddition(entry.getValue(), mailboxMessage.getFullContentOctets()); | ||
} | ||
} | ||
} | ||
|
||
private Map<QuotaRoot, Integer> buildMapQuotaRoot(Collection<MailboxId> mailboxIdsToBeAdded, Collection<MailboxId> mailboxIdsToBeRemove, MailboxMapper mailboxMapper) throws MailboxException { | ||
Map<QuotaRoot, Integer> messageCountByQuotaRoot = new HashMap<QuotaRoot, Integer>(); | ||
for (MailboxId mailboxId : mailboxIdsToBeAdded) { | ||
QuotaRoot quotaRoot = retrieveQuotaRoot(mailboxMapper, mailboxId); | ||
int currentCount = Optional.fromNullable(messageCountByQuotaRoot.get(quotaRoot)).or(0); | ||
messageCountByQuotaRoot.put(quotaRoot, currentCount + 1); | ||
} | ||
for (MailboxId mailboxId : mailboxIdsToBeRemove) { | ||
QuotaRoot quotaRoot = retrieveQuotaRoot(mailboxMapper, mailboxId); | ||
int currentCount = Optional.fromNullable(messageCountByQuotaRoot.get(quotaRoot)).or(0); | ||
messageCountByQuotaRoot.put(quotaRoot, currentCount - 1); | ||
} | ||
return messageCountByQuotaRoot; | ||
} | ||
|
||
private QuotaRoot retrieveQuotaRoot(MailboxMapper mailboxMapper, MailboxId mailboxId) throws MailboxException { | ||
Mailbox mailbox = mailboxMapper.findMailboxById(mailboxId); | ||
return quotaRootResolver.getQuotaRoot(new MailboxPath(mailbox.getNamespace(), mailbox.getUser(), mailbox.getName())); | ||
} | ||
|
||
private void addMessageToMailboxes(MessageIdMapper messageIdMapper, MailboxMessage mailboxMessage, SetView<MailboxId> mailboxIds, MailboxSession mailboxSession) throws MailboxException { | ||
MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession); | ||
for (MailboxId mailboxId : mailboxIds) { | ||
SimpleMailboxMessage copy = SimpleMailboxMessage.copy(mailboxId, mailboxMessage); | ||
MessageMetaData metaData = save(mailboxSession, messageIdMapper, mailboxId, copy); | ||
dispatcher.added(mailboxSession, metaData, mailboxMapper.findMailboxById(mailboxId)); | ||
} | ||
} | ||
|
||
private MessageMetaData save(MailboxSession mailboxSession, MessageIdMapper messageIdMapper, MailboxId mailboxId, MailboxMessage mailboxMessage) throws MailboxException { | ||
validateQuota(ImmutableList.of(mailboxId), ImmutableList.<MailboxId>of(), mailboxSession, mailboxMessage); | ||
long modSeq = mailboxSessionMapperFactory.getModSeqProvider().nextModSeq(mailboxSession, mailboxId); | ||
MessageUid uid = mailboxSessionMapperFactory.getUidProvider().nextUid(mailboxSession, mailboxId); | ||
mailboxMessage.setModSeq(modSeq); | ||
mailboxMessage.setUid(uid); | ||
messageIdMapper.save(mailboxMessage); | ||
return new SimpleMessageMetaData(uid, modSeq, mailboxMessage.createFlags(), mailboxMessage.getFullContentOctets(), mailboxMessage.getInternalDate(), mailboxMessage.getMessageId()); | ||
} | ||
|
||
private Function<MailboxMessage, MessageResult> messageResultConverter(final MessageResult.FetchGroup fetchGroup) { | ||
return new Function<MailboxMessage, MessageResult>() { | ||
@Override | ||
public MessageResult apply(MailboxMessage input) { | ||
try { | ||
return ResultUtils.loadMessageResult(input, fetchGroup); | ||
} catch (MailboxException e) { | ||
throw new WrappedException(e); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
private Predicate<MailboxMessage> inMailbox(final List<MailboxId> mailboxIds) { | ||
return new Predicate<MailboxMessage>() { | ||
@Override | ||
public boolean apply(MailboxMessage mailboxMessage) { | ||
return mailboxIds.contains(mailboxMessage.getMailboxId()); | ||
} | ||
}; | ||
} | ||
|
||
private Predicate<MailboxId> notIn(final List<MailboxId> alreadyInMailboxes) { | ||
return new Predicate<MailboxId>() { | ||
@Override | ||
public boolean apply(MailboxId input) { | ||
return !alreadyInMailboxes.contains(input); | ||
} | ||
}; | ||
} | ||
|
||
private static class MetadataWithMailboxId { | ||
private final MessageMetaData messageMetaData; | ||
private final MailboxId mailboxId; | ||
|
||
public MetadataWithMailboxId(MessageMetaData messageMetaData, MailboxId mailboxId) { | ||
this.messageMetaData = messageMetaData; | ||
this.mailboxId = mailboxId; | ||
} | ||
} | ||
|
||
private static class WrappedException extends RuntimeException { | ||
private final MailboxException cause; | ||
|
||
public WrappedException(MailboxException cause) { | ||
this.cause = cause; | ||
} | ||
|
||
public MailboxException unwrap() throws MailboxException { | ||
throw cause; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.