blob: 4cb412622844603dd75a3ed9127fcf181fb5cba8 [file] [log] [blame]
/****************************************************************
* 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.jmap.draft.model;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import jakarta.inject.Inject;
import org.apache.james.core.Username;
import org.apache.james.jmap.draft.model.mailbox.Mailbox;
import org.apache.james.jmap.draft.model.mailbox.MailboxNamespace;
import org.apache.james.jmap.draft.model.mailbox.SortOrder;
import org.apache.james.jmap.draft.utils.quotas.DefaultQuotaLoader;
import org.apache.james.jmap.draft.utils.quotas.QuotaLoader;
import org.apache.james.jmap.model.mailbox.Rights;
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MessageManager;
import org.apache.james.mailbox.Role;
import org.apache.james.mailbox.exception.MailboxNotFoundException;
import org.apache.james.mailbox.model.MailboxACL;
import org.apache.james.mailbox.model.MailboxCounters;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MailboxMetaData;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.quota.QuotaManager;
import org.apache.james.mailbox.quota.QuotaRootResolver;
import com.github.fge.lambdas.Throwing;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.primitives.Booleans;
import reactor.core.publisher.Mono;
public class MailboxFactory {
private static class MailboxTuple {
public static MailboxTuple from(MailboxMetaData metaData) {
return new MailboxTuple(metaData.getPath(), metaData.getCounters().sanitize(), metaData.getResolvedAcls());
}
public static Mono<MailboxTuple> from(MessageManager messageManager, MailboxSession session) {
return Mono.from(messageManager.getMailboxCountersReactive(session))
.map(MailboxCounters::sanitize)
.map(Throwing.function(counters -> new MailboxTuple(messageManager.getMailboxPath(), counters, messageManager.getResolvedAcl(session))));
}
private final MailboxPath mailboxPath;
private final MailboxCounters.Sanitized mailboxCounters;
private final MailboxACL acl;
private MailboxTuple(MailboxPath mailboxPath, MailboxCounters.Sanitized mailboxCounters, MailboxACL acl) {
this.mailboxPath = mailboxPath;
this.mailboxCounters = mailboxCounters;
this.acl = acl;
}
}
private final MailboxManager mailboxManager;
private final QuotaManager quotaManager;
private final QuotaRootResolver quotaRootResolver;
public static class MailboxBuilder {
private final MailboxFactory mailboxFactory;
private QuotaLoader quotaLoader;
private MailboxSession session;
private Optional<MailboxId> id = Optional.empty();
private Optional<MailboxMetaData> mailboxMetaData = Optional.empty();
private Optional<Map<MailboxPath, MailboxMetaData>> userMailboxesMetadata = Optional.empty();
private MailboxBuilder(MailboxFactory mailboxFactory, QuotaLoader quotaLoader) {
this.mailboxFactory = mailboxFactory;
this.quotaLoader = quotaLoader;
}
public MailboxBuilder id(MailboxId id) {
this.id = Optional.of(id);
return this;
}
public MailboxBuilder mailboxMetadata(MailboxMetaData mailboxMetaData) {
this.mailboxMetaData = Optional.of(mailboxMetaData);
return this;
}
public MailboxBuilder session(MailboxSession session) {
this.session = session;
return this;
}
public MailboxBuilder quotaLoader(QuotaLoader quotaLoader) {
this.quotaLoader = quotaLoader;
return this;
}
public MailboxBuilder usingPreloadedMailboxesMetadata(Optional<Map<MailboxPath, MailboxMetaData>> userMailboxesMetadata) {
this.userMailboxesMetadata = userMailboxesMetadata;
return this;
}
public Mono<Mailbox> build() {
Preconditions.checkNotNull(session);
MailboxId mailboxId = computeMailboxId();
Mono<MailboxTuple> mailbox = mailboxMetaData.map(MailboxTuple::from)
.map(Mono::just)
.orElse(Mono.from(mailboxFactory.mailboxManager.getMailboxReactive(mailboxId, session))
.flatMap(messageManager -> MailboxTuple.from(messageManager, session)));
return mailbox.flatMap(tuple -> mailboxFactory.from(
mailboxId,
tuple.mailboxPath,
tuple.mailboxCounters,
tuple.acl,
userMailboxesMetadata,
quotaLoader,
session))
.onErrorResume(MailboxNotFoundException.class, e -> Mono.empty());
}
private MailboxId computeMailboxId() {
int idCount = Booleans.countTrue(id.isPresent(), mailboxMetaData.isPresent());
Preconditions.checkState(idCount == 1, "You need exactly one 'id' 'mailboxMetaData'");
return id.or(
() -> mailboxMetaData.map(MailboxMetaData::getId))
.get();
}
private Mono<MessageManager> mailbox(MailboxId mailboxId) {
return Mono.from(mailboxFactory.mailboxManager.getMailboxReactive(mailboxId, session));
}
private Mono<MessageManager> retrieveCachedMailbox(MailboxId mailboxId, Mono<MessageManager> mailbox) throws MailboxNotFoundException {
return mailbox
.onErrorResume(MailboxNotFoundException.class, any -> Mono.empty())
.switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxId)));
}
}
@Inject
public MailboxFactory(MailboxManager mailboxManager, QuotaManager quotaManager, QuotaRootResolver quotaRootResolver) {
this.mailboxManager = mailboxManager;
this.quotaManager = quotaManager;
this.quotaRootResolver = quotaRootResolver;
}
public MailboxBuilder builder() {
QuotaLoader defaultQuotaLoader = new DefaultQuotaLoader(quotaRootResolver, quotaManager);
return new MailboxBuilder(this, defaultQuotaLoader);
}
private Mono<Mailbox> from(MailboxId mailboxId,
MailboxPath mailboxPath,
MailboxCounters.Sanitized mailboxCounters,
MailboxACL resolvedAcl,
Optional<Map<MailboxPath, MailboxMetaData>> userMailboxesMetadata,
QuotaLoader quotaLoader,
MailboxSession mailboxSession) {
boolean isOwner = mailboxPath.belongsTo(mailboxSession);
Optional<Role> role = Role.from(mailboxPath.getName())
.filter(any -> mailboxPath.belongsTo(mailboxSession));
Rights rights = Rights.fromACL(resolvedAcl)
.removeEntriesFor(mailboxPath.getUser());
Username username = mailboxSession.getUser();
return Mono.zip(
quotaLoader.getQuotas(mailboxPath),
getParentIdFromMailboxPath(mailboxPath, userMailboxesMetadata, mailboxSession))
.map(tuple -> Mailbox.builder()
.id(mailboxId)
.name(getName(mailboxPath, mailboxSession))
.parentId(tuple.getT2().orElse(null))
.role(role)
.unreadMessages(mailboxCounters.getUnseen())
.totalMessages(mailboxCounters.getCount())
.sortOrder(SortOrder.getSortOrder(role))
.sharedWith(rights)
.mayAddItems(rights.mayAddItems(username).orElse(isOwner))
.mayCreateChild(rights.mayCreateChild(username).orElse(isOwner))
.mayDelete(rights.mayDelete(username).orElse(isOwner))
.mayReadItems(rights.mayReadItems(username).orElse(isOwner))
.mayRemoveItems(rights.mayRemoveItems(username).orElse(isOwner))
.mayRename(rights.mayRename(username).orElse(isOwner))
.namespace(getNamespace(mailboxPath, isOwner))
.quotas(tuple.getT1())
.build());
}
private MailboxNamespace getNamespace(MailboxPath mailboxPath, boolean isOwner) {
if (isOwner) {
return MailboxNamespace.personal();
}
return MailboxNamespace.delegated(mailboxPath.getUser());
}
@VisibleForTesting
String getName(MailboxPath mailboxPath, MailboxSession mailboxSession) {
String name = mailboxPath.getName();
if (name.contains(String.valueOf(mailboxSession.getPathDelimiter()))) {
List<String> levels = Splitter.on(mailboxSession.getPathDelimiter()).splitToList(name);
return levels.get(levels.size() - 1);
}
return name;
}
@VisibleForTesting
Mono<Optional<MailboxId>> getParentIdFromMailboxPath(MailboxPath mailboxPath, Optional<Map<MailboxPath, MailboxMetaData>> userMailboxesMetadata,
MailboxSession mailboxSession) {
List<MailboxPath> levels = mailboxPath.getHierarchyLevels(mailboxSession.getPathDelimiter());
if (levels.size() <= 1) {
return Mono.just(Optional.empty());
}
MailboxPath parent = levels.get(levels.size() - 2);
return userMailboxesMetadata.map(list -> Mono.just(retrieveParentFromMetadata(parent, list)))
.orElseGet(Throwing.supplier(() -> retrieveParentFromBackend(mailboxSession, parent)).sneakyThrow());
}
private Mono<Optional<MailboxId>> retrieveParentFromBackend(MailboxSession mailboxSession, MailboxPath parent) {
return Mono.from(mailboxManager.getMailboxReactive(parent, mailboxSession))
.map(MessageManager::getId)
.map(Optional::of);
}
private Optional<MailboxId> retrieveParentFromMetadata(MailboxPath parent, Map<MailboxPath, MailboxMetaData> userMailboxesMetadata) {
return Optional.ofNullable(userMailboxesMetadata.get(parent)).map(MailboxMetaData::getId);
}
}