You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2017/10/03 00:54:28 UTC

[1/6] james-project git commit: JAMES-2162 Introduce GuavaUtils

Repository: james-project
Updated Branches:
  refs/heads/master f458495a0 -> 651a03db6


JAMES-2162 Introduce GuavaUtils


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/6e9ad73a
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/6e9ad73a
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/6e9ad73a

Branch: refs/heads/master
Commit: 6e9ad73afc6d7ec13aeb8bf3e82861b901f3cabe
Parents: f458495
Author: benwa <bt...@linagora.com>
Authored: Mon Oct 2 17:30:54 2017 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Oct 3 07:51:58 2017 +0700

----------------------------------------------------------------------
 .../java/org/apache/james/util/GuavaUtils.java  | 37 +++++++++
 .../org/apache/james/util/GuavaUtilsTest.java   | 80 ++++++++++++++++++++
 2 files changed, 117 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/6e9ad73a/server/container/util-java8/src/main/java/org/apache/james/util/GuavaUtils.java
----------------------------------------------------------------------
diff --git a/server/container/util-java8/src/main/java/org/apache/james/util/GuavaUtils.java b/server/container/util-java8/src/main/java/org/apache/james/util/GuavaUtils.java
new file mode 100644
index 0000000..518ead1
--- /dev/null
+++ b/server/container/util-java8/src/main/java/org/apache/james/util/GuavaUtils.java
@@ -0,0 +1,37 @@
+/****************************************************************
+ * 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.util;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableListMultimap;
+
+public class GuavaUtils {
+    public static <K, V> ImmutableListMultimap<K, V> toMultimap(Map<K, List<V>> rights) {
+        return rights.entrySet()
+            .stream()
+            .flatMap(e -> e.getValue().stream().map(right -> Pair.of(e.getKey(), right)))
+            .collect(Guavate.toImmutableListMultimap(Pair::getKey, Pair::getValue));
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/6e9ad73a/server/container/util-java8/src/test/java/org/apache/james/util/GuavaUtilsTest.java
----------------------------------------------------------------------
diff --git a/server/container/util-java8/src/test/java/org/apache/james/util/GuavaUtilsTest.java b/server/container/util-java8/src/test/java/org/apache/james/util/GuavaUtilsTest.java
new file mode 100644
index 0000000..3702e5e
--- /dev/null
+++ b/server/container/util-java8/src/test/java/org/apache/james/util/GuavaUtilsTest.java
@@ -0,0 +1,80 @@
+/****************************************************************
+ * 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.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableListMultimap;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
+
+public class GuavaUtilsTest {
+
+    @Test
+    public void toMultimapShouldAcceptEmptyMaps() {
+        assertThat(GuavaUtils.toMultimap(ImmutableMap
+            .<String, List<String>>builder()
+            .build())
+            .asMap())
+            .isEqualTo(ImmutableMap.of());
+    }
+
+    @Test
+    public void toMultimapShouldAcceptSingleValuesMaps() {
+        assertThat(GuavaUtils.toMultimap(ImmutableMap
+            .<String, List<String>>builder()
+            .put("k1", ImmutableList.of("v1"))
+            .put("k2", ImmutableList.of("v2"))
+            .build())
+            .asMap())
+            .isEqualTo(ImmutableListMultimap.of(
+                "k1", "v1",
+                "k2", "v2")
+            .asMap());
+    }
+
+    @Test
+    public void toMultimapShouldAcceptMultiplesValuesMaps() {
+        assertThat(GuavaUtils.toMultimap(ImmutableMap
+            .<String, List<String>>builder()
+            .put("k1", ImmutableList.of("v1"))
+            .put("k2", ImmutableList.of("v2", "v2.1"))
+            .build())
+            .asMap())
+            .isEqualTo(ImmutableListMultimap.of(
+                "k1", "v1",
+                "k2", "v2",
+                "k2", "v2.1")
+                .asMap());
+    }
+
+    @Test
+    public void shouldStripEntriesWithEmptyList() {
+        assertThat(GuavaUtils.toMultimap(ImmutableMap
+            .<String, List<String>>builder()
+            .put("k1", ImmutableList.of())
+            .build())
+            .asMap())
+            .isEqualTo(ImmutableListMultimap.of().asMap());
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[5/6] james-project git commit: JAMES-2162 Rename confusing optional method

Posted by bt...@apache.org.
JAMES-2162 Rename confusing optional method


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/23dd9053
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/23dd9053
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/23dd9053

Branch: refs/heads/master
Commit: 23dd9053ab65bbde40e9383ccd9ebe1a580576f4
Parents: 69d783e
Author: benwa <bt...@linagora.com>
Authored: Mon Oct 2 15:58:23 2017 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Oct 3 07:52:17 2017 +0700

----------------------------------------------------------------------
 .../james/mailbox/cassandra/mail/CassandraBlobsDAO.java     | 2 +-
 .../mailbox/cassandra/mail/CassandraMailboxMapper.java      | 2 +-
 .../mailbox/cassandra/mail/CassandraMessageMapper.java      | 3 ++-
 .../src/main/java/org/apache/james/util/OptionalUtils.java  | 2 +-
 .../test/java/org/apache/james/util/OptionalUtilsTest.java  | 9 +++++----
 5 files changed, 10 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/23dd9053/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java
index 031a429..b96e5c4 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraBlobsDAO.java
@@ -168,7 +168,7 @@ public class CassandraBlobsDAO {
 
     private byte[] concatenateDataParts(Stream<BlobPart> blobParts) {
         ImmutableList<byte[]> parts = blobParts
-            .map(blobPart -> OptionalUtils.ifEmpty(
+            .map(blobPart -> OptionalUtils.peekOnEmpty(
                 blobPart.row,
                 () -> LOGGER.warn("Missing blob part for blobId {} and position {}", blobPart.blobId, blobPart.position)))
             .flatMap(OptionalUtils::toStream)

http://git-wip-us.apache.org/repos/asf/james-project/blob/23dd9053/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
index 1b6d2f8..d9d5c10 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
@@ -141,7 +141,7 @@ public class CassandraMailboxMapper implements MailboxMapper {
 
     private CompletableFuture<Optional<SimpleMailbox>> retrieveMailbox(CassandraMailboxPathDAO.CassandraIdAndPath idAndPath) {
         return retrieveMailbox(idAndPath.getCassandraId())
-            .thenApply(optional -> OptionalUtils.ifEmpty(optional,
+            .thenApply(optional -> OptionalUtils.peekOnEmpty(optional,
                 () -> LOGGER.warn("Could not retrieve mailbox {} with path {} in mailbox table.", idAndPath.getCassandraId(), idAndPath.getMailboxPath())));
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/23dd9053/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index 957595a..cf38640 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Stream;
+
 import javax.mail.Flags;
 import javax.mail.Flags.Flag;
 
@@ -225,7 +226,7 @@ public class CassandraMessageMapper implements MessageMapper {
 
     private CompletableFuture<Optional<ComposedMessageIdWithMetaData>> retrieveComposedId(CassandraId mailboxId, MessageUid uid) {
         return messageIdDAO.retrieve(mailboxId, uid)
-            .thenApply(optional -> OptionalUtils.ifEmpty(optional,
+            .thenApply(optional -> OptionalUtils.peekOnEmpty(optional,
                 () -> LOGGER.warn("Could not retrieve message {} {}", mailboxId, uid)));
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/23dd9053/server/container/util-java8/src/main/java/org/apache/james/util/OptionalUtils.java
----------------------------------------------------------------------
diff --git a/server/container/util-java8/src/main/java/org/apache/james/util/OptionalUtils.java b/server/container/util-java8/src/main/java/org/apache/james/util/OptionalUtils.java
index 709981b..66628ba 100644
--- a/server/container/util-java8/src/main/java/org/apache/james/util/OptionalUtils.java
+++ b/server/container/util-java8/src/main/java/org/apache/james/util/OptionalUtils.java
@@ -28,7 +28,7 @@ public class OptionalUtils {
         void perform();
     }
 
-    public static <T> Optional<T> ifEmpty(Optional<T> optional, Operation operation) {
+    public static <T> Optional<T> peekOnEmpty(Optional<T> optional, Operation operation) {
         if (!optional.isPresent()) {
             operation.perform();
         }

http://git-wip-us.apache.org/repos/asf/james-project/blob/23dd9053/server/container/util-java8/src/test/java/org/apache/james/util/OptionalUtilsTest.java
----------------------------------------------------------------------
diff --git a/server/container/util-java8/src/test/java/org/apache/james/util/OptionalUtilsTest.java b/server/container/util-java8/src/test/java/org/apache/james/util/OptionalUtilsTest.java
index 79b993c..a270469 100644
--- a/server/container/util-java8/src/test/java/org/apache/james/util/OptionalUtilsTest.java
+++ b/server/container/util-java8/src/test/java/org/apache/james/util/OptionalUtilsTest.java
@@ -19,6 +19,7 @@
 package org.apache.james.util;
 
 import static org.assertj.core.api.Assertions.assertThat;
+
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -35,7 +36,7 @@ public class OptionalUtilsTest {
 
     @Test
     public void ifEmptyShouldPreserveValueOfEmptyOptionals() {
-        Optional<Object> expected = OptionalUtils.ifEmpty(Optional.empty(), () -> { });
+        Optional<Object> expected = OptionalUtils.peekOnEmpty(Optional.empty(), () -> { });
 
         assertThat(expected).isEmpty();
     }
@@ -43,7 +44,7 @@ public class OptionalUtilsTest {
     @Test
     public void ifEmptyShouldPreserveValueOfPresentOptionals() {
         String value = "value";
-        Optional<String> expected = OptionalUtils.ifEmpty(Optional.of(value), () -> { });
+        Optional<String> expected = OptionalUtils.peekOnEmpty(Optional.of(value), () -> { });
 
         assertThat(expected).contains(value);
     }
@@ -52,7 +53,7 @@ public class OptionalUtilsTest {
     public void ifEmptyShouldPerformOperationIfEmpty() {
         AtomicInteger operationCounter = new AtomicInteger(0);
 
-        OptionalUtils.ifEmpty(Optional.empty(), operationCounter::incrementAndGet);
+        OptionalUtils.peekOnEmpty(Optional.empty(), operationCounter::incrementAndGet);
 
         assertThat(operationCounter.get()).isEqualTo(1);
     }
@@ -61,7 +62,7 @@ public class OptionalUtilsTest {
     public void ifEmptyShouldNotPerformOperationIfPresent() {
         AtomicInteger operationCounter = new AtomicInteger(0);
 
-        OptionalUtils.ifEmpty(Optional.of("value"), operationCounter::incrementAndGet);
+        OptionalUtils.peekOnEmpty(Optional.of("value"), operationCounter::incrementAndGet);
 
         assertThat(operationCounter.get()).isEqualTo(0);
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[6/6] james-project git commit: JAMES-2147 Add mentions of JIRA labels on the website

Posted by bt...@apache.org.
JAMES-2147 Add mentions of JIRA labels on the website


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/651a03db
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/651a03db
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/651a03db

Branch: refs/heads/master
Commit: 651a03db6bf957853cf122d0427f0130b48c18e3
Parents: 23dd905
Author: benwa <bt...@linagora.com>
Authored: Fri Sep 22 17:23:33 2017 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Oct 3 07:53:12 2017 +0700

----------------------------------------------------------------------
 src/homepage/index.html | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/651a03db/src/homepage/index.html
----------------------------------------------------------------------
diff --git a/src/homepage/index.html b/src/homepage/index.html
index ab42cf1..fd9d076 100644
--- a/src/homepage/index.html
+++ b/src/homepage/index.html
@@ -175,7 +175,20 @@ WHAT WILL YOU TRY:</b><br>
                   <b>Sources on Git:</b>
                   <br/>Read our sources from the <a href="git://git.apache.org/james-project.git" class="comm" alt="James Git sources">Apache Git</a>.<br/>
                   You can also come and interact with us on <a href="https://github.com/apache/james-project" class="comm" alt="James on GitHub">GitHub</a>.<br/>
-                  Here are some more instructions to <a href="/contribute.html" class="comm" alt="Contributing to James: guidelines">get starting contributing</a>.
+                </span>
+              </li>
+              <li class="post-template center-icon">
+                <a href="https://issues.apache.org/jira/issues/?jql=project%20%3D%20JAMES%20AND%20resolution%20%3D%20Unresolved%20AND%20labels%20%3D%20newbie%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC" alt="contribute to the code"><span class="icon fa-cogs"></span></a>
+                <span class="details">
+                  <b>Get started and contribute to this project:</b>
+                  <br/>James is a project that lives from the contributions of its community! Read the <a href="/contribute.html" class="comm" alt="Contributing to James: guidelines">contributing guidelines</a>.
+                  <br/>We more than welcome <b>articles</b> and <b>blog posts</b> about James. Contact us by <a href="https://james.apache.org/mail.html" class="comm" alt="mailing lists">email</a> or on <a href="https://gitter.im/apache/james-project" class="comm" alt="Gitter chat room">Gitter</a> to share your experiences.
+                  <br/><b>Documentation</b> is an easy way to get started, and more than wanted! Check out the <a class="comm" alt="Documentation" href="https://issues.apache.org/jira/issues/?jql=project%20%3D%20JAMES%20AND%20resolution%20%3D%20Unresolved%20AND%20labels%20%3D%20documentation%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC">~documentation</a> label on JIRA.
+                  <br/>And to get started with <b>code contributions</b>, search out the
+                  <a class="comm" alt="Newbie tasks" href="https://issues.apache.org/jira/issues/?jql=project%20%3D%20JAMES%20AND%20resolution%20%3D%20Unresolved%20AND%20labels%20%3D%20newbie%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC">~newbie</a>,
+                  <a class="comm" alt="Easy fixes" href="https://issues.apache.org/jira/issues/?jql=project%20%3D%20JAMES%20AND%20resolution%20%3D%20Unresolved%20AND%20labels%20%3D%20easyfix%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC">~easyfix</a>,
+                  <a class="comm" alt="Cool features" href="https://issues.apache.org/jira/issues/?jql=project%20%3D%20JAMES%20AND%20resolution%20%3D%20Unresolved%20AND%20labels%20%3D%20feature%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC">~feature</a> labels on JIRA</a>.
+                  <br/>There is many other ways one can help us: packaging, communication, etc . . .
                 </span>
               </li>
               <li class="post-template center-icon">


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[4/6] james-project git commit: JAMES-2162 JMAP implementation for reading GetMailboxes::sharedWith

Posted by bt...@apache.org.
JAMES-2162 JMAP implementation for reading GetMailboxes::sharedWith


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/0ac2d416
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/0ac2d416
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/0ac2d416

Branch: refs/heads/master
Commit: 0ac2d416aceaeaf8c3b0e2c0384bccbf52bedf3d
Parents: 672f24f
Author: benwa <bt...@linagora.com>
Authored: Wed Sep 27 17:42:30 2017 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Oct 3 07:52:12 2017 +0700

----------------------------------------------------------------------
 .../james/mailbox/store/probe/ACLProbe.java     |  28 ++++
 .../james/cli/probe/impl/JmxMailboxProbe.java   |  11 +-
 .../org/apache/james/modules/ACLProbeImpl.java  |  51 +++++++
 .../org/apache/james/modules/MailboxModule.java |   1 +
 .../apache/james/modules/MailboxProbeImpl.java  |   1 -
 .../integration/GetMailboxesMethodTest.java     | 147 ++++++++++++++-----
 .../apache/james/jmap/model/MailboxFactory.java |   5 +
 .../james/jmap/model/mailbox/Mailbox.java       |  25 +++-
 .../james/jmap/model/mailbox/MailboxTest.java   |   2 +-
 9 files changed, 222 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/mailbox/store/src/main/java/org/apache/james/mailbox/store/probe/ACLProbe.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/probe/ACLProbe.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/probe/ACLProbe.java
new file mode 100644
index 0000000..986186e
--- /dev/null
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/probe/ACLProbe.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * 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.probe;
+
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
+import org.apache.james.mailbox.model.MailboxPath;
+
+public interface ACLProbe {
+    void replaceRights(MailboxPath mailboxPath, String targetUser, Rfc4314Rights rights) throws MailboxException;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/container/cli/src/main/java/org/apache/james/cli/probe/impl/JmxMailboxProbe.java
----------------------------------------------------------------------
diff --git a/server/container/cli/src/main/java/org/apache/james/cli/probe/impl/JmxMailboxProbe.java b/server/container/cli/src/main/java/org/apache/james/cli/probe/impl/JmxMailboxProbe.java
index ab9005a..ebc1f0e 100644
--- a/server/container/cli/src/main/java/org/apache/james/cli/probe/impl/JmxMailboxProbe.java
+++ b/server/container/cli/src/main/java/org/apache/james/cli/probe/impl/JmxMailboxProbe.java
@@ -23,9 +23,11 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
 import java.util.Date;
+
 import javax.mail.Flags;
 import javax.management.MalformedObjectNameException;
 
+import org.apache.commons.lang.NotImplementedException;
 import org.apache.james.adapter.mailbox.MailboxCopierManagementMBean;
 import org.apache.james.adapter.mailbox.MailboxManagerManagementMBean;
 import org.apache.james.adapter.mailbox.ReIndexerManagementMBean;
@@ -97,23 +99,20 @@ public class JmxMailboxProbe implements MailboxProbe, JmxProbe {
         reIndexerManagement.reIndex();
     }
 
-
     @Override
     public Mailbox getMailbox(String namespace, String user, String name) {
-        return null;
+        throw new NotImplementedException();
     }
 
-
     @Override
     public ComposedMessageId appendMessage(String username, MailboxPath mailboxPath, InputStream message,
             Date internalDate, boolean isRecent, Flags flags) throws MailboxException {
-        return null;
+        throw new NotImplementedException();
     }
 
 
     @Override
     public Collection<String> listSubscriptions(String user) throws Exception {
-        return null;
+        throw new NotImplementedException();
     }
-
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/container/guice/mailbox/src/main/java/org/apache/james/modules/ACLProbeImpl.java
----------------------------------------------------------------------
diff --git a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/ACLProbeImpl.java b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/ACLProbeImpl.java
new file mode 100644
index 0000000..a25c4d9
--- /dev/null
+++ b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/ACLProbeImpl.java
@@ -0,0 +1,51 @@
+/****************************************************************
+ * 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.modules;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxACL.ACLCommand;
+import org.apache.james.mailbox.model.MailboxACL.EditMode;
+import org.apache.james.mailbox.model.MailboxACL.EntryKey;
+import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.probe.ACLProbe;
+import org.apache.james.utils.GuiceProbe;
+
+public class ACLProbeImpl implements GuiceProbe, ACLProbe {
+    private final MailboxManager mailboxManager;
+
+    @Inject
+    private ACLProbeImpl(MailboxManager mailboxManager) {
+        this.mailboxManager = mailboxManager;
+    }
+
+    @Override
+    public void replaceRights(MailboxPath mailboxPath, String targetUser, Rfc4314Rights rights) throws MailboxException {
+        MailboxSession mailboxSession = mailboxManager.createSystemSession(mailboxPath.getUser());
+
+        EntryKey key = EntryKey.createUser(targetUser);
+        ACLCommand mailboxACLCommand = new ACLCommand(key, EditMode.REPLACE, rights);
+        mailboxManager.applyRightsCommand(mailboxPath, mailboxACLCommand, mailboxSession);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxModule.java b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxModule.java
index 48a9077..a91ada3 100644
--- a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxModule.java
+++ b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxModule.java
@@ -30,6 +30,7 @@ public class MailboxModule extends AbstractModule {
         Multibinder<GuiceProbe> probeMultiBinder = Multibinder.newSetBinder(binder(), GuiceProbe.class);
         probeMultiBinder.addBinding().to(MailboxProbeImpl.class);
         probeMultiBinder.addBinding().to(QuotaProbesImpl.class);
+        probeMultiBinder.addBinding().to(ACLProbeImpl.class);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
----------------------------------------------------------------------
diff --git a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
index 6a9588f..b2462ee 100644
--- a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
+++ b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
@@ -49,7 +49,6 @@ import org.apache.james.utils.GuiceProbe;
 import com.google.common.base.Throwables;
 
 public class MailboxProbeImpl implements GuiceProbe, MailboxProbe {
-
     private final MailboxManager mailboxManager;
     private final MailboxMapperFactory mailboxMapperFactory;
     private final SubscriptionManager subscriptionManager;

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMailboxesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMailboxesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMailboxesMethodTest.java
index 60806e7..c9136f3 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMailboxesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMailboxesMethodTest.java
@@ -24,9 +24,11 @@ import static com.jayway.restassured.config.EncoderConfig.encoderConfig;
 import static com.jayway.restassured.config.RestAssuredConfig.newConfig;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.nullValue;
@@ -43,21 +45,26 @@ import org.apache.james.GuiceJamesServer;
 import org.apache.james.jmap.DefaultMailboxes;
 import org.apache.james.jmap.HttpJmapAuthentication;
 import org.apache.james.jmap.api.access.AccessToken;
+import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
+import org.apache.james.mailbox.model.MailboxACL.Right;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
+import org.apache.james.mailbox.store.probe.ACLProbe;
 import org.apache.james.mailbox.store.probe.MailboxProbe;
+import org.apache.james.modules.ACLProbeImpl;
 import org.apache.james.modules.MailboxProbeImpl;
 import org.apache.james.probe.DataProbe;
-import org.apache.james.utils.JmapGuiceProbe;
 import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.JmapGuiceProbe;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.jayway.restassured.RestAssured;
 import com.jayway.restassured.builder.RequestSpecBuilder;
 import com.jayway.restassured.http.ContentType;
@@ -65,6 +72,11 @@ import com.jayway.restassured.http.ContentType;
 public abstract class GetMailboxesMethodTest {
     private static final String NAME = "[0][0]";
     private static final String ARGUMENTS = "[0][1]";
+    private static final String FIRST_MAILBOX = ARGUMENTS + ".list[0]";
+
+    public static final String READ = String.valueOf(Right.Read.asCharacter());
+    public static final String LOOKUP = String.valueOf(Right.Lookup.asCharacter());
+    public static final String ADMINISTER = String.valueOf(Right.Administer.asCharacter());
 
     protected abstract GuiceJamesServer createJmapServer();
 
@@ -72,13 +84,15 @@ public abstract class GetMailboxesMethodTest {
     private String username;
     private GuiceJamesServer jmapServer;
     private MailboxProbe mailboxProbe;
+    private ACLProbe aclProbe;
     
     @Before
     public void setup() throws Throwable {
         jmapServer = createJmapServer();
         jmapServer.start();
         mailboxProbe = jmapServer.getProbe(MailboxProbeImpl.class);
-        
+        aclProbe = jmapServer.getProbe(ACLProbeImpl.class);
+
         RestAssured.requestSpecification = new RequestSpecBuilder()
                 .setContentType(ContentType.JSON)
                 .setAccept(ContentType.JSON)
@@ -195,7 +209,7 @@ public abstract class GetMailboxesMethodTest {
             .statusCode(200)
             .body(NAME, equalTo("mailboxes"))
             .body(ARGUMENTS + ".list", hasSize(1))
-            .body(ARGUMENTS + ".list[0].id", equalTo(mailboxId));
+            .body(FIRST_MAILBOX + ".id", equalTo(mailboxId));
     }
 
     @Test
@@ -235,6 +249,67 @@ public abstract class GetMailboxesMethodTest {
             .body(ARGUMENTS + ".list", hasSize(7))
             .body(ARGUMENTS + ".list.name", hasItems(expectedMailboxes.toArray()));
     }
+
+    @Test
+    public void getMailboxesShouldReturnSharedWithProperty() throws Exception {
+        String mailboxName = "myMailbox";
+        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, username, mailboxName);
+        String targetUser1 = "toUser1@domain.com";
+        String targetUser2 = "toUser2@domain.com";
+        Mailbox myMailbox = mailboxProbe.getMailbox(MailboxConstants.USER_NAMESPACE, username, mailboxName);
+        aclProbe.replaceRights(myMailbox.generateAssociatedPath(), targetUser1, new Rfc4314Rights(Right.Read, Right.Administer));
+        aclProbe.replaceRights(myMailbox.generateAssociatedPath(), targetUser2, new Rfc4314Rights(Right.Read, Right.Lookup));
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + myMailbox.getMailboxId().serialize() + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(mailboxName))
+            .body(FIRST_MAILBOX + ".sharedWith", hasEntry(targetUser1, ImmutableList.of(ADMINISTER, READ)))
+            .body(FIRST_MAILBOX + ".sharedWith", hasEntry(targetUser2, ImmutableList.of(LOOKUP, READ)));
+    }
+
+    @Test
+    public void getMailboxShouldReturnEmptySharedWithWhenNoDelegation() throws Exception {
+        String mailboxName = "myMailbox";
+        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, username, mailboxName);
+        Mailbox myMailbox = mailboxProbe.getMailbox(MailboxConstants.USER_NAMESPACE, username, mailboxName);
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + myMailbox.getMailboxId().serialize() + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(mailboxName))
+            .body(FIRST_MAILBOX + ".sharedWith", is(ImmutableMap.of()));
+    }
+
+    @Test
+    public void nonHandledRightsShouldBeFilteredOut() throws Exception {
+        String mailboxName = "myMailbox";
+        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, username, mailboxName);
+        String targetUser1 = "toUser1@domain.com";
+        Mailbox myMailbox = mailboxProbe.getMailbox(MailboxConstants.USER_NAMESPACE, username, mailboxName);
+        aclProbe.replaceRights(myMailbox.generateAssociatedPath(), targetUser1, new Rfc4314Rights(Right.Read, Right.Post));
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + myMailbox.getMailboxId().serialize() + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(mailboxName))
+            .body(FIRST_MAILBOX + ".sharedWith", hasEntry(targetUser1, ImmutableList.of(READ)));
+    }
     
     @Test
     public void getMailboxesShouldErrorInvalidArgumentsWhenRequestIsInvalid() throws Exception {
@@ -325,19 +400,19 @@ public abstract class GetMailboxesMethodTest {
             .statusCode(200)
             .body(NAME, equalTo("mailboxes"))
             .body(ARGUMENTS + ".list.name", hasItem("name"))
-            .body(ARGUMENTS + ".list[0].parentId", nullValue())
-            .body(ARGUMENTS + ".list[0].role", nullValue())
-            .body(ARGUMENTS + ".list[0].sortOrder", equalTo(1000))
-            .body(ARGUMENTS + ".list[0].mustBeOnlyMailbox", equalTo(false))
-            .body(ARGUMENTS + ".list[0].mayReadItems", equalTo(false))
-            .body(ARGUMENTS + ".list[0].mayAddItems", equalTo(false))
-            .body(ARGUMENTS + ".list[0].mayRemoveItems", equalTo(false))
-            .body(ARGUMENTS + ".list[0].mayCreateChild", equalTo(false))
-            .body(ARGUMENTS + ".list[0].mayRename", equalTo(false))
-            .body(ARGUMENTS + ".list[0].mayDelete", equalTo(false))
-            .body(ARGUMENTS + ".list[0].totalMessages", equalTo(1))
-            .body(ARGUMENTS + ".list[0].unreadMessages", equalTo(1))
-            .body(ARGUMENTS + ".list[0].unreadThreads", equalTo(0));
+            .body(FIRST_MAILBOX + ".parentId", nullValue())
+            .body(FIRST_MAILBOX + ".role", nullValue())
+            .body(FIRST_MAILBOX + ".sortOrder", equalTo(1000))
+            .body(FIRST_MAILBOX + ".mustBeOnlyMailbox", equalTo(false))
+            .body(FIRST_MAILBOX + ".mayReadItems", equalTo(false))
+            .body(FIRST_MAILBOX + ".mayAddItems", equalTo(false))
+            .body(FIRST_MAILBOX + ".mayRemoveItems", equalTo(false))
+            .body(FIRST_MAILBOX + ".mayCreateChild", equalTo(false))
+            .body(FIRST_MAILBOX + ".mayRename", equalTo(false))
+            .body(FIRST_MAILBOX + ".mayDelete", equalTo(false))
+            .body(FIRST_MAILBOX + ".totalMessages", equalTo(1))
+            .body(FIRST_MAILBOX + ".unreadMessages", equalTo(1))
+            .body(FIRST_MAILBOX + ".unreadThreads", equalTo(0));
     }
 
     @Test
@@ -353,21 +428,21 @@ public abstract class GetMailboxesMethodTest {
         .then()
             .statusCode(200)
             .body(NAME, equalTo("mailboxes"))
-            .body(ARGUMENTS + ".list[0].id", not(isEmptyOrNullString()))
-            .body(ARGUMENTS + ".list[0].name", nullValue())
-            .body(ARGUMENTS + ".list[0].parentId", nullValue())
-            .body(ARGUMENTS + ".list[0].role", nullValue())
-            .body(ARGUMENTS + ".list[0].sortOrder", equalTo(1000))
-            .body(ARGUMENTS + ".list[0].mustBeOnlyMailbox", nullValue())
-            .body(ARGUMENTS + ".list[0].mayReadItems", nullValue())
-            .body(ARGUMENTS + ".list[0].mayAddItems", nullValue())
-            .body(ARGUMENTS + ".list[0].mayRemoveItems", nullValue())
-            .body(ARGUMENTS + ".list[0].mayCreateChild", nullValue())
-            .body(ARGUMENTS + ".list[0].mayRename", nullValue())
-            .body(ARGUMENTS + ".list[0].mayDelete", nullValue())
-            .body(ARGUMENTS + ".list[0].totalMessages", nullValue())
-            .body(ARGUMENTS + ".list[0].unreadMessages", equalTo(0))
-            .body(ARGUMENTS + ".list[0].unreadThreads", nullValue());
+            .body(FIRST_MAILBOX + ".id", not(isEmptyOrNullString()))
+            .body(FIRST_MAILBOX + ".name", nullValue())
+            .body(FIRST_MAILBOX + ".parentId", nullValue())
+            .body(FIRST_MAILBOX + ".role", nullValue())
+            .body(FIRST_MAILBOX + ".sortOrder", equalTo(1000))
+            .body(FIRST_MAILBOX + ".mustBeOnlyMailbox", nullValue())
+            .body(FIRST_MAILBOX + ".mayReadItems", nullValue())
+            .body(FIRST_MAILBOX + ".mayAddItems", nullValue())
+            .body(FIRST_MAILBOX + ".mayRemoveItems", nullValue())
+            .body(FIRST_MAILBOX + ".mayCreateChild", nullValue())
+            .body(FIRST_MAILBOX + ".mayRename", nullValue())
+            .body(FIRST_MAILBOX + ".mayDelete", nullValue())
+            .body(FIRST_MAILBOX + ".totalMessages", nullValue())
+            .body(FIRST_MAILBOX + ".unreadMessages", equalTo(0))
+            .body(FIRST_MAILBOX + ".unreadThreads", nullValue());
     }
 
     @Test
@@ -382,8 +457,8 @@ public abstract class GetMailboxesMethodTest {
         .then()
             .statusCode(200)
             .body(NAME, equalTo("mailboxes"))
-            .body(ARGUMENTS + ".list[0].id", not(isEmptyOrNullString()))
-            .body(ARGUMENTS + ".list[0].name", nullValue());
+            .body(FIRST_MAILBOX + ".id", not(isEmptyOrNullString()))
+            .body(FIRST_MAILBOX + ".name", nullValue());
     }
 
     @Test
@@ -398,8 +473,8 @@ public abstract class GetMailboxesMethodTest {
         .then()
             .statusCode(200)
             .body(NAME, equalTo("mailboxes"))
-            .body(ARGUMENTS + ".list[0].id", not(isEmptyOrNullString()))
-            .body(ARGUMENTS + ".list[0].name", nullValue());
+            .body(FIRST_MAILBOX + ".id", not(isEmptyOrNullString()))
+            .body(FIRST_MAILBOX + ".name", nullValue());
     }
 
     @Test
@@ -438,7 +513,7 @@ public abstract class GetMailboxesMethodTest {
             .statusCode(200)
             .body(NAME, equalTo("mailboxes"))
             .body(ARGUMENTS + ".list", hasSize(1))
-            .body(ARGUMENTS + ".list[0].role", equalTo(DefaultMailboxes.OUTBOX.toLowerCase(Locale.US)));
+            .body(FIRST_MAILBOX + ".role", equalTo(DefaultMailboxes.OUTBOX.toLowerCase(Locale.US)));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MailboxFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MailboxFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MailboxFactory.java
index 23d327c..9890c85 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MailboxFactory.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MailboxFactory.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import javax.inject.Inject;
 
 import org.apache.james.jmap.model.mailbox.Mailbox;
+import org.apache.james.jmap.model.mailbox.Rights;
 import org.apache.james.jmap.model.mailbox.Role;
 import org.apache.james.jmap.model.mailbox.SortOrder;
 import org.apache.james.mailbox.MailboxManager;
@@ -42,6 +43,7 @@ import com.google.common.base.Splitter;
 import com.google.common.base.Throwables;
 
 public class MailboxFactory {
+    public static final boolean NO_RESET_RECENT = false;
     private final MailboxManager mailboxManager;
 
     public static class MailboxBuilder {
@@ -98,6 +100,8 @@ public class MailboxFactory {
         MailboxPath mailboxPath = messageManager.getMailboxPath();
         Optional<Role> role = Role.from(mailboxPath.getName());
         MailboxCounters mailboxCounters = messageManager.getMailboxCounters(mailboxSession);
+        MessageManager.MetaData metaData = messageManager.getMetaData(NO_RESET_RECENT, mailboxSession, MessageManager.MetaData.FetchGroup.NO_COUNT);
+
         return Mailbox.builder()
             .id(messageManager.getId())
             .name(getName(mailboxPath, mailboxSession))
@@ -106,6 +110,7 @@ public class MailboxFactory {
             .unreadMessages(mailboxCounters.getUnseen())
             .totalMessages(mailboxCounters.getCount())
             .sortOrder(SortOrder.getSortOrder(role))
+            .sharedWith(Rights.fromACL(metaData.getACL()))
             .build();
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Mailbox.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Mailbox.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Mailbox.java
index cddb101..d0b8144 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Mailbox.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Mailbox.java
@@ -60,9 +60,11 @@ public class Mailbox {
         private long unreadMessages;
         private long totalThreads;
         private long unreadThreads;
+        private Optional<Rights> sharedWith;
 
         private Builder() {
             parentId = Optional.empty();
+            sharedWith = Optional.empty();
         }
 
         public Builder id(MailboxId id) {
@@ -147,12 +149,17 @@ public class Mailbox {
             return this;
         }
 
+        public Builder sharedWith(Rights sharedWith) {
+            this.sharedWith = Optional.of(sharedWith);
+            return this;
+        }
+
         public Mailbox build() {
             Preconditions.checkState(!Strings.isNullOrEmpty(name), "'name' is mandatory");
             Preconditions.checkState(id != null, "'id' is mandatory");
 
             return new Mailbox(id, name, parentId, role, sortOrder, mustBeOnlyMailbox, mayReadItems, mayAddItems, mayRemoveItems, mayCreateChild, mayRename, mayDelete,
-                    totalMessages, unreadMessages, totalThreads, unreadThreads);
+                    totalMessages, unreadMessages, totalThreads, unreadThreads, sharedWith.orElse(Rights.EMPTY));
         }
     }
 
@@ -172,10 +179,11 @@ public class Mailbox {
     private final long unreadMessages;
     private final long totalThreads;
     private final long unreadThreads;
+    private final Rights sharedWith;
 
     @VisibleForTesting Mailbox(MailboxId id, String name, Optional<MailboxId> parentId, Optional<Role> role, SortOrder sortOrder, boolean mustBeOnlyMailbox,
-            boolean mayReadItems, boolean mayAddItems, boolean mayRemoveItems, boolean mayCreateChild, boolean mayRename, boolean mayDelete,
-            long totalMessages, long unreadMessages, long totalThreads, long unreadThreads) {
+                               boolean mayReadItems, boolean mayAddItems, boolean mayRemoveItems, boolean mayCreateChild, boolean mayRename, boolean mayDelete,
+                               long totalMessages, long unreadMessages, long totalThreads, long unreadThreads, Rights sharedWith) {
 
         this.id = id;
         this.name = name;
@@ -193,6 +201,7 @@ public class Mailbox {
         this.unreadMessages = unreadMessages;
         this.totalThreads = totalThreads;
         this.unreadThreads = unreadThreads;
+        this.sharedWith = sharedWith;
     }
 
     public MailboxId getId() {
@@ -259,6 +268,10 @@ public class Mailbox {
         return unreadThreads;
     }
 
+    public Rights getSharedWith() {
+        return sharedWith;
+    }
+
     @Override
     public final boolean equals(Object obj) {
         if (obj instanceof Mailbox) {
@@ -278,7 +291,8 @@ public class Mailbox {
                 && Objects.equals(this.totalMessages, other.totalMessages)
                 && Objects.equals(this.unreadMessages, other.unreadMessages)
                 && Objects.equals(this.totalThreads, other.totalThreads)
-                && Objects.equals(this.unreadThreads, other.unreadThreads);
+                && Objects.equals(this.unreadThreads, other.unreadThreads)
+                && Objects.equals(this.sharedWith, other.sharedWith);
         }
         return false;
     }
@@ -286,7 +300,8 @@ public class Mailbox {
     @Override
     public final int hashCode() {
         return Objects.hash(id, name, parentId, role, sortOrder, mustBeOnlyMailbox, mayReadItems, mayAddItems, 
-                mayRemoveItems, mayCreateChild, mayRename, mayDelete, totalMessages, unreadMessages, totalThreads, unreadThreads);
+            mayRemoveItems, mayCreateChild, mayRename, mayDelete, totalMessages, unreadMessages, totalThreads,
+            unreadThreads, sharedWith);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/james-project/blob/0ac2d416/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/MailboxTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/MailboxTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/MailboxTest.java
index 1ac869f..91275df 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/MailboxTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/MailboxTest.java
@@ -63,7 +63,7 @@ public class MailboxTest {
     @Test
     public void buildShouldWork() {
         Mailbox expectedMailbox = new Mailbox(InMemoryId.of(1), "name", Optional.of(InMemoryId.of(0)), Optional.of(Role.DRAFTS), SortOrder.of(123),
-                true, true, true, true, true, true, true, 456, 789, 741, 852);
+                true, true, true, true, true, true, true, 456, 789, 741, 852, Rights.EMPTY);
 
         Mailbox mailbox = Mailbox.builder()
             .id(InMemoryId.of(1))


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[2/6] james-project git commit: JAMES-2162 Introduce JMAP Rights object

Posted by bt...@apache.org.
JAMES-2162 Introduce JMAP Rights object


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/672f24f1
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/672f24f1
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/672f24f1

Branch: refs/heads/master
Commit: 672f24f1e1034d02f9e11bfae0674a5d8409272d
Parents: 6e9ad73
Author: benwa <bt...@linagora.com>
Authored: Wed Sep 27 17:43:00 2017 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Oct 3 07:52:07 2017 +0700

----------------------------------------------------------------------
 .../apache/james/mailbox/model/MailboxACL.java  |   5 +
 .../apache/james/jmap/model/mailbox/Rights.java | 240 +++++++++++++++++++
 .../james/jmap/model/mailbox/RightsTest.java    | 164 +++++++++++++
 3 files changed, 409 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/672f24f1/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
index d1e1487..a831302 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
@@ -333,6 +333,11 @@ public class MailboxACL {
         public Entry(String key, String value) throws UnsupportedRightException {
             this(EntryKey.deserialize(key), Rfc4314Rights.fromSerializedRfc4314Rights(value));
         }
+
+        public Entry(String key, Right... rights) throws UnsupportedRightException {
+            this(EntryKey.deserialize(key), new Rfc4314Rights(rights));
+        }
+
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/james-project/blob/672f24f1/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
new file mode 100644
index 0000000..d44e4d3
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
@@ -0,0 +1,240 @@
+/****************************************************************
+ * 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.model.mailbox;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BinaryOperator;
+
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxACL.EntryKey;
+import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
+import org.apache.james.util.GuavaUtils;
+import org.apache.james.util.OptionalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+
+public class Rights {
+    public enum Right {
+        Administer(MailboxACL.Right.Administer),
+        Expunge(MailboxACL.Right.PerformExpunge),
+        Insert(MailboxACL.Right.Insert),
+        Lookup(MailboxACL.Right.Lookup),
+        Read(MailboxACL.Right.Read),
+        Seen(MailboxACL.Right.WriteSeenFlag),
+        DeleteMessages(MailboxACL.Right.DeleteMessages),
+        Write(MailboxACL.Right.Write);
+
+        private final MailboxACL.Right right;
+
+        Right(MailboxACL.Right right) {
+            this.right = right;
+        }
+
+        @JsonValue
+        public char asCharacter() {
+            return right.asCharacter();
+        }
+
+        public MailboxACL.Right toMailboxRight() {
+            return right;
+        }
+
+        public static Optional<Right> forRight(MailboxACL.Right right) {
+            return OptionalUtils.peekOnEmpty(
+                Arrays.stream(values())
+                    .filter(jmapRight -> jmapRight.right == right)
+                    .findAny(),
+                () -> LOGGER.warn("Non handled right '" + right + "'"));
+        }
+
+        public static Right forChar(char c) {
+            return Arrays.stream(values())
+                .filter(right -> right.asCharacter() == c)
+                .findAny()
+                .orElseThrow(() -> new IllegalArgumentException("No matching right for '" + c + "'"));
+        }
+    }
+
+    public static class Username {
+        private final String value;
+
+     //   @JsonCreator
+        public Username(String value) {
+            this.value = value;
+        }
+
+        @JsonValue
+        public String getValue() {
+            return value;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof Username) {
+                Username username = (Username) o;
+
+                return Objects.equals(this.value, username.value);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(value);
+        }
+    }
+
+    public static class Builder {
+        private Multimap<Username, Right> rights;
+
+        public Builder() {
+            rights = ArrayListMultimap.create();
+        }
+
+        public Builder delegateTo(Username username, Right... rights) {
+            delegateTo(username, Arrays.asList(rights));
+            return this;
+        }
+
+        public Builder delegateTo(Username username, Collection<Right> rights) {
+            this.rights.putAll(username, rights);
+            return this;
+        }
+
+        public Builder combine(Builder builder) {
+            this.rights.putAll(builder.rights);
+            return this;
+        }
+
+        public Rights build() {
+            return new Rights(rights);
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static Rights fromACL(MailboxACL acl) {
+        return acl.getEntries()
+            .entrySet()
+            .stream()
+            .filter(entry -> isSupported(entry.getKey()))
+            .map(Rights::toRightsBuilder)
+            .reduce(builder(), Builder::combine)
+            .build();
+    }
+
+    private static Builder toRightsBuilder(Map.Entry<EntryKey, MailboxACL.Rfc4314Rights> entry) {
+        return builder().delegateTo(
+            new Username(entry.getKey().getName()),
+            fromACL(entry.getValue()));
+    }
+
+    private static List<Right> fromACL(MailboxACL.Rfc4314Rights rights) {
+        return rights.list()
+            .stream()
+            .flatMap(right -> OptionalUtils.toStream(Right.forRight(right)))
+            .collect(Guavate.toImmutableList());
+    }
+
+    private static boolean isSupported(EntryKey key) {
+        if (key.isNegative()) {
+            LOGGER.info("Negative keys are not supported");
+            return false;
+        }
+        if (key.getNameType() != MailboxACL.NameType.user) {
+            LOGGER.info(key.getNameType() + " is not supported. Only 'user' is.");
+            return false;
+        }
+        return true;
+    }
+
+    public static final Rights EMPTY = new Rights(ArrayListMultimap.create());
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Rights.class);
+
+    private final Multimap<Username, Right> rights;
+
+    @JsonCreator
+    public Rights(Map<Username, List<Right>> rights) {
+        this(GuavaUtils.toMultimap(rights));
+    }
+
+    private Rights(Multimap<Username, Right> rights) {
+        this.rights = rights;
+    }
+
+    @JsonAnyGetter
+    public Map<Username, Collection<Right>> getRights() {
+        return rights.asMap();
+    }
+
+    public MailboxACL toMailboxAcl() {
+        BinaryOperator<MailboxACL> union = Throwing.binaryOperator(MailboxACL::union);
+
+        return rights.asMap()
+            .entrySet()
+            .stream()
+            .map(entry -> new MailboxACL(
+                ImmutableMap.of(
+                    EntryKey.createUser(entry.getKey().value),
+                    toMailboxAclRights(entry.getValue()))))
+            .reduce(MailboxACL.EMPTY, union);
+    }
+
+    private Rfc4314Rights toMailboxAclRights(Collection<Right> rights) {
+        BinaryOperator<Rfc4314Rights> union = Throwing.binaryOperator(Rfc4314Rights::union);
+
+        return rights.stream()
+            .map(Right::toMailboxRight)
+            .map(Throwing.function(Rfc4314Rights::new))
+            .reduce(new Rfc4314Rights(), union);
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof Rights) {
+            Rights that = (Rights) o;
+
+            return Objects.equals(this.rights, that.rights);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(rights);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/672f24f1/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java
new file mode 100644
index 0000000..dae6130
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/mailbox/RightsTest.java
@@ -0,0 +1,164 @@
+/****************************************************************
+ * 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.model.mailbox;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxACL.Entry;
+import org.apache.james.mailbox.model.MailboxACL.EntryKey;
+import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class RightsTest {
+
+    public static final boolean NEGATIVE = true;
+
+    @Test
+    public void rightsShouldMatchBeanContract() {
+        EqualsVerifier.forClass(Rights.class)
+            .allFieldsShouldBeUsed()
+            .verify();
+    }
+
+    @Test
+    public void usernameShouldMatchBeanContract() {
+        EqualsVerifier.forClass(Rights.Username.class)
+            .allFieldsShouldBeUsed()
+            .verify();
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenA() {
+        assertThat(Rights.Right.forChar('a'))
+            .isEqualTo(Rights.Right.Administer);
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenE() {
+        assertThat(Rights.Right.forChar('e'))
+            .isEqualTo(Rights.Right.Expunge);
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenI() {
+        assertThat(Rights.Right.forChar('i'))
+            .isEqualTo(Rights.Right.Insert);
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenL() {
+        assertThat(Rights.Right.forChar('l'))
+            .isEqualTo(Rights.Right.Lookup);
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenR() {
+        assertThat(Rights.Right.forChar('r'))
+            .isEqualTo(Rights.Right.Read);
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenW() {
+        assertThat(Rights.Right.forChar('w'))
+            .isEqualTo(Rights.Right.Write);
+    }
+
+    @Test
+    public void forCharShouldReturnRightWhenT() {
+        assertThat(Rights.Right.forChar('t'))
+            .isEqualTo(Rights.Right.DeleteMessages);
+    }
+
+    @Test
+    public void forCharShouldThrowOnUnsupportedRight() {
+        assertThatThrownBy(() -> Rights.Right.forChar('k'))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void fromACLShouldFilterOutGroups() throws Exception {
+        MailboxACL acl = new MailboxACL(ImmutableMap.of(
+            EntryKey.createGroup("group"), Rfc4314Rights.fromSerializedRfc4314Rights("aet")));
+
+        assertThat(Rights.fromACL(acl))
+            .isEqualTo(Rights.EMPTY);
+    }
+
+    @Test
+    public void fromACLShouldFilterNegatedUsers() throws Exception {
+        MailboxACL acl = new MailboxACL(ImmutableMap.of(
+            EntryKey.createUser("user", NEGATIVE), Rfc4314Rights.fromSerializedRfc4314Rights("aet")));
+
+        assertThat(Rights.fromACL(acl))
+            .isEqualTo(Rights.EMPTY);
+    }
+
+    @Test
+    public void fromACLShouldAcceptUsers() throws Exception {
+        MailboxACL acl = new MailboxACL(ImmutableMap.of(
+            EntryKey.createUser("user"), Rfc4314Rights.fromSerializedRfc4314Rights("aet")));
+
+        assertThat(Rights.fromACL(acl))
+            .isEqualTo(Rights.builder()
+                .delegateTo(new Rights.Username("user"), Rights.Right.Administer, Rights.Right.Expunge, Rights.Right.DeleteMessages)
+                .build());
+    }
+
+    @Test
+    public void fromACLShouldFilterOutUnknownRights() throws Exception {
+        MailboxACL acl = new MailboxACL(ImmutableMap.of(
+            EntryKey.createUser("user"), Rfc4314Rights.fromSerializedRfc4314Rights("aetpk")));
+
+        assertThat(Rights.fromACL(acl))
+            .isEqualTo(Rights.builder()
+                .delegateTo(new Rights.Username("user"), Rights.Right.Administer, Rights.Right.Expunge, Rights.Right.DeleteMessages)
+                .build());
+    }
+
+    @Test
+    public void toMailboxAclShouldReturnEmptyAclWhenEmpty() {
+        Rights rights = Rights.EMPTY;
+
+        assertThat(rights.toMailboxAcl())
+            .isEqualTo(new MailboxACL());
+    }
+
+    @Test
+    public void toMailboxAclShouldReturnAclConversion() throws Exception {
+        String user1 = "user1";
+        String user2 = "user2";
+        Rights rights = Rights.builder()
+            .delegateTo(new Rights.Username(user1), Rights.Right.Administer, Rights.Right.DeleteMessages)
+            .delegateTo(new Rights.Username(user2), Rights.Right.Expunge, Rights.Right.Lookup)
+            .build();
+
+        assertThat(rights.toMailboxAcl())
+            .isEqualTo(new MailboxACL(
+                new Entry(user1, MailboxACL.Right.Administer, MailboxACL.Right.DeleteMessages),
+                new Entry(user2, MailboxACL.Right.PerformExpunge, MailboxACL.Right.Lookup)));
+    }
+
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[3/6] james-project git commit: JAMES-2162 Allow to update "sharedWith" JMAP mailbox property

Posted by bt...@apache.org.
JAMES-2162 Allow to update "sharedWith" JMAP mailbox property


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/69d783e6
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/69d783e6
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/69d783e6

Branch: refs/heads/master
Commit: 69d783e6ff1fcf0c8cb863332be0cd215e0c53d4
Parents: 0ac2d41
Author: benwa <bt...@linagora.com>
Authored: Wed Sep 27 17:44:46 2017 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Oct 3 07:52:12 2017 +0700

----------------------------------------------------------------------
 .../integration/SetMailboxesMethodTest.java     | 478 ++++++++++++++++---
 .../james/jmap/json/ObjectMapperFactory.java    |  22 +
 .../methods/SetMailboxesUpdateProcessor.java    |   6 +-
 .../model/mailbox/MailboxUpdateRequest.java     |  32 +-
 .../apache/james/jmap/model/mailbox/Rights.java |   1 -
 .../jmap/json/ObjectMapperFactoryTest.java      |  57 +++
 6 files changed, 519 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/69d783e6/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 87d0c94..cc48707 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
@@ -32,6 +32,7 @@ import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasKey;
 import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.nullValue;
@@ -43,20 +44,23 @@ import org.apache.james.GuiceJamesServer;
 import org.apache.james.jmap.DefaultMailboxes;
 import org.apache.james.jmap.HttpJmapAuthentication;
 import org.apache.james.jmap.api.access.AccessToken;
+import org.apache.james.mailbox.model.MailboxACL.Right;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.apache.james.mailbox.store.probe.MailboxProbe;
 import org.apache.james.modules.MailboxProbeImpl;
 import org.apache.james.probe.DataProbe;
-import org.apache.james.utils.JmapGuiceProbe;
 import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.JmapGuiceProbe;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.jayway.restassured.RestAssured;
 import com.jayway.restassured.builder.RequestSpecBuilder;
 import com.jayway.restassured.http.ContentType;
@@ -65,7 +69,13 @@ public abstract class SetMailboxesMethodTest {
 
     private static final String NAME = "[0][0]";
     private static final String ARGUMENTS = "[0][1]";
+    private static final String FIRST_MAILBOX = ARGUMENTS + ".list[0]";
     private static final String USERS_DOMAIN = "domain.tld";
+
+    private static final String ADMINISTER = String.valueOf(Right.Administer.asCharacter());
+    private static final String WRITE = String.valueOf(Right.Write.asCharacter());
+    private static final String DELETE_MESSAGES = String.valueOf(Right.DeleteMessages.asCharacter());
+
     private static int MAILBOX_NAME_LENGTH_64K = 65536;
 
     protected abstract GuiceJamesServer createJmapServer();
@@ -135,11 +145,11 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
-            .when()
+        .when()
             .post("/jmap")
-            .then()
+        .then()
             .statusCode(200)
             .body(NAME, equalTo("mailboxesSet"))
             .body(ARGUMENTS + ".notCreated", aMapWithSize(1))
@@ -169,7 +179,7 @@ public abstract class SetMailboxesMethodTest {
                 "  ]" +
                 "]";
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .when()
             .post("/jmap")
@@ -201,7 +211,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .when()
             .post("/jmap")
@@ -231,7 +241,7 @@ public abstract class SetMailboxesMethodTest {
                 "  ]" +
                 "]";
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .when()
             .post("/jmap")
@@ -260,7 +270,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -292,7 +302,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap");
@@ -322,7 +332,7 @@ public abstract class SetMailboxesMethodTest {
                 "  ]" +
                 "]";
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
@@ -346,7 +356,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -373,7 +383,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
@@ -395,7 +405,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -422,7 +432,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -444,7 +454,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -472,7 +482,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -500,7 +510,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -527,7 +537,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -554,13 +564,13 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .then()
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -574,7 +584,7 @@ public abstract class SetMailboxesMethodTest {
     public void setMailboxesShouldReturnCreatedMailboxWhenChildOfInboxMailbox() {
         String inboxId =
             with()
-                .header("Authorization", this.accessToken.serialize())
+                .header("Authorization", accessToken.serialize())
                 .body("[[\"getMailboxes\", {}, \"#0\"]]")
             .when()
                 .post("/jmap")
@@ -599,7 +609,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -632,13 +642,13 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -666,7 +676,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -700,7 +710,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -737,7 +747,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -771,7 +781,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -806,7 +816,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -841,7 +851,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
             given()
-                .header("Authorization", this.accessToken.serialize())
+                .header("Authorization", accessToken.serialize())
                 .body(requestBody)
             .when()
                 .post("/jmap")
@@ -871,7 +881,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -896,7 +906,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -904,7 +914,7 @@ public abstract class SetMailboxesMethodTest {
             .statusCode(200);
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -928,7 +938,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -958,7 +968,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -986,7 +996,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1018,7 +1028,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1047,7 +1057,7 @@ public abstract class SetMailboxesMethodTest {
                 "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1082,7 +1092,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1113,7 +1123,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1143,7 +1153,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1154,6 +1164,344 @@ public abstract class SetMailboxesMethodTest {
     }
 
     @Test
+    public void updateShouldReturnOkWhenClearingSharedWith() {
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+        String requestBody =
+            "[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxesSet"))
+            .body(ARGUMENTS + ".updated", contains(mailboxId));
+    }
+
+    @Test
+    public void updateShouldReturnOkWhenSettingNewACL() {
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"user\": [\"a\", \"w\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxesSet"))
+            .body(ARGUMENTS + ".updated", contains(mailboxId));
+    }
+
+    @Test
+    public void updateShouldRejectInvalidRights() {
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"user\": [\"aw\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+        .when()
+            .post("/jmap").prettyPeek()
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("error"))
+            .body(ARGUMENTS + ".type", is("invalidArguments"))
+            .body(ARGUMENTS + ".description", containsString("Rights should be represented as single value characters"));
+    }
+
+    @Test
+    public void updateShouldRejectUnhandledRight() {
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"user\": [\"p\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("error"))
+            .body(ARGUMENTS + ".type", is("invalidArguments"))
+            .body(ARGUMENTS + ".description", containsString("No matching right for 'p'"));
+    }
+
+    @Test
+    public void updateShouldRejectNonExistingRights() {
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"user\": [\"z\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("error"))
+            .body(ARGUMENTS + ".type", is("invalidArguments"))
+            .body(ARGUMENTS + ".description", containsString("No matching right for 'z'"));
+    }
+
+    @Test
+    public void updateShouldApplyWhenSettingNewACL() {
+        String myBox = "myBox";
+        String user = "user";
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, myBox);
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"" + user + "\": [\"a\", \"w\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(myBox))
+            .body(FIRST_MAILBOX + ".sharedWith", hasEntry(user, ImmutableList.of(ADMINISTER, WRITE)));
+    }
+
+    @Test
+    public void updateShouldModifyStoredDataWhenUpdatingACL() {
+        String myBox = "myBox";
+        String user = "user";
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, myBox);
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"" + user + "\": [\"a\", \"w\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"" + user + "\": [\"a\", \"t\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(myBox))
+            .body(FIRST_MAILBOX + ".sharedWith", hasEntry(user, ImmutableList.of(ADMINISTER, DELETE_MESSAGES)));
+    }
+
+    @Test
+    public void updateShouldClearStoredDataWhenDeleteACL() {
+        String myBox = "myBox";
+        String user = "user";
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, myBox);
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"" + user + "\": [\"a\", \"w\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(myBox))
+            .body(FIRST_MAILBOX + ".sharedWith", is(ImmutableMap.of()));
+    }
+
+    @Test
+    public void updateShouldModifyStoredDataWhenSwitchingACLUser() {
+        String myBox = "myBox";
+        String user1 = "user1";
+        String user2 = "user2";
+        jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, myBox);
+        Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
+        String mailboxId = mailbox.getMailboxId().serialize();
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"" + user1 + "\": [\"a\", \"w\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[" +
+                "  [ \"setMailboxes\"," +
+                "    {" +
+                "      \"update\": {" +
+                "        \"" + mailboxId + "\" : {" +
+                "          \"sharedWith\" : {\"" + user2 + "\": [\"a\", \"w\"]}" +
+                "        }" +
+                "      }" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]")
+            .post("/jmap");
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("mailboxes"))
+            .body(FIRST_MAILBOX + ".name", equalTo(myBox))
+            .body(FIRST_MAILBOX + ".sharedWith", hasEntry(user2, ImmutableList.of(ADMINISTER, WRITE)));
+    }
+
+    @Test
     public void setMailboxesShouldUpdateMailboxNameWhenNameUpdateAskedOnExistingMailbox() {
         jmapServer.getProbe(MailboxProbeImpl.class).createMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
         Mailbox mailbox = jmapServer.getProbe(MailboxProbeImpl.class).getMailbox(MailboxConstants.USER_NAMESPACE, username, "myBox");
@@ -1173,12 +1521,12 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -1214,7 +1562,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1249,12 +1597,12 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -1292,7 +1640,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1329,12 +1677,12 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -1368,7 +1716,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1401,12 +1749,12 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -1445,7 +1793,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1483,12 +1831,12 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -1520,7 +1868,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1553,7 +1901,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1595,7 +1943,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1630,7 +1978,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1665,7 +2013,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1698,12 +2046,12 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         with()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
             .post("/jmap");
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body("[[\"getMailboxes\", {\"ids\": [\"" + mailboxId + "\"]}, \"#0\"]]")
         .when()
             .post("/jmap")
@@ -1735,7 +2083,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1769,7 +2117,7 @@ public abstract class SetMailboxesMethodTest {
                     "]";
 
         given()
-            .header("Authorization", this.accessToken.serialize())
+            .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .when()
             .post("/jmap")
@@ -1810,7 +2158,7 @@ public abstract class SetMailboxesMethodTest {
                   "]";
 
       given()
-          .header("Authorization", this.accessToken.serialize())
+          .header("Authorization", accessToken.serialize())
           .body(requestBody)
       .when()
           .post("/jmap")

http://git-wip-us.apache.org/repos/asf/james-project/blob/69d783e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/ObjectMapperFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/ObjectMapperFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/ObjectMapperFactory.java
index 71a1b2b..c48527e 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/ObjectMapperFactory.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/json/ObjectMapperFactory.java
@@ -24,6 +24,7 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import org.apache.james.jmap.model.mailbox.Rights;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MessageId;
 
@@ -43,6 +44,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.datatype.guava.GuavaModule;
 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
 
 public class ObjectMapperFactory {
@@ -66,6 +68,8 @@ public class ObjectMapperFactory {
         mailboxIdModule.addSerializer(MessageId.class, new MessageIdSerializer());
         mailboxIdModule.addKeyDeserializer(MessageId.class, new MessageIdKeyDeserializer(messageIdFactory));
         mailboxIdModule.addKeySerializer(MessageId.class, new MessageIdKeySerializer());
+        mailboxIdModule.addKeyDeserializer(Rights.Username.class, new UsernameKeyDeserializer());
+        mailboxIdModule.addDeserializer(Rights.Right.class, new RightDeserializer());
         jacksonModules = JACKSON_BASE_MODULES.add(mailboxIdModule).build();
     }
 
@@ -102,6 +106,24 @@ public class ObjectMapperFactory {
         }
     }
 
+    public static class UsernameKeyDeserializer extends KeyDeserializer {
+        @Override
+        public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+            return new Rights.Username(key);
+        }
+    }
+
+    public static class RightDeserializer extends JsonDeserializer<Rights.Right> {
+
+        @Override
+        public Rights.Right deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+            String nodeValue = p.getValueAsString();
+            Preconditions.checkArgument(nodeValue.length() == 1, "Rights should be represented as single value characters");
+
+            return Rights.Right.forChar(nodeValue.charAt(0));
+        }
+    }
+
     public static class MailboxIdKeyDeserializer extends KeyDeserializer {
         private MailboxId.Factory factory;
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/69d783e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesUpdateProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesUpdateProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesUpdateProcessor.java
index 459753c..0e2ad14 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesUpdateProcessor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMailboxesUpdateProcessor.java
@@ -83,8 +83,7 @@ public class SetMailboxesUpdateProcessor implements SetMailboxesProcessor {
 
         SetMailboxesResponse.Builder responseBuilder = SetMailboxesResponse.builder();
         request.getUpdate()
-            .entrySet()
-            .forEach(update -> handleUpdate(update.getKey(), update.getValue(), responseBuilder, mailboxSession));
+            .forEach((key, value) -> handleUpdate(key, value, responseBuilder, mailboxSession));
         timeMetric.stopAndPublish();
         return responseBuilder.build();
     }
@@ -210,6 +209,9 @@ public class SetMailboxesUpdateProcessor implements SetMailboxesProcessor {
     private void updateMailbox(Mailbox mailbox, MailboxUpdateRequest updateRequest, MailboxSession mailboxSession) throws MailboxException {
         MailboxPath originMailboxPath = mailboxManager.getMailbox(mailbox.getId(), mailboxSession).getMailboxPath();
         MailboxPath destinationMailboxPath = computeNewMailboxPath(mailbox, originMailboxPath, updateRequest, mailboxSession);
+        if (updateRequest.getSharedWith().isPresent()) {
+            mailboxManager.setRights(originMailboxPath, updateRequest.getSharedWith().get().toMailboxAcl(), mailboxSession);
+        }
         if (!originMailboxPath.equals(destinationMailboxPath)) {
             mailboxManager.renameMailbox(originMailboxPath, destinationMailboxPath, mailboxSession);
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/69d783e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/MailboxUpdateRequest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/MailboxUpdateRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/MailboxUpdateRequest.java
index 65af555..81cfc4a 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/MailboxUpdateRequest.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/MailboxUpdateRequest.java
@@ -46,12 +46,14 @@ public class MailboxUpdateRequest {
         private Optional<MailboxId> parentId;
         private Optional<Role> role;
         private Optional<SortOrder> sortOrder;
+        private Optional<Rights> sharedWith;
 
         private Builder() {
             name = Optional.empty();
             role = Optional.empty();
             sortOrder = Optional.empty();
             parentId = Optional.empty();
+            sharedWith = Optional.empty();
         }
 
         public Builder name(String name) throws MailboxException {
@@ -80,9 +82,14 @@ public class MailboxUpdateRequest {
             throw new NotImplementedException();
         }
 
+        public Builder sharedWith(Rights rights) {
+            Preconditions.checkNotNull(rights);
+            this.sharedWith = Optional.of(rights);
+            return this;
+        }
 
         public MailboxUpdateRequest build() {
-            return new MailboxUpdateRequest(name, parentId, role, sortOrder);
+            return new MailboxUpdateRequest(name, parentId, role, sortOrder, sharedWith);
         }
     }
 
@@ -90,14 +97,16 @@ public class MailboxUpdateRequest {
     private final Optional<MailboxId> parentId;
     private final Optional<Role> role;
     private final Optional<SortOrder> sortOrder;
+    private final Optional<Rights> sharedWith;
 
     @VisibleForTesting
-    MailboxUpdateRequest(Optional<String> name, Optional<MailboxId> parentId, Optional<Role> role, Optional<SortOrder> sortOrder) {
+    MailboxUpdateRequest(Optional<String> name, Optional<MailboxId> parentId, Optional<Role> role, Optional<SortOrder> sortOrder, Optional<Rights> sharedWith) {
 
         this.name = name;
         this.parentId = parentId;
         this.role = role;
         this.sortOrder = sortOrder;
+        this.sharedWith = sharedWith;
     }
 
     public Optional<String> getName() {
@@ -116,6 +125,9 @@ public class MailboxUpdateRequest {
         return sortOrder;
     }
 
+    public Optional<Rights> getSharedWith() {
+        return sharedWith;
+    }
 
     @Override
     public final boolean equals(Object obj) {
@@ -124,23 +136,25 @@ public class MailboxUpdateRequest {
             return Objects.equals(this.name, other.name)
                 && Objects.equals(this.parentId, other.parentId)
                 && Objects.equals(this.role, other.role)
-                && Objects.equals(this.sortOrder, other.sortOrder);
+                && Objects.equals(this.sortOrder, other.sortOrder)
+                && Objects.equals(this.sharedWith, other.sharedWith);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(name, parentId, role, sortOrder);
+        return Objects.hash(name, parentId, role, sortOrder, sharedWith);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
-                .add("name", name)
-                .add("parentId", parentId)
-                .add("role", role)
-                .add("sortOrder", sortOrder)
-                .toString();
+            .add("name", name)
+            .add("parentId", parentId)
+            .add("role", role)
+            .add("sortOrder", sortOrder)
+            .add("sharedWith", sharedWith)
+            .toString();
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/69d783e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
index d44e4d3..bd27bd0 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/mailbox/Rights.java
@@ -89,7 +89,6 @@ public class Rights {
     public static class Username {
         private final String value;
 
-     //   @JsonCreator
         public Username(String value) {
             this.value = value;
         }

http://git-wip-us.apache.org/repos/asf/james-project/blob/69d783e6/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ObjectMapperFactoryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ObjectMapperFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ObjectMapperFactoryTest.java
index 0985081..9e892fc 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ObjectMapperFactoryTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ObjectMapperFactoryTest.java
@@ -19,9 +19,11 @@
 package org.apache.james.jmap.json;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.util.Map;
 
+import org.apache.james.jmap.model.mailbox.Rights;
 import org.apache.james.mailbox.inmemory.InMemoryId;
 import org.apache.james.mailbox.inmemory.InMemoryMessageId;
 import org.apache.james.mailbox.model.MailboxId;
@@ -72,6 +74,61 @@ public class ObjectMapperFactoryTest {
         assertThat(actual).isEqualTo(expectedJson);
     }
 
+    @Test
+    public void readValueShouldParseRightObject() throws Exception {
+        Rights.Right actual = testee.forParsing()
+            .readValue("\"a\"", Rights.Right.class);
+
+        assertThat(actual)
+            .isEqualTo(Rights.Right.Administer);
+    }
+
+    @Test
+    public void readValueShouldParseUsernameObject() throws Exception {
+        String username = "username";
+        Rights.Username actual = testee.forParsing()
+            .readValue("\"" + username + "\"", Rights.Username.class);
+
+        assertThat(actual)
+            .isEqualTo(new Rights.Username(username));
+    }
+
+    @Test
+    public void readValueShouldParseRightsObject() throws Exception {
+        String username = "username";
+        Rights actual = testee.forParsing()
+            .readValue("{\"" + username + "\" : [\"a\", \"e\"]}", Rights.class);
+
+        assertThat(actual)
+            .isEqualTo(Rights.builder()
+                .delegateTo(new Rights.Username(username), Rights.Right.Administer, Rights.Right.Expunge)
+                .build());
+    }
+
+    @Test
+    public void readValueShouldRejectMultiCharacterRights() throws Exception {
+        assertThatThrownBy(() ->
+            testee.forParsing()
+                .readValue("\"ae\"", Rights.Right.class))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void readValueShouldRejectUnsupportedRights() throws Exception {
+        assertThatThrownBy(() ->
+            testee.forParsing()
+                .readValue("\"p\"", Rights.Right.class))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void readValueShouldRejectUnExistingRights() throws Exception {
+        assertThatThrownBy(() ->
+            testee.forParsing()
+                .readValue("\"z\"", Rights.Right.class))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
     public static class MailboxIdTestContainer {
         public MailboxId mailboxId;
 


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org