You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/02/25 09:06:48 UTC

[james-project] branch master updated (c099168 -> a2a77ad)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from c099168  JAMES-3225 Tag RabbitMQEventDeadLettersIntegrationTest multipleFailedEventsShouldBeCorrectlyProcessedByListenerAfterSuccessfulGroupRedelivery as unstable
     new a784640  JAMES-3491 WebSocket PUSH should support pushState
     new 69d7f22  JAMES-3504 MDC context for POP3 commands
     new f590da4  JAMES-3504 POP3 should log authentication failures
     new d91703c  JAMES-3504 Log trace upon received POP3 commands
     new 28badc0  JAMES-3504 Correlate POP3 mailbox and mailbox/api mailbox
     new 4d01371  JAMES-3504 Move POP3ProtocolHandlerChain to tests
     new 33e9b70  JAMES-3504 Register metrics for POP3 commands
     new bf2535a  JAMES-3400 Add remove and list domain aliases
     new bbb46da  JAMES-3477 WebAdmin CLI commands to manage global quotas
     new 83328cf  JAMES-3400 Move HTTP status codes to a constant
     new c6e1d4d  JAMES-3353 Document urn:apache:james:params:jmap:mail:shares JMAP extension
     new a2a77ad  JAMES-3353 Document urn:apache:james:params:jmap:mail:quota JMAP extension

The 12 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 protocols/pop3/pom.xml                             |  13 ++
 .../pop3/core/AbstractApopCmdHandler.java          |   8 +
 .../pop3/core/AbstractPassCmdHandler.java          |  22 ++-
 .../james/protocols/pop3/core/CapaCmdHandler.java  |  19 ++
 .../james/protocols/pop3/core/DeleCmdHandler.java  |  20 ++
 .../james/protocols/pop3/core/ListCmdHandler.java  |  24 ++-
 .../{NoopCmdHandler.java => MDCConstants.java}     | 111 ++++++-----
 .../james/protocols/pop3/core/NoopCmdHandler.java  |  25 ++-
 .../james/protocols/pop3/core/QuitCmdHandler.java  |  21 +++
 .../james/protocols/pop3/core/RetrCmdHandler.java  |  26 ++-
 .../james/protocols/pop3/core/RsetCmdHandler.java  |  22 ++-
 .../james/protocols/pop3/core/StatCmdHandler.java  |  24 +++
 .../james/protocols/pop3/core/StlsCmdHandler.java  |  25 ++-
 .../james/protocols/pop3/core/TopCmdHandler.java   |  31 ++-
 .../james/protocols/pop3/core/UidlCmdHandler.java  |  29 ++-
 .../protocols/pop3/core/UnknownCmdHandler.java     |  11 ++
 .../james/protocols/pop3/core/UserCmdHandler.java  |  26 ++-
 .../protocols/pop3/AbstractPOP3ServerTest.java     |  30 +--
 .../pop3/AbstractStartTlsPOP3ServerTest.java       |   3 +-
 .../protocols/pop3/POP3ProtocolHandlerChain.java   |  27 +--
 .../protocols/pop3/core/RetrCmdHandlerTest.java    |   5 +-
 .../protocols/pop3/utils/TestPassCmdHandler.java   |   7 +-
 .../jmap/rfc8621/contract/WebSocketContract.scala  |  95 +++++++---
 .../jmap-rfc-8621/doc/specs/spec/mail/intro.mdown  |  25 +++
 .../jmap-rfc-8621/doc/specs/spec/mail/rights.mdown | 136 +++++++++++++
 .../doc/specs/spec/quotas/quotas.mdown             | 103 ++++++++++
 .../org/apache/james/jmap/change/StateChange.scala |   8 +-
 .../james/jmap/core/WebSocketTransport.scala       |  25 ++-
 .../james/jmap/json/ResponseSerializer.scala       |  14 +-
 .../apache/james/jmap/routes/WebSocketRoutes.scala |  35 +++-
 .../jmap/change/StateChangeListenerTest.scala      |   9 +-
 .../james/pop3server/core/PassCmdHandler.java      |  26 ++-
 .../apache/james/pop3server/POP3ServerTest.java    |   3 +
 server/protocols/webadmin-cli/README.md            |  18 ++
 server/protocols/webadmin-cli/pom.xml              |  14 +-
 .../java/org/apache/james/cli/WebAdminCli.java     |   7 +
 ...leteCommand.java => AddDomainAliasCommand.java} |  25 ++-
 .../org/apache/james/cli/domain/DomainCommand.java |   5 +-
 .../james/cli/domain/DomainCreateCommand.java      |   7 +-
 .../james/cli/domain/DomainDeleteCommand.java      |   7 +-
 .../james/cli/domain/DomainExistCommand.java       |  11 +-
 ...istCommand.java => ListDomainAliasCommand.java} |  18 +-
 ...eCommand.java => RemoveDomainAliasCommand.java} |  22 ++-
 .../DeleteGlobalQuotaCountCommand.java}            |  29 ++-
 .../DeleteGlobalQuotaSizeCommand.java}             |  29 ++-
 .../GetGlobalQuotaCountCommand.java}               |  27 +--
 .../GetGlobalQuotaSizeCommand.java}                |  26 +--
 .../quota/GlobalQuotaCommand.java}                 |  32 ++--
 .../GlobalQuotaCountCommand.java}                  |  30 ++-
 .../GlobalQuotaSizeCommand.java}                   |  30 ++-
 .../UserCommand.java => quota/QuotaCommand.java}   |  26 ++-
 .../SetGlobalQuotaCountCommand.java}               |  29 ++-
 .../SetGlobalQuotaSizeCommand.java}                |  30 +--
 .../org/apache/james/httpclient/Constants.java     |  46 +++--
 .../org/apache/james/httpclient/DomainClient.java  |  39 ++++
 .../{DomainClient.java => QuotaClient.java}        |  25 +--
 .../org/apache/james/cli/DomainManageTest.java     | 206 +++++++++++++-------
 .../java/org/apache/james/cli/QuotaManageTest.java | 210 +++++++++++++++++++++
 58 files changed, 1523 insertions(+), 433 deletions(-)
 copy protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/{NoopCmdHandler.java => MDCConstants.java} (56%)
 rename protocols/pop3/src/{main => test}/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java (81%)
 create mode 100644 server/protocols/jmap-rfc-8621/doc/specs/spec/mail/rights.mdown
 create mode 100644 server/protocols/jmap-rfc-8621/doc/specs/spec/quotas/quotas.mdown
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/{DomainDeleteCommand.java => AddDomainAliasCommand.java} (71%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/{DomainListCommand.java => ListDomainAliasCommand.java} (81%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/{DomainCreateCommand.java => RemoveDomainAliasCommand.java} (77%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserDeleteCommand.java => quota/DeleteGlobalQuotaCountCommand.java} (72%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserDeleteCommand.java => quota/DeleteGlobalQuotaSizeCommand.java} (72%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainListCommand.java => quota/GetGlobalQuotaCountCommand.java} (67%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserListCommand.java => quota/GetGlobalQuotaSizeCommand.java} (65%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/{httpclient/JwtToken.java => cli/quota/GlobalQuotaCommand.java} (68%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainListCommand.java => quota/GlobalQuotaCountCommand.java} (67%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainListCommand.java => quota/GlobalQuotaSizeCommand.java} (67%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserCommand.java => quota/QuotaCommand.java} (73%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainDeleteCommand.java => quota/SetGlobalQuotaCountCommand.java} (71%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainCreateCommand.java => quota/SetGlobalQuotaSizeCommand.java} (70%)
 copy backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/ReceiverProvider.java => server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/Constants.java (85%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/{DomainClient.java => QuotaClient.java} (73%)
 create mode 100644 server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/QuotaManageTest.java


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


[james-project] 03/12: JAMES-3504 POP3 should log authentication failures

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit f590da44aa7ffaec4a087ccd305023777a0871e2
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Feb 23 11:57:52 2021 +0700

    JAMES-3504 POP3 should log authentication failures
    
     - Valuable diagnosis information
     - This also enables some `failtoban` like approach to mitigate brute
     force attacks
---
 .../src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
index eb3f944..6456d15 100644
--- a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
+++ b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
@@ -95,6 +95,9 @@ public class PassCmdHandler extends AbstractPassCmdHandler  {
             MessageManager mailbox = manager.getMailbox(MailboxPath.inbox(mSession), mSession);
             return new MailboxAdapter(manager, mailbox, mSession);
         } catch (BadCredentialsException e) {
+            LOGGER.info("Bad credential supplied for {} with remote address {}",
+                session.getUsername().asString(),
+                session.getRemoteAddress().getAddress());
             return null;
         } catch (MailboxException e) {
             throw new IOException("Unable to access mailbox for user " + session.getUsername().asString(), e);


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


[james-project] 05/12: JAMES-3504 Correlate POP3 mailbox and mailbox/api mailbox

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 28badc08695820a5fb7e3e736dbd71d1f5bec2e9
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Feb 23 12:08:21 2021 +0700

    JAMES-3504 Correlate POP3 mailbox and mailbox/api mailbox
---
 .../main/java/org/apache/james/pop3server/core/PassCmdHandler.java | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
index 6456d15..dca994f 100644
--- a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
+++ b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
@@ -93,7 +93,12 @@ public class PassCmdHandler extends AbstractPassCmdHandler  {
                 LOGGER.info("Provisioning INBOX. {} created.", mailboxId);
             }
             MessageManager mailbox = manager.getMailbox(MailboxPath.inbox(mSession), mSession);
-            return new MailboxAdapter(manager, mailbox, mSession);
+            MailboxAdapter mailboxAdapter = new MailboxAdapter(manager, mailbox, mSession);
+            LOGGER.info("Opening mailbox {} {} with mailbox session {}",
+                mailbox.getId().serialize(),
+                mailbox.getMailboxPath().asString(),
+                mSession.getSessionId().getValue());
+            return mailboxAdapter;
         } catch (BadCredentialsException e) {
             LOGGER.info("Bad credential supplied for {} with remote address {}",
                 session.getUsername().asString(),


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


[james-project] 07/12: JAMES-3504 Register metrics for POP3 commands

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 33e9b7010b49d7ce64f6d56d590149d96abe4e09
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Feb 23 15:51:07 2021 +0700

    JAMES-3504 Register metrics for POP3 commands
---
 protocols/pop3/pom.xml                             |  9 +++++++
 .../pop3/core/AbstractApopCmdHandler.java          |  8 ++++++
 .../pop3/core/AbstractPassCmdHandler.java          | 13 ++++++++--
 .../james/protocols/pop3/core/CapaCmdHandler.java  | 19 +++++++++++---
 .../james/protocols/pop3/core/DeleCmdHandler.java  | 21 +++++++++++----
 .../james/protocols/pop3/core/ListCmdHandler.java  | 21 +++++++++++----
 .../james/protocols/pop3/core/NoopCmdHandler.java  | 21 +++++++++++----
 .../james/protocols/pop3/core/QuitCmdHandler.java  | 21 +++++++++++----
 .../james/protocols/pop3/core/RetrCmdHandler.java  | 23 ++++++++++++-----
 .../james/protocols/pop3/core/RsetCmdHandler.java  | 22 +++++++++++-----
 .../james/protocols/pop3/core/StatCmdHandler.java  | 21 +++++++++++----
 .../james/protocols/pop3/core/StlsCmdHandler.java  | 21 +++++++++++----
 .../james/protocols/pop3/core/TopCmdHandler.java   | 24 ++++++++++++-----
 .../james/protocols/pop3/core/UidlCmdHandler.java  | 23 ++++++++++++-----
 .../james/protocols/pop3/core/UserCmdHandler.java  | 23 ++++++++++++-----
 .../protocols/pop3/AbstractPOP3ServerTest.java     | 30 +++++++++++++---------
 .../pop3/AbstractStartTlsPOP3ServerTest.java       |  3 ++-
 .../protocols/pop3/POP3ProtocolHandlerChain.java   | 27 ++++++++++---------
 .../protocols/pop3/core/RetrCmdHandlerTest.java    |  5 ++--
 .../protocols/pop3/utils/TestPassCmdHandler.java   |  7 ++++-
 .../james/pop3server/core/PassCmdHandler.java      |  4 ++-
 .../apache/james/pop3server/POP3ServerTest.java    |  3 +++
 22 files changed, 274 insertions(+), 95 deletions(-)

diff --git a/protocols/pop3/pom.xml b/protocols/pop3/pom.xml
index f77786e..784c795 100644
--- a/protocols/pop3/pom.xml
+++ b/protocols/pop3/pom.xml
@@ -39,6 +39,15 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>metrics-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>metrics-tests</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractApopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractApopCmdHandler.java
index 521c1df..06af37e 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractApopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractApopCmdHandler.java
@@ -21,7 +21,10 @@ package org.apache.james.protocols.pop3.core;
 
 import java.util.Collection;
 
+import javax.inject.Inject;
+
 import org.apache.james.core.Username;
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -39,6 +42,11 @@ public abstract class AbstractApopCmdHandler extends AbstractPassCmdHandler {
     private static final Collection<String> COMMANDS = ImmutableSet.of("APOP");
     private static final String MISSING_APOP_TIMESTAMP = "";
 
+    @Inject
+    public AbstractApopCmdHandler(MetricFactory metricFactory) {
+        super(metricFactory);
+    }
+
     @Override
     public Response onCommand(POP3Session session, Request request) {
         if (!session.getAttachment(POP3Session.APOP_TIMESTAMP, State.Connection).isPresent()) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
index 8b1a48e..ba70287 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Optional;
 
 import org.apache.james.core.Username;
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.pop3.POP3Response;
@@ -43,17 +44,25 @@ public abstract class AbstractPassCmdHandler extends RsetCmdHandler {
     private static final Response UNEXPECTED_ERROR = new POP3Response(POP3Response.ERR_RESPONSE, "Unexpected error accessing mailbox").immutable();
     protected static final Response AUTH_FAILED = new POP3Response(POP3Response.ERR_RESPONSE, "Authentication failed.").immutable();
 
+    private final MetricFactory metricFactory;
+
+    public AbstractPassCmdHandler(MetricFactory metricFactory) {
+        super(metricFactory);
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a PASS command. Reads in and
      * validates the password.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-pass", () ->
+            MDCBuilder.withMdc(
             MDCBuilder.create()
                 .addContext(MDCBuilder.ACTION, "AUTH")
                 .addContext(MDCConstants.withSession(session)),
-            () -> doAuth(session, request));
+            () -> doAuth(session, request)));
     }
 
     private Response doAuth(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java
index b902f6e..8c93abc 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java
@@ -24,6 +24,9 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
@@ -43,12 +46,20 @@ public class CapaCmdHandler implements CommandHandler<POP3Session>, ExtensibleHa
     private static final Collection<String> COMMANDS = ImmutableSet.of("CAPA");
     private static final Set<String> CAPS = ImmutableSet.of("PIPELINING");
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public CapaCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "CAPA")
-                .addContext(MDCConstants.withSession(session)),
-            () -> capa(session));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-capa", () ->
+            MDCBuilder.withMdc(MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "CAPA")
+                    .addContext(MDCConstants.withSession(session)),
+                () -> capa(session)));
     }
 
     private Response capa(POP3Session session) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java
index d32624a..cdc0771 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java
@@ -23,6 +23,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -43,6 +46,13 @@ public class DeleCmdHandler implements CommandHandler<POP3Session> {
     private static final Response SYNTAX_ERROR = new POP3Response(POP3Response.ERR_RESPONSE, "Usage: DELE [mail number]").immutable();
     private static final Response DELETED = new POP3Response(POP3Response.OK_RESPONSE, "Message deleted").immutable();
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public DeleCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a DELE command. This command
      * deletes a particular mail message from the mailbox.
@@ -50,11 +60,12 @@ public class DeleCmdHandler implements CommandHandler<POP3Session> {
     @Override
     @SuppressWarnings("unchecked")
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "DELE")
-                .addContext(MDCConstants.withSession(session))
-                .addContext(MDCConstants.forRequest(request)),
-            () -> delete(session, request));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-dele", () ->
+            MDCBuilder.withMdc(MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "DELE")
+                    .addContext(MDCConstants.withSession(session))
+                    .addContext(MDCConstants.forRequest(request)),
+                () -> delete(session, request)));
     }
 
     private Response delete(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java
index 4c16b54..60649de 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java
@@ -23,6 +23,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -42,6 +45,13 @@ public class ListCmdHandler implements CommandHandler<POP3Session> {
 
     private static final Collection<String> COMMANDS = ImmutableSet.of("LIST");
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public ListCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a LIST command. Returns the number
      * of messages in the mailbox and its aggregate size, or optionally, the
@@ -56,11 +66,12 @@ public class ListCmdHandler implements CommandHandler<POP3Session> {
     @Override
     @SuppressWarnings("unchecked")
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "LIST")
-                .addContext(MDCConstants.withSession(session))
-                .addContext(MDCConstants.forRequest(request)),
-            () -> list(session, request));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-list", () ->
+            MDCBuilder.withMdc(MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "LIST")
+                    .addContext(MDCConstants.withSession(session))
+                    .addContext(MDCConstants.forRequest(request)),
+                () -> list(session, request)));
     }
 
     private Response list(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
index 0796771..4bdc7b5 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
@@ -21,6 +21,9 @@ package org.apache.james.protocols.pop3.core;
 
 import java.util.Collection;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
@@ -39,17 +42,25 @@ public class NoopCmdHandler implements CommandHandler<POP3Session> {
     private static final Logger LOGGER = LoggerFactory.getLogger(NoopCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("NOOP");
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public NoopCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a NOOP command. Like all good
      * NOOPs, does nothing much.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "NOOP")
-                .addContext(MDCConstants.withSession(session)),
-            () -> noop(session));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-noop", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "NOOP")
+                    .addContext(MDCConstants.withSession(session)),
+                () -> noop(session)));
     }
 
     private Response noop(POP3Session session) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
index 248cd4d..902ae72 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
@@ -23,6 +23,9 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -56,17 +59,25 @@ public class QuitCmdHandler implements CommandHandler<POP3Session> {
         SIGN_OFF_NOT_CLEAN = response.immutable();
     }
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public QuitCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a QUIT command. This method handles
      * cleanup of the POP3Handler state.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "QUIT")
-                .addContext(MDCConstants.withSession(session)),
-            () -> quit(session));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-quit", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "QUIT")
+                    .addContext(MDCConstants.withSession(session)),
+                () -> quit(session)));
     }
 
     private Response quit(POP3Session session) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
index 8544741..fc05faa 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
@@ -24,6 +24,9 @@ import java.io.InputStream;
 import java.util.Collection;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -50,6 +53,13 @@ public class RetrCmdHandler implements CommandHandler<POP3Session> {
     static final Response SYNTAX_ERROR = new POP3Response(POP3Response.ERR_RESPONSE, "Usage: RETR [mail number]").immutable();
     private static final Response ERROR_MESSAGE_RETRIEVE = new POP3Response(POP3Response.ERR_RESPONSE, "Error while retrieving message.").immutable();
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public RetrCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a RETR command. This command
      * retrieves a particular mail message from the mailbox.
@@ -57,12 +67,13 @@ public class RetrCmdHandler implements CommandHandler<POP3Session> {
     @Override
     @SuppressWarnings("unchecked")
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "RETR")
-                .addContext(MDCConstants.withSession(session))
-                .addContext(MDCConstants.forRequest(request)),
-            () -> retr(session, request));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-retr", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "RETR")
+                    .addContext(MDCConstants.withSession(session))
+                    .addContext(MDCConstants.forRequest(request)),
+                () -> retr(session, request)));
     }
 
     private Response retr(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
index b74e4ec..003e0d0 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
@@ -24,6 +24,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -44,18 +47,25 @@ public class RsetCmdHandler implements CommandHandler<POP3Session> {
     private static final Collection<String> COMMANDS = ImmutableSet.of("RSET");
     private static final Logger LOGGER = LoggerFactory.getLogger(RsetCmdHandler.class);
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public RsetCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a RSET command. Calls stat() to
      * reset the mailbox.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "RSET")
-                .addContext(MDCConstants.withSession(session)),
-            () -> rset(session));
-
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-rset", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "RSET")
+                    .addContext(MDCConstants.withSession(session)),
+                () -> rset(session)));
     }
 
     private Response rset(POP3Session session) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
index 09b3bdc..c2a3e83 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
@@ -23,6 +23,9 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -44,17 +47,25 @@ public class StatCmdHandler implements CommandHandler<POP3Session> {
     private static final Logger LOGGER = LoggerFactory.getLogger(StatCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("STAT");
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public StatCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a STAT command. Returns the number
      * of messages in the mailbox and its aggregate size.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "STAT")
-                .addContext(MDCConstants.withSession(session)),
-            () -> stat(session));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-stat", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "STAT")
+                    .addContext(MDCConstants.withSession(session)),
+                () -> stat(session)));
     }
 
     private Response stat(POP3Session session) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
index c242172..7e5ab10 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
@@ -23,6 +23,9 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
@@ -46,13 +49,21 @@ public class StlsCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
 
     private static final Response BEGIN_TLS = new POP3StartTlsResponse(POP3Response.OK_RESPONSE, "Begin TLS negotiation").immutable();
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public StlsCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "START_TLS")
-                .addContext(MDCConstants.withSession(session)),
-            () -> stls(session));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-stls", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "START_TLS")
+                    .addContext(MDCConstants.withSession(session)),
+                () -> stls(session)));
     }
 
     private Response stls(POP3Session session) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
index 7351b20..f7d8a52 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
@@ -27,6 +27,9 @@ import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -52,6 +55,14 @@ public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
     private static final Response SYNTAX_ERROR = new POP3Response(POP3Response.ERR_RESPONSE, "Usage: TOP [mail number] [Line number]").immutable();
     private static final Response ERROR_MESSAGE_RETR = new POP3Response(POP3Response.ERR_RESPONSE, "Error while retrieving message.").immutable();
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public TopCmdHandler(MetricFactory metricFactory) {
+        super(metricFactory);
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a TOP command. This command
      * retrieves the top N lines of a specified message in the mailbox.
@@ -62,12 +73,13 @@ public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
     @SuppressWarnings("unchecked")
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "TOP")
-                .addContext(MDCConstants.withSession(session))
-                .addContext(MDCConstants.forRequest(request)),
-            () -> top(session, request));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-top", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "TOP")
+                    .addContext(MDCConstants.withSession(session))
+                    .addContext(MDCConstants.forRequest(request)),
+                () -> top(session, request)));
     }
 
     private Response top(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
index ef3aa8f..feee247 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
@@ -25,6 +25,9 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
+import javax.inject.Inject;
+
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.ProtocolSession.State;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -47,18 +50,26 @@ public class UidlCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
     private static final Collection<String> COMMANDS = ImmutableSet.of("UIDL");
     private static final Set<String> CAPS = ImmutableSet.of("UIDL");
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public UidlCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a UIDL command. Returns a listing
      * of message ids to the client.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "UIDL")
-                .addContext(MDCConstants.withSession(session))
-                .addContext(MDCConstants.forRequest(request)),
-            () -> uidl(session, request));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-uidl", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "UIDL")
+                    .addContext(MDCConstants.withSession(session))
+                    .addContext(MDCConstants.forRequest(request)),
+                () -> uidl(session, request)));
     }
 
     private Response uidl(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
index a08780f..5980485 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
@@ -22,7 +22,10 @@ package org.apache.james.protocols.pop3.core;
 import java.util.Collection;
 import java.util.Set;
 
+import javax.inject.Inject;
+
 import org.apache.james.core.Username;
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
@@ -42,18 +45,26 @@ public class UserCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
     private static final Collection<String> COMMANDS = ImmutableSet.of("USER");
     private static final Set<String> CAPS = ImmutableSet.of("USER");
 
+    private final MetricFactory metricFactory;
+
+    @Inject
+    public UserCmdHandler(MetricFactory metricFactory) {
+        this.metricFactory = metricFactory;
+    }
+
     /**
      * Handler method called upon receipt of a USER command. Reads in the user
      * id.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
-        return MDCBuilder.withMdc(
-            MDCBuilder.create()
-                .addContext(MDCBuilder.ACTION, "USER")
-                .addContext(MDCConstants.withSession(session))
-                .addContext(MDCConstants.forRequest(request)),
-            () -> user(session, request));
+        return metricFactory.decorateSupplierWithTimerMetric("pop3-user", () ->
+            MDCBuilder.withMdc(
+                MDCBuilder.create()
+                    .addContext(MDCBuilder.ACTION, "USER")
+                    .addContext(MDCConstants.withSession(session))
+                    .addContext(MDCConstants.forRequest(request)),
+                () -> user(session, request)));
     }
 
     private Response user(POP3Session session, Request request) {
diff --git a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractPOP3ServerTest.java b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractPOP3ServerTest.java
index 4cbc395..a9b0212 100644
--- a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractPOP3ServerTest.java
+++ b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractPOP3ServerTest.java
@@ -32,6 +32,8 @@ import org.apache.commons.net.pop3.POP3Client;
 import org.apache.commons.net.pop3.POP3MessageInfo;
 import org.apache.commons.net.pop3.POP3Reply;
 import org.apache.james.core.Username;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.protocols.api.Protocol;
 import org.apache.james.protocols.api.ProtocolServer;
 import org.apache.james.protocols.api.handler.WiringException;
@@ -63,7 +65,7 @@ public abstract class AbstractPOP3ServerTest {
     public void testInvalidAuth() throws Exception {
         ProtocolServer server = null;
         try {
-            server = createServer(createProtocol(new TestPassCmdHandler()));
+            server = createServer(createProtocol(new TestPassCmdHandler(new RecordingMetricFactory())));
             server.bind();
             
             POP3Client client =  createClient();
@@ -86,7 +88,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler handler = new TestPassCmdHandler();
+            TestPassCmdHandler handler = new TestPassCmdHandler(new RecordingMetricFactory());
             
             handler.add("valid", new MockMailbox(identifier));
             server = createServer(createProtocol(handler));
@@ -117,7 +119,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler handler = new TestPassCmdHandler();
+            TestPassCmdHandler handler = new TestPassCmdHandler(new RecordingMetricFactory());
             
             handler.add("valid", new MockMailbox(identifier, MESSAGE1, MESSAGE2));
             server = createServer(createProtocol(handler));
@@ -173,7 +175,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier, MESSAGE1, MESSAGE2));
             server = createServer(createProtocol(factory));
@@ -215,7 +217,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier, MESSAGE1, MESSAGE2));
             server = createServer(createProtocol(factory));
@@ -260,7 +262,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier, MESSAGE1, MESSAGE2));
             server = createServer(createProtocol(factory));
@@ -312,7 +314,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier));
             server = createServer(createProtocol(factory));
@@ -339,7 +341,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier, MESSAGE1));
             server = createServer(createProtocol(factory));
@@ -373,7 +375,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier, MESSAGE1, MESSAGE2));
             server = createServer(createProtocol(factory));
@@ -402,7 +404,7 @@ public abstract class AbstractPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler factory = new TestPassCmdHandler();
+            TestPassCmdHandler factory = new TestPassCmdHandler(new RecordingMetricFactory());
             
             factory.add("valid", new MockMailbox(identifier, MESSAGE1, MESSAGE2));
             server = createServer(createProtocol(factory));
@@ -451,7 +453,7 @@ public abstract class AbstractPOP3ServerTest {
     public void testAPop() throws Exception {
         ProtocolServer server = null;
         try {
-            TestApopCmdHandler handler = new TestApopCmdHandler();
+            TestApopCmdHandler handler = new TestApopCmdHandler(new RecordingMetricFactory());
             server = createServer(createProtocol(handler));
             server.bind();
             
@@ -515,7 +517,11 @@ public abstract class AbstractPOP3ServerTest {
 
     private final class TestApopCmdHandler extends AbstractApopCmdHandler {
         private final Map<String, Mailbox> mailboxes = new HashMap<>();
-       
+
+        public TestApopCmdHandler(MetricFactory metricFactory) {
+            super(metricFactory);
+        }
+
         public void add(String username, Mailbox mailbox) {
             mailboxes.put(username, mailbox);
         }
diff --git a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractStartTlsPOP3ServerTest.java b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractStartTlsPOP3ServerTest.java
index 388404b..03c1aea 100644
--- a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractStartTlsPOP3ServerTest.java
+++ b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/AbstractStartTlsPOP3ServerTest.java
@@ -25,6 +25,7 @@ import java.util.Arrays;
 
 import org.apache.commons.net.pop3.POP3Reply;
 import org.apache.commons.net.pop3.POP3SClient;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.protocols.api.Encryption;
 import org.apache.james.protocols.api.Protocol;
 import org.apache.james.protocols.api.ProtocolServer;
@@ -62,7 +63,7 @@ public abstract class AbstractStartTlsPOP3ServerTest {
         ProtocolServer server = null;
         try {
             String identifier = "id";
-            TestPassCmdHandler handler = new TestPassCmdHandler();
+            TestPassCmdHandler handler = new TestPassCmdHandler(new RecordingMetricFactory());
             
             handler.add("valid", new MockMailbox(identifier));
             server = createServer(createProtocol(handler), address, Encryption.createStartTls(BogusSslContextFactory.getServerContext()));
diff --git a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java
index 8035ff7..fa0e632 100644
--- a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java
+++ b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java
@@ -23,6 +23,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.protocols.api.handler.CommandDispatcher;
 import org.apache.james.protocols.api.handler.CommandHandlerResultLogger;
 import org.apache.james.protocols.api.handler.ProtocolHandler;
@@ -50,6 +52,7 @@ import org.apache.james.protocols.pop3.core.WelcomeMessageHandler;
  *
  */
 public class POP3ProtocolHandlerChain extends ProtocolHandlerChainImpl {
+    private final MetricFactory metricFactory = new RecordingMetricFactory();
 
     /**
      * The {@link AbstractPassCmdHandler}'s to use. If at least one {@link AbstractPassCmdHandler} is given, the {@link POP3ProtocolHandlerChain}
@@ -67,20 +70,20 @@ public class POP3ProtocolHandlerChain extends ProtocolHandlerChainImpl {
         // add all pass handlers
         Collections.addAll(handlers, authHandlers);
         
-        handlers.add(new CapaCmdHandler());
-        handlers.add(new UserCmdHandler());
-        handlers.add(new ListCmdHandler());
-        handlers.add(new UidlCmdHandler());
-        handlers.add(new RsetCmdHandler());
-        handlers.add(new DeleCmdHandler());
-        handlers.add(new NoopCmdHandler());
-        handlers.add(new RetrCmdHandler());
-        handlers.add(new TopCmdHandler());
-        handlers.add(new StatCmdHandler());
-        handlers.add(new QuitCmdHandler());
+        handlers.add(new CapaCmdHandler(metricFactory));
+        handlers.add(new UserCmdHandler(metricFactory));
+        handlers.add(new ListCmdHandler(metricFactory));
+        handlers.add(new UidlCmdHandler(metricFactory));
+        handlers.add(new RsetCmdHandler(metricFactory));
+        handlers.add(new DeleCmdHandler(metricFactory));
+        handlers.add(new NoopCmdHandler(metricFactory));
+        handlers.add(new RetrCmdHandler(metricFactory));
+        handlers.add(new TopCmdHandler(metricFactory));
+        handlers.add(new StatCmdHandler(metricFactory));
+        handlers.add(new QuitCmdHandler(metricFactory));
+        handlers.add(new StlsCmdHandler(metricFactory));
         handlers.add(new WelcomeMessageHandler());
         handlers.add(new UnknownCmdHandler());
-        handlers.add(new StlsCmdHandler());
         handlers.add(new CommandDispatcher<POP3Session>());
         handlers.add(new CommandHandlerResultLogger());
        
diff --git a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/core/RetrCmdHandlerTest.java b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/core/RetrCmdHandlerTest.java
index 7ffb6d0..dd0ebac 100644
--- a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/core/RetrCmdHandlerTest.java
+++ b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/core/RetrCmdHandlerTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
 import java.util.Collections;
 import java.util.stream.Collectors;
 
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.junit.jupiter.api.Test;
@@ -44,7 +45,7 @@ class RetrCmdHandlerTest {
         String overflowedNumber = Collections.nCopies(pad, "\\xff").stream().collect(Collectors.joining());
         when(request.getArgument()).thenReturn(overflowedNumber);
 
-        assertThat(new RetrCmdHandler().onCommand(session, request))
+        assertThat(new RetrCmdHandler(new RecordingMetricFactory()).onCommand(session, request))
             .isEqualTo(RetrCmdHandler.SYNTAX_ERROR);
     }
 
@@ -57,7 +58,7 @@ class RetrCmdHandlerTest {
         String overflowedNumber = Long.toString(Long.MAX_VALUE);
         when(request.getArgument()).thenReturn(overflowedNumber);
 
-        assertThat(new RetrCmdHandler().onCommand(session, request))
+        assertThat(new RetrCmdHandler(new RecordingMetricFactory()).onCommand(session, request))
             .isEqualTo(RetrCmdHandler.SYNTAX_ERROR);
     }
 }
diff --git a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/utils/TestPassCmdHandler.java b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/utils/TestPassCmdHandler.java
index fdaa284..b0baa42 100644
--- a/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/utils/TestPassCmdHandler.java
+++ b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/utils/TestPassCmdHandler.java
@@ -22,13 +22,18 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.james.core.Username;
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.core.AbstractPassCmdHandler;
 import org.apache.james.protocols.pop3.mailbox.Mailbox;
 
 public class TestPassCmdHandler extends AbstractPassCmdHandler {
     private final Map<String, Mailbox> mailboxes = new HashMap<>();
-   
+
+    public TestPassCmdHandler(MetricFactory metricFactory) {
+        super(metricFactory);
+    }
+
     public void add(String username, Mailbox mailbox) {
         mailboxes.put(username, mailbox);
     }
diff --git a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
index dca994f..0e4fffe 100644
--- a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
+++ b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
@@ -32,6 +32,7 @@ import org.apache.james.mailbox.exception.BadCredentialsException;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.pop3server.mailbox.MailboxAdapter;
 import org.apache.james.protocols.api.Request;
 import org.apache.james.protocols.api.Response;
@@ -58,7 +59,8 @@ public class PassCmdHandler extends AbstractPassCmdHandler  {
     private final MailboxManager manager;
 
     @Inject
-    public PassCmdHandler(@Named("mailboxmanager") MailboxManager manager) {
+    public PassCmdHandler(@Named("mailboxmanager") MailboxManager manager, MetricFactory metricFactory) {
+        super(metricFactory);
         this.manager = manager;
     }
 
diff --git a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java
index 10120fa..0eaa19c 100644
--- a/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java
+++ b/server/protocols/protocols-pop3/src/test/java/org/apache/james/pop3server/POP3ServerTest.java
@@ -41,6 +41,8 @@ import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.StoreMailboxManager;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.pop3server.netty.POP3Server;
 import org.apache.james.protocols.api.utils.ProtocolServerUtils;
@@ -738,6 +740,7 @@ class POP3ServerTest {
             .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository))
             .put(binder -> binder.bind(MailboxManager.class).annotatedWith(Names.named("mailboxmanager")).toInstance(mailboxManager))
             .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem))
+            .put(binder -> binder.bind(MetricFactory.class).toInstance(new RecordingMetricFactory()))
             .build();
     }
 


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


[james-project] 09/12: JAMES-3477 WebAdmin CLI commands to manage global quotas

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit bbb46daa0b9de75224f288ccf90a7923ba8d37ae
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Feb 15 16:20:37 2021 +0700

    JAMES-3477 WebAdmin CLI commands to manage global quotas
---
 server/protocols/webadmin-cli/pom.xml              |  12 +-
 .../java/org/apache/james/cli/WebAdminCli.java     |   2 +
 .../cli/quota/DeleteGlobalQuotaCountCommand.java   |  54 ++++++
 .../cli/quota/DeleteGlobalQuotaSizeCommand.java    |  54 ++++++
 .../cli/quota/GetGlobalQuotaCountCommand.java      |  51 +++++
 .../james/cli/quota/GetGlobalQuotaSizeCommand.java |  52 +++++
 .../apache/james/cli/quota/GlobalQuotaCommand.java |  43 +++++
 .../james/cli/quota/GlobalQuotaCountCommand.java   |  44 +++++
 .../james/cli/quota/GlobalQuotaSizeCommand.java    |  44 +++++
 .../org/apache/james/cli/quota/QuotaCommand.java   |  56 ++++++
 .../cli/quota/SetGlobalQuotaCountCommand.java      |  57 ++++++
 .../james/cli/quota/SetGlobalQuotaSizeCommand.java |  58 ++++++
 .../org/apache/james/httpclient/QuotaClient.java   |  43 +++++
 .../java/org/apache/james/cli/QuotaManageTest.java | 210 +++++++++++++++++++++
 14 files changed, 776 insertions(+), 4 deletions(-)

diff --git a/server/protocols/webadmin-cli/pom.xml b/server/protocols/webadmin-cli/pom.xml
index bdfe312..ee31404 100644
--- a/server/protocols/webadmin-cli/pom.xml
+++ b/server/protocols/webadmin-cli/pom.xml
@@ -45,17 +45,21 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-webadmin-integration-test-common</artifactId>
+            <artifactId>james-server-memory-guice</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>testing-base</artifactId>
+            <artifactId>james-server-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-webadmin-integration-test-common</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>${project.groupId}</groupId>
-            <artifactId>james-server-memory-guice</artifactId>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
index 02acc00..817963a 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
@@ -26,6 +26,7 @@ import java.util.concurrent.Callable;
 
 import org.apache.james.cli.domain.DomainCommand;
 import org.apache.james.cli.mailbox.MailboxCommand;
+import org.apache.james.cli.quota.QuotaCommand;
 import org.apache.james.cli.user.UserCommand;
 import org.apache.james.httpclient.FeignClientFactory;
 import org.apache.james.httpclient.JwtToken;
@@ -78,6 +79,7 @@ public class WebAdminCli implements Callable<Integer> {
             .addSubcommand(new DomainCommand(out, parent, err))
             .addSubcommand(new UserCommand(out, parent, err))
             .addSubcommand(new MailboxCommand(out, parent, err))
+            .addSubcommand(new QuotaCommand(out, parent, err))
             .execute(args);
     }
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java
new file mode 100644
index 0000000..829d88a
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java
@@ -0,0 +1,54 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+
+import feign.Response;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "delete",
+    description = "Delete quota counts limit that applies for all users")
+public class DeleteGlobalQuotaCountCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaCountCommand parentCommand;
+
+    @Override
+    public Integer call() {
+        try {
+            QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
+            Response rs = quotaClient.deleteQuotaCount();
+            if (rs.status() == DELETED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(parentCommand.parentCommand.quotaCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java
new file mode 100644
index 0000000..1aecc73
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java
@@ -0,0 +1,54 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+
+import feign.Response;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "delete",
+    description = "Delete quota sizes limit that applies for all users")
+public class DeleteGlobalQuotaSizeCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaSizeCommand parentCommand;
+
+    @Override
+    public Integer call() {
+        try {
+            QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
+            Response rs = quotaClient.deleteQuotaSize();
+            if (rs.status() == DELETED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(parentCommand.parentCommand.quotaCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GetGlobalQuotaCountCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GetGlobalQuotaCountCommand.java
new file mode 100644
index 0000000..3270e90
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GetGlobalQuotaCountCommand.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.cli.quota;
+
+import java.util.Optional;
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "get",
+    description = "Get quota counts limit that applies for all users")
+public class GetGlobalQuotaCountCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaCountCommand parentCommand;
+
+    @Override
+    public Integer call() {
+        try {
+            QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
+            String message = Optional.ofNullable(quotaClient.getQuotaCount())
+                .map(Object::toString)
+                .orElse("No global quota defined");
+            parentCommand.parentCommand.quotaCommand.out.println(message);
+            return WebAdminCli.CLI_FINISHED_SUCCEED;
+        } catch (Exception e) {
+            e.printStackTrace(parentCommand.parentCommand.quotaCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GetGlobalQuotaSizeCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GetGlobalQuotaSizeCommand.java
new file mode 100644
index 0000000..24eca51
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GetGlobalQuotaSizeCommand.java
@@ -0,0 +1,52 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import java.util.Optional;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "get",
+    description = "Get quota sizes limit that applies for all users")
+public class GetGlobalQuotaSizeCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaSizeCommand parentCommand;
+
+    @Override
+    public Integer call() {
+        try {
+            QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
+            String message = Optional.ofNullable(quotaClient.getQuotaSize())
+                .map(FileUtils::byteCountToDisplaySize)
+                .orElse("No global quota defined");
+            parentCommand.parentCommand.quotaCommand.out.println(message);
+            return WebAdminCli.CLI_FINISHED_SUCCEED;
+        } catch (Exception e) {
+            e.printStackTrace(parentCommand.parentCommand.quotaCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaCommand.java
new file mode 100644
index 0000000..0b17c76
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaCommand.java
@@ -0,0 +1,43 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "global",
+    description = "Quota that applies for all users",
+    subcommands = {
+        GlobalQuotaCountCommand.class,
+        GlobalQuotaSizeCommand.class
+    })
+public class GlobalQuotaCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    QuotaCommand quotaCommand;
+
+    @Override
+    public Integer call() {
+        return WebAdminCli.CLI_FINISHED_SUCCEED;
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaCountCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaCountCommand.java
new file mode 100644
index 0000000..bd4ee8d
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaCountCommand.java
@@ -0,0 +1,44 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "count",
+    description = "Quota counts limit that applies for all users",
+    subcommands = {
+        DeleteGlobalQuotaCountCommand.class,
+        GetGlobalQuotaCountCommand.class,
+        SetGlobalQuotaCountCommand.class
+    })
+public class GlobalQuotaCountCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaCommand parentCommand;
+
+    @Override
+    public Integer call() {
+        return WebAdminCli.CLI_FINISHED_SUCCEED;
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaSizeCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaSizeCommand.java
new file mode 100644
index 0000000..e0e77dd
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/GlobalQuotaSizeCommand.java
@@ -0,0 +1,44 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "size",
+    description = "Quota sizes limit that applies for all users",
+    subcommands = {
+        DeleteGlobalQuotaSizeCommand.class,
+        GetGlobalQuotaSizeCommand.class,
+        SetGlobalQuotaSizeCommand.class
+    })
+public class GlobalQuotaSizeCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaCommand parentCommand;
+
+    @Override
+    public Integer call() {
+        return WebAdminCli.CLI_FINISHED_SUCCEED;
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/QuotaCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/QuotaCommand.java
new file mode 100644
index 0000000..c3bf9b3
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/QuotaCommand.java
@@ -0,0 +1,56 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import java.io.PrintStream;
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+        name = "quota",
+        description = "Manage Quotas",
+        subcommands = {
+            GlobalQuotaCommand.class
+        })
+public class QuotaCommand implements Callable<Integer> {
+
+    protected final WebAdminCli webAdminCli;
+    protected final PrintStream out;
+    protected final PrintStream err;
+
+    public QuotaCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
+        this.out = out;
+        this.webAdminCli = webAdminCli;
+        this.err = err;
+    }
+
+    @Override
+    public Integer call() {
+        return WebAdminCli.CLI_FINISHED_SUCCEED;
+    }
+
+    public QuotaClient fullyQualifiedURL() {
+        return webAdminCli.feignClientFactory(err).target(QuotaClient.class, webAdminCli.jamesUrl);
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java
new file mode 100644
index 0000000..be4bbdc
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java
@@ -0,0 +1,57 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+
+import feign.Response;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "set",
+    description = "Quota counts limit that applies for all users")
+public class SetGlobalQuotaCountCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaCountCommand parentCommand;
+
+    @CommandLine.Parameters
+    Long count;
+
+    @Override
+    public Integer call() {
+        try {
+            QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
+            Response rs = quotaClient.setQuotaCount(count);
+            if (rs.status() == DELETED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(parentCommand.parentCommand.quotaCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java
new file mode 100644
index 0000000..9a5f42c
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java
@@ -0,0 +1,58 @@
+/******************************************************************
+ * 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.cli.quota;
+
+import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.QuotaClient;
+import org.apache.james.util.Size;
+
+import feign.Response;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "set",
+    description = "Quota counts limit that applies for all users")
+public class SetGlobalQuotaSizeCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand
+    GlobalQuotaSizeCommand parentCommand;
+
+    @CommandLine.Parameters
+    String size;
+
+    @Override
+    public Integer call() {
+        try {
+            QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
+            Response rs = quotaClient.setQuotaSize(Size.parse(size).asBytes());
+            if (rs.status() == DELETED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(parentCommand.parentCommand.quotaCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/QuotaClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/QuotaClient.java
new file mode 100644
index 0000000..7aa7153
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/QuotaClient.java
@@ -0,0 +1,43 @@
+/******************************************************************
+ * 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.httpclient;
+
+import feign.RequestLine;
+import feign.Response;
+
+public interface QuotaClient {
+    @RequestLine("GET /quota/count")
+    Long getQuotaCount();
+
+    @RequestLine("GET /quota/size")
+    Long getQuotaSize();
+
+    @RequestLine("PUT /quota/count")
+    Response setQuotaCount(Long count);
+
+    @RequestLine("PUT /quota/size")
+    Response setQuotaSize(Long size);
+
+    @RequestLine("DELETE /quota/count")
+    Response deleteQuotaCount();
+
+    @RequestLine("DELETE /quota/size")
+    Response deleteQuotaSize();
+}
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/QuotaManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/QuotaManageTest.java
new file mode 100644
index 0000000..0cc556f
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/QuotaManageTest.java
@@ -0,0 +1,210 @@
+/******************************************************************
+ * 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.cli;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.util.Port;
+import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.webadmin.integration.WebadminIntegrationTestModule;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.common.collect.ImmutableList;
+
+class QuotaManageTest {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+            .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+                    .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+                    .overrideWith(new WebadminIntegrationTestModule())
+                    .overrideWith(new TestJMAPServerModule()))
+            .build();
+
+    private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
+    private final ByteArrayOutputStream errorStreamCaptor = new ByteArrayOutputStream();
+
+    Port port;
+
+    @BeforeEach
+    void setUp(GuiceJamesServer server) {
+        port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+    }
+
+    @AfterEach
+    void tearDown() {
+        System.err.println(new String(errorStreamCaptor.toByteArray(), StandardCharsets.UTF_8));
+    }
+
+    @Nested
+    class Global {
+        @Nested
+        class Count {
+            @Test
+            void getShouldReturnNoneByDefault() {
+                int exitCode = executeFluent("quota", "global", "count", "get");
+
+                SoftAssertions.assertSoftly( softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("No global quota defined".toCharArray());
+                });
+            }
+
+            @Test
+            void getShouldReturnSetValue() {
+                executeFluent("quota", "global", "count", "set", "128");
+
+                int exitCode = executeFluent("quota", "global", "count", "get");
+
+                SoftAssertions.assertSoftly( softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("128".toCharArray());
+                });
+            }
+
+            @Test
+            void getShouldNotReturnDeletedValue() {
+                executeFluent("quota", "global", "count", "set", "128");
+
+                executeFluent("quota", "global", "count", "delete");
+
+                int exitCode = executeFluent("quota", "global", "count", "get");
+
+                SoftAssertions.assertSoftly(softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("No global quota defined".toCharArray());
+                });
+            }
+
+            @Test
+            void deleteShouldBeIdempotent() {
+                executeFluent("quota", "global", "count", "delete");
+                int exitCode = executeFluent("quota", "global", "count", "delete");
+
+                assertThat(exitCode).isEqualTo(0);
+            }
+
+            @Test
+            void setShouldRespectLastWriteWin() {
+                executeFluent("quota", "global", "count", "set", "128");
+                executeFluent("quota", "global", "count", "set", "256");
+
+                int exitCode = executeFluent("quota", "global", "count", "get");
+
+                SoftAssertions.assertSoftly(softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("256".toCharArray());
+                });
+            }
+        }
+
+        @Nested
+        class Size {
+            @Test
+            void getShouldReturnNoneByDefault() {
+                int exitCode = executeFluent("quota", "global", "size", "get");
+
+                SoftAssertions.assertSoftly( softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("No global quota defined".toCharArray());
+                });
+            }
+
+            @Test
+            void getShouldReturnSetValue() {
+                executeFluent("quota", "global", "size", "set", "128");
+
+                int exitCode = executeFluent("quota", "global", "size", "get");
+
+                SoftAssertions.assertSoftly( softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("128 bytes".toCharArray());
+                });
+            }
+
+            @Test
+            void unitsShouldBeSupported() {
+                executeFluent("quota", "global", "size", "set", "128M");
+
+                int exitCode = executeFluent("quota", "global", "size", "get");
+
+                SoftAssertions.assertSoftly( softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("128 MB".toCharArray());
+                });
+            }
+
+            @Test
+            void getShouldNotReturnDeletedValue() {
+                executeFluent("quota", "global", "size", "set", "128");
+
+                executeFluent("quota", "global", "size", "delete");
+
+                int exitCode = executeFluent("quota", "global", "size", "get");
+
+                SoftAssertions.assertSoftly(softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("No global quota defined".toCharArray());
+                });
+            }
+
+            @Test
+            void deleteShouldBeIdempotent() {
+                executeFluent("quota", "global", "size", "delete");
+                int exitCode = executeFluent("quota", "global", "size", "delete");
+
+                assertThat(exitCode).isEqualTo(0);
+            }
+
+            @Test
+            void setShouldRespectLastWriteWin() {
+                executeFluent("quota", "global", "size", "set", "128");
+                executeFluent("quota", "global", "size", "set", "256");
+
+                int exitCode = executeFluent("quota", "global", "size", "get");
+
+                SoftAssertions.assertSoftly(softly -> {
+                    assertThat(exitCode).isEqualTo(0);
+                    assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("256 bytes".toCharArray());
+                });
+            }
+        }
+    }
+
+    private int executeFluent(String... args) {
+        return WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            ImmutableList.<String>builder().add("--url", "http://127.0.0.1:" + port.getValue())
+                .addAll(ImmutableList.copyOf(args))
+                .build());
+    }
+}


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


[james-project] 04/12: JAMES-3504 Log trace upon received POP3 commands

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit d91703c871cd39e62c26989f1ca8568e6984453f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Feb 23 12:05:00 2021 +0700

    JAMES-3504 Log trace upon received POP3 commands
    
    Includes MDC context, it enables advanced debugging, as well
    allows to understand client behaviour.
---
 .../org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java | 1 +
 .../java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java    | 5 ++++-
 .../java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java    | 1 +
 .../java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java    | 5 ++++-
 .../java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java    | 1 +
 .../java/org/apache/james/protocols/pop3/core/StatCmdHandler.java    | 4 ++++
 .../java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java    | 5 ++++-
 .../java/org/apache/james/protocols/pop3/core/TopCmdHandler.java     | 4 ++++
 .../java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java    | 4 ++++
 .../java/org/apache/james/protocols/pop3/core/UserCmdHandler.java    | 5 ++++-
 10 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
index cd6bd61..8b1a48e 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
@@ -57,6 +57,7 @@ public abstract class AbstractPassCmdHandler extends RsetCmdHandler {
     }
 
     private Response doAuth(POP3Session session, Request request) {
+        LOGGER.trace("PASS command received");
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.AUTHENTICATION_USERSET && parameters != null) {
             return doAuth(session, session.getUsername(), parameters);
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
index 747797f..0796771 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
@@ -27,6 +27,8 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -34,7 +36,7 @@ import com.google.common.collect.ImmutableSet;
  * Handles NOOP command
  */
 public class NoopCmdHandler implements CommandHandler<POP3Session> {
-
+    private static final Logger LOGGER = LoggerFactory.getLogger(NoopCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("NOOP");
 
     /**
@@ -51,6 +53,7 @@ public class NoopCmdHandler implements CommandHandler<POP3Session> {
     }
 
     private Response noop(POP3Session session) {
+        LOGGER.trace("NOOP command received");
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
             return POP3Response.OK;
         } else {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
index b213ac8..248cd4d 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
@@ -70,6 +70,7 @@ public class QuitCmdHandler implements CommandHandler<POP3Session> {
     }
 
     private Response quit(POP3Session session) {
+        LOGGER.trace("QUIT command received");
         Response response = null;
         if (session.getHandlerState() == POP3Session.AUTHENTICATION_READY || session.getHandlerState() == POP3Session.AUTHENTICATION_USERSET) {
             return SIGN_OFF;
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
index 0167317..8544741 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
@@ -33,6 +33,8 @@ import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.POP3StreamResponse;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
@@ -42,7 +44,7 @@ import com.google.common.collect.ImmutableSet;
  * Handles RETR command
  */
 public class RetrCmdHandler implements CommandHandler<POP3Session> {
-
+    private static final Logger LOGGER = LoggerFactory.getLogger(RetrCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("RETR");
     @VisibleForTesting
     static final Response SYNTAX_ERROR = new POP3Response(POP3Response.ERR_RESPONSE, "Usage: RETR [mail number]").immutable();
@@ -64,6 +66,7 @@ public class RetrCmdHandler implements CommandHandler<POP3Session> {
     }
 
     private Response retr(POP3Session session, Request request) {
+        LOGGER.trace("RETR command received");
         POP3Response response = null;
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
index 87f2aa1..b74e4ec 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
@@ -59,6 +59,7 @@ public class RsetCmdHandler implements CommandHandler<POP3Session> {
     }
 
     private Response rset(POP3Session session) {
+        LOGGER.trace("RETR command received");
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
             stat(session);
             return POP3Response.OK;
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
index a2fbc74..09b3bdc 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
@@ -31,6 +31,8 @@ import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -39,6 +41,7 @@ import com.google.common.collect.ImmutableSet;
  * Handles STAT command
  */
 public class StatCmdHandler implements CommandHandler<POP3Session> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StatCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("STAT");
 
     /**
@@ -55,6 +58,7 @@ public class StatCmdHandler implements CommandHandler<POP3Session> {
     }
 
     private Response stat(POP3Session session) {
+        LOGGER.trace("STAT command received");
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
 
             List<MessageMetaData> uidList = session.getAttachment(POP3Session.UID_LIST, State.Transaction).orElse(ImmutableList.of());
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
index d84f853..c242172 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
@@ -30,6 +30,8 @@ import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.POP3StartTlsResponse;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -38,7 +40,7 @@ import com.google.common.collect.ImmutableSet;
  * with the STSL command
  */
 public class StlsCmdHandler implements CommandHandler<POP3Session>, CapaCapability {
-
+    private static final Logger LOGGER = LoggerFactory.getLogger(StlsCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("STLS");
     private static final Set<String> CAPS = ImmutableSet.of("STLS");
 
@@ -54,6 +56,7 @@ public class StlsCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
     }
 
     private Response stls(POP3Session session) {
+        LOGGER.trace("STLS command received");
         // check if starttls is supported, the state is the right one and it was
         // not started before
         if (session.isStartTLSSupported() && session.getHandlerState() == POP3Session.AUTHENTICATION_READY && session.isTLSStarted() == false) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
index 7ffb13f..7351b20 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
@@ -35,6 +35,8 @@ import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.POP3StreamResponse;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -43,6 +45,7 @@ import com.google.common.collect.ImmutableSet;
  * Handles TOP command
  */
 public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TopCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableList.of("TOP");
     private static final Set<String> CAPS = ImmutableSet.of("TOP");
     
@@ -68,6 +71,7 @@ public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
     }
 
     private Response top(POP3Session session, Request request) {
+        LOGGER.trace("TOP command received");
         String parameters = request.getArgument();
         if (parameters == null) {
             return SYNTAX_ERROR;
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
index 161934d..ef3aa8f 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
@@ -33,6 +33,8 @@ import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -41,6 +43,7 @@ import com.google.common.collect.ImmutableSet;
  * Handles UIDL command
  */
 public class UidlCmdHandler implements CommandHandler<POP3Session>, CapaCapability {
+    private static final Logger LOGGER = LoggerFactory.getLogger(UidlCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("UIDL");
     private static final Set<String> CAPS = ImmutableSet.of("UIDL");
 
@@ -59,6 +62,7 @@ public class UidlCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
     }
 
     private Response uidl(POP3Session session, Request request) {
+        LOGGER.trace("UIDL command received");
         POP3Response response = null;
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
index fe6e79a..a08780f 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
@@ -29,6 +29,8 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -36,7 +38,7 @@ import com.google.common.collect.ImmutableSet;
  * Handles USER command
  */
 public class UserCmdHandler implements CommandHandler<POP3Session>, CapaCapability {
-
+    private static final Logger LOGGER = LoggerFactory.getLogger(UserCmdHandler.class);
     private static final Collection<String> COMMANDS = ImmutableSet.of("USER");
     private static final Set<String> CAPS = ImmutableSet.of("USER");
 
@@ -55,6 +57,7 @@ public class UserCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
     }
 
     private Response user(POP3Session session, Request request) {
+        LOGGER.trace("USER command received");
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.AUTHENTICATION_READY && parameters != null) {
             session.setUsername(Username.of(parameters));


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


[james-project] 11/12: JAMES-3353 Document urn:apache:james:params:jmap:mail:shares JMAP extension

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit c6e1d4d377f232dba4ffdcfc9902249c65520b4e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jul 30 16:21:37 2020 +0700

    JAMES-3353 Document urn:apache:james:params:jmap:mail:shares JMAP extension
---
 .../jmap-rfc-8621/doc/specs/spec/mail/intro.mdown  |  13 ++
 .../jmap-rfc-8621/doc/specs/spec/mail/rights.mdown | 136 +++++++++++++++++++++
 2 files changed, 149 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown
index 17cd2fa..bedcfc3 100644
--- a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown
@@ -130,6 +130,19 @@ The value of this property in an account's *accountCapabilities* property is an
 
 This represents support for the VacationResponse data type and associated API methods. The value of this property is an empty object in both the JMAP session *capabilities* property and an account's *accountCapabilities* property.
 
+### urn:apache:james:params:jmap:mail:shares extension
+
+<aside class="notice">
+  Implemented
+</aside>
+
+This extension is specific to the Apache James server as it relies on a common shared storage allowing arbitrary cross-account entity access.
+
+This extension to the JMAP specification enables to:
+
+ - Grant access on mailboxes, and the emails they contain to other users.
+ - Enable display of shared mailboxes, and shared messages as part of the main user account, making effectively account sharing transparent from a client perspective.
+
 ## Data Type Support in Different Accounts
 
 The server MUST include the appropriate capability strings as keys in the *accountCapabilities* property of any account with which the user may use the data types represented by that URI. Supported data types may differ between accounts the user has access to. For example, in the user's personal account, they may have access to all three sets of data, but in a shared account, they may only have data for `urn:ietf:params:jmap:mail`. This means they can access Mailbox/Thread/Email data in  [...]
diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/rights.mdown b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/rights.mdown
new file mode 100644
index 0000000..4a17e2b
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/rights.mdown
@@ -0,0 +1,136 @@
+# urn:apache:james:params:jmap:mail:shares extension
+
+<aside class="notice">
+  Implemented
+</aside>
+
+This extension is specific to the Apache James server as it relies on a common shared storage allowing arbitrary cross-account entity access.
+
+This extension to the JMAP specification enables to:
+
+ - Grant access on mailboxes, and the email they contain to other users.
+ - Enable display of shared mailboxes, and shared messages as part of the main user account, making effectively account sharing transparent from a client perspective.
+
+## Defined capability
+
+The new capability `urn:apache:james:params:jmap:mail:shares` is defined.
+
+This extension defines both behavioral changes and additional fields for the Mailbox object.
+
+If specified, behavioral changes and additional fields defined hereafter MUST apply.
+
+If unspecified no additional fields to the Mailbox object are returned, and the behaviour needs to be exactly the one of `urn:ietf:params:jmap:mail`.
+
+## Addition to existing data types
+
+The following data types are defined:
+
+`Username` of type `String` corresponds to a username.
+
+`Right` of type `String` defines an action that can be carried out on a mailbox or its content.
+
+The following `Right`s are defined:
+
+ - `"a"` Administer: the user can view and modify the rights of this mailbox
+ - `"i"` Insert: the user can use the id of this mailbox in `mailboxId` fields of Emails.
+ - `"l"` Lookup: the user can see the name of this mailbox but information about the content of this mailbox MUST not be disclosed.
+ - `"r"` Read: the user can read the content of this mailbox, and see the assiociated Email and Thread counts.
+ - `"t"` DeleteMessages: the user can delete Email in this Mailbox
+ - `"w"` Write: the user can alter the Keywords of messages contained in this Mailbox, except the `$Seen` keyword
+ - `"s"` Seen: the user can alter the `$Seen` keyword of messages contained in this Mailbox
+ - `"e"` Expunge: the user can mark Email as Expunged in this Mailbox. This action is not achievable using JMAP.
+
+`UserRights` is an array of `Right`, representing the actions a user can perform on a mailbox.
+
+`Rights` indicates which user can perform which operation on the Mailbox and the related entities.
+
+Of type `Username[UserRights]` this map associate to each `Username` the associated `UserRights` actions that can be performed.
+
+## Additional fields for Mailbox object
+
+The following additional properties are defined for the `Mailbox` object :
+
+ - `namespace` of type `String` (server set) provides information about mailbox ownership.
+   - `"Personal"` value indicates that the Mailbox belongs to the current user.
+   - `"Delegated[.*]"` indicates that the Mailbox has been delegated to the owner of this account. The user owning the delegated account is held as a value of the `"Delegated"` property, enclosed with `[]` characters.
+ - `rights` of type `Rights`. The owner can see the full rights, but sharee MUST only see their rights if they do not have the administer right on the mailbox. Sharee without administer right MUST NOT be able to see another SHAREE rights.
+
+## Behavioral changes
+
+When `urn:apache:james:params:jmap:mail:shares` is specified, access to delegated resources is enabled as part of standard JMAP `urn:ietf:params:jmap:mail` API calls.
+
+Namely:
+
+ - `Mailbox/get`, `Mailbox/query`, `Mailbox/queryChanges`, `Mailbox/changes` need to accept or return delegated.
+ - `Thread/get`, `Thread/changes` need to accept of return Threads in mailboxes the account user can access.
+ - `Email/get`, `Email/set`, `Email/query`, `Email/changes`, `Email/queryChanges`, `Email/copy`, `Email/import`, `Email/import` need to accept or return matching Emails in delegated mailboxes the account user can access.
+ - `SearchSnippet/get` needs to accept delegated Emails as a valid input.
+
+Sharee MUST not be able to modify a shared mailbox via `Mailbox/set`.
+
+## Examples
+
+Here is a `Mailbox` object as accessed by the owner:
+
+```
+{
+  "id":"2",
+  "name":"inbox",
+  "parentId":"1",
+  "role":"inbox",
+  "sortOrder":10,
+  "totalEmails":1234,
+  "unreadEmails":123,
+  "totalThreads":58,
+  "unreadThreads":22,
+  "myRights":{
+    "mayReadItems":false,
+    "mayAddItems":true,
+    "mayRemoveItems":false,
+    "maySetSeen":true,
+    "maySetKeywords":false,
+    "mayCreateChild":true,
+    "mayRename":true,
+    "mayDelete":false,
+    "maySubmit":false
+  },
+  "isSubscribed":true,
+  "namespace":"Personal",
+  "rights":{
+    "bob":["e","l"],
+    "alice":["r","w"]
+  }
+}
+```
+
+Here is a `Mailbox` object as accessed by the sharee alice, when it belongs to the owner bob:
+
+```
+{
+  "id":"2",
+  "name":"inbox",
+  "parentId":"1",
+  "role":"inbox",
+  "sortOrder":10,
+  "totalEmails":1234,
+  "unreadEmails":123,
+  "totalThreads":58,
+  "unreadThreads":22,
+  "myRights":{
+    "mayReadItems":false,
+    "mayAddItems":true,
+    "mayRemoveItems":false,
+    "maySetSeen":true,
+    "maySetKeywords":false,
+    "mayCreateChild":true,
+    "mayRename":true,
+    "mayDelete":false,
+    "maySubmit":false
+  },
+  "isSubscribed":true,
+  "namespace":{"Delegated": "bob"},
+  "rights":{
+    "alice":["r","w"]
+  }
+}
+```


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


[james-project] 10/12: JAMES-3400 Move HTTP status codes to a constant

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 83328cf2a057f3385954538340eab15e54c27aef
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 17 12:31:59 2021 +0700

    JAMES-3400 Move HTTP status codes to a constant
---
 .../james/cli/domain/AddDomainAliasCommand.java    |  7 ++--
 .../james/cli/domain/DomainCreateCommand.java      |  7 ++--
 .../james/cli/domain/DomainDeleteCommand.java      |  7 ++--
 .../james/cli/domain/DomainExistCommand.java       | 11 +++---
 .../james/cli/domain/RemoveDomainAliasCommand.java |  7 ++--
 .../cli/quota/DeleteGlobalQuotaCountCommand.java   |  4 +-
 .../cli/quota/DeleteGlobalQuotaSizeCommand.java    |  4 +-
 .../cli/quota/SetGlobalQuotaCountCommand.java      |  4 +-
 .../james/cli/quota/SetGlobalQuotaSizeCommand.java |  4 +-
 .../Constants.java}                                | 43 +++-------------------
 10 files changed, 30 insertions(+), 68 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java
index d669dfd..d7bdbfd 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.cli.domain;
 
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
+
 import java.nio.charset.StandardCharsets;
 import java.util.concurrent.Callable;
 
@@ -33,9 +35,6 @@ import picocli.CommandLine;
     name = "addAlias",
     description = "Create a new domain alias")
 public class AddDomainAliasCommand implements Callable<Integer> {
-
-    public static final int CREATED_CODE = 204;
-
     @CommandLine.ParentCommand DomainCommand domainCommand;
 
     @CommandLine.Parameters(description = "Destination of the domain alias. This is the domain this alias belongs to.")
@@ -49,7 +48,7 @@ public class AddDomainAliasCommand implements Callable<Integer> {
         try {
             DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
             Response rs = domainClient.addADomainAlias(destinationDomain, sourceDomain);
-            if (rs.status() == CREATED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 domainCommand.err.println(IOUtils.toString(rs.body().asInputStream(), StandardCharsets.UTF_8));
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCreateCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCreateCommand.java
index af0b074..b6502f0 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCreateCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCreateCommand.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.cli.domain;
 
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
+
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
@@ -31,9 +33,6 @@ import picocli.CommandLine;
     name = "create",
     description = "Create a new domain")
 public class DomainCreateCommand implements Callable<Integer> {
-
-    public static final int CREATED_CODE = 204;
-
     @CommandLine.ParentCommand DomainCommand domainCommand;
 
     @CommandLine.Parameters
@@ -44,7 +43,7 @@ public class DomainCreateCommand implements Callable<Integer> {
         try {
             DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
             Response rs = domainClient.createADomain(domainName);
-            if (rs.status() == CREATED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java
index af2115e..64b0234 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.cli.domain;
 
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
+
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
@@ -31,9 +33,6 @@ import picocli.CommandLine;
     name = "delete",
     description = "Delete a domain")
 public class DomainDeleteCommand implements Callable<Integer> {
-
-    public static final int DELETED_CODE = 204;
-
     @CommandLine.ParentCommand DomainCommand domainCommand;
 
     @CommandLine.Parameters
@@ -44,7 +43,7 @@ public class DomainDeleteCommand implements Callable<Integer> {
         try {
             DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
             Response rs = domainClient.deleteADomain(domainName);
-            if (rs.status() == DELETED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainExistCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainExistCommand.java
index 094c16b..948e1b5 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainExistCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainExistCommand.java
@@ -19,6 +19,9 @@
 
 package org.apache.james.cli.domain;
 
+import static org.apache.james.httpclient.Constants.NOT_FOUND;
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
+
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
@@ -31,10 +34,6 @@ import picocli.CommandLine;
     name = "exist",
     description = "Check if a domain is exist")
 public class DomainExistCommand implements Callable<Integer> {
-
-    public static final int EXISTED_CODE = 204;
-    public static final int NOT_EXISTED_CODE = 404;
-
     @CommandLine.ParentCommand DomainCommand domainCommand;
 
     @CommandLine.Parameters
@@ -45,10 +44,10 @@ public class DomainExistCommand implements Callable<Integer> {
         try {
             DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
             Response rs = domainClient.doesExist(domainName);
-            if (rs.status() == EXISTED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 domainCommand.out.println(domainName + " exists");
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
-            } else if (rs.status() == NOT_EXISTED_CODE) {
+            } else if (rs.status() == NOT_FOUND) {
                 domainCommand.out.println(domainName + " does not exist");
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             }
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java
index 5e715d3..eb5a2de 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.cli.domain;
 
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
+
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
@@ -31,9 +33,6 @@ import picocli.CommandLine;
     name = "removeAlias",
     description = "Remove a domain alias")
 public class RemoveDomainAliasCommand implements Callable<Integer> {
-
-    public static final int CREATED_CODE = 204;
-
     @CommandLine.ParentCommand DomainCommand domainCommand;
 
     @CommandLine.Parameters(description = "Destination of the domain alias. This is the domain this alias belongs to.")
@@ -47,7 +46,7 @@ public class RemoveDomainAliasCommand implements Callable<Integer> {
         try {
             DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
             Response rs = domainClient.deleteADomainAlias(destinationDomain, sourceDomain);
-            if (rs.status() == CREATED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java
index 829d88a..4b48404 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaCountCommand.java
@@ -19,7 +19,7 @@
 
 package org.apache.james.cli.quota;
 
-import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
 
 import java.util.concurrent.Callable;
 
@@ -41,7 +41,7 @@ public class DeleteGlobalQuotaCountCommand implements Callable<Integer> {
         try {
             QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
             Response rs = quotaClient.deleteQuotaCount();
-            if (rs.status() == DELETED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java
index 1aecc73..d9012f7 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/DeleteGlobalQuotaSizeCommand.java
@@ -19,7 +19,7 @@
 
 package org.apache.james.cli.quota;
 
-import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
 
 import java.util.concurrent.Callable;
 
@@ -41,7 +41,7 @@ public class DeleteGlobalQuotaSizeCommand implements Callable<Integer> {
         try {
             QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
             Response rs = quotaClient.deleteQuotaSize();
-            if (rs.status() == DELETED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java
index be4bbdc..be665a2 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaCountCommand.java
@@ -19,7 +19,7 @@
 
 package org.apache.james.cli.quota;
 
-import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
 
 import java.util.concurrent.Callable;
 
@@ -44,7 +44,7 @@ public class SetGlobalQuotaCountCommand implements Callable<Integer> {
         try {
             QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
             Response rs = quotaClient.setQuotaCount(count);
-            if (rs.status() == DELETED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java
index 9a5f42c..9c8b49b 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/quota/SetGlobalQuotaSizeCommand.java
@@ -19,7 +19,7 @@
 
 package org.apache.james.cli.quota;
 
-import static org.apache.james.cli.domain.DomainDeleteCommand.DELETED_CODE;
+import static org.apache.james.httpclient.Constants.NO_CONTENT;
 
 import java.util.concurrent.Callable;
 
@@ -45,7 +45,7 @@ public class SetGlobalQuotaSizeCommand implements Callable<Integer> {
         try {
             QuotaClient quotaClient = parentCommand.parentCommand.quotaCommand.fullyQualifiedURL();
             Response rs = quotaClient.setQuotaSize(Size.parse(size).asBytes());
-            if (rs.status() == DELETED_CODE) {
+            if (rs.status() == NO_CONTENT) {
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else {
                 return WebAdminCli.CLI_FINISHED_FAILED;
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/Constants.java
similarity index 53%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/Constants.java
index af2115e..74c49b7 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainDeleteCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/Constants.java
@@ -17,42 +17,9 @@
  * under the License.                                             *
  ******************************************************************/
 
-package org.apache.james.cli.domain;
+package org.apache.james.httpclient;
 
-import java.util.concurrent.Callable;
-
-import org.apache.james.cli.WebAdminCli;
-import org.apache.james.httpclient.DomainClient;
-
-import feign.Response;
-import picocli.CommandLine;
-
-@CommandLine.Command(
-    name = "delete",
-    description = "Delete a domain")
-public class DomainDeleteCommand implements Callable<Integer> {
-
-    public static final int DELETED_CODE = 204;
-
-    @CommandLine.ParentCommand DomainCommand domainCommand;
-
-    @CommandLine.Parameters
-    String domainName;
-
-    @Override
-    public Integer call() {
-        try {
-            DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
-            Response rs = domainClient.deleteADomain(domainName);
-            if (rs.status() == DELETED_CODE) {
-                return WebAdminCli.CLI_FINISHED_SUCCEED;
-            } else {
-                return WebAdminCli.CLI_FINISHED_FAILED;
-            }
-        } catch (Exception e) {
-            e.printStackTrace(domainCommand.err);
-            return WebAdminCli.CLI_FINISHED_FAILED;
-        }
-    }
-
-}
\ No newline at end of file
+public interface Constants {
+    int NO_CONTENT = 204;
+    int NOT_FOUND = 404;
+}


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


[james-project] 02/12: JAMES-3504 MDC context for POP3 commands

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 69d7f22b9965be146b61633f0f24745a2365b812
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Feb 23 11:53:56 2021 +0700

    JAMES-3504 MDC context for POP3 commands
    
    Includes:
     - Request arguments (except for auth)
     - Session context (state, mailbox)
     - Action performed
---
 protocols/pop3/pom.xml                             |   4 +
 .../pop3/core/AbstractPassCmdHandler.java          |  12 ++-
 .../james/protocols/pop3/core/CapaCmdHandler.java  |   8 ++
 .../james/protocols/pop3/core/DeleCmdHandler.java  |   9 ++
 .../james/protocols/pop3/core/ListCmdHandler.java  |  13 ++-
 .../{NoopCmdHandler.java => MDCConstants.java}     | 111 ++++++++++-----------
 .../james/protocols/pop3/core/NoopCmdHandler.java  |   9 ++
 .../james/protocols/pop3/core/QuitCmdHandler.java  |   9 ++
 .../james/protocols/pop3/core/RetrCmdHandler.java  |  10 ++
 .../james/protocols/pop3/core/RsetCmdHandler.java  |  11 +-
 .../james/protocols/pop3/core/StatCmdHandler.java  |   9 ++
 .../james/protocols/pop3/core/StlsCmdHandler.java  |   9 ++
 .../james/protocols/pop3/core/TopCmdHandler.java   |  15 ++-
 .../james/protocols/pop3/core/UidlCmdHandler.java  |  14 ++-
 .../protocols/pop3/core/UnknownCmdHandler.java     |  11 ++
 .../james/protocols/pop3/core/UserCmdHandler.java  |  10 ++
 .../james/pop3server/core/PassCmdHandler.java      |  12 ++-
 17 files changed, 207 insertions(+), 69 deletions(-)

diff --git a/protocols/pop3/pom.xml b/protocols/pop3/pom.xml
index 47fc277..f77786e 100644
--- a/protocols/pop3/pom.xml
+++ b/protocols/pop3/pom.xml
@@ -35,6 +35,10 @@
     <dependencies>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
index 9358025..cd6bd61 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/AbstractPassCmdHandler.java
@@ -28,6 +28,7 @@ import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.Mailbox;
+import org.apache.james.util.MDCBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -48,6 +49,14 @@ public abstract class AbstractPassCmdHandler extends RsetCmdHandler {
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "AUTH")
+                .addContext(MDCConstants.withSession(session)),
+            () -> doAuth(session, request));
+    }
+
+    private Response doAuth(POP3Session session, Request request) {
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.AUTHENTICATION_USERSET && parameters != null) {
             return doAuth(session, session.getUsername(), parameters);
@@ -56,8 +65,7 @@ public abstract class AbstractPassCmdHandler extends RsetCmdHandler {
             return AUTH_FAILED;
         }
     }
-    
-    
+
     /**
      * Authenticate a user and return the {@link Response}
      */
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java
index fe6a335..b902f6e 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/CapaCmdHandler.java
@@ -31,6 +31,7 @@ import org.apache.james.protocols.api.handler.ExtensibleHandler;
 import org.apache.james.protocols.api.handler.WiringException;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -44,6 +45,13 @@ public class CapaCmdHandler implements CommandHandler<POP3Session>, ExtensibleHa
 
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "CAPA")
+                .addContext(MDCConstants.withSession(session)),
+            () -> capa(session));
+    }
+
+    private Response capa(POP3Session session) {
         POP3Response response = new POP3Response(POP3Response.OK_RESPONSE, "Capability list follows");
 
         for (CapaCapability capabilities : caps) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java
index ce499d7..d32624a 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/DeleCmdHandler.java
@@ -30,6 +30,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -49,6 +50,14 @@ public class DeleCmdHandler implements CommandHandler<POP3Session> {
     @Override
     @SuppressWarnings("unchecked")
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "DELE")
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> delete(session, request));
+    }
+
+    private Response delete(POP3Session session, Request request) {
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
             int num = 0;
             try {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java
index b706146..4c16b54 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/ListCmdHandler.java
@@ -30,6 +30,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -55,6 +56,14 @@ public class ListCmdHandler implements CommandHandler<POP3Session> {
     @Override
     @SuppressWarnings("unchecked")
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "LIST")
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> list(session, request));
+    }
+
+    private Response list(POP3Session session, Request request) {
         String parameters = request.getArgument();
         List<MessageMetaData> uidList = session.getAttachment(POP3Session.UID_LIST, State.Transaction).orElse(ImmutableList.of());
         List<String> deletedUidList = session.getAttachment(POP3Session.DELETED_UID_LIST, State.Transaction).orElse(ImmutableList.of());
@@ -89,13 +98,13 @@ public class ListCmdHandler implements CommandHandler<POP3Session> {
                 int num = 0;
                 try {
                     num = Integer.parseInt(parameters);
-                    
+
                     MessageMetaData data = MessageMetaDataUtils.getMetaData(session, num);
                     if (data == null) {
                         StringBuilder responseBuffer = new StringBuilder(64).append("Message (").append(num).append(") does not exist.");
                         return  new POP3Response(POP3Response.ERR_RESPONSE, responseBuffer.toString());
                     }
-                    
+
                     if (!deletedUidList.contains(data.getUid())) {
                         StringBuilder responseBuffer = new StringBuilder(64).append(num).append(" ").append(data.getSize());
                         response = new POP3Response(POP3Response.OK_RESPONSE, responseBuffer.toString());
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/MDCConstants.java
similarity index 56%
copy from protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
copy to protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/MDCConstants.java
index 11012c6..de4f9af 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/MDCConstants.java
@@ -1,57 +1,54 @@
-/****************************************************************
- * 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.protocols.pop3.core;
-
-import java.util.Collection;
-
-import org.apache.james.protocols.api.Request;
-import org.apache.james.protocols.api.Response;
-import org.apache.james.protocols.api.handler.CommandHandler;
-import org.apache.james.protocols.pop3.POP3Response;
-import org.apache.james.protocols.pop3.POP3Session;
-
-import com.google.common.collect.ImmutableSet;
-
-/**
- * Handles NOOP command
- */
-public class NoopCmdHandler implements CommandHandler<POP3Session> {
-
-    private static final Collection<String> COMMANDS = ImmutableSet.of("NOOP");
-
-    /**
-     * Handler method called upon receipt of a NOOP command. Like all good
-     * NOOPs, does nothing much.
-     */
-    @Override
-    public Response onCommand(POP3Session session, Request request) {
-        if (session.getHandlerState() == POP3Session.TRANSACTION) {
-            return POP3Response.OK;
-        } else {
-            return POP3Response.ERR;
-        }
-    }
-
-    @Override
-    public Collection<String> getImplCommands() {
-        return COMMANDS;
-    }
-
-}
+/****************************************************************
+ * 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.protocols.pop3.core;
+
+import java.util.Optional;
+
+import org.apache.james.protocols.api.Request;
+import org.apache.james.protocols.pop3.POP3Session;
+import org.apache.james.protocols.pop3.mailbox.Mailbox;
+import org.apache.james.util.MDCBuilder;
+
+import com.github.fge.lambdas.Throwing;
+
+public interface MDCConstants {
+    String MAILBOX = "mailbox";
+    String ARGUMENT = "argument";
+    String STATE = "state";
+
+    static MDCBuilder withMailbox(POP3Session session) {
+        return Optional.ofNullable(session.getUserMailbox())
+            .map(Throwing.function(Mailbox::getIdentifier).sneakyThrow())
+            .map(id -> MDCBuilder.create().addContext(MAILBOX, id))
+            .orElse(MDCBuilder.create());
+    }
+
+    static MDCBuilder forRequest(Request request) {
+        return Optional.ofNullable(request.getArgument())
+            .map(argument -> MDCBuilder.create().addContext(ARGUMENT, argument))
+            .orElse(MDCBuilder.create());
+    }
+
+    static MDCBuilder withSession(POP3Session session) {
+        return MDCBuilder.create()
+            .addContext(withMailbox(session))
+            .addContext(STATE, session.getHandlerState());
+    }
+}
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
index 11012c6..747797f 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/NoopCmdHandler.java
@@ -26,6 +26,7 @@ import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -42,6 +43,14 @@ public class NoopCmdHandler implements CommandHandler<POP3Session> {
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "NOOP")
+                .addContext(MDCConstants.withSession(session)),
+            () -> noop(session));
+    }
+
+    private Response noop(POP3Session session) {
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
             return POP3Response.OK;
         } else {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
index 345dd0b..b213ac8 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/QuitCmdHandler.java
@@ -30,6 +30,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.Mailbox;
+import org.apache.james.util.MDCBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -61,6 +62,14 @@ public class QuitCmdHandler implements CommandHandler<POP3Session> {
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "QUIT")
+                .addContext(MDCConstants.withSession(session)),
+            () -> quit(session));
+    }
+
+    private Response quit(POP3Session session) {
         Response response = null;
         if (session.getHandlerState() == POP3Session.AUTHENTICATION_READY || session.getHandlerState() == POP3Session.AUTHENTICATION_USERSET) {
             return SIGN_OFF;
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
index 99d050c..0167317 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RetrCmdHandler.java
@@ -32,6 +32,7 @@ import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.POP3StreamResponse;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
@@ -54,6 +55,15 @@ public class RetrCmdHandler implements CommandHandler<POP3Session> {
     @Override
     @SuppressWarnings("unchecked")
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "RETR")
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> retr(session, request));
+    }
+
+    private Response retr(POP3Session session, Request request) {
         POP3Response response = null;
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
index f88ec0b..87f2aa1 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/RsetCmdHandler.java
@@ -31,6 +31,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,13 +50,21 @@ public class RsetCmdHandler implements CommandHandler<POP3Session> {
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "RSET")
+                .addContext(MDCConstants.withSession(session)),
+            () -> rset(session));
+
+    }
+
+    private Response rset(POP3Session session) {
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
             stat(session);
             return POP3Response.OK;
         } else {
             return POP3Response.ERR;
         }
-        
     }
 
     /**
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
index f942124..a2fbc74 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StatCmdHandler.java
@@ -30,6 +30,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -46,6 +47,14 @@ public class StatCmdHandler implements CommandHandler<POP3Session> {
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "STAT")
+                .addContext(MDCConstants.withSession(session)),
+            () -> stat(session));
+    }
+
+    private Response stat(POP3Session session) {
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
 
             List<MessageMetaData> uidList = session.getAttachment(POP3Session.UID_LIST, State.Transaction).orElse(ImmutableList.of());
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
index 28c03180..d84f853 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/StlsCmdHandler.java
@@ -29,6 +29,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.POP3StartTlsResponse;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -45,6 +46,14 @@ public class StlsCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
 
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "START_TLS")
+                .addContext(MDCConstants.withSession(session)),
+            () -> stls(session));
+    }
+
+    private Response stls(POP3Session session) {
         // check if starttls is supported, the state is the right one and it was
         // not started before
         if (session.isStartTLSSupported() && session.getHandlerState() == POP3Session.AUTHENTICATION_READY && session.isTLSStarted() == false) {
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
index 5931a2b..7ffb13f 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/TopCmdHandler.java
@@ -34,6 +34,7 @@ import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.POP3StreamResponse;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -58,6 +59,15 @@ public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
     @SuppressWarnings("unchecked")
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "TOP")
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> top(session, request));
+    }
+
+    private Response top(POP3Session session, Request request) {
         String parameters = request.getArgument();
         if (parameters == null) {
             return SYNTAX_ERROR;
@@ -81,13 +91,13 @@ public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
                 return SYNTAX_ERROR;
             }
             try {
-                
+
                 MessageMetaData data = MessageMetaDataUtils.getMetaData(session, num);
                 if (data == null) {
                     StringBuilder responseBuffer = new StringBuilder(64).append("Message (").append(num).append(") does not exist.");
                     return  new POP3Response(POP3Response.ERR_RESPONSE, responseBuffer.toString());
                 }
-                
+
                 List<String> deletedUidList = session.getAttachment(POP3Session.DELETED_UID_LIST, State.Transaction).orElse(ImmutableList.of());
 
                 String uid = data.getUid();
@@ -109,7 +119,6 @@ public class TopCmdHandler extends RetrCmdHandler implements CapaCapability {
         } else {
             return POP3Response.ERR;
         }
-
     }
 
     @Override
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
index 4155a65..161934d 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UidlCmdHandler.java
@@ -32,6 +32,7 @@ import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.mailbox.MessageMetaData;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -49,6 +50,15 @@ public class UidlCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "UIDL")
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> uidl(session, request));
+    }
+
+    private Response uidl(POP3Session session, Request request) {
         POP3Response response = null;
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.TRANSACTION) {
@@ -72,7 +82,7 @@ public class UidlCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
                     int num = 0;
                     try {
                         num = Integer.parseInt(parameters);
-                        
+
                         MessageMetaData metadata = MessageMetaDataUtils.getMetaData(session, num);
 
                         if (metadata == null) {
@@ -98,7 +108,7 @@ public class UidlCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
             } catch (IOException e) {
                 return POP3Response.ERR;
             }
-            
+
         } else {
             return POP3Response.ERR;
         }
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UnknownCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UnknownCmdHandler.java
index 6193a31..7ec03f4 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UnknownCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UnknownCmdHandler.java
@@ -24,17 +24,28 @@ import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.UnknownCommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
+import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default command handler for handling unknown commands
  */
 public class UnknownCmdHandler extends UnknownCommandHandler<POP3Session> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(UnknownCmdHandler.class);
+
     /**
      * Handler method called upon receipt of an unrecognized command. Returns an
      * error response and logs the command.
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, request.getCommand())
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> LOGGER.info("Unknown command received"));
         return POP3Response.ERR;
     }
 }
diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
index c4ded58..fe6e79a 100644
--- a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
+++ b/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/core/UserCmdHandler.java
@@ -28,6 +28,7 @@ import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.CommandHandler;
 import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
+import org.apache.james.util.MDCBuilder;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -45,6 +46,15 @@ public class UserCmdHandler implements CommandHandler<POP3Session>, CapaCapabili
      */
     @Override
     public Response onCommand(POP3Session session, Request request) {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "USER")
+                .addContext(MDCConstants.withSession(session))
+                .addContext(MDCConstants.forRequest(request)),
+            () -> user(session, request));
+    }
+
+    private Response user(POP3Session session, Request request) {
         String parameters = request.getArgument();
         if (session.getHandlerState() == POP3Session.AUTHENTICATION_READY && parameters != null) {
             session.setUsername(Username.of(parameters));
diff --git a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
index 47a1945..eb3f944 100644
--- a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
+++ b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/core/PassCmdHandler.java
@@ -40,9 +40,12 @@ import org.apache.james.protocols.pop3.POP3Response;
 import org.apache.james.protocols.pop3.POP3Session;
 import org.apache.james.protocols.pop3.core.AbstractPassCmdHandler;
 import org.apache.james.protocols.pop3.mailbox.Mailbox;
+import org.apache.james.util.MDCBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
+
 import reactor.core.publisher.Mono;
 
 /**
@@ -71,6 +74,13 @@ public class PassCmdHandler extends AbstractPassCmdHandler  {
 
     @Override
     protected Mailbox auth(POP3Session session, Username username, String password) throws Exception {
+        return MDCBuilder.withMdc(
+            MDCBuilder.create()
+                .addContext(MDCBuilder.USER, username.asString()),
+            Throwing.supplier(() -> auth(session, password)).sneakyThrow());
+    }
+
+    private Mailbox auth(POP3Session session, String password) throws IOException {
         MailboxSession mSession = null;
         try {
             mSession = manager.login(session.getUsername(), password);
@@ -93,7 +103,5 @@ public class PassCmdHandler extends AbstractPassCmdHandler  {
                 manager.endProcessingRequest(mSession);
             }
         }
-
     }
-
 }


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


[james-project] 01/12: JAMES-3491 WebSocket PUSH should support pushState

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit a78464039155607e89297360776daeca909fab9f
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Feb 8 17:17:33 2021 +0700

    JAMES-3491 WebSocket PUSH should support pushState
---
 .../jmap/rfc8621/contract/WebSocketContract.scala  | 95 +++++++++++++++++-----
 .../org/apache/james/jmap/change/StateChange.scala |  8 +-
 .../james/jmap/core/WebSocketTransport.scala       | 25 +++++-
 .../james/jmap/json/ResponseSerializer.scala       | 14 +++-
 .../apache/james/jmap/routes/WebSocketRoutes.scala | 35 +++++++-
 .../jmap/change/StateChangeListenerTest.scala      |  9 +-
 6 files changed, 150 insertions(+), 36 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
index b80740d..c92112b 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
@@ -23,7 +23,9 @@ import java.nio.charset.StandardCharsets
 
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.api.change.State
 import org.apache.james.jmap.api.model.AccountId
+import org.apache.james.jmap.core.PushState
 import org.apache.james.jmap.draft.JmapGuiceProbe
 import org.apache.james.jmap.rfc8621.contract.Fixture._
 import org.apache.james.mailbox.MessageManager.AppendCommand
@@ -504,11 +506,13 @@ trait WebSocketContract {
     Thread.sleep(100)
 
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
-    val emailState: String = jmapGuiceProbe.getLatestEmailState(accountId).getValue.toString
-    val mailboxState: String = jmapGuiceProbe.getLatestMailboxState(accountId).getValue.toString
+    val emailState: State = jmapGuiceProbe.getLatestEmailState(accountId)
+    val mailboxState: State = jmapGuiceProbe.getLatestMailboxState(accountId)
 
-    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
-    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"$emailState"}}}"""
+    val globalState1: String = PushState.fromOption(Some(mailboxState), None).get.value
+    val globalState2: String = PushState.fromOption(None, Some(emailState)).get.value
+    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState1"}"""
+    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}"}},"pushState":"$globalState2"}"""
 
     assertThat(response.toOption.get.asJava)
       .hasSize(3) // email notification + mailbox notification + API response
@@ -630,11 +634,13 @@ trait WebSocketContract {
     Thread.sleep(100)
 
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
-    val emailState: String = jmapGuiceProbe.getLatestEmailState(accountId).getValue.toString
-    val mailboxState: String = jmapGuiceProbe.getLatestMailboxState(accountId).getValue.toString
+    val mailboxState: State = jmapGuiceProbe.getLatestMailboxState(accountId)
+    val emailState: State = jmapGuiceProbe.getLatestEmailState(accountId)
 
-    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
-    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"EmailDelivery":"$emailState","Email":"$emailState"}}}"""
+    val globalState1: String = PushState.fromOption(Some(mailboxState), None).get.value
+    val globalState2: String = PushState.fromOption(None, Some(emailState)).get.value
+    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState1"}"""
+    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"EmailDelivery":"${emailState.getValue}","Email":"${emailState.getValue}"}},"pushState":"$globalState2"}"""
 
     assertThat(response.toOption.get.asJava)
       .hasSize(3) // email notification + mailbox notification + API response
@@ -646,7 +652,6 @@ trait WebSocketContract {
   // For client compatibility purposes
   def emailDeliveryShouldNotIncludeFlagUpdatesAndDeletes(server: GuiceJamesServer): Unit = {
     val bobPath = MailboxPath.inbox(BOB)
-    val accountId: AccountId = AccountId.fromUsername(BOB)
     val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
 
     Thread.sleep(100)
@@ -824,11 +829,13 @@ trait WebSocketContract {
     Thread.sleep(100)
 
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
-    val emailState: String = jmapGuiceProbe.getLatestEmailState(accountId).getValue.toString
-    val mailboxState: String = jmapGuiceProbe.getLatestMailboxState(accountId).getValue.toString
+    val emailState: State = jmapGuiceProbe.getLatestEmailState(accountId)
+    val mailboxState: State = jmapGuiceProbe.getLatestMailboxState(accountId)
 
-    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
-    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"EmailDelivery":"$emailState","Email":"$emailState"}}}"""
+    val globalState1: String = PushState.fromOption(Some(mailboxState), None).get.value
+    val globalState2: String = PushState.fromOption(None, Some(emailState)).get.value
+    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState1"}"""
+    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"EmailDelivery":"${emailState.getValue}","Email":"${emailState.getValue}"}},"pushState":"$globalState2"}"""
 
     assertThat(response.toOption.get.asJava)
       .hasSize(3) // email notification + mailbox notification + API response
@@ -895,11 +902,13 @@ trait WebSocketContract {
 
     Thread.sleep(100)
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
-    val emailState: String = jmapGuiceProbe.getLatestEmailState(accountId).getValue.toString
-    val mailboxState: String = jmapGuiceProbe.getLatestMailboxState(accountId).getValue.toString
+    val emailState: State = jmapGuiceProbe.getLatestEmailState(accountId)
+    val mailboxState: State = jmapGuiceProbe.getLatestMailboxState(accountId)
 
-    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
-    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"$emailState"}}}"""
+    val globalState1: String = PushState.fromOption(Some(mailboxState), None).get.value
+    val globalState2: String = PushState.fromOption(None, Some(emailState)).get.value
+    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState1"}"""
+    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}"}},"pushState":"$globalState2"}"""
 
     assertThat(response.toOption.get.asJava)
       .hasSize(2) // No Email notification
@@ -957,11 +966,13 @@ trait WebSocketContract {
 
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
     val accountId: AccountId = AccountId.fromUsername(BOB)
-    val emailState: String = jmapGuiceProbe.getLatestEmailStateWithDelegation(accountId).getValue.toString
-    val mailboxState: String = jmapGuiceProbe.getLatestMailboxStateWithDelegation(accountId).getValue.toString
+    val emailState: State = jmapGuiceProbe.getLatestEmailStateWithDelegation(accountId)
+    val mailboxState: State = jmapGuiceProbe.getLatestMailboxStateWithDelegation(accountId)
 
-    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
-    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"$emailState"}}}"""
+    val globalState1: String = PushState.fromOption(Some(mailboxState), None).get.value
+    val globalState2: String = PushState.fromOption(None, Some(emailState)).get.value
+    val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState1"}"""
+    val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}"}},"pushState":"$globalState2"}"""
 
     assertThat(response.toOption.get.asJava)
       .hasSize(2) // email notification + mailbox notification
@@ -1068,6 +1079,48 @@ trait WebSocketContract {
       .body
   }
 
+  @Test
+  @Timeout(180)
+  def pushEnableRequestWithPushStateShouldReturnServerState(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    val accountId: AccountId = AccountId.fromUsername(BOB)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    Thread.sleep(100)
+
+    val response: Either[String, String] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, String] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "WebSocketPushEnable",
+                |  "dataTypes": ["Mailbox", "Email"],
+                |  "pushState": "aaa"
+                |}""".stripMargin))
+
+            Thread.sleep(100)
+
+            ws.receive()
+              .map { case t: Text =>
+                t.payload
+              }
+        })
+        .send(backend)
+        .body
+
+    Thread.sleep(100)
+
+    val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    val emailState: State = jmapGuiceProbe.getLatestEmailState(accountId)
+    val mailboxState: State = jmapGuiceProbe.getLatestMailboxState(accountId)
+    val globalState: PushState = PushState.from(mailboxState, emailState)
+    val pushEnableResponse: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"${mailboxState.getValue}","Email":"${emailState.getValue}"}},"pushState":"${globalState.value}"}"""
+
+    assertThat(response.toOption.get)
+      .isEqualTo(pushEnableResponse)
+  }
+
   private def authenticatedRequest(server: GuiceJamesServer): RequestT[Identity, Either[String, String], Any] = {
     val port = server.getProbe(classOf[JmapGuiceProbe])
       .getJmapPort
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala
index 25ea386..0d556a9 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala
@@ -22,7 +22,8 @@ package org.apache.james.jmap.change
 import org.apache.james.core.Username
 import org.apache.james.events.Event
 import org.apache.james.events.Event.EventId
-import org.apache.james.jmap.core.{AccountId, State, StateChange}
+import org.apache.james.jmap.api.change.{State => JavaState}
+import org.apache.james.jmap.core.{AccountId, PushState, State, StateChange}
 
 object TypeName {
   val ALL: Set[TypeName] = Set(EmailTypeName, MailboxTypeName, ThreadTypeName, IdentityTypeName, EmailSubmissionTypeName, EmailDeliveryTypeName)
@@ -91,7 +92,10 @@ case class StateChangeEvent(eventId: EventId,
         VacationResponseTypeName.asMap(vacationResponseState) ++
           MailboxTypeName.asMap(mailboxState) ++
           EmailDeliveryTypeName.asMap(emailDeliveryState) ++
-          EmailTypeName.asMap(emailState))))
+          EmailTypeName.asMap(emailState))),
+      PushState.fromOption(
+        mailboxState.map(state => JavaState.of(state.value)),
+        emailState.map(state => JavaState.of(state.value))))
 
   override val getUsername: Username = username
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/WebSocketTransport.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/WebSocketTransport.scala
index 5597901..196497d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/WebSocketTransport.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/WebSocketTransport.scala
@@ -19,6 +19,10 @@
 
 package org.apache.james.jmap.core
 
+import java.nio.charset.StandardCharsets
+
+import com.google.common.hash.Hashing
+import org.apache.james.jmap.api.change.State
 import org.apache.james.jmap.change.{TypeName, TypeState}
 import org.apache.james.jmap.routes.PingPolicy.Interval
 
@@ -36,15 +40,30 @@ case class WebSocketResponse(requestId: Option[RequestId], responseObject: Respo
 
 case class WebSocketError(requestId: Option[RequestId], problemDetails: ProblemDetails) extends OutboundMessage
 
-case class StateChange(changes: Map[AccountId, TypeState]) extends OutboundMessage {
+object PushState {
+  def from(mailboxState: State, emailState: State): PushState =
+    PushState(hashStates(List(mailboxState, emailState)))
+
+  def fromOption(mailboxState: Option[State], emailState: Option[State]): Option[PushState] =
+    List(mailboxState, emailState).flatten match {
+      case Nil => None
+      case states => Some(PushState(hashStates(states)))
+    }
+
+  private def hashStates(states: List[State]): String = Hashing.sha256().hashString(states.mkString("_"), StandardCharsets.UTF_8).toString
+}
+
+case class PushState(value: String)
+
+case class StateChange(changes: Map[AccountId, TypeState], pushState: Option[PushState]) extends OutboundMessage {
 
   def filter(types: Set[TypeName]): Option[StateChange] =
     Option(changes.flatMap {
       case (accountId, typeState) => typeState.filter(types).map(typeState => (accountId, typeState))
     })
     .filter(_.nonEmpty)
-    .map(StateChange)
+    .map(changes => StateChange(changes, pushState))
 }
 
-case class WebSocketPushEnable(dataTypes: Option[Set[TypeName]]) extends WebSocketInboundMessage
+case class WebSocketPushEnable(dataTypes: Option[Set[TypeName]], pushState: Option[PushState]) extends WebSocketInboundMessage
 case object WebSocketPushDisable extends WebSocketInboundMessage
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
index 847acab..89d4305 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
@@ -191,6 +191,7 @@ object ResponseSerializer {
       .fold(errorMessage => JsError(errorMessage), JsSuccess(_))
     case _ => JsError("Expecting a JsString as typeName")
   }
+  private implicit val pushStateReads: Reads[PushState] = Json.valueReads[PushState]
   private implicit val webSocketPushEnableReads: Reads[WebSocketPushEnable] = Json.reads[WebSocketPushEnable]
   private implicit val webSocketInboundReads: Reads[WebSocketInboundMessage] = {
     case json: JsObject =>
@@ -208,10 +209,17 @@ object ResponseSerializer {
   private implicit val typeStateMapWrites: Writes[Map[TypeName, State]] = mapWrites[TypeName, State](_.asString(), stateWrites)
   private implicit val typeStateWrites: Writes[TypeState] = Json.valueWrites[TypeState]
   private implicit val changeWrites: OWrites[Map[AccountId, TypeState]] = mapWrites[AccountId, TypeState](_.id.value, typeStateWrites)
+  private implicit val pushStateWrites: Writes[PushState] = Json.valueWrites[PushState]
   private implicit val stateChangeWrites: Writes[StateChange] = stateChange =>
-    JsObject(Map(
-      "@type" -> JsString("StateChange"),
-      "changed" -> changeWrites.writes(stateChange.changes)))
+    stateChange.pushState.map(pushState =>
+      JsObject(Map(
+        "@type" -> JsString("StateChange"),
+        "changed" -> changeWrites.writes(stateChange.changes),
+        "pushState" -> pushStateWrites.writes(pushState))))
+      .getOrElse(
+        JsObject(Map(
+          "@type" -> JsString("StateChange"),
+          "changed" -> changeWrites.writes(stateChange.changes))))
 
   private implicit val webSocketResponseWrites: Writes[WebSocketResponse] = response => {
     val apiResponseJson: JsObject = responseObjectFormat.writes(response.responseObject)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
index 5f556a1..360e1bc 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/WebSocketRoutes.scala
@@ -27,11 +27,14 @@ import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
 import io.netty.handler.codec.http.HttpMethod
 import io.netty.handler.codec.http.websocketx.WebSocketFrame
 import javax.inject.{Inject, Named}
+import org.apache.james.core.Username
 import org.apache.james.events.{EventBus, Registration}
 import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
 import org.apache.james.jmap.JMAPUrls.JMAP_WS
-import org.apache.james.jmap.change.{AccountIdRegistrationKey, StateChangeListener, TypeName}
-import org.apache.james.jmap.core.{OutboundMessage, ProblemDetails, RequestId, WebSocketError, WebSocketPushDisable, WebSocketPushEnable, WebSocketRequest, WebSocketResponse}
+import org.apache.james.jmap.api.change.{EmailChangeRepository, MailboxChangeRepository}
+import org.apache.james.jmap.api.model.{AccountId => JavaAccountId}
+import org.apache.james.jmap.change.{AccountIdRegistrationKey, StateChangeListener, TypeName, _}
+import org.apache.james.jmap.core.{OutboundMessage, ProblemDetails, RequestId, WebSocketError, WebSocketPushDisable, WebSocketPushEnable, WebSocketRequest, WebSocketResponse, _}
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
 import org.apache.james.jmap.http.{Authenticator, UserProvisioning}
 import org.apache.james.jmap.json.ResponseSerializer
@@ -64,7 +67,9 @@ case class ClientContext(outbound: Sinks.Many[OutboundMessage], pushRegistration
 class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
                                  userProvisioner: UserProvisioning,
                                  @Named(JMAPInjectionKeys.JMAP) eventBus: EventBus,
-                                 jmapApi: JMAPApi) extends JMAPRoutes {
+                                 jmapApi: JMAPApi,
+                                 mailboxChangeRepository: MailboxChangeRepository,
+                                 emailChangeRepository: EmailChangeRepository) extends JMAPRoutes {
 
   override def routes(): stream.Stream[JMAPRoute] = stream.Stream.of(
     JMAPRoute.builder
@@ -131,10 +136,32 @@ class WebSocketRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticato
                 StateChangeListener(pushEnable.dataTypes.getOrElse(TypeName.ALL), clientContext.outbound),
                 AccountIdRegistrationKey.of(clientContext.session.getUser)))
               .doOnNext(newRegistration => clientContext.withRegistration(newRegistration))
-              .`then`()
+              .`then`(sendPushStateIfRequested(pushEnable, clientContext))
           case WebSocketPushDisable => SMono.fromCallable(() => clientContext.clean())
       })
 
+  private def sendPushStateIfRequested(pushEnable: WebSocketPushEnable, clientContext: ClientContext): SMono[Unit] =
+    pushEnable.pushState
+      .map(_ => sendPushState(clientContext))
+      .getOrElse(SMono.empty)
+
+  private def sendPushState(clientContext: ClientContext): SMono[Unit] = {
+    val username: Username = clientContext.session.getUser
+    val accountId: AccountId = AccountId.from(username).fold(
+      failure => throw new IllegalArgumentException(failure),
+      success => success)
+    SMono(
+      for {
+        mailboxState <- mailboxChangeRepository.getLatestStateWithDelegation(JavaAccountId.fromUsername(username))
+        emailState <- emailChangeRepository.getLatestStateWithDelegation(JavaAccountId.fromUsername(username))
+      } yield {
+        clientContext.outbound.emitNext(StateChange(Map(accountId -> TypeState(
+          MailboxTypeName.asMap(Some(State.fromJava(mailboxState))) ++
+            EmailTypeName.asMap(Some(State.fromJava(emailState))))),
+          Some(PushState.from(mailboxState, emailState))), FAIL_FAST)
+      })
+  }
+
   private def handleHttpHandshakeError(throwable: Throwable, response: HttpServerResponse): SMono[Void] =
     respondDetails(response, ProblemDetails.forThrowable(throwable))
 
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala
index d2d717a..e9b318c 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala
@@ -21,7 +21,8 @@ package org.apache.james.jmap.change
 
 import org.apache.james.core.Username
 import org.apache.james.events.Event.EventId
-import org.apache.james.jmap.core.{AccountId, OutboundMessage, State, StateChange}
+import org.apache.james.jmap.api.change.{State => JavaState}
+import org.apache.james.jmap.core.{AccountId, OutboundMessage, PushState, State, StateChange}
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.Test
 import reactor.core.publisher.Sinks
@@ -48,10 +49,11 @@ class StateChangeListenerTest {
     SMono(listener.reactiveEvent(event)).subscribeOn(Schedulers.elastic()).block()
     sink.emitComplete(EmitFailureHandler.FAIL_FAST)
 
+    val globalState = PushState.from(JavaState.of(mailboxState.value), JavaState.of(emailState.value))
     assertThat(sink.asFlux().collectList().block())
       .containsExactly(StateChange(Map(AccountId.from(Username.of("bob")).toOption.get  -> TypeState(Map(
         MailboxTypeName -> mailboxState,
-        EmailTypeName -> emailState)))))
+        EmailTypeName -> emailState))), Some(globalState)))
   }
 
   @Test
@@ -68,9 +70,10 @@ class StateChangeListenerTest {
     SMono(listener.reactiveEvent(event)).subscribeOn(Schedulers.elastic()).block()
     sink.emitComplete(EmitFailureHandler.FAIL_FAST)
 
+    val globalState = PushState.from(JavaState.of(mailboxState.value), JavaState.of(emailState.value))
     assertThat(sink.asFlux().collectList().block())
       .containsExactly(StateChange(Map(AccountId.from(Username.of("bob")).toOption.get -> TypeState(Map(
-        MailboxTypeName -> mailboxState)))))
+        MailboxTypeName -> mailboxState))), Some(globalState)))
   }
 
   @Test


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


[james-project] 08/12: JAMES-3400 Add remove and list domain aliases

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit bf2535a45e6a4270c1a7713a5a01b10104710190
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat Feb 13 10:50:48 2021 +0700

    JAMES-3400 Add remove and list domain aliases
---
 server/protocols/webadmin-cli/README.md            |  18 ++
 server/protocols/webadmin-cli/pom.xml              |   4 +
 .../java/org/apache/james/cli/WebAdminCli.java     |   5 +
 ...mainCommand.java => AddDomainAliasCommand.java} |  54 +++---
 .../org/apache/james/cli/domain/DomainCommand.java |   5 +-
 ...ainCommand.java => ListDomainAliasCommand.java} |  42 ++---
 ...nCommand.java => RemoveDomainAliasCommand.java} |  51 ++---
 .../org/apache/james/httpclient/DomainClient.java  |  39 ++++
 .../org/apache/james/cli/DomainManageTest.java     | 206 ++++++++++++++-------
 9 files changed, 283 insertions(+), 141 deletions(-)

diff --git a/server/protocols/webadmin-cli/README.md b/server/protocols/webadmin-cli/README.md
index b8b4235..0b45d48 100644
--- a/server/protocols/webadmin-cli/README.md
+++ b/server/protocols/webadmin-cli/README.md
@@ -124,6 +124,24 @@ Show all domains' name on the list.
 {cli} domain list
 ```
 
+### List domain aliases for a domain
+Show all domains' name on the list.
+```
+{cli} domain listAliases <domain>
+```
+
+### Create a domain alias for a domain
+
+```
+{cli} domain addAlias <domain> <sourceOfTheAlias>
+```
+
+### Remove a domain alias for a domain
+
+```
+{cli} domain removeAlias <domain> <sourceOfTheAlias>
+```
+
 
 
 ## Manage Users
diff --git a/server/protocols/webadmin-cli/pom.xml b/server/protocols/webadmin-cli/pom.xml
index 49ec90e..bdfe312 100644
--- a/server/protocols/webadmin-cli/pom.xml
+++ b/server/protocols/webadmin-cli/pom.xml
@@ -59,6 +59,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
             <groupId>info.picocli</groupId>
             <artifactId>picocli</artifactId>
             <version>4.6.1</version>
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
index 74b450c..02acc00 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
@@ -20,6 +20,7 @@
 package org.apache.james.cli;
 
 import java.io.PrintStream;
+import java.util.Collection;
 import java.util.Optional;
 import java.util.concurrent.Callable;
 
@@ -84,6 +85,10 @@ public class WebAdminCli implements Callable<Integer> {
         return execute(out, err, args);
     }
 
+    public static int executeFluent(PrintStream out, PrintStream err, Collection<String> args) {
+        return execute(out, err, args.stream().toArray(String[]::new));
+    }
+
     public Feign.Builder feignClientFactory(PrintStream err) {
         return new FeignClientFactory(new JwtToken(Optional.ofNullable(jwt),
             Optional.ofNullable(jwtFilePath), err))
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java
similarity index 54%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java
index ec38083..d669dfd 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/AddDomainAliasCommand.java
@@ -19,42 +19,46 @@
 
 package org.apache.james.cli.domain;
 
-import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.Callable;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.james.cli.WebAdminCli;
 import org.apache.james.httpclient.DomainClient;
 
+import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-        name = "domain",
-        description = "Manage Domains",
-        subcommands = {
-            DomainListCommand.class,
-            DomainCreateCommand.class,
-            DomainDeleteCommand.class,
-            DomainExistCommand.class
-        })
-public class DomainCommand implements Callable<Integer> {
-
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public DomainCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+    name = "addAlias",
+    description = "Create a new domain alias")
+public class AddDomainAliasCommand implements Callable<Integer> {
+
+    public static final int CREATED_CODE = 204;
+
+    @CommandLine.ParentCommand DomainCommand domainCommand;
+
+    @CommandLine.Parameters(description = "Destination of the domain alias. This is the domain this alias belongs to.")
+    String destinationDomain;
+
+    @CommandLine.Parameters(description = "Source of the domain alias.")
+    String sourceDomain;
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
-    }
-
-    public DomainClient fullyQualifiedURL(String partOfUrl) {
-        return webAdminCli.feignClientFactory(err).target(DomainClient.class, webAdminCli.jamesUrl + partOfUrl);
+        try {
+            DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
+            Response rs = domainClient.addADomainAlias(destinationDomain, sourceDomain);
+            if (rs.status() == CREATED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                domainCommand.err.println(IOUtils.toString(rs.body().asInputStream(), StandardCharsets.UTF_8));
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(domainCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
index ec38083..1036ec5 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
@@ -34,7 +34,10 @@ import picocli.CommandLine;
             DomainListCommand.class,
             DomainCreateCommand.class,
             DomainDeleteCommand.class,
-            DomainExistCommand.class
+            DomainExistCommand.class,
+            AddDomainAliasCommand.class,
+            ListDomainAliasCommand.class,
+            RemoveDomainAliasCommand.class
         })
 public class DomainCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/ListDomainAliasCommand.java
similarity index 63%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/ListDomainAliasCommand.java
index ec38083..79b6006 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/ListDomainAliasCommand.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.cli.domain;
 
-import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
@@ -28,33 +27,26 @@ import org.apache.james.httpclient.DomainClient;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-        name = "domain",
-        description = "Manage Domains",
-        subcommands = {
-            DomainListCommand.class,
-            DomainCreateCommand.class,
-            DomainDeleteCommand.class,
-            DomainExistCommand.class
-        })
-public class DomainCommand implements Callable<Integer> {
+    name = "listAliases",
+    description = "List domain aliases for a given domain")
+public class ListDomainAliasCommand implements Callable<Integer> {
+    @CommandLine.ParentCommand DomainCommand domainCommand;
 
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public DomainCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+    @CommandLine.Parameters
+    String domainName;
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
+        try {
+            DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
+            domainClient.getDomainAliasList(domainName)
+                .stream()
+                .map(DomainClient.DomainAliasResponse::getSource)
+                .forEach(domainCommand.out::println);
+            return WebAdminCli.CLI_FINISHED_SUCCEED;
+        } catch (Exception e) {
+            e.printStackTrace(domainCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
-
-    public DomainClient fullyQualifiedURL(String partOfUrl) {
-        return webAdminCli.feignClientFactory(err).target(DomainClient.class, webAdminCli.jamesUrl + partOfUrl);
-    }
-
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java
similarity index 58%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java
index ec38083..5e715d3 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/DomainCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/domain/RemoveDomainAliasCommand.java
@@ -19,42 +19,43 @@
 
 package org.apache.james.cli.domain;
 
-import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
 import org.apache.james.httpclient.DomainClient;
 
+import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-        name = "domain",
-        description = "Manage Domains",
-        subcommands = {
-            DomainListCommand.class,
-            DomainCreateCommand.class,
-            DomainDeleteCommand.class,
-            DomainExistCommand.class
-        })
-public class DomainCommand implements Callable<Integer> {
-
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public DomainCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+    name = "removeAlias",
+    description = "Remove a domain alias")
+public class RemoveDomainAliasCommand implements Callable<Integer> {
+
+    public static final int CREATED_CODE = 204;
+
+    @CommandLine.ParentCommand DomainCommand domainCommand;
+
+    @CommandLine.Parameters(description = "Destination of the domain alias. This is the domain this alias belongs to.")
+    String destinationDomain;
+
+    @CommandLine.Parameters(description = "Source of the domain alias.")
+    String sourceDomain;
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
-    }
-
-    public DomainClient fullyQualifiedURL(String partOfUrl) {
-        return webAdminCli.feignClientFactory(err).target(DomainClient.class, webAdminCli.jamesUrl + partOfUrl);
+        try {
+            DomainClient domainClient = domainCommand.fullyQualifiedURL("/domains");
+            Response rs = domainClient.deleteADomainAlias(destinationDomain, sourceDomain);
+            if (rs.status() == CREATED_CODE) {
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(domainCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/DomainClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/DomainClient.java
index d195b79..a67746d 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/DomainClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/DomainClient.java
@@ -20,12 +20,43 @@
 package org.apache.james.httpclient;
 
 import java.util.List;
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 import feign.Param;
 import feign.RequestLine;
 import feign.Response;
 
 public interface DomainClient {
+    class DomainAliasResponse {
+        private final String source;
+
+        @JsonCreator
+        public DomainAliasResponse(@JsonProperty("source") String source) {
+            this.source = source;
+        }
+
+        public String getSource() {
+            return source;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof DomainAliasResponse) {
+                DomainAliasResponse that = (DomainAliasResponse) o;
+
+                return Objects.equals(this.source, that.source);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(source);
+        }
+    }
 
     @RequestLine("GET")
     List<String> getDomainList();
@@ -39,4 +70,12 @@ public interface DomainClient {
     @RequestLine("GET /{domainName}")
     Response doesExist(@Param("domainName") String domainName);
 
+    @RequestLine("DELETE /{destinationDomain}/aliases/{sourceDomain}")
+    Response deleteADomainAlias(@Param("destinationDomain") String destinationDomain, @Param("sourceDomain") String sourceDomain);
+
+    @RequestLine("PUT /{destinationDomain}/aliases/{sourceDomain}")
+    Response addADomainAlias(@Param("destinationDomain") String destinationDomain, @Param("sourceDomain") String sourceDomain);
+
+    @RequestLine("GET /{domainName}/aliases")
+    List<DomainAliasResponse> getDomainAliasList(@Param("domainName") String domainName);
 }
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/DomainManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/DomainManageTest.java
index a219812..ea56084 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/DomainManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/DomainManageTest.java
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
@@ -32,11 +33,16 @@ import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.util.Port;
 import org.apache.james.utils.WebAdminGuiceProbe;
 import org.apache.james.webadmin.integration.WebadminIntegrationTestModule;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-public class DomainManageTest {
+import com.google.common.collect.ImmutableList;
 
+class DomainManageTest {
     @RegisterExtension
     static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
             .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
@@ -48,113 +54,183 @@ public class DomainManageTest {
     private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
     private final ByteArrayOutputStream errorStreamCaptor = new ByteArrayOutputStream();
 
-    @Test
-    void domainListCommandShouldWShowOnlyDefaultDomain(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+    Port port;
 
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-                "--url", "http://127.0.0.1:" + port.getValue(), "domain", "list");
-        assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("localhost".toCharArray());
+    @BeforeEach
+    void setUp(GuiceJamesServer server) {
+        port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+    }
+
+    @AfterEach
+    void tearDown() {
+        System.err.println(new String(errorStreamCaptor.toByteArray(), StandardCharsets.UTF_8));
     }
 
     @Test
-    void domainCreateCommandWithValidNameShouldSuccessfully(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+    void domainListCommandShouldWShowOnlyDefaultDomain() {
+        int exitCode = executeFluent("domain", "list");
 
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "create", "linagora.com");
+        SoftAssertions.assertSoftly( softly -> {
+            assertThat(exitCode).isEqualTo(0);
+            assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("localhost".toCharArray());
+        });
+    }
 
-        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "list");
+    @Test
+    void domainCreateCommandWithValidNameShouldSuccessfully() {
+        int exitCode = executeFluent("domain", "create", "linagora.com");
 
-        assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString()).contains("linagora.com");
+        executeFluent("domain", "list");
+
+        SoftAssertions.assertSoftly( softly -> {
+            assertThat(exitCode).isEqualTo(0);
+            assertThat(outputStreamCaptor.toString()).contains("linagora.com");
+        });
     }
 
     @Test
-    void domainCreateCommandWithInvalidNameShouldFailed(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
-
-        int exitCode1 = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "create", "@linagora.com");
+    void domainCreateCommandWithInvalidNameShouldFailed() {
+        int exitCode1 = executeFluent("domain", "create", "@linagora.com");
 
-        int exitCode2 = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "create", "linagora.com/");
+        int exitCode2 = executeFluent("domain", "create", "linagora.com/");
 
-        int exitCode3 = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "create", "");
+        int exitCode3 = executeFluent("domain", "create", "");
 
         WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
             "--url", "http://127.0.0.1:" + port.getValue(), "domain", "list");
 
-        assertThat(exitCode1).isEqualTo(1);
-        assertThat(exitCode2).isEqualTo(1);
-        assertThat(exitCode3).isEqualTo(1);
-        assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("localhost".toCharArray());
+        SoftAssertions.assertSoftly( softly -> {
+            assertThat(exitCode1).isEqualTo(1);
+            assertThat(exitCode2).isEqualTo(1);
+            assertThat(exitCode3).isEqualTo(1);
+            assertThat(outputStreamCaptor.toString().trim().toCharArray()).containsOnly("localhost".toCharArray());
+        });
     }
 
     @Test
-    void domainDeleteCommandWithValidDomainShouldSucceed(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
-
-        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "create", "linagora.com");
+    void domainDeleteCommandWithValidDomainShouldSucceed() {
+        executeFluent("domain", "create", "linagora.com");
 
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "delete", "linagora.com");
+        int exitCode = executeFluent("domain", "delete", "linagora.com");
 
-        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "list");
+        executeFluent("domain", "list");
 
-        assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().contains("linagora.com")).isFalse();
+        SoftAssertions.assertSoftly( softly -> {
+            assertThat(exitCode).isEqualTo(0);
+            assertThat(outputStreamCaptor.toString().contains("linagora.com")).isFalse();
+        });
     }
 
     @Test
-    void domainDeleteCommandWithDefaultDomainShouldFail(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
-
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "delete", "localhost");
+    void domainDeleteCommandWithDefaultDomainShouldFail() {
+        int exitCode = executeFluent("domain", "delete", "localhost");
 
         assertThat(exitCode).isEqualTo(1);
     }
 
     @Test
-    void domainExistCommandWithDefaultDomainShouldExist(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
-
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "exist", "localhost");
+    void domainExistCommandWithDefaultDomainShouldExist() {
+        int exitCode = executeFluent("domain", "exist", "localhost");
 
-        assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("localhost exists");
+        SoftAssertions.assertSoftly( softly -> {
+            assertThat(exitCode).isEqualTo(0);
+            assertThat(outputStreamCaptor.toString().trim()).isEqualTo("localhost exists");
+        });
     }
 
     @Test
-    void domainExistCommandWithNonExistingDomainShouldFail(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
-
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "exist", "linagora.com");
+    void domainExistCommandWithNonExistingDomainShouldFail() {
+        int exitCode = executeFluent("domain", "exist", "linagora.com");
 
         assertThat(exitCode).isEqualTo(0);
         assertThat(outputStreamCaptor.toString().trim()).isEqualTo("linagora.com does not exist");
     }
 
     @Test
-    void domainExistCommandWithAddedDomainShouldSucceed(GuiceJamesServer server) {
-        Port port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+    void domainExistCommandWithAddedDomainShouldSucceed() {
+        executeFluent("domain", "create", "linagora.com");
 
-        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "create", "linagora.com");
+        int exitCode = executeFluent("domain", "exist", "linagora.com");
 
-        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
-            "--url", "http://127.0.0.1:" + port.getValue(), "domain", "exist", "linagora.com");
+        SoftAssertions.assertSoftly( softly -> {
+            assertThat(exitCode).isEqualTo(0);
+            assertThat(outputStreamCaptor.toString().trim()).isEqualTo("linagora.com exists");
+        });
+    }
 
-        assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("linagora.com exists");
+    @Nested
+    class DomainAliases {
+        @BeforeEach
+        void setUp() {
+            executeFluent("domain", "create", "linagora.com");
+            executeFluent("domain", "create", "linagora-james.com");
+        }
+
+        @Test
+        void listDomainAliasShouldReturnEmptyByDefault() {
+            int exitCode = executeFluent("domain", "listAliases", "linagora.com");
+
+            SoftAssertions.assertSoftly( softly -> {
+                assertThat(exitCode).isEqualTo(0);
+                assertThat(outputStreamCaptor.toString().trim()).hasSize(0);
+            });
+        }
+
+        @Test
+        void addDomainAliasShouldBeIdempotent() {
+            executeFluent("domain", "addAlias", "linagora.com", "linagora-james.com");
+            int exitCode = executeFluent("domain", "addAlias", "linagora.com", "linagora-james.com");
+
+            assertThat(exitCode).isEqualTo(0);
+        }
+
+        @Test
+        void removeDomainAliasShouldBeIdempotent() {
+            int exitCode = executeFluent("domain", "removeAlias", "linagora.com", "linagora-james.com");
+
+            assertThat(exitCode).isEqualTo(0);
+        }
+
+        @Test
+        void listDomainAliasShouldNotReturnRemovedValues() {
+            executeFluent("domain", "addAlias", "linagora.com", "linagora-james.com");
+            executeFluent("domain", "removeAlias", "linagora.com", "linagora-james.com");
+
+            int exitCode = executeFluent("domain", "listAliases", "linagora.com");
+
+            SoftAssertions.assertSoftly( softly -> {
+                assertThat(exitCode).isEqualTo(0);
+                assertThat(outputStreamCaptor.toString().trim()).hasSize(0);
+            });
+        }
+
+        @Test
+        void listDomainAliasShouldReturnAddedValues() {
+            executeFluent("domain", "addAlias", "linagora.com", "linagora-james.com");
+
+            int exitCode = executeFluent("domain", "listAliases", "linagora.com");
+
+            SoftAssertions.assertSoftly( softly -> {
+                assertThat(exitCode).isEqualTo(0);
+                assertThat(outputStreamCaptor.toString().trim()).contains("linagora-james.com");
+            });
+        }
+
+        @Test
+        void addAliasShouldRequireAManageDomain() {
+            int exitCode = executeFluent("domain", "addAlias", "linagora.com", "unknown.com");
+
+            SoftAssertions.assertSoftly( softly -> {
+                assertThat(exitCode).isEqualTo(1);
+                assertThat(errorStreamCaptor.toString().trim()).isEqualTo("{\"statusCode\":404,\"type\":\"InvalidArgument\",\"message\":\"The domain list does not contain: unknown.com\",\"details\":null}");
+            });
+        }
     }
 
+    private int executeFluent(String... args) {
+        return WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            ImmutableList.<String>builder().add("--url", "http://127.0.0.1:" + port.getValue())
+                .addAll(ImmutableList.copyOf(args))
+                .build());
+    }
 }


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


[james-project] 06/12: JAMES-3504 Move POP3ProtocolHandlerChain to tests

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 4d01371907533398c0c52cb946020ae22a8ab70f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Feb 23 14:59:01 2021 +0700

    JAMES-3504 Move POP3ProtocolHandlerChain to tests
    
    This class is not used by production code
---
 .../java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/protocols/pop3/src/main/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java b/protocols/pop3/src/test/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java
similarity index 100%
rename from protocols/pop3/src/main/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java
rename to protocols/pop3/src/test/java/org/apache/james/protocols/pop3/POP3ProtocolHandlerChain.java


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


[james-project] 12/12: JAMES-3353 Document urn:apache:james:params:jmap:mail:quota JMAP extension

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit a2a77ad247330ceb148ee0c7751cbe7e347467bd
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu Jul 30 16:54:56 2020 +0700

    JAMES-3353 Document urn:apache:james:params:jmap:mail:quota JMAP extension
---
 .../jmap-rfc-8621/doc/specs/spec/mail/intro.mdown  |  12 +++
 .../doc/specs/spec/quotas/quotas.mdown             | 103 +++++++++++++++++++++
 2 files changed, 115 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown
index bedcfc3..e79551e 100644
--- a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/intro.mdown
@@ -143,6 +143,18 @@ This extension to the JMAP specification enables to:
  - Grant access on mailboxes, and the emails they contain to other users.
  - Enable display of shared mailboxes, and shared messages as part of the main user account, making effectively account sharing transparent from a client perspective.
 
+### urn:apache:james:params:jmap:mail:quota
+
+<aside class="notice">
+  Implemented
+</aside>
+
+This represents support for the Quota data type and associated API methods. Servers supporting this specification MUST add this property to the capabilities object.
+
+This extension is specific to the Apache James server and enables to:
+
+* read quotas usage and limits on mailboxes.
+
 ## Data Type Support in Different Accounts
 
 The server MUST include the appropriate capability strings as keys in the *accountCapabilities* property of any account with which the user may use the data types represented by that URI. Supported data types may differ between accounts the user has access to. For example, in the user's personal account, they may have access to all three sets of data, but in a shared account, they may only have data for `urn:ietf:params:jmap:mail`. This means they can access Mailbox/Thread/Email data in  [...]
diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/quotas/quotas.mdown b/server/protocols/jmap-rfc-8621/doc/specs/spec/quotas/quotas.mdown
new file mode 100644
index 0000000..bc13260
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/quotas/quotas.mdown
@@ -0,0 +1,103 @@
+# Introduction
+
+<aside class="notice">
+  Implemented
+</aside>
+
+This specification defines a data model for handling mail quotas over JMAP, allowing you to read quota information.
+
+This specification does not address quota administration, which should be handled by other means.
+
+This specification is not official and proper to the James project usage at the moment.
+
+## Terminology
+
+### Quota
+
+A quota is a numeric upper limit that the server is enforcing. Quotas are applied to JMAP Mailbox objects.
+
+## Addition to the capabilities object
+
+The capabilities object is returned as part of the JMAP Session object; see [@!RFC8620], section 2.
+
+This document defines one additional capability URI.
+
+### urn:apache:james:params:jmap:mail:quota
+
+This represents support for the Quota data type and associated API methods.
+Servers supporting this specification MUST add this property to the capabilities object.
+
+This extension is specific to the Apache James server and is a temporary solution while waiting for the main work on
+quotas as part of the IETF group to be finalized as a proper RFC document.
+
+This extension enables to:
+
+* read quotas usage and limits on mailboxes.
+
+If specified, behavioral changes and additional fields defined hereafter MUST apply.
+
+If unspecified no additional fields to the Mailbox object are returned, and the behaviour needs to be exactly the one of `urn:ietf:params:jmap:mail`.
+
+# Quotas definition
+
+The quota is an addition to the Mailbox object that displays the limit set to a mailbox usage as well as the current usage in regard to that limit.
+
+## The QuotaId Data Type
+
+The **QuotaId** is a `String` defined with the name of the **QuotaRoot** (as defined in [RFC2087](https://tools.ietf.org/html/rfc2087.html)) it represents.
+It usually represents the ID attached to one or more **Quota** objects defined for that Mailbox.
+
+## The Quotas.Type Data Type
+
+The **Quotas.Type** is a `String` that represents the type of quota. It should either be "Storage" (amount of bytes) or "Message" (number of emails).
+
+## The Value Object
+
+The **Value** object represents the usage and limit of a type of quota. It MUST contain the folowing field:
+
+* **used**: `UnsignedInt` The current usage of that type of quota on the current mailbox. Computation of this value is handled by the server.
+
+The **Value** object MAY contain the following field:
+
+* **max**: `UnsignedInt` The limit set on this type of quota on the current mailbox.
+
+## The Quota Object
+
+The **Quota** object is a `Map[Quotas.Type, Value]` representing the quotas depending from a **QuotaId**. A quota can have more than one **Quotas.Type** associated to it.
+
+## The Quotas Object
+
+The **quotas** is a `Map[QuotaId, Quota]` of all quotas applying to the Mailbox object, with each Quota associated to its QuotaId.
+This object is returned as an extra field of the Mailbox object.
+
+## Example
+
+    {
+      "id": "2",
+      "name": "inbox",
+      "parentId":"1",
+      "role":"inbox",
+      "sortOrder":10,
+      "totalEmails": 42,
+      "unreadEmails": 10,
+      "totalThreads": 42,
+      "unreadThreads": 10,
+      "myRights": {
+        "mayReadItems": true,
+        "mayAddItems": true,
+        "mayRemoveItems": true,
+        "maySetSeen": true,
+        "maySetKeywords": true,
+        "mayCreateChild": true,
+        "mayRename": true,
+        "mayDelete": true,
+        "maySubmit": true
+      },
+      "isSubscribed": false,
+      "quotas": {
+        "#private&bob@domain.tld": {
+          "Storage": { "used": 1024, "max": 4098},
+          "Message": {"used": 42, "max": 64}
+        }
+      }
+    }


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