JAMES-1712 Implement SetMailboxes: create
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/68ab6176 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/68ab6176 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/68ab6176 Branch: refs/heads/master Commit: 68ab61763d954d8b0d38015649c1fa63d8b0082d Parents: 166ee8f Author: Antoine Duprat <[email protected]> Authored: Thu Mar 31 11:22:28 2016 +0200 Committer: Antoine Duprat <[email protected]> Committed: Mon Apr 4 16:27:12 2016 +0200 ---------------------------------------------------------------------- .../integration/SetMailboxesMethodTest.java | 428 ++++++++++++++++++- .../jmap/exceptions/MailboxNameException.java | 27 ++ .../MailboxParentNotFoundException.java | 34 ++ .../methods/SetMailboxesCreationProcessor.java | 124 +++++- .../james/jmap/methods/SetMailboxesMethod.java | 2 +- .../jmap/methods/SetMailboxesProcessor.java | 3 +- .../org/apache/james/jmap/model/SetError.java | 17 + .../james/jmap/model/SetMailboxesResponse.java | 25 +- .../jmap/utils/CollectionHierarchySorter.java | 64 --- .../utils/SortingHierarchicalCollections.java | 64 +++ .../SetMailboxesCreationProcessorTest.java | 71 +++ .../jmap/methods/SetMailboxesMethodTest.java | 2 +- .../utils/CollectionHierarchySorterTest.java | 140 ------ .../SortingHierarchicalCollectionsTest.java | 140 ++++++ 14 files changed, 924 insertions(+), 217 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMailboxesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMailboxesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMailboxesMethodTest.java index 162bb6e..176ec06 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMailboxesMethodTest.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMailboxesMethodTest.java @@ -20,14 +20,22 @@ package org.apache.james.jmap.methods.integration; import static com.jayway.restassured.RestAssured.given; +import static com.jayway.restassured.RestAssured.with; import static com.jayway.restassured.config.EncoderConfig.encoderConfig; import static com.jayway.restassured.config.RestAssuredConfig.newConfig; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.collection.IsMapWithSize.aMapWithSize; import org.apache.james.GuiceJamesServer; import org.apache.james.jmap.JmapAuthentication; import org.apache.james.jmap.api.access.AccessToken; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -56,6 +64,7 @@ public abstract class SetMailboxesMethodTest { jmapServer.start(); RestAssured.port = jmapServer.getJmapPort(); RestAssured.config = newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)); + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); username = "username@" + USERS_DOMAIN; String password = "password"; @@ -71,9 +80,69 @@ public abstract class SetMailboxesMethodTest { public void teardown() { jmapServer.stop(); } - + @Test - public void setMailboxesShouldCreateMailbox() throws Exception { + public void setMailboxesShouldErrorNotSupportedWhenRoleGiven() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"foo\"," + + " \"role\" : \"Inbox\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("error")) + .body(ARGUMENTS + ".type", equalTo("Not yet implemented")); + } + + @Test + public void setMailboxesShouldErrorNotSupportedWhenSortOrderGiven() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"foo\"," + + " \"sortOrder\" : 11" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("error")) + .body(ARGUMENTS + ".type", equalTo("Not yet implemented")); + } + + @Test + public void setMailboxesShouldReturnCreatedMailbox() throws Exception { String requestBody = "[" + " [ \"setMailboxes\"," + @@ -100,4 +169,359 @@ public abstract class SetMailboxesMethodTest { .body(NAME, equalTo("mailboxesSet")) .body(ARGUMENTS + ".created", hasKey("create-id01")); } + + @Test + public void setMailboxesShouldCreateMailbox() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"foo\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + with() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .then() + .post("/jmap"); + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body("[[\"getMailboxes\", {}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxes")) + .body(ARGUMENTS + ".list", hasSize(2)) + .body(ARGUMENTS + ".list.name", hasItems("foo")); + } + + @Test + public void setMailboxesShouldReturnCreatedMailboxWhenChildOfInboxMailbox() throws Exception { + String inboxId = + with() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body("[[\"getMailboxes\", {}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .extract() + .jsonPath() + .getString(ARGUMENTS + ".list[0].id"); + + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"foo\"," + + " \"parentId\" : \"" + inboxId + "\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".created", aMapWithSize(1)) + .body(ARGUMENTS + ".created", hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("parentId"), equalTo(inboxId)), + hasEntry(equalTo("name"), equalTo("foo"))))); + } + + @Test + public void setMailboxesShouldCreateMailboxWhenChildOfInboxMailbox() throws Exception { + String inboxId = + with() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body("[[\"getMailboxes\", {}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .extract() + .jsonPath() + .getString(ARGUMENTS + ".list[0].id"); + + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"foo\"," + + " \"parentId\" : \"" + inboxId + "\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap"); + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body("[[\"getMailboxes\", {}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxes")) + .body(ARGUMENTS + ".list", hasSize(2)) + .body(ARGUMENTS + ".list.name", hasItems("foo")); + } + + @Test + public void setMailboxesShouldReturnNotCreatedWhenInvalidParentId() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"foo\"," + + " \"parentId\" : \"123\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".notCreated", aMapWithSize(1)) + .body(ARGUMENTS + ".notCreated", hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("type"), equalTo("invalidArguments")), + hasEntry(equalTo("description"), equalTo("The parent mailbox '123' was not found."))))); + } + + @Test + public void setMailboxesShouldReturnCreatedMailboxWhenCreatingParentThenChildMailboxes() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id00\" : {" + + " \"name\" : \"parent\"" + + " }," + + " \"create-id01\" : {" + + " \"name\" : \"child\"," + + " \"parentId\" : \"create-id00\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".created", aMapWithSize(2)) + .body(ARGUMENTS + ".created", hasEntry(equalTo("create-id00"), Matchers.allOf( + hasEntry(equalTo("parentId"), isEmptyOrNullString()), + hasEntry(equalTo("name"), equalTo("parent"))))) + .body(ARGUMENTS + ".created", hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("parentId"), not(isEmptyOrNullString())), + hasEntry(equalTo("name"), equalTo("child"))))); + } + + @Test + public void setMailboxesShouldReturnCreatedMailboxWhenCreatingChildThenParentMailboxes() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"child\"," + + " \"parentId\" : \"create-id00\"" + + " }," + + " \"create-id00\" : {" + + " \"name\" : \"parent\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".created", aMapWithSize(2)) + .body(ARGUMENTS + ".created", hasEntry(equalTo("create-id00"), Matchers.allOf( + hasEntry(equalTo("parentId"), isEmptyOrNullString()), + hasEntry(equalTo("name"), equalTo("parent"))))) + .body(ARGUMENTS + ".created", hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("parentId"), not(isEmptyOrNullString())), + hasEntry(equalTo("name"), equalTo("child"))))); + } + + @Test + public void setMailboxesShouldReturnNotCreatedWhenMailboxAlreadyExists() throws Exception { + jmapServer.serverProbe().createMailbox("#private", username, "myBox"); + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"myBox\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".notCreated", aMapWithSize(1)) + .body(ARGUMENTS + ".notCreated", hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("type"), equalTo("invalidArguments")), + hasEntry(equalTo("description"), equalTo("The mailbox 'create-id01' already exists."))))); + } + + @Test + public void setMailboxesShouldReturnNotCreatedWhenCycleDetected() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"A\"," + + " \"parentId\" : \"create-id02\"" + + " }," + + " \"create-id02\" : {" + + " \"name\" : \"B\"," + + " \"parentId\" : \"create-id01\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".notCreated", aMapWithSize(2)) + .body(ARGUMENTS + ".notCreated", Matchers.allOf( + hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("type"), equalTo("invalidArguments")), + hasEntry(equalTo("description"), equalTo("The created mailboxes introduce a cycle.")))), + hasEntry(equalTo("create-id02"), Matchers.allOf( + hasEntry(equalTo("type"), equalTo("invalidArguments")), + hasEntry(equalTo("description"), equalTo("The created mailboxes introduce a cycle.")))) + )); + } + + @Test + public void setMailboxesShouldReturnNotCreatedWhenMailboxNameContainsPathDelimiter() throws Exception { + String requestBody = + "[" + + " [ \"setMailboxes\"," + + " {" + + " \"create\": {" + + " \"create-id01\" : {" + + " \"name\" : \"A.B.C.D\"" + + " }" + + " }" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("mailboxesSet")) + .body(ARGUMENTS + ".notCreated", aMapWithSize(1)) + .body(ARGUMENTS + ".notCreated", hasEntry(equalTo("create-id01"), Matchers.allOf( + hasEntry(equalTo("type"), equalTo("invalidArguments")), + hasEntry(equalTo("description"), equalTo("The mailbox 'A.B.C.D' contains an illegal character: '.'"))) + )); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxNameException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxNameException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxNameException.java new file mode 100644 index 0000000..1828eee --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxNameException.java @@ -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); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxParentNotFoundException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxParentNotFoundException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxParentNotFoundException.java new file mode 100644 index 0000000..aadf5eb --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/MailboxParentNotFoundException.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessor.java index 73426f5..205528f 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessor.java @@ -19,20 +19,134 @@ 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.SetMailboxesResponse; 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.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 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(); - request.getCreate().entrySet().stream() - .forEach(entry -> builder.creation( - entry.getKey(), - Mailbox.builder().name(entry.getValue().getName()).id("serverId").build())); + try { + Map<MailboxCreationId, String> creationIdsToCreatedMailboxId = new HashMap<>(); + sortingHierarchicalCollections.sortFromRootToLeaf(request.getCreate().entrySet()) + .forEach(entry -> + createMailbox(entry.getKey(), entry.getValue(), mailboxSession, creationIdsToCreatedMailboxId, builder)); + } catch (CycleDetectedException e) { + markRequestsAsNotCreatedDueToCycle(request, builder); + } 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()); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesMethod.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesMethod.java index 70b6e90..51ae92f 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesMethod.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesMethod.java @@ -63,7 +63,7 @@ public class SetMailboxesMethod<Id extends MailboxId> implements Method { Preconditions.checkArgument(request instanceof SetMailboxesRequest); SetMailboxesRequest setMailboxesRequest = (SetMailboxesRequest) request; return processors.stream() - .map(processor -> processor.process(setMailboxesRequest)) + .map(processor -> processor.process(setMailboxesRequest, mailboxSession)) .map(response -> toJmapResponse(clientId, response)); } http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesProcessor.java index c9b7ef7..80f960e 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesProcessor.java @@ -21,10 +21,11 @@ package org.apache.james.jmap.methods; import org.apache.james.jmap.model.SetMailboxesRequest; import org.apache.james.jmap.model.SetMailboxesResponse; +import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.store.mail.model.MailboxId; public interface SetMailboxesProcessor<Id extends MailboxId> { - SetMailboxesResponse process(SetMailboxesRequest request); + SetMailboxesResponse process(SetMailboxesRequest request, MailboxSession mailboxSession); } http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java index aa520b3..75aacd2 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -99,4 +100,20 @@ public class SetError { public Optional<ImmutableSet<MessageProperty>> getProperties() { 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); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMailboxesResponse.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMailboxesResponse.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMailboxesResponse.java index 4322328..2d38706 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMailboxesResponse.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMailboxesResponse.java @@ -18,6 +18,8 @@ ****************************************************************/ package org.apache.james.jmap.model; +import java.util.Map; + import org.apache.james.jmap.methods.Method; import org.apache.james.jmap.model.mailbox.Mailbox; @@ -31,10 +33,12 @@ public class SetMailboxesResponse implements Method.Response { 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() { created = ImmutableMap.builder(); + notCreated = ImmutableMap.builder(); } public Builder creation(MailboxCreationId creationId, Mailbox mailbox) { @@ -42,20 +46,35 @@ public class SetMailboxesResponse implements Method.Response { 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() { - return new SetMailboxesResponse(created.build()); + return new SetMailboxesResponse(created.build(), notCreated.build()); } } 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.notCreated = notCreated; } public ImmutableMap<MailboxCreationId, Mailbox> getCreated() { return created; } + public Map<MailboxCreationId, SetError> getNotCreated() { + return notCreated; + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/CollectionHierarchySorter.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/CollectionHierarchySorter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/CollectionHierarchySorter.java deleted file mode 100644 index 66fbf50..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/CollectionHierarchySorter.java +++ /dev/null @@ -1,64 +0,0 @@ -/**************************************************************** - * 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.utils; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.apache.james.jmap.utils.DependencyGraph.CycleDetectedException; - -import com.google.common.collect.Lists; - -public class CollectionHierarchySorter<T, Id> { - - private final Function<T, Id> index; - private final Function<T, Optional<Id>> parentId; - - public CollectionHierarchySorter(Function<T, Id> index, - Function<T, Optional<Id>> parentId) { - this.index = index; - this.parentId = parentId; - } - - public List<T> sortFromRootToLeaf(Collection<T> elements) throws CycleDetectedException { - - Map<Id, T> mapOfElementsById = indexElementsById(elements); - - DependencyGraph<T> graph = new DependencyGraph<>(m -> - parentId.apply(m).map(mapOfElementsById::get)); - - elements.stream().forEach(graph::registerItem); - - return graph.getBuildChain().collect(Collectors.toList()); - } - - private Map<Id, T> indexElementsById(Collection<T> elements) { - return elements.stream() - .collect(Collectors.toMap(index, Function.identity())); - } - - public List<T> sortFromLeafToRoot(Collection<T> elements) throws CycleDetectedException { - return Lists.reverse(sortFromRootToLeaf(elements)); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SortingHierarchicalCollections.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SortingHierarchicalCollections.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SortingHierarchicalCollections.java new file mode 100644 index 0000000..bbf0d2e --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SortingHierarchicalCollections.java @@ -0,0 +1,64 @@ +/**************************************************************** + * 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.utils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.james.jmap.utils.DependencyGraph.CycleDetectedException; + +import com.google.common.collect.Lists; + +public class SortingHierarchicalCollections<T, Id> { + + private final Function<T, Id> index; + private final Function<T, Optional<Id>> parentId; + + public SortingHierarchicalCollections(Function<T, Id> index, + Function<T, Optional<Id>> parentId) { + this.index = index; + this.parentId = parentId; + } + + public List<T> sortFromRootToLeaf(Collection<T> elements) throws CycleDetectedException { + + Map<Id, T> mapOfElementsById = indexElementsById(elements); + + DependencyGraph<T> graph = new DependencyGraph<>(m -> + parentId.apply(m).map(mapOfElementsById::get)); + + elements.stream().forEach(graph::registerItem); + + return graph.getBuildChain().collect(Collectors.toList()); + } + + private Map<Id, T> indexElementsById(Collection<T> elements) { + return elements.stream() + .collect(Collectors.toMap(index, Function.identity())); + } + + public List<T> sortFromLeafToRoot(Collection<T> elements) throws CycleDetectedException { + return Lists.reverse(sortFromRootToLeaf(elements)); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessorTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessorTest.java new file mode 100644 index 0000000..fe10145 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesCreationProcessorTest.java @@ -0,0 +1,71 @@ +/**************************************************************** + * 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.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +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.SetMailboxesResponse; +import org.apache.james.jmap.model.mailbox.MailboxRequest; +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.store.TestId; +import org.junit.Before; +import org.junit.Test; + +public class SetMailboxesCreationProcessorTest { + + private MailboxUtils<TestId> mailboxUtils; + private SetMailboxesCreationProcessor<TestId> sut; + + @Before + @SuppressWarnings("unchecked") + public void setup() { + mailboxUtils = mock(MailboxUtils.class); + sut = new SetMailboxesCreationProcessor<>(mock(MailboxManager.class), mailboxUtils); + } + + @Test + public void processShouldReturnNotCreatedWhenMailboxExceptionOccured() throws Exception { + String parentId = "parentId"; + MailboxCreationId mailboxCreationId = MailboxCreationId.of("1"); + SetMailboxesRequest request = SetMailboxesRequest.builder() + .create(mailboxCreationId, MailboxRequest.builder().name("name").parentId(parentId).build()) + .build(); + + MailboxSession mailboxSession = mock(MailboxSession.class); + when(mailboxUtils.getMailboxNameFromId(parentId, mailboxSession)) + .thenThrow(new MailboxException()); + + SetMailboxesResponse setMailboxesResponse = sut.process(request, mailboxSession); + assertThat(setMailboxesResponse.getCreated()).isEmpty(); + assertThat(setMailboxesResponse.getNotCreated()).containsEntry(mailboxCreationId, + SetError.builder() + .type("anErrorOccurred") + .description("An error occurred when creating the mailbox '1'") + .build()); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesMethodTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesMethodTest.java index af0d4a3..0019460 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesMethodTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMailboxesMethodTest.java @@ -103,7 +103,7 @@ public class SetMailboxesMethodTest { MailboxSession session = mock(MailboxSession.class); @SuppressWarnings("unchecked") SetMailboxesProcessor<TestId> creatorProcessor = mock(SetMailboxesProcessor.class); - when(creatorProcessor.process(creationRequest)).thenReturn(creationResponse); + when(creatorProcessor.process(creationRequest, session)).thenReturn(creationResponse); Stream<JmapResponse> actual = new SetMailboxesMethod<>(ImmutableSet.of(creatorProcessor)) http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/CollectionHierarchySorterTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/CollectionHierarchySorterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/CollectionHierarchySorterTest.java deleted file mode 100644 index 1870f9e..0000000 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/CollectionHierarchySorterTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/**************************************************************** - * 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.utils; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.james.jmap.model.mailbox.Mailbox; -import org.apache.james.jmap.utils.DependencyGraph.CycleDetectedException; -import org.junit.Before; -import org.junit.Test; - -import com.google.common.collect.ImmutableList; - -public class CollectionHierarchySorterTest { - - private CollectionHierarchySorter<Mailbox, String> sut; - - @Before - public void setup() { - sut = new CollectionHierarchySorter<>(Mailbox::getId, Mailbox::getParentId); - } - - @Test - public void sortFromRootToLeafShouldReturnOrderedMailbox() { - // Given - Mailbox inbox = Mailbox.builder().name("INBOX").id("INBOX").build(); - Mailbox a = Mailbox.builder().name("A").id("A").parentId("INBOX").build(); - Mailbox b = Mailbox.builder().name("B").id("B").parentId("INBOX").build(); - Mailbox c = Mailbox.builder().name("C").id("C").parentId("B").build(); - Mailbox d = Mailbox.builder().name("D").id("D").parentId("A").build(); - Mailbox e = Mailbox.builder().name("E").id("E").parentId("C").build(); - ImmutableList<Mailbox> input = ImmutableList.of(b, c, d, a, inbox, e); - - // When - List<Mailbox> result = sut.sortFromRootToLeaf(input); - - // Then - assertThat(result).extracting(Mailbox::getName).endsWith("C", "D", "E").startsWith("INBOX"); - } - - @Test - public void sortFromRootToLeafEmptyMailboxShouldReturnEmpty() { - ImmutableList<Mailbox> input = ImmutableList.of(); - List<Mailbox> result = sut.sortFromRootToLeaf(input); - assertThat(result).isEmpty(); - } - - @Test - public void sortFromRootToLeafOrphanMailboxesShouldReturnInput() { - Mailbox a = Mailbox.builder().name("A").id("A").build(); - Mailbox b = Mailbox.builder().name("B").id("B").build(); - Mailbox c = Mailbox.builder().name("C").id("C").build(); - - ImmutableList<Mailbox> input = ImmutableList.of(a, b, c); - List<String> result = sut.sortFromRootToLeaf(input).stream() - .map(Mailbox::getName) - .collect(Collectors.toList()); - - assertThat(result).containsExactly("A", "B", "C"); - } - - @Test(expected=CycleDetectedException.class) - public void sortFromRootToLeafWithLoopShouldThrow() { - Mailbox a = Mailbox.builder().name("A").id("A").parentId("B").build(); - Mailbox b = Mailbox.builder().name("B").id("B").parentId("A").build(); - - ImmutableList<Mailbox> input = ImmutableList.of(a, b); - - sut.sortFromRootToLeaf(input); - } - - @Test - public void sortFromLeafToRootShouldReturnOrderedMailbox() { - //Given - Mailbox inbox = Mailbox.builder().name("INBOX").id("INBOX").build(); - Mailbox a = Mailbox.builder().name("A").id("A").parentId("INBOX").build(); - Mailbox b = Mailbox.builder().name("B").id("B").parentId("INBOX").build(); - Mailbox c = Mailbox.builder().name("C").id("C").parentId("B").build(); - Mailbox d = Mailbox.builder().name("D").id("D").parentId("A").build(); - Mailbox e = Mailbox.builder().name("E").id("E").parentId("C").build(); - - ImmutableList<Mailbox> input = ImmutableList.of(b, c, d, a, inbox, e); - - //When - List<Mailbox> result = sut.sortFromLeafToRoot(input); - - assertThat(result).extracting(Mailbox::getName).endsWith("INBOX").startsWith("E"); - } - - @Test - public void sortFromLeafToRootEmptyMailboxShouldReturnEmpty() { - ImmutableList<Mailbox> input = ImmutableList.of(); - List<Mailbox> result = sut.sortFromLeafToRoot(input); - assertThat(result).isEmpty(); - } - - @Test - public void sortFromLeafToRootOrphanMailboxesShouldReturnInput() { - Mailbox a = Mailbox.builder().name("A").id("A").build(); - Mailbox b = Mailbox.builder().name("B").id("B").build(); - Mailbox c = Mailbox.builder().name("C").id("C").build(); - - ImmutableList<Mailbox> input = ImmutableList.of(a, b, c); - List<String> result = sut.sortFromLeafToRoot(input).stream() - .map(Mailbox::getName) - .collect(Collectors.toList()); - - assertThat(result).containsExactly("C", "B", "A"); - } - - @Test(expected=CycleDetectedException.class) - public void sortFromLeafToRootWithLoopShouldThrow() { - Mailbox a = Mailbox.builder().name("A").id("A").parentId("B").build(); - Mailbox b = Mailbox.builder().name("B").id("B").parentId("A").build(); - - ImmutableList<Mailbox> input = ImmutableList.of(a, b); - - sut.sortFromLeafToRoot(input); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/68ab6176/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/SortingHierarchicalCollectionsTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/SortingHierarchicalCollectionsTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/SortingHierarchicalCollectionsTest.java new file mode 100644 index 0000000..1437f88 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/SortingHierarchicalCollectionsTest.java @@ -0,0 +1,140 @@ +/**************************************************************** + * 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.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.james.jmap.model.mailbox.Mailbox; +import org.apache.james.jmap.utils.DependencyGraph.CycleDetectedException; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class SortingHierarchicalCollectionsTest { + + private SortingHierarchicalCollections<Mailbox, String> sut; + + @Before + public void setup() { + sut = new SortingHierarchicalCollections<>(Mailbox::getId, Mailbox::getParentId); + } + + @Test + public void sortFromRootToLeafShouldReturnOrderedMailbox() { + // Given + Mailbox inbox = Mailbox.builder().name("INBOX").id("INBOX").build(); + Mailbox a = Mailbox.builder().name("A").id("A").parentId("INBOX").build(); + Mailbox b = Mailbox.builder().name("B").id("B").parentId("INBOX").build(); + Mailbox c = Mailbox.builder().name("C").id("C").parentId("B").build(); + Mailbox d = Mailbox.builder().name("D").id("D").parentId("A").build(); + Mailbox e = Mailbox.builder().name("E").id("E").parentId("C").build(); + ImmutableList<Mailbox> input = ImmutableList.of(b, c, d, a, inbox, e); + + // When + List<Mailbox> result = sut.sortFromRootToLeaf(input); + + // Then + assertThat(result).extracting(Mailbox::getName).endsWith("C", "D", "E").startsWith("INBOX"); + } + + @Test + public void sortFromRootToLeafEmptyMailboxShouldReturnEmpty() { + ImmutableList<Mailbox> input = ImmutableList.of(); + List<Mailbox> result = sut.sortFromRootToLeaf(input); + assertThat(result).isEmpty(); + } + + @Test + public void sortFromRootToLeafOrphanMailboxesShouldReturnInput() { + Mailbox a = Mailbox.builder().name("A").id("A").build(); + Mailbox b = Mailbox.builder().name("B").id("B").build(); + Mailbox c = Mailbox.builder().name("C").id("C").build(); + + ImmutableList<Mailbox> input = ImmutableList.of(a, b, c); + List<String> result = sut.sortFromRootToLeaf(input).stream() + .map(Mailbox::getName) + .collect(Collectors.toList()); + + assertThat(result).containsExactly("A", "B", "C"); + } + + @Test(expected=CycleDetectedException.class) + public void sortFromRootToLeafWithLoopShouldThrow() { + Mailbox a = Mailbox.builder().name("A").id("A").parentId("B").build(); + Mailbox b = Mailbox.builder().name("B").id("B").parentId("A").build(); + + ImmutableList<Mailbox> input = ImmutableList.of(a, b); + + sut.sortFromRootToLeaf(input); + } + + @Test + public void sortFromLeafToRootShouldReturnOrderedMailbox() { + //Given + Mailbox inbox = Mailbox.builder().name("INBOX").id("INBOX").build(); + Mailbox a = Mailbox.builder().name("A").id("A").parentId("INBOX").build(); + Mailbox b = Mailbox.builder().name("B").id("B").parentId("INBOX").build(); + Mailbox c = Mailbox.builder().name("C").id("C").parentId("B").build(); + Mailbox d = Mailbox.builder().name("D").id("D").parentId("A").build(); + Mailbox e = Mailbox.builder().name("E").id("E").parentId("C").build(); + + ImmutableList<Mailbox> input = ImmutableList.of(b, c, d, a, inbox, e); + + //When + List<Mailbox> result = sut.sortFromLeafToRoot(input); + + assertThat(result).extracting(Mailbox::getName).endsWith("INBOX").startsWith("E"); + } + + @Test + public void sortFromLeafToRootEmptyMailboxShouldReturnEmpty() { + ImmutableList<Mailbox> input = ImmutableList.of(); + List<Mailbox> result = sut.sortFromLeafToRoot(input); + assertThat(result).isEmpty(); + } + + @Test + public void sortFromLeafToRootOrphanMailboxesShouldReturnInput() { + Mailbox a = Mailbox.builder().name("A").id("A").build(); + Mailbox b = Mailbox.builder().name("B").id("B").build(); + Mailbox c = Mailbox.builder().name("C").id("C").build(); + + ImmutableList<Mailbox> input = ImmutableList.of(a, b, c); + List<String> result = sut.sortFromLeafToRoot(input).stream() + .map(Mailbox::getName) + .collect(Collectors.toList()); + + assertThat(result).containsExactly("C", "B", "A"); + } + + @Test(expected=CycleDetectedException.class) + public void sortFromLeafToRootWithLoopShouldThrow() { + Mailbox a = Mailbox.builder().name("A").id("A").parentId("B").build(); + Mailbox b = Mailbox.builder().name("B").id("B").parentId("A").build(); + + ImmutableList<Mailbox> input = ImmutableList.of(a, b); + + sut.sortFromLeafToRoot(input); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
