Skip to content

Commit

Permalink
JAMES-1712 Implement SetMailboxes: create
Browse files Browse the repository at this point in the history
  • Loading branch information
aduprat committed Apr 4, 2016
1 parent 166ee8f commit 68ab617
Show file tree
Hide file tree
Showing 12 changed files with 725 additions and 18 deletions.

Large diffs are not rendered by default.

@@ -0,0 +1,27 @@
/****************************************************************
* 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.exceptions;

public class MailboxNameException extends RuntimeException {

public MailboxNameException(String message) {
super(message);
}
}
@@ -0,0 +1,34 @@
/****************************************************************
* 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.exceptions;

public class MailboxParentNotFoundException extends RuntimeException {

private final String parentId;

public MailboxParentNotFoundException(String parentId) {
super(String.format("The parent mailbox '%s' was not found.", parentId));
this.parentId = parentId;
}

public String getParentId() {
return parentId;
}
}
Expand Up @@ -19,20 +19,134 @@


package org.apache.james.jmap.methods; package org.apache.james.jmap.methods;


import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.inject.Inject;

import org.apache.james.jmap.exceptions.MailboxNameException;
import org.apache.james.jmap.exceptions.MailboxParentNotFoundException;
import org.apache.james.jmap.model.MailboxCreationId;
import org.apache.james.jmap.model.SetError;
import org.apache.james.jmap.model.SetMailboxesRequest; import org.apache.james.jmap.model.SetMailboxesRequest;
import org.apache.james.jmap.model.SetMailboxesResponse; import org.apache.james.jmap.model.SetMailboxesResponse;
import org.apache.james.jmap.model.mailbox.Mailbox; import org.apache.james.jmap.model.mailbox.Mailbox;
import org.apache.james.jmap.model.mailbox.MailboxRequest;
import org.apache.james.jmap.utils.SortingHierarchicalCollections;
import org.apache.james.jmap.utils.DependencyGraph.CycleDetectedException;
import org.apache.james.jmap.utils.MailboxUtils;
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.exception.MailboxExistsException;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.store.mail.model.MailboxId; import org.apache.james.mailbox.store.mail.model.MailboxId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.fge.lambdas.Throwing;
import com.google.common.annotations.VisibleForTesting;


public class SetMailboxesCreationProcessor<Id extends MailboxId> implements SetMailboxesProcessor<Id> { public class SetMailboxesCreationProcessor<Id extends MailboxId> implements SetMailboxesProcessor<Id> {


public SetMailboxesResponse process(SetMailboxesRequest request) { private static final Logger LOGGER = LoggerFactory.getLogger(SetMailboxesCreationProcessor.class);

private final MailboxManager mailboxManager;
private final SortingHierarchicalCollections<Map.Entry<MailboxCreationId, MailboxRequest>, String> sortingHierarchicalCollections;
private final MailboxUtils<Id> mailboxUtils;

@Inject
@VisibleForTesting
SetMailboxesCreationProcessor(MailboxManager mailboxManager, MailboxUtils<Id> mailboxUtils) {
this.mailboxManager = mailboxManager;
this.sortingHierarchicalCollections =
new SortingHierarchicalCollections<>(
x -> x.getKey().getCreationId(),
x -> x.getValue().getParentId());
this.mailboxUtils = mailboxUtils;
}

public SetMailboxesResponse process(SetMailboxesRequest request, MailboxSession mailboxSession) {
SetMailboxesResponse.Builder builder = SetMailboxesResponse.builder(); SetMailboxesResponse.Builder builder = SetMailboxesResponse.builder();
request.getCreate().entrySet().stream() try {
.forEach(entry -> builder.creation( Map<MailboxCreationId, String> creationIdsToCreatedMailboxId = new HashMap<>();
entry.getKey(), sortingHierarchicalCollections.sortFromRootToLeaf(request.getCreate().entrySet())
Mailbox.builder().name(entry.getValue().getName()).id("serverId").build())); .forEach(entry ->
createMailbox(entry.getKey(), entry.getValue(), mailboxSession, creationIdsToCreatedMailboxId, builder));
} catch (CycleDetectedException e) {
markRequestsAsNotCreatedDueToCycle(request, builder);
}
return builder.build(); return builder.build();
} }


private void markRequestsAsNotCreatedDueToCycle(SetMailboxesRequest request, SetMailboxesResponse.Builder builder) {
request.getCreate().entrySet()
.forEach(entry ->
builder.notCreated(entry.getKey(),
SetError.builder()
.type("invalidArguments")
.description("The created mailboxes introduce a cycle.")
.build()));
}

private void createMailbox(MailboxCreationId mailboxCreationId, MailboxRequest mailboxRequest, MailboxSession mailboxSession,
Map<MailboxCreationId, String> creationIdsToCreatedMailboxId, SetMailboxesResponse.Builder builder) {
try {
ensureValidMailboxName(mailboxRequest, mailboxSession);
MailboxPath mailboxPath = getMailboxPath(mailboxRequest, creationIdsToCreatedMailboxId, mailboxSession);
mailboxManager.createMailbox(mailboxPath, mailboxSession);
Optional<Mailbox> mailbox = mailboxUtils.mailboxFromMailboxPath(mailboxPath, mailboxSession);
if (mailbox.isPresent()) {
builder.creation(mailboxCreationId, mailbox.get());
creationIdsToCreatedMailboxId.put(mailboxCreationId, mailbox.get().getId());
} else {
builder.notCreated(mailboxCreationId, SetError.builder()
.type("anErrorOccurred")
.description("An error occurred when creating the mailbox")
.build());
}
} catch (MailboxNameException | MailboxParentNotFoundException e) {
builder.notCreated(mailboxCreationId, SetError.builder()
.type("invalidArguments")
.description(e.getMessage())
.build());
} catch (MailboxExistsException e) {
String message = String.format("The mailbox '%s' already exists.", mailboxCreationId.getCreationId());
builder.notCreated(mailboxCreationId, SetError.builder()
.type("invalidArguments")
.description(message)
.build());
} catch (MailboxException e) {
String message = String.format("An error occurred when creating the mailbox '%s'", mailboxCreationId.getCreationId());
LOGGER.error(message, e);
builder.notCreated(mailboxCreationId, SetError.builder()
.type("anErrorOccurred")
.description(message)
.build());
}
}

private void ensureValidMailboxName(MailboxRequest mailboxRequest, MailboxSession mailboxSession) {
String name = mailboxRequest.getName();
char pathDelimiter = mailboxSession.getPathDelimiter();
if (name.contains(String.valueOf(pathDelimiter))) {
throw new MailboxNameException(String.format("The mailbox '%s' contains an illegal character: '%c'", name, pathDelimiter));
}
}

private MailboxPath getMailboxPath(MailboxRequest mailboxRequest, Map<MailboxCreationId, String> creationIdsToCreatedMailboxId, MailboxSession mailboxSession) throws MailboxException {
if (mailboxRequest.getParentId().isPresent()) {
String parentId = mailboxRequest.getParentId().get();
String parentName = mailboxUtils.getMailboxNameFromId(parentId, mailboxSession)
.orElseGet(Throwing.supplier(() ->
mailboxUtils.getMailboxNameFromId(creationIdsToCreatedMailboxId.get(MailboxCreationId.of(parentId)), mailboxSession)
.orElseThrow(() -> new MailboxParentNotFoundException(parentId))
));

return new MailboxPath(mailboxSession.getPersonalSpace(), mailboxSession.getUser().getUserName(),
parentName + mailboxSession.getPathDelimiter() + mailboxRequest.getName());
}
return new MailboxPath(mailboxSession.getPersonalSpace(), mailboxSession.getUser().getUserName(), mailboxRequest.getName());
}
} }
Expand Up @@ -63,7 +63,7 @@ public Stream<JmapResponse> process(JmapRequest request, ClientId clientId, Mail
Preconditions.checkArgument(request instanceof SetMailboxesRequest); Preconditions.checkArgument(request instanceof SetMailboxesRequest);
SetMailboxesRequest setMailboxesRequest = (SetMailboxesRequest) request; SetMailboxesRequest setMailboxesRequest = (SetMailboxesRequest) request;
return processors.stream() return processors.stream()
.map(processor -> processor.process(setMailboxesRequest)) .map(processor -> processor.process(setMailboxesRequest, mailboxSession))
.map(response -> toJmapResponse(clientId, response)); .map(response -> toJmapResponse(clientId, response));
} }


Expand Down
Expand Up @@ -21,10 +21,11 @@


import org.apache.james.jmap.model.SetMailboxesRequest; import org.apache.james.jmap.model.SetMailboxesRequest;
import org.apache.james.jmap.model.SetMailboxesResponse; import org.apache.james.jmap.model.SetMailboxesResponse;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.store.mail.model.MailboxId; import org.apache.james.mailbox.store.mail.model.MailboxId;


public interface SetMailboxesProcessor<Id extends MailboxId> { public interface SetMailboxesProcessor<Id extends MailboxId> {


SetMailboxesResponse process(SetMailboxesRequest request); SetMailboxesResponse process(SetMailboxesRequest request, MailboxSession mailboxSession);


} }
Expand Up @@ -28,6 +28,7 @@
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
Expand Down Expand Up @@ -99,4 +100,20 @@ public Optional<String> getDescription() {
public Optional<ImmutableSet<MessageProperty>> getProperties() { public Optional<ImmutableSet<MessageProperty>> getProperties() {
return properties; return properties;
} }

@Override
public boolean equals(Object obj) {
if (obj instanceof SetError) {
SetError other = (SetError) obj;
return Objects.equal(this.type, other.type)
&& Objects.equal(this.description, other.description)
&& Objects.equal(this.properties, other.properties);
}
return false;
}

@Override
public int hashCode() {
return Objects.hashCode(type, description, properties);
}
} }
Expand Up @@ -18,6 +18,8 @@
****************************************************************/ ****************************************************************/
package org.apache.james.jmap.model; package org.apache.james.jmap.model;


import java.util.Map;

import org.apache.james.jmap.methods.Method; import org.apache.james.jmap.methods.Method;
import org.apache.james.jmap.model.mailbox.Mailbox; import org.apache.james.jmap.model.mailbox.Mailbox;


Expand All @@ -31,31 +33,48 @@ public static Builder builder() {


public static class Builder { public static class Builder {


private ImmutableMap.Builder<MailboxCreationId, Mailbox> created; private final ImmutableMap.Builder<MailboxCreationId, Mailbox> created;
private final ImmutableMap.Builder<MailboxCreationId, SetError> notCreated;


private Builder() { private Builder() {
created = ImmutableMap.builder(); created = ImmutableMap.builder();
notCreated = ImmutableMap.builder();
} }


public Builder creation(MailboxCreationId creationId, Mailbox mailbox) { public Builder creation(MailboxCreationId creationId, Mailbox mailbox) {
created.put(creationId, mailbox); created.put(creationId, mailbox);
return this; return this;
} }


public Builder notCreated(Map<MailboxCreationId, SetError> notCreated) {
this.notCreated.putAll(notCreated);
return this;
}

public Builder notCreated(MailboxCreationId mailboxCreationId, SetError setError) {
this.notCreated.put(mailboxCreationId, setError);
return this;
}

public SetMailboxesResponse build() { public SetMailboxesResponse build() {
return new SetMailboxesResponse(created.build()); return new SetMailboxesResponse(created.build(), notCreated.build());
} }


} }


private final ImmutableMap<MailboxCreationId, Mailbox> created; private final ImmutableMap<MailboxCreationId, Mailbox> created;
private final ImmutableMap<MailboxCreationId, SetError> notCreated;


private SetMailboxesResponse(ImmutableMap<MailboxCreationId, Mailbox> created) { private SetMailboxesResponse(ImmutableMap<MailboxCreationId, Mailbox> created, ImmutableMap<MailboxCreationId, SetError> notCreated) {
this.created = created; this.created = created;
this.notCreated = notCreated;
} }


public ImmutableMap<MailboxCreationId, Mailbox> getCreated() { public ImmutableMap<MailboxCreationId, Mailbox> getCreated() {
return created; return created;
} }


public Map<MailboxCreationId, SetError> getNotCreated() {
return notCreated;
}
} }
Expand Up @@ -30,12 +30,12 @@


import com.google.common.collect.Lists; import com.google.common.collect.Lists;


public class CollectionHierarchySorter<T, Id> { public class SortingHierarchicalCollections<T, Id> {


private final Function<T, Id> index; private final Function<T, Id> index;
private final Function<T, Optional<Id>> parentId; private final Function<T, Optional<Id>> parentId;


public CollectionHierarchySorter(Function<T, Id> index, public SortingHierarchicalCollections(Function<T, Id> index,
Function<T, Optional<Id>> parentId) { Function<T, Optional<Id>> parentId) {
this.index = index; this.index = index;
this.parentId = parentId; this.parentId = parentId;
Expand Down

0 comments on commit 68ab617

Please sign in to comment.