You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by rc...@apache.org on 2020/06/16 09:46:03 UTC

[james-project] branch master updated (db80c6c -> 3c2277b)

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

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


    from db80c6c  JAMES-3204 Push limit to Cassandra backend when reading messages
     new d73aa9c  [Refactoring] don't use raw array for listRights
     new b494b75  JAMES-3176 Rewritte MDN parsing with Parboiled scala
     new 3c2277b  JAMES-3176 backport fixes done in JAMES-3200

The 3 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:
 .../org/apache/james/mailbox/RightManager.java     |   4 +-
 .../james/mailbox/acl/MailboxACLResolver.java      |   4 +-
 .../james/mailbox/acl/UnionMailboxACLResolver.java |  30 +-
 .../james/mailbox/store/StoreMailboxManager.java   |   2 +-
 .../james/mailbox/store/StoreRightManager.java     |   3 +-
 mdn/pom.xml                                        |  19 +-
 .../main/java/org/apache/james/mdn/BaseParser.java |  37 -
 .../java/org/apache/james/mdn/MDNReportParser.java | 758 ---------------------
 .../org/apache/james/mdn/MDNReportParser.scala     | 478 +++++++++++++
 .../org/apache/james/mdn/MDNReportParserTest.java  | 314 ---------
 .../org/apache/james/mdn/MDNReportParserTest.scala | 287 ++++++++
 .../imap/encode/ListRightsResponseEncoder.java     |   3 +-
 .../imap/message/response/ListRightsResponse.java  |  12 +-
 .../james/imap/processor/ListRightsProcessor.java  |   2 +-
 .../imap/processor/ListRightsProcessorTest.java    |   9 +-
 .../AutomaticallySentMailDetectorImpl.java         |   5 +-
 .../mailet/ExtractMDNOriginalJMAPMessageId.java    |  12 +-
 17 files changed, 833 insertions(+), 1146 deletions(-)
 delete mode 100644 mdn/src/main/java/org/apache/james/mdn/BaseParser.java
 delete mode 100644 mdn/src/main/java/org/apache/james/mdn/MDNReportParser.java
 create mode 100644 mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala
 delete mode 100644 mdn/src/test/java/org/apache/james/mdn/MDNReportParserTest.java
 create mode 100644 mdn/src/test/scala/org/apache/james/mdn/MDNReportParserTest.scala


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


[james-project] 03/03: JAMES-3176 backport fixes done in JAMES-3200

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

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

commit 3c2277bf4989439f0cc1a19d978a593cd18cca18
Author: Rémi Kowalski <rk...@linagora.com>
AuthorDate: Tue Jun 9 10:37:20 2020 +0200

    JAMES-3176 backport fixes done in JAMES-3200
---
 mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala b/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala
index bd5b685..ea50183 100644
--- a/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala
+++ b/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala
@@ -354,7 +354,7 @@ class MDNReportParser(val input: ParserInput) extends Parser {
                          %d14-31 /          ;  return, line feed, and
                          %d127              ;  white space characters   */
   private def obsNoWsCtl = rule {
-    CharPredicate(33.toChar to 39.toChar) |
+    CharPredicate(1.toChar to 8.toChar) |
     ch(11) |
     ch(12) |
     CharPredicate(14.toChar to 31.toChar) |
@@ -368,7 +368,7 @@ class MDNReportParser(val input: ParserInput) extends Parser {
   private def vchar: Rule0 = rule { CharPredicate(21.toChar to 0x7e.toChar) }
 
   //   obs-qp          =   "\" (%d0 / obs-NO-WS-CTL / LF / CR)
-  private def obsQp: Rule0 = rule { "\\" ~ (ch(0xd0) | obsCText | lf | cr) }
+  private def obsQp: Rule0 = rule { "\\" ~ (ch(0.toChar) | obsNoWsCtl | lf | cr) }
 
   //   word            =   atom / quoted-string
   private def word: Rule0 = rule { atom | quotedString }
@@ -437,6 +437,7 @@ class MDNReportParser(val input: ParserInput) extends Parser {
     obsQtext
   }
 
+  //obs-qtext       =   obs-NO-WS-CTL
   private def obsQtext: Rule0 = obsNoWsCtl
 
   //   domain          =   dot-atom / domain-literal / obs-domain


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


[james-project] 02/03: JAMES-3176 Rewritte MDN parsing with Parboiled scala

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

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

commit b494b75c30776391725b07e814bd7ac7f69fd145
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Thu May 14 11:07:57 2020 +0200

    JAMES-3176 Rewritte MDN parsing with Parboiled scala
---
 mdn/pom.xml                                        |  19 +-
 .../main/java/org/apache/james/mdn/BaseParser.java |  37 -
 .../java/org/apache/james/mdn/MDNReportParser.java | 758 ---------------------
 .../org/apache/james/mdn/MDNReportParser.scala     | 477 +++++++++++++
 .../org/apache/james/mdn/MDNReportParserTest.java  | 314 ---------
 .../org/apache/james/mdn/MDNReportParserTest.scala | 287 ++++++++
 .../AutomaticallySentMailDetectorImpl.java         |   5 +-
 .../mailet/ExtractMDNOriginalJMAPMessageId.java    |  12 +-
 8 files changed, 790 insertions(+), 1119 deletions(-)

diff --git a/mdn/pom.xml b/mdn/pom.xml
index 1f3ec88..61a91da 100644
--- a/mdn/pom.xml
+++ b/mdn/pom.xml
@@ -49,11 +49,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.parboiled</groupId>
-            <artifactId>parboiled-java</artifactId>
-            <version>1.3.1</version>
-        </dependency>
-        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
@@ -69,6 +64,16 @@
             <groupId>javax.activation</groupId>
             <artifactId>javax.activation-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.parboiled</groupId>
+            <artifactId>parboiled_${scala.base}</artifactId>
+            <version>2.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.scala-lang.modules</groupId>
+            <artifactId>scala-java8-compat_${scala.base}</artifactId>
+            <scope>test</scope>
+        </dependency>
 </dependencies>
 
     <build>
@@ -81,6 +86,10 @@
                     <forkCount>1C</forkCount>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>net.alchim31.maven</groupId>
+                <artifactId>scala-maven-plugin</artifactId>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/mdn/src/main/java/org/apache/james/mdn/BaseParser.java b/mdn/src/main/java/org/apache/james/mdn/BaseParser.java
deleted file mode 100644
index f8da4d9..0000000
--- a/mdn/src/main/java/org/apache/james/mdn/BaseParser.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.mdn;
-
-public abstract class BaseParser<V> extends org.parboiled.BaseParser<V> {
-    @SuppressWarnings("unchecked")
-    <T> T popT() {
-        return (T) pop();
-    }
-
-    @SuppressWarnings("unchecked")
-    <T> T peekParent() {
-        return (T) peek(1);
-    }
-
-    @SuppressWarnings("unchecked")
-    <T> T peekT() {
-        return (T) peek();
-    }
-}
diff --git a/mdn/src/main/java/org/apache/james/mdn/MDNReportParser.java b/mdn/src/main/java/org/apache/james/mdn/MDNReportParser.java
deleted file mode 100644
index 8db2e95..0000000
--- a/mdn/src/main/java/org/apache/james/mdn/MDNReportParser.java
+++ /dev/null
@@ -1,758 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.mdn;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Optional;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.james.mdn.action.mode.DispositionActionMode;
-import org.apache.james.mdn.fields.AddressType;
-import org.apache.james.mdn.fields.Disposition;
-import org.apache.james.mdn.fields.Error;
-import org.apache.james.mdn.fields.ExtensionField;
-import org.apache.james.mdn.fields.FinalRecipient;
-import org.apache.james.mdn.fields.Gateway;
-import org.apache.james.mdn.fields.OriginalMessageId;
-import org.apache.james.mdn.fields.OriginalRecipient;
-import org.apache.james.mdn.fields.ReportingUserAgent;
-import org.apache.james.mdn.fields.Text;
-import org.apache.james.mdn.modifier.DispositionModifier;
-import org.apache.james.mdn.sending.mode.DispositionSendingMode;
-import org.apache.james.mdn.type.DispositionType;
-import org.parboiled.Parboiled;
-import org.parboiled.Rule;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.support.ParsingResult;
-
-import com.google.common.annotations.VisibleForTesting;
-
-public class MDNReportParser {
-    public MDNReportParser() {
-    }
-
-    public Optional<MDNReport> parse(InputStream is, String charset) throws IOException {
-        return parse(IOUtils.toString(is, charset));
-    }
-
-    public Optional<MDNReport> parse(String mdnReport) {
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionNotificationContent()).run(mdnReport);
-        if (result.matched) {
-            return Optional.of((MDNReport)result.resultValue);
-        }
-        return Optional.empty();
-    }
-
-    @VisibleForTesting
-    static class Parser extends BaseParser<Object> {
-        //   CFWS            =   (1*([FWS] comment) [FWS]) / FWS
-        Rule cfws() {
-            return FirstOf(
-                Sequence(
-                    OneOrMore(Sequence(Optional(fws()), comment())),
-                    Optional(fws())),
-                fws());
-        }
-
-        //   FWS             =   ([*WSP CRLF] 1*WSP) /  obs-FWS
-        Rule fws() {
-            return FirstOf(
-                Sequence(
-                    Optional(Sequence(
-                        ZeroOrMore(wsp()),
-                        crlf())),
-                    OneOrMore(wsp())),
-                obsFWS());
-        }
-
-        //         WSP            =  SP / HTAB
-        Rule wsp() {
-            return FirstOf(sp(), htab());
-        }
-
-        //         SP             =  %x20
-        Rule sp() {
-            return Ch((char)0x20);
-        }
-
-        //         HTAB           =  %x09
-        Rule htab() {
-            return Ch((char)0x09);
-        }
-
-        //         CRLF           =  CR LF
-        Rule crlf() {
-            return Sequence(cr(), lf());
-        }
-
-        //         CR             =  %x0D
-        Rule cr() {
-            return Ch((char)0x0D);
-        }
-
-        //         LF             =  %x0A
-        Rule lf() {
-            return Ch((char)0x0A);
-        }
-
-        //   obs-FWS         =   1*WSP *(CRLF 1*WSP)
-        Rule obsFWS() {
-            return Sequence(
-                OneOrMore(wsp()),
-                ZeroOrMore(Sequence(
-                    crlf(),
-                    OneOrMore(wsp()))));
-        }
-
-        //   comment         =   "(" *([FWS] ccontent) [FWS] ")"
-        Rule comment() {
-            return Sequence(
-                "(",
-                ZeroOrMore(Sequence(
-                    Optional(fws()),
-                    ccontent()
-                    )),
-                Optional(fws()),
-                ")");
-        }
-
-        //   ccontent        =   ctext / quoted-pair / comment
-        Rule ccontent() {
-            return FirstOf(ctext(), quotedPair(), comment());
-        }
-
-        /*   ctext           =   %d33-39 /          ; Printable US-ASCII
-                                 %d42-91 /          ;  characters not including
-                                 %d93-126 /         ;  "(", ")", or "\"
-                                 obs-ctext   */
-        Rule ctext() {
-            return FirstOf(
-                CharRange((char)33, (char)39),
-                CharRange((char)42, (char)91),
-                CharRange((char)93, (char)126),
-                obsCtext());
-        }
-
-        //   obs-ctext       =   obs-NO-WS-CTL
-        Rule obsCtext() {
-            return obsNoWsCtl();
-        }
-
-        /*   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control
-                                 %d11 /             ;  characters that do not
-                                 %d12 /             ;  include the carriage
-                                 %d14-31 /          ;  return, line feed, and
-                                 %d127              ;  white space characters   */
-        Rule obsNoWsCtl() {
-            return FirstOf(
-                CharRange((char)1, (char)8),
-                Ch((char)11),
-                Ch((char)12),
-                CharRange((char)14, (char)31),
-                Ch((char)127));
-        }
-
-        //   quoted-pair     =   ("\" (VCHAR / WSP)) / obs-qp
-        Rule quotedPair() {
-            return FirstOf(
-                Sequence(
-                    "\\",
-                    FirstOf(vchar(), wsp())),
-                obsQp());
-        }
-
-        //         VCHAR          =  %x21-7E
-        Rule vchar() {
-            return CharRange((char)0x21, (char)0x7E);
-        }
-
-        //   obs-qp          =   "\" (%d0 / obs-NO-WS-CTL / LF / CR)
-        Rule obsQp() {
-            return Sequence(
-                "\\",
-                FirstOf(
-                    Ch((char)0),
-                    obsCtext(),
-                    lf(),
-                    cr()));
-        }
-
-        //   word            =   atom / quoted-string
-        Rule word() {
-            return FirstOf(atom(), quotedString());
-        }
-
-        //    atom            =   [CFWS] 1*atext [CFWS]
-        Rule atom() {
-            return Sequence(
-                Optional(cfws()),
-                OneOrMore(atext()),
-                Optional(cfws()));
-        }
-
-        /*   atext           =   ALPHA / DIGIT /    ; Printable US-ASCII
-                                 "!" / "#" /        ;  characters not including
-                                 "$" / "%" /        ;  specials.  Used for atoms.
-                                 "&" / "'" /
-                                 "*" / "+" /
-                                 "-" / "/" /
-                                 "=" / "?" /
-                                 "^" / "_" /
-                                 "`" / "{" /
-                                 "|" / "}" /
-                                 "~"   */
-        Rule atext() {
-            return FirstOf(
-                alpha(), digit(),
-                "!", "#",
-                "$", "%",
-                "&", "'",
-                "*", "+",
-                "-", "/",
-                "=", "?",
-                "^", "_",
-                "`", "{",
-                "|", "}",
-                "~");
-        }
-
-        //         ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
-        Rule alpha() {
-            return FirstOf(CharRange((char)0x41, (char)0x5A), CharRange((char)0x61, (char)0x7A));
-        }
-
-        //         DIGIT          =  %x30-39
-        Rule digit() {
-            return CharRange((char)0x30, (char)0x39);
-        }
-
-        /*   quoted-string   =   [CFWS]
-                                 DQUOTE *([FWS] qcontent) [FWS] DQUOTE
-                                 [CFWS]   */
-        Rule quotedString() {
-            return Sequence(
-                Optional(cfws()),
-                Sequence(dquote(), ZeroOrMore(Sequence(Optional(fws()), qcontent()), Optional(fws()), dquote())),
-                Optional(cfws()));
-        }
-
-        //         DQUOTE         =  %x22
-        Rule dquote() {
-            return Ch((char)0x22);
-        }
-
-        //   obs-qtext       =   obs-NO-WS-CTL
-        Rule obsQtext() {
-            return obsNoWsCtl();
-        }
-
-        /*   qtext           =   %d33 /             ; Printable US-ASCII
-                                 %d35-91 /          ;  characters not including
-                                 %d93-126 /         ;  "\" or the quote character
-                                 obs-qtext  */
-        Rule qtext() {
-            return FirstOf(
-                (char)33,
-                CharRange((char)35, (char)91),
-                CharRange((char)93, (char)126),
-                obsQtext());
-        }
-
-        //   qcontent        =   qtext / quoted-pair
-        Rule qcontent() {
-            return FirstOf(qtext(), quotedPair());
-        }
-
-        //   domain          =   dot-atom / domain-literal / obs-domain
-        Rule domain() {
-            return FirstOf(dotAtom(), domainLiteral(), obsDomain());
-        }
-
-        //   dot-atom        =   [CFWS] dot-atom-text [CFWS]
-        Rule dotAtom() {
-            return Sequence(Optional(cfws()), dotAtomText(), Optional(cfws()));
-        }
-
-        //   dot-atom-text   =   1*atext *("." 1*atext)
-        Rule dotAtomText() {
-            return Sequence(OneOrMore(atext()), ZeroOrMore(Sequence(".", OneOrMore(atext()))));
-        }
-
-        //   domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
-        Rule domainLiteral() {
-            return Sequence(Optional(cfws()), "[", ZeroOrMore(Sequence(Optional(fws()), dtext()), Optional(fws()), "]", Optional(cfws())));
-        }
-
-        /*   dtext           =   %d33-90 /          ; Printable US-ASCII
-                                 %d94-126 /         ;  characters not including
-                                 obs-dtext          ;  "[", "]", or "\"   */
-        Rule dtext() {
-            return FirstOf(
-                CharRange((char)33, (char)90),
-                CharRange((char)94, (char)126),
-                obsDtext());
-        }
-
-        //   obs-dtext       =   obs-NO-WS-CTL / quoted-pair
-        Rule obsDtext() {
-            return FirstOf(obsNoWsCtl(), quotedPair());
-        }
-
-        //   obs-domain      =   atom *("." atom)
-        Rule obsDomain() {
-            return Sequence(atom(), ZeroOrMore(Sequence(".", atom())));
-        }
-
-        //   local-part      =   dot-atom / quoted-string / obs-local-part
-        Rule localPart() {
-            return FirstOf(dotAtom(), quotedString(), obsLocalPart());
-        }
-
-        //   obs-local-part  =   word *("." word)
-        Rule obsLocalPart() {
-            return Sequence(word(), ZeroOrMore(Sequence(".", word())));
-        }
-        
-        /*    disposition-notification-content =
-                     [ reporting-ua-field CRLF ]
-                     [ mdn-gateway-field CRLF ]
-                     [ original-recipient-field CRLF ]
-                     final-recipient-field CRLF
-                     [ original-message-id-field CRLF ]
-                     disposition-field CRLF
-                     *( error-field CRLF )
-                     *( extension-field CRLF )    */
-        Rule dispositionNotificationContent() {
-            return Sequence(
-                push(MDNReport.builder()),
-                Optional(Sequence(reportingUaField(), ACTION(setReportingUaField()), crlf())),
-                Optional(Sequence(mdnGatewayField(), ACTION(setMdnGatewayField()), crlf())),
-                Optional(Sequence(originalRecipientField(), ACTION(setOriginalRecipientField()), crlf())),
-                Sequence(finalRecipientField(), ACTION(setFinalRecipientField()), crlf()),
-                Optional(Sequence(originalMessageIdField(), ACTION(setOriginalMessageIdField()), crlf())),
-                Sequence(dispositionField(), ACTION(setDispositionField()), crlf()),
-                ZeroOrMore(Sequence(errorField(), ACTION(addErrorField()), crlf())),
-                ZeroOrMore(Sequence(extentionField(), ACTION(addExtensionField()), crlf())),
-                ACTION(buildMDNReport()));
-        }
-
-        boolean setReportingUaField() {
-            this.<MDNReport.Builder>peekParent().reportingUserAgentField(popT());
-            return true;
-        }
-
-        boolean setMdnGatewayField() {
-            this.<MDNReport.Builder>peekParent().gatewayField(popT());
-            return true;
-        }
-
-        boolean setOriginalRecipientField() {
-            this.<MDNReport.Builder>peekParent().originalRecipientField(this.<OriginalRecipient>popT());
-            return true;
-        }
-
-        boolean setFinalRecipientField() {
-            this.<MDNReport.Builder>peekParent().finalRecipientField(this.<FinalRecipient>popT());
-            return true;
-        }
-
-        boolean setOriginalMessageIdField() {
-            this.<MDNReport.Builder>peekParent().originalMessageIdField(this.<OriginalMessageId>popT());
-            return true;
-        }
-
-        boolean setDispositionField() {
-            this.<MDNReport.Builder>peekParent().dispositionField(popT());
-            return true;
-        }
-
-        boolean addErrorField() {
-            this.<MDNReport.Builder>peekParent().addErrorField(this.<Error>popT());
-            return true;
-        }
-
-        boolean addExtensionField() {
-            this.<MDNReport.Builder>peekParent().withExtensionField(this.<ExtensionField>popT());
-            return true;
-        }
-
-        boolean buildMDNReport() {
-            push(this.<MDNReport.Builder>popT().build());
-            return true;
-        }
-
-        /*    reporting-ua-field = "Reporting-UA" ":" OWS ua-name OWS [
-                                   ";" OWS ua-product OWS ]    */
-        Rule reportingUaField() {
-            return Sequence(
-                push(ReportingUserAgent.builder()),
-                "Reporting-UA", ":", ows(), uaName(), ACTION(setUserAgentName()), ows(),
-                Optional(Sequence(";", ows(), uaProduct(), ACTION(setUserAgentProduct()), ows())),
-                ACTION(buildReportingUserAgent())
-                );
-        }
-
-        boolean buildReportingUserAgent() {
-            push(this.<ReportingUserAgent.Builder>popT().build());
-            return true;
-        }
-
-        boolean setUserAgentName() {
-            this.<ReportingUserAgent.Builder>peekT().userAgentName(match());
-            return true;
-        }
-
-        boolean setUserAgentProduct() {
-            this.<ReportingUserAgent.Builder>peekT().userAgentProduct(match());
-            return true;
-        }
-
-        //    ua-name = *text-no-semi
-        Rule uaName() {
-            return ZeroOrMore(textNoSemi());
-        }
-
-        /*    text-no-semi = %d1-9 /        ; "text" characters excluding NUL, CR,
-                             %d11 / %d12 / %d14-58 / %d60-127      ; LF, or semi-colon    */
-        Rule textNoSemi() {
-            return FirstOf(
-                CharRange((char)1, (char)9),
-                Character.toChars(11),
-                Character.toChars(12),
-                CharRange((char)14, (char)58),
-                CharRange((char)60, (char)127));
-        }
-
-        //    ua-product = *([FWS] text)
-        Rule uaProduct() {
-            return ZeroOrMore(Sequence(Optional(fws()), text()));
-        }
-
-        /*   text            =   %d1-9 /            ; Characters excluding CR
-                                 %d11 /             ;  and LF
-                                 %d12 /
-                                 %d14-127   */
-        Rule text() {
-            return FirstOf(
-                    CharRange((char)1, (char)9),
-                    Character.toChars(11),
-                    Character.toChars(12),
-                    CharRange((char)14, (char)127));
-        }
-
-        /*    OWS = [CFWS]
-                    ; Optional whitespace.
-                    ; MDN generators SHOULD use "*WSP"
-                    ; (Typically a single space or nothing.
-                    ; It SHOULD be nothing at the end of a field.),
-                    ; unless an RFC 5322 "comment" is required.
-                    ;
-                    ; MDN parsers MUST parse it as "[CFWS]".    */
-        Rule ows() {
-            return Optional(cfws());
-        }
-
-        /*    mdn-gateway-field = "MDN-Gateway" ":" OWS mta-name-type OWS
-                                  ";" OWS mta-name    */
-        Rule mdnGatewayField() {
-            return Sequence(
-                push(Gateway.builder()),
-                "MDN-Gateway", ":",
-                ows(),
-                mtaNameType(), ACTION(setMtaNameType()),
-                ows(),
-                ";",
-                ows(),
-                mtaName(), ACTION(setMtaName()),
-                ACTION(buildGateway()));
-        }
-
-        boolean setMtaNameType() {
-            this.<Gateway.Builder>peekT().nameType(new AddressType(match()));
-            return true;
-        }
-
-        boolean setMtaName() {
-            this.<Gateway.Builder>peekT().name(Text.fromRawText(match()));
-            return true;
-        }
-
-        boolean buildGateway() {
-            push(this.<Gateway.Builder>popT().build());
-            return true;
-        }
-
-        //    mta-name-type = Atom
-        Rule mtaNameType() {
-            return atom();
-        }
-
-        //    mta-name = *text
-        Rule mtaName() {
-            return ZeroOrMore(text());
-        }
-
-        /*    original-recipient-field =
-                     "Original-Recipient" ":" OWS address-type OWS
-                     ";" OWS generic-address OWS    */
-        Rule originalRecipientField() {
-            return Sequence(
-                push(OriginalRecipient.builder()),
-                "Original-Recipient", ":",
-                ows(),
-                addressType(), ACTION(setOriginalAddressType()),
-                ows(),
-                ";",
-                ows(),
-                genericAddress(), ACTION(setOriginalGenericAddress()),
-                ows(),
-                ACTION(buildOriginalRecipient()));
-        }
-
-        boolean setOriginalAddressType() {
-            this.<OriginalRecipient.Builder>peekT().addressType(new AddressType(match()));
-            return true;
-        }
-
-        boolean setOriginalGenericAddress() {
-            this.<OriginalRecipient.Builder>peekT().originalRecipient(Text.fromRawText(match()));
-            return true;
-        }
-
-        boolean buildOriginalRecipient() {
-            push(this.<OriginalRecipient.Builder>popT().build());
-            return true;
-        }
-
-        //    address-type = Atom
-        Rule addressType() {
-            return atom();
-        }
-
-        //    generic-address = *text
-        Rule genericAddress() {
-            return ZeroOrMore(text());
-        }
-
-        /*    final-recipient-field =
-                     "Final-Recipient" ":" OWS address-type OWS
-                     ";" OWS generic-address OWS    */
-        Rule finalRecipientField() {
-            return Sequence(
-                push(FinalRecipient.builder()),
-                "Final-Recipient", ":",
-                ows(),
-                addressType(), ACTION(setFinalAddressType()),
-                ows(),
-                ";",
-                ows(),
-                genericAddress(), ACTION(setFinalGenericAddress()),
-                ows(),
-                ACTION(buildFinalRecipient()));
-        }
-
-        boolean setFinalAddressType() {
-            this.<FinalRecipient.Builder>peekT().addressType(new AddressType(match()));
-            return true;
-        }
-
-        boolean setFinalGenericAddress() {
-            this.<FinalRecipient.Builder>peekT().finalRecipient(Text.fromRawText(match()));
-            return true;
-        }
-
-        boolean buildFinalRecipient() {
-            push(this.<FinalRecipient.Builder>popT().build());
-            return true;
-        }
-
-        //    original-message-id-field = "Original-Message-ID" ":" msg-id
-        Rule originalMessageIdField() {
-            return Sequence("Original-Message-ID", ":", msgId(), push(new OriginalMessageId(match())));
-        }
-
-        //    msg-id          =   [CFWS] "<" id-left "@" id-right ">" [CFWS]
-        Rule msgId() {
-            return Sequence(Optional(cfws()), "<", idLeft(), "@", idRight(), ">", Optional(cfws()));
-        }
-
-        //   id-left         =   dot-atom-text / obs-id-left
-        Rule idLeft() {
-            return FirstOf(dotAtomText(), obsIdLeft());
-        }
-
-        //   obs-id-left     =   local-part
-        Rule obsIdLeft() {
-            return localPart();
-        }
-
-        //   obs-id-right    =   domain
-        Rule idRight() {
-            return domain();
-        }
-
-        /*    disposition-field =
-                     "Disposition" ":" OWS disposition-mode OWS ";"
-                     OWS disposition-type
-                     [ OWS "/" OWS disposition-modifier
-                     *( OWS "," OWS disposition-modifier ) ] OWS    */
-        Rule dispositionField() {
-            return Sequence(
-                push(Disposition.builder()),
-                "Disposition", ":",
-                ows(),
-                dispositionMode(),
-                ows(),
-                ";",
-                ows(),
-                dispositionType(),
-                Optional(
-                    Sequence(
-                        ows(),
-                        "/",
-                        ows(),
-                        dispositionModifier(), ACTION(addDispositionModifier()),
-                        ZeroOrMore(
-                            Sequence(
-                                ows(),
-                                ",",
-                                dispositionModifier(), ACTION(addDispositionModifier()))))),
-                ows(),
-                ACTION(buildDispositionField()));
-        }
-
-        boolean addDispositionModifier() {
-            this.<Disposition.Builder>peekT().addModifier(new DispositionModifier(match()));
-            return true;
-        }
-
-        boolean buildDispositionField() {
-            push(this.<Disposition.Builder>popT().build());
-            return true;
-        }
-
-        //    disposition-mode = action-mode OWS "/" OWS sending-mode
-        Rule dispositionMode() {
-            return Sequence(actionMode(), ows(), "/", ows(), sendingMode());
-        }
-
-        //    action-mode = "manual-action" / "automatic-action"
-        Rule actionMode() {
-            return FirstOf(
-                Sequence("manual-action", ACTION(setActionMode(DispositionActionMode.Manual))),
-                Sequence("automatic-action", ACTION(setActionMode(DispositionActionMode.Automatic))));
-        }
-
-        boolean setActionMode(DispositionActionMode actionMode) {
-            this.<Disposition.Builder>peekT().actionMode(actionMode);
-            return true;
-        }
-
-        //    sending-mode = "MDN-sent-manually" / "MDN-sent-automatically"
-        Rule sendingMode() {
-            return FirstOf(
-                Sequence("MDN-sent-manually", ACTION(setSendingMode(DispositionSendingMode.Manual))),
-                Sequence("MDN-sent-automatically", ACTION(setSendingMode(DispositionSendingMode.Automatic))));
-        }
-
-        boolean setSendingMode(DispositionSendingMode sendingMode) {
-            this.<Disposition.Builder>peekT().sendingMode(sendingMode);
-            return true;
-        }
-
-        /*    disposition-type = "displayed" / "deleted" / "dispatched" /
-                      "processed"    */
-        Rule dispositionType() {
-            return FirstOf(
-                Sequence("displayed", ACTION(setDispositionType(DispositionType.Displayed))),
-                Sequence("deleted", ACTION(setDispositionType(DispositionType.Deleted))),
-                Sequence("dispatched", ACTION(setDispositionType(DispositionType.Dispatched))),
-                Sequence("processed", ACTION(setDispositionType(DispositionType.Processed))));
-        }
-
-        boolean setDispositionType(DispositionType type) {
-            this.<Disposition.Builder>peekT().type(type);
-            return true;
-        }
-
-        //    disposition-modifier = "error" / disposition-modifier-extension
-        Rule dispositionModifier() {
-            return FirstOf("error", dispositionModifierExtension());
-        }
-
-        //    disposition-modifier-extension = Atom
-        Rule dispositionModifierExtension() {
-            return atom();
-        }
-
-        //    error-field = "Error" ":" *([FWS] text)
-        Rule errorField() {
-            return Sequence(
-                "Error", ":",
-                ZeroOrMore(Sequence(Optional(fws()), text())), push(new Error(Text.fromRawText(match()))));
-        }
-
-        //    extension-field = extension-field-name ":" *([FWS] text)
-        Rule extentionField() {
-            return Sequence(
-                push(ExtensionField.builder()),
-                extensionFieldName(), ACTION(setExtensionFieldName()),
-                ":",
-                ZeroOrMore(Sequence(Optional(fws()), text())), ACTION(setExtensionText()),
-                ACTION(buildExtension()));
-        }
-
-        boolean setExtensionFieldName() {
-            this.<ExtensionField.Builder>peekT().fieldName(match());
-            return true;
-        }
-
-        boolean setExtensionText() {
-            this.<ExtensionField.Builder>peekT().rawValue(match());
-            return true;
-        }
-
-        boolean buildExtension() {
-            push(this.<ExtensionField.Builder>popT().build());
-            return true;
-        }
-
-        //    extension-field-name = field-name
-        Rule extensionFieldName() {
-            return fieldName();
-        }
-
-        //   field-name      =   1*ftext
-        Rule fieldName() {
-            return OneOrMore(ftext());
-        }
-
-        /*   ftext           =   %d33-57 /          ; Printable US-ASCII
-                                 %d59-126           ;  characters not including
-                                                    ;  ":".   */
-        Rule ftext() {
-            return FirstOf(
-                    CharRange((char)33, (char)57),
-                    CharRange((char)59, (char)126));
-        }
-    }
-}
diff --git a/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala b/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala
new file mode 100644
index 0000000..bd5b685
--- /dev/null
+++ b/mdn/src/main/scala/org/apache/james/mdn/MDNReportParser.scala
@@ -0,0 +1,477 @@
+/****************************************************************
+ * 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.mdn
+
+import java.io.InputStream
+
+import org.apache.commons.io.IOUtils
+import org.apache.james.mdn.`type`.DispositionType
+import org.apache.james.mdn.action.mode.DispositionActionMode
+import org.apache.james.mdn.fields._
+import org.apache.james.mdn.modifier.DispositionModifier
+import org.apache.james.mdn.sending.mode.DispositionSendingMode
+import org.parboiled2._
+import org.slf4j.LoggerFactory
+import shapeless.HNil
+
+import scala.util.{Failure, Try}
+
+object MDNReportParser {
+  private val LOGGER = LoggerFactory.getLogger(classOf[MDNReportParser])
+
+  def parse(is: InputStream, charset: String): Try[MDNReport] = new MDNReportParser(IOUtils.toString(is, charset)).dispositionNotificationContent.run()
+
+  def parse(input : String): Try[MDNReport] = {
+    val parser = new MDNReportParser(input)
+    val result = parser.dispositionNotificationContent.run()
+
+    result match {
+      case res@Failure(e : ParseError) =>
+        LOGGER.debug(parser.formatError(e))
+        res
+      case res => res
+    }
+  }
+}
+
+class MDNReportParser(val input: ParserInput) extends Parser {
+
+  /*    disposition-notification-content =
+                     [ reporting-ua-field CRLF ]
+                     [ mdn-gateway-field CRLF ]
+                     [ original-recipient-field CRLF ]
+                     final-recipient-field CRLF
+                     [ original-message-id-field CRLF ]
+                     disposition-field CRLF
+                     *( error-field CRLF )
+                     *( extension-field CRLF )    */
+  private def dispositionNotificationContent: Rule1[MDNReport] = rule {
+    (
+      (reportingUaField ~ crlf).? ~
+        (mdnGatewayField ~ crlf).? ~
+        (originalRecipientField ~ crlf).? ~
+        finalRecipientField ~ crlf ~
+        (originalMessageIdField ~ crlf).? ~
+        dispositionField ~ crlf ~
+        zeroOrMore(errorField ~ crlf) ~
+        zeroOrMore(extentionField ~ crlf)
+      ) ~> ((reportingUserAgent : Option[ReportingUserAgent],
+             gateway : Option[Gateway],
+             originalRecipient : Option[OriginalRecipient],
+             finalRecipient: FinalRecipient,
+             originalMessageId: Option[OriginalMessageId],
+             disposition: Disposition,
+             errors: Seq[Error],
+             extensions: Seq[ExtensionField]) => {
+      val builder = MDNReport.builder()
+        .finalRecipientField(finalRecipient)
+        .dispositionField(disposition)
+        .addErrorFields(errors:_*)
+        .withExtensionFields(extensions:_*)
+
+      val builderWithUa = reportingUserAgent.fold(builder)(builder.reportingUserAgentField)
+      val builderWithGateway = gateway.fold(builderWithUa)(builder.gatewayField)
+      val builderWithOriginalRecipent = originalRecipient.fold(builderWithGateway)(builder.originalRecipientField)
+      val builderWithOriginalMessageId = originalMessageId.fold(builderWithOriginalRecipent)(builder.originalMessageIdField)
+      builderWithOriginalMessageId.build()
+    })
+  }
+
+  /*    reporting-ua-field = "Reporting-UA" ":" OWS ua-name OWS [
+                                   ";" OWS ua-product OWS ]    */
+  private[mdn] def reportingUaField: Rule1[ReportingUserAgent] = rule {
+    ("Reporting-UA" ~ ":" ~ ows ~ capture(uaName) ~ ows ~ (";" ~ ows ~ capture(uaProduct) ~ ows).?) ~> ((uaName: String, uaProduct: Option[String]) => {
+     val builder = ReportingUserAgent.builder()
+        .userAgentName(uaName)
+      (uaProduct match {
+         case Some(product) => builder.userAgentProduct(product)
+         case None => builder
+       }).build()
+    })
+  }
+
+  //    ua-name = *text-no-semi
+  private def uaName: Rule0 = rule { zeroOrMore(textNoSemi) }
+
+  /*    text-no-semi = %d1-9 /        ; "text" characters excluding NUL, CR,
+                             %d11 / %d12 / %d14-58 / %d60-127      ; LF, or semi-colon    */
+  private def textNoSemi: Rule0 = rule {
+    CharPredicate(1.toChar to 9.toChar) |
+      ch(11) |
+      ch(12) |
+      CharPredicate(14.toChar to 58.toChar) |
+      CharPredicate(60.toChar to 127.toChar)
+  }
+
+  //    ua-product = *([FWS] text)
+  private def uaProduct: Rule0 = rule { zeroOrMore(fws.? ~ text) }
+
+  /*   text            =   %d1-9 /            ; Characters excluding CR
+                                 %d11 /             ;  and LF
+                                 %d12 /
+                                 %d14-127   */
+  private def text = rule {
+    CharPredicate(1.toChar to 9.toChar) |
+      ch(11) |
+      ch(12) |
+      CharPredicate(14.toChar to 127.toChar)
+  }
+
+  /*    OWS = [CFWS]
+            ; Optional whitespace.
+            ; MDN generators SHOULD use "*WSP"
+            ; (Typically a single space or nothing.
+            ; It SHOULD be nothing at the end of a field.),
+            ; unless an RFC 5322 "comment" is required.
+            ;
+            ; MDN parsers MUST parse it as "[CFWS]".    */
+  private def ows = rule {
+    cfws.?
+  }
+
+  /*    mdn-gateway-field = "MDN-Gateway" ":" OWS mta-name-type OWS
+                                  ";" OWS mta-name    */
+  def mdnGatewayField : Rule1[Gateway] = rule {
+    ("MDN-Gateway" ~ ":" ~ ows ~ capture(mtaNameType) ~ ows ~ ";" ~ ows ~ capture(mtaName) ~ ows) ~> ((gatewayType : String, name : String) => Gateway
+      .builder()
+      .name(Text.fromRawText(name))
+      .nameType(new AddressType(gatewayType))
+      .build())
+  }
+
+  //    mta-name-type = Atom
+  private def mtaNameType = rule { atom }
+
+  //    mta-name = *text
+  private def mtaName = rule { zeroOrMore(text) }
+
+  /*    original-recipient-field =
+                     "Original-Recipient" ":" OWS address-type OWS
+                     ";" OWS generic-address OWS    */
+  private[mdn] def originalRecipientField : Rule1[OriginalRecipient] = rule {
+    ("Original-Recipient" ~ ":" ~ ows ~ capture(addressType) ~ ows ~ ";" ~ ows ~ capture(genericAddress) ~ ows) ~> ((addrType : String, genericAddr : String) =>
+      OriginalRecipient
+        .builder()
+      .addressType(new AddressType(addrType))
+      .originalRecipient(Text.fromRawText(genericAddr))
+      .build()
+      )
+  }
+
+  //    address-type = Atom
+  private def addressType = rule { atom }
+
+  //    generic-address = *text
+  private def genericAddress = rule { zeroOrMore(text) }
+
+  /*    final-recipient-field =
+             "Final-Recipient" ":" OWS address-type OWS
+             ";" OWS generic-address OWS    */
+  private[mdn] def finalRecipientField : Rule1[FinalRecipient] = rule {
+    ("Final-Recipient" ~ ":" ~ ows ~ capture(addressType) ~ ows ~ ";" ~ ows ~ capture(genericAddress) ~ ows) ~> ((addrType : String, genericAddr : String) =>
+    FinalRecipient
+      .builder()
+      .addressType(new AddressType(addrType))
+      .finalRecipient(Text.fromRawText(genericAddr))
+      .build()
+    )
+  }
+
+  //    original-message-id-field = "Original-Message-ID" ":" msg-id
+  private[mdn] def originalMessageIdField: Rule1[OriginalMessageId] = rule {
+    "Original-Message-ID" ~ ":" ~ capture(msgId) ~> ((msgId: String) => new OriginalMessageId(msgId))
+  }
+
+  //    msg-id          =   [CFWS] "<" id-left "@" id-right ">" [CFWS]
+  private def msgId: Rule0 = rule { cfws.? ~ "<" ~ idLeft ~ "@" ~ idRight ~ ">" ~ cfws.? }
+
+  //   id-left         =   dot-atom-text / obs-id-left
+  private def idLeft: Rule0 = rule { dotAtomText | obsIdLeft }
+
+  //   obs-id-left     =   local-part
+  private def obsIdLeft: Rule0 = rule { localPart }
+
+  //   obs-id-right    =   domain
+  private def idRight = rule { domain }
+
+  /*    disposition-field =
+                     "Disposition" ":" OWS disposition-mode OWS ";"
+                     OWS disposition-type
+                     [ OWS "/" OWS disposition-modifier
+                     *( OWS "," OWS disposition-modifier ) ] OWS    */
+  private[mdn] def dispositionField : Rule1[Disposition] = rule {
+    ("Disposition" ~ ":" ~ ows ~ dispositionMode ~ ows ~ ";" ~
+    ows ~ dispositionType ~
+    dispositionModifiers.? ~ ows) ~> ((modes: (DispositionActionMode, DispositionSendingMode),
+                                                                              dispositionType: DispositionType,
+                                                                              dispositionModifiers: Option[Seq[DispositionModifier]]) =>
+       Disposition.builder()
+         .actionMode(modes._1)
+         .sendingMode(modes._2)
+         .`type`(dispositionType)
+         .addModifiers(dispositionModifiers.getOrElse(Nil):_*)
+         .build()
+      )
+  }
+
+
+
+  //    disposition-mode = action-mode OWS "/" OWS sending-mode
+  private def dispositionMode: Rule1[(DispositionActionMode, DispositionSendingMode)] = rule {
+    (capture(actionMode) ~ ows ~ "/" ~ ows ~ capture(sendingMode)) ~> ((actionMode: String, sendingMode: String) => {
+      val action = actionMode match {
+        case "manual-action" => DispositionActionMode.Manual
+        case "automatic-action" => DispositionActionMode.Automatic
+      }
+      val sending = sendingMode match {
+        case "MDN-sent-manually" => DispositionSendingMode.Manual
+        case "MDN-sent-automatically" => DispositionSendingMode.Automatic
+      }
+      (action, sending)
+    })
+  }
+
+  //    action-mode = "manual-action" / "automatic-action"
+  private def actionMode = rule { "manual-action" | "automatic-action" }
+
+  //    sending-mode = "MDN-sent-manually" / "MDN-sent-automatically"
+  private def sendingMode = rule {"MDN-sent-manually" | "MDN-sent-automatically" }
+
+  /*    disposition-type = "displayed" / "deleted" / "dispatched" /
+                      "processed"    */
+  private def dispositionType : Rule1[DispositionType] = rule {
+    "displayed" ~ push(DispositionType.Displayed) |
+    "deleted" ~ push(DispositionType.Deleted) |
+    "dispatched" ~ push(DispositionType.Dispatched) |
+    "processed" ~ push(DispositionType.Processed)
+  }
+  //subpart of disposition-field corresponding to :
+  // [ OWS "/" OWS disposition-modifier
+  //                     *( OWS "," OWS disposition-modifier ) ]
+  private def dispositionModifiers: Rule1[Seq[DispositionModifier]] = rule { (ows ~ "/" ~ ows ~ capture(dispositionModifier) ~
+      zeroOrMore(ows ~ "," ~ ows ~ capture(dispositionModifier))) ~> ((head: String, tail: Seq[String]) =>
+      tail.prepended(head).map(new DispositionModifier(_)))
+    }
+
+
+  //    disposition-modifier = "error" / disposition-modifier-extension
+  private def dispositionModifier = rule { "error" | dispositionModifierExtension }
+
+  //    disposition-modifier-extension = Atom
+  private def dispositionModifierExtension = rule { atom }
+
+  //    error-field = "Error" ":" *([FWS] text)
+  private[mdn] def errorField: Rule1[Error] = rule { ("Error" ~ ":" ~ capture(zeroOrMore(fws.? ~ text))) ~> ((error: String) =>  new Error(Text.fromRawText(error))) }
+
+  //    extension-field = extension-field-name ":" *([FWS] text)
+  private[mdn] def extentionField: Rule1[ExtensionField] = rule { capture(extensionFieldName) ~ ":" ~ capture(zeroOrMore(fws.? ~ text)) ~> ((extensionFieldName: String, text : String) =>
+    ExtensionField.builder()
+      .fieldName(extensionFieldName)
+      .rawValue(text)
+      .build()) }
+
+  //    extension-field-name = field-name
+  private def extensionFieldName: Rule0 = rule { fieldName }
+
+  //   field-name      =   1*ftext
+  private def fieldName: Rule0 = rule { oneOrMore(ftext) }
+
+  /*   ftext           =   %d33-57 /          ; Printable US-ASCII
+                         %d59-126           ;  characters not including
+                                            ;  ":".   */
+  private def ftext: Rule0 = rule {
+    CharPredicate(33.toChar to 57.toChar) |
+    CharPredicate(59.toChar to 126.toChar)
+  }
+
+  //   CFWS            =   (1*([FWS] comment) [FWS]) / FWS
+  private def cfws: Rule0 = rule { (oneOrMore(fws.? ~ comment) ~ fws) | fws }
+
+  //   FWS             =   ([*WSP CRLF] 1*WSP) /  obs-FWS
+  private def fws: Rule0 = rule { ((zeroOrMore(wsp) ~ crlf).? ~ oneOrMore(wsp)) | obsFWS }
+
+  //         WSP            =  SP / HTAB
+  private def wsp: Rule0 = rule { sp | htab }
+
+  //         SP             =  %x20
+  private def sp: Rule0 = rule { ch(0x20) }
+
+  //         HTAB           =  %x09
+  private def htab: Rule0 = rule { ch(0x09) }
+
+  //         CRLF           =  CR LF
+  private def crlf: Rule0 = rule { cr ~ lf }
+
+  //         CR             =  %x0D
+  private def cr: Rule0 = rule { ch(0x0d) }
+
+  //         LF             =  %x0A
+  private def lf: Rule0 = rule { ch(0x0a) }
+
+  //   obs-FWS         =   1*WSP *(CRLF 1*WSP)
+  private def obsFWS: Rule0 = rule { oneOrMore(wsp) ~ zeroOrMore(crlf ~ oneOrMore(wsp)) }
+
+  //   comment         =   "(" *([FWS] ccontent) [FWS] ")"
+  private def comment: Rule[HNil, HNil] = rule { "(" ~ zeroOrMore(fws.? ~ ccontent) ~ fws.? ~ ")" }
+
+  //   ccontent        =   ctext / quoted-pair / comment
+  private def ccontent: Rule[HNil, HNil] = rule { ctext | quotedPair | comment }
+
+  /*   ctext           =   %d33-39 /          ; Printable US-ASCII
+                         %d42-91 /          ;  characters not including
+                         %d93-126 /         ;  "(", ")", or "\"
+                         obs-ctext   */
+  private def ctext = rule {
+    CharPredicate(33.toChar to 39.toChar) |
+    CharPredicate(42.toChar to 91.toChar) |
+    CharPredicate(93.toChar to 126.toChar) |
+    obsCText
+  }
+
+  //   obs-ctext       =   obs-NO-WS-CTL
+  private def obsCText = rule { obsNoWsCtl }
+
+  /*   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control
+                         %d11 /             ;  characters that do not
+                         %d12 /             ;  include the carriage
+                         %d14-31 /          ;  return, line feed, and
+                         %d127              ;  white space characters   */
+  private def obsNoWsCtl = rule {
+    CharPredicate(33.toChar to 39.toChar) |
+    ch(11) |
+    ch(12) |
+    CharPredicate(14.toChar to 31.toChar) |
+    ch(127)
+  }
+
+  //   quoted-pair     =   ("\" (VCHAR / WSP)) / obs-qp
+  private def quotedPair: Rule0 = rule { ("\\" ~ (vchar | wsp)) | obsQp }
+
+  //         VCHAR          =  %x21-7E
+  private def vchar: Rule0 = rule { CharPredicate(21.toChar to 0x7e.toChar) }
+
+  //   obs-qp          =   "\" (%d0 / obs-NO-WS-CTL / LF / CR)
+  private def obsQp: Rule0 = rule { "\\" ~ (ch(0xd0) | obsCText | lf | cr) }
+
+  //   word            =   atom / quoted-string
+  private def word: Rule0 = rule { atom | quotedString }
+
+  //    atom            =   [CFWS] 1*atext [CFWS]
+  private def atom: Rule0 = rule { cfws.? ~ oneOrMore(atext) ~ cfws.? }
+
+  /*   atext           =   ALPHA / DIGIT /    ; Printable US-ASCII
+                         "!" / "#" /        ;  characters not including
+                         "$" / "%" /        ;  specials.  Used for atoms.
+                         "&" / "'" /
+                         "*" / "+" /
+                         "-" / "/" /
+                         "=" / "?" /
+                         "^" / "_" /
+                         "`" / "{" /
+                         "|" / "}" /
+                         "~"   */
+  private def atext: Rule0 = rule {
+    alpha | digit |
+    "!" | "#" |
+    "$" | "%" |
+    "&" | "'" |
+    "*" | "+" |
+    "-" | "/" |
+    "=" | "?" |
+    "^" | "_" |
+    "`" | "{" |
+    "|" | "}" |
+    "~"
+  }
+
+  //         ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
+  private def alpha = rule {
+    CharPredicate(0x41.toChar to 0x5a.toChar) |
+    CharPredicate(0x61.toChar to 0x7a.toChar)
+  }
+
+  //         DIGIT          =  %x30-39
+  private def digit = rule { CharPredicate(0x30.toChar to 0x39.toChar) }
+
+  /*   quoted-string   =   [CFWS]
+                                 DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+                                 [CFWS]   */
+
+  private def quotedString: Rule0 = rule {
+    cfws.? ~
+    dquote ~ zeroOrMore(fws.? ~ qcontent) ~ fws.? ~ dquote ~
+    cfws.?
+  }
+
+  //         DQUOTE         =  %x22
+  private def dquote = rule { ch(0x22) }
+
+  //   qcontent        =   qtext / quoted-pair
+  private def qcontent: Rule0 = rule { qtext | quotedPair }
+
+  //   qtext           =   %d33 /             ; Printable US-ASCII
+  //                       %d35-91 /          ;  characters not including
+  //                       %d93-126 /         ;  "\" or the quote character
+  //                       obs-qtext
+  private def qtext: Rule0 = rule {
+    ch(33) |
+    CharPredicate(35.toChar to 91.toChar) |
+    CharPredicate(93.toChar to 126.toChar) |
+    obsQtext
+  }
+
+  private def obsQtext: Rule0 = obsNoWsCtl
+
+  //   domain          =   dot-atom / domain-literal / obs-domain
+  private def domain = rule { dotAtom | domainLiteral | dotAtom }
+
+  //   dot-atom        =   [CFWS] dot-atom-text [CFWS]
+  private def dotAtom = rule { cfws.? ~ dotAtomText ~ cfws.? }
+
+  //   dot-atom-text   =   1*atext *("." 1*atext)
+  private def dotAtomText = rule { oneOrMore(atext) ~ zeroOrMore("." ~ oneOrMore(atext)) }
+
+  //   domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
+  private def domainLiteral = rule {
+    cfws.? ~ "[" ~ zeroOrMore(fws.? ~ dtext) ~ fws.? ~ "]" ~ cfws.?
+  }
+
+  /*   dtext           =   %d33-90 /          ; Printable US-ASCII
+                                 %d94-126 /         ;  characters not including
+                                 obs-dtext          ;  "[", "]", or "\"   */
+  private def dtext = rule {
+    CharPredicate(33.toChar to 90.toChar) |
+    CharPredicate(94.toChar to 126.toChar) |
+    obsDtext
+  }
+
+  //   obs-dtext       =   obs-NO-WS-CTL / quoted-pair
+  private def obsDtext = rule { obsNoWsCtl | quotedPair }
+
+  //   obs-domain      =   atom *("." atom)
+  private def obsDomain = rule { atom ~ zeroOrMore("." ~ atom) }
+
+  //   local-part      =   dot-atom / quoted-string / obs-local-part
+  private def localPart: Rule0 = rule { dotAtom | quotedString | obsLocalPart }
+
+  //   obs-local-part  =   word *("." word)
+  private def obsLocalPart: Rule0 = rule { word ~ zeroOrMore("." ~ word) }
+
+}
\ No newline at end of file
diff --git a/mdn/src/test/java/org/apache/james/mdn/MDNReportParserTest.java b/mdn/src/test/java/org/apache/james/mdn/MDNReportParserTest.java
deleted file mode 100644
index 50c2815..0000000
--- a/mdn/src/test/java/org/apache/james/mdn/MDNReportParserTest.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.mdn;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.Optional;
-
-import org.apache.james.mdn.MDNReportParser.Parser;
-import org.apache.james.mdn.action.mode.DispositionActionMode;
-import org.apache.james.mdn.fields.AddressType;
-import org.apache.james.mdn.fields.Disposition;
-import org.apache.james.mdn.fields.Error;
-import org.apache.james.mdn.fields.ExtensionField;
-import org.apache.james.mdn.fields.FinalRecipient;
-import org.apache.james.mdn.fields.Gateway;
-import org.apache.james.mdn.fields.OriginalMessageId;
-import org.apache.james.mdn.fields.OriginalRecipient;
-import org.apache.james.mdn.fields.ReportingUserAgent;
-import org.apache.james.mdn.fields.Text;
-import org.apache.james.mdn.modifier.DispositionModifier;
-import org.apache.james.mdn.sending.mode.DispositionSendingMode;
-import org.apache.james.mdn.type.DispositionType;
-import org.junit.Test;
-import org.parboiled.Parboiled;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.support.ParsingResult;
-
-public class MDNReportParserTest {
-
-    @Test
-    public void parseShouldReturnEmptyWhenMissingFinalRecipient() {
-        String missing = "Disposition: automatic-action/MDN-sent-automatically;processed\r\n";
-        MDNReportParser testee = new MDNReportParser();
-        assertThat(testee.parse(missing)).isEmpty();
-    }
-
-    @Test
-    public void parseShouldReturnMdnReportWhenMaximalSubset() {
-        String maximal = "Reporting-UA: UA_name; UA_product\r\n" +
-            "MDN-Gateway: smtp; apache.org\r\n" +
-            "Original-Recipient: rfc822; originalRecipient\r\n" +
-            "Final-Recipient: rfc822; final_recipient\r\n" +
-            "Original-Message-ID: <or...@message.id>\r\n" +
-            "Disposition: automatic-action/MDN-sent-automatically;processed/error,failed\r\n" +
-            "Error: Message1\r\n" +
-            "Error: Message2\r\n" +
-            "X-OPENPAAS-IP: 177.177.177.77\r\n" +
-            "X-OPENPAAS-PORT: 8000\r\n";
-        Optional<MDNReport> expected = Optional.of(MDNReport.builder()
-            .reportingUserAgentField(ReportingUserAgent.builder()
-                .userAgentName("UA_name")
-                .userAgentProduct("UA_product")
-                .build())
-            .gatewayField(Gateway.builder()
-                .nameType(new AddressType("smtp"))
-                .name(Text.fromRawText("apache.org"))
-                .build())
-            .originalRecipientField("originalRecipient")
-            .finalRecipientField("final_recipient")
-            .originalMessageIdField("<or...@message.id>")
-            .dispositionField(Disposition.builder()
-                .actionMode(DispositionActionMode.Automatic)
-                .sendingMode(DispositionSendingMode.Automatic)
-                .type(DispositionType.Processed)
-                .addModifier(DispositionModifier.Error)
-                .addModifier(DispositionModifier.Failed)
-                .build())
-            .addErrorField("Message1")
-            .addErrorField("Message2")
-            .withExtensionField(ExtensionField.builder()
-                .fieldName("X-OPENPAAS-IP")
-                .rawValue(" 177.177.177.77")
-                .build())
-            .withExtensionField(ExtensionField.builder()
-                .fieldName("X-OPENPAAS-PORT")
-                .rawValue(" 8000")
-                .build())
-            .build());
-        MDNReportParser testee = new MDNReportParser();
-        Optional<MDNReport> actual = testee.parse(maximal);
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void parseShouldReturnMdnReportWhenMinimalSubset() {
-        String minimal = "Final-Recipient: rfc822; final_recipient\r\n" +
-            "Disposition: automatic-action/MDN-sent-automatically;processed\r\n";
-        Optional<MDNReport> expected = Optional.of(MDNReport.builder()
-            .finalRecipientField("final_recipient")
-            .dispositionField(Disposition.builder()
-                .actionMode(DispositionActionMode.Automatic)
-                .sendingMode(DispositionSendingMode.Automatic)
-                .type(DispositionType.Processed)
-                .build())
-            .build());
-        MDNReportParser testee = new MDNReportParser();
-        Optional<MDNReport> actual = testee.parse(minimal);
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void parseShouldReturnEmptyWhenDuplicatedFields() {
-        String duplicated = "Final-Recipient: rfc822; final_recipient\r\n" +
-            "Final-Recipient: rfc822; final_recipient\r\n" +
-            "Disposition: automatic-action/MDN-sent-automatically;processed\r\n";
-        MDNReportParser testee = new MDNReportParser();
-        assertThat(testee.parse(duplicated)).isEmpty();
-    }
-
-    @Test
-    public void reportingUserAgentShouldParseWithoutProduct() {
-        String minimal = "Reporting-UA: UA_name";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.reportingUaField()).run(minimal);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(ReportingUserAgent.class);
-        assertThat((ReportingUserAgent)result.resultValue).isEqualTo(ReportingUserAgent.builder().userAgentName("UA_name").build());
-    }
-
-    @Test
-    public void reportingUserAgentShouldParseWithProduct() {
-        String minimal = "Reporting-UA: UA_name; UA_product";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.reportingUaField()).run(minimal);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(ReportingUserAgent.class);
-        assertThat((ReportingUserAgent)result.resultValue).isEqualTo(ReportingUserAgent.builder().userAgentName("UA_name").userAgentProduct("UA_product").build());
-    }
-
-    @Test
-    public void mdnGatewayFieldShouldParse() {
-        String gateway = "MDN-Gateway: smtp; apache.org";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.mdnGatewayField()).run(gateway);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Gateway.class);
-        assertThat((Gateway)result.resultValue).isEqualTo(Gateway.builder().nameType(new AddressType("smtp")).name(Text.fromRawText("apache.org")).build());
-    }
-
-    @Test
-    public void originalRecipientFieldShouldParse() {
-        String originalRecipient = "Original-Recipient: rfc822; originalRecipient";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.originalRecipientField()).run(originalRecipient);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(OriginalRecipient.class);
-        assertThat((OriginalRecipient)result.resultValue).isEqualTo(OriginalRecipient.builder().addressType(new AddressType("rfc822")).originalRecipient(Text.fromRawText("originalRecipient")).build());
-    }
-
-    @Test
-    public void finalRecipientFieldShouldParse() {
-        String finalRecipient = "Final-Recipient: rfc822; final_recipient";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.finalRecipientField()).run(finalRecipient);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(FinalRecipient.class);
-        assertThat((FinalRecipient)result.resultValue).isEqualTo(FinalRecipient.builder().addressType(new AddressType("rfc822")).finalRecipient(Text.fromRawText("final_recipient")).build());
-    }
-
-    @Test
-    public void originalMessageIdShouldParse() {
-        String originalMessageId = "Original-Message-ID: <or...@message.id>";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.originalMessageIdField()).run(originalMessageId);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(OriginalMessageId.class);
-        assertThat((OriginalMessageId)result.resultValue).isEqualTo(new OriginalMessageId("<or...@message.id>"));
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenMinimal() {
-        String minimal = "Disposition: automatic-action/MDN-sent-automatically;processed";
-        Disposition expected = Disposition.builder()
-            .actionMode(DispositionActionMode.Automatic)
-            .sendingMode(DispositionSendingMode.Automatic)
-            .type(DispositionType.Processed)
-            .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(minimal);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenMaximal() {
-        String maximal = "Disposition: automatic-action/MDN-sent-automatically;processed/error,failed";
-        Disposition expected = Disposition.builder()
-                .actionMode(DispositionActionMode.Automatic)
-                .sendingMode(DispositionSendingMode.Automatic)
-                .type(DispositionType.Processed)
-                .addModifier(DispositionModifier.Error)
-                .addModifier(DispositionModifier.Failed)
-                .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(maximal);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenManualAutomaticWithDisplayedType() {
-        String disposition = "Disposition: manual-action/MDN-sent-automatically;processed";
-        Disposition expected = Disposition.builder()
-            .actionMode(DispositionActionMode.Manual)
-            .sendingMode(DispositionSendingMode.Automatic)
-            .type(DispositionType.Processed)
-            .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(disposition);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenAutomaticManualWithDisplayedType() {
-        String disposition = "Disposition: automatic-action/MDN-sent-manually;processed";
-        Disposition expected = Disposition.builder()
-            .actionMode(DispositionActionMode.Automatic)
-            .sendingMode(DispositionSendingMode.Manual)
-            .type(DispositionType.Processed)
-            .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(disposition);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenDeletedType() {
-        String disposition = "Disposition: automatic-action/MDN-sent-manually;deleted";
-        Disposition expected = Disposition.builder()
-            .actionMode(DispositionActionMode.Automatic)
-            .sendingMode(DispositionSendingMode.Manual)
-            .type(DispositionType.Deleted)
-            .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(disposition);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenDispatchedType() {
-        String disposition = "Disposition: automatic-action/MDN-sent-manually;dispatched";
-        Disposition expected = Disposition.builder()
-            .actionMode(DispositionActionMode.Automatic)
-            .sendingMode(DispositionSendingMode.Manual)
-            .type(DispositionType.Dispatched)
-            .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(disposition);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void dispositionFieldShouldParseWhenDisplayedType() {
-        String disposition = "Disposition: automatic-action/MDN-sent-manually;displayed";
-        Disposition expected = Disposition.builder()
-            .actionMode(DispositionActionMode.Automatic)
-            .sendingMode(DispositionSendingMode.Manual)
-            .type(DispositionType.Displayed)
-            .build();
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.dispositionField()).run(disposition);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Disposition.class);
-        assertThat((Disposition)result.resultValue).isEqualTo(expected);
-    }
-
-    @Test
-    public void errorFieldShouldParse() {
-        String error = "Error: Message1";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.errorField()).run(error);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(Error.class);
-        assertThat((Error)result.resultValue).isEqualTo(new Error(Text.fromRawText("Message1")));
-    }
-
-    @Test
-    public void extensionFieldShouldParse() {
-        String extension = "X-OPENPAAS-IP: 177.177.177.77";
-        Parser parser = Parboiled.createParser(MDNReportParser.Parser.class);
-        ParsingResult<Object> result = new ReportingParseRunner<>(parser.extentionField()).run(extension);
-        assertThat(result.matched).isTrue();
-        assertThat(result.resultValue).isInstanceOf(ExtensionField.class);
-        assertThat((ExtensionField)result.resultValue).isEqualTo(ExtensionField.builder().fieldName("X-OPENPAAS-IP").rawValue(" 177.177.177.77").build());
-    }
-}
diff --git a/mdn/src/test/scala/org/apache/james/mdn/MDNReportParserTest.scala b/mdn/src/test/scala/org/apache/james/mdn/MDNReportParserTest.scala
new file mode 100644
index 0000000..9eeda58
--- /dev/null
+++ b/mdn/src/test/scala/org/apache/james/mdn/MDNReportParserTest.scala
@@ -0,0 +1,287 @@
+/****************************************************************
+ * 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.mdn
+
+import org.apache.james.mdn.`type`.DispositionType
+import org.apache.james.mdn.action.mode.DispositionActionMode
+import org.apache.james.mdn.fields.{AddressType, Disposition, Error, ExtensionField, FinalRecipient, Gateway, OriginalMessageId, OriginalRecipient, ReportingUserAgent, Text}
+import org.apache.james.mdn.modifier.DispositionModifier
+import org.apache.james.mdn.sending.mode.DispositionSendingMode
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+class MDNReportParserTest {
+
+  @Test
+  def parseShouldReturnEmptyWhenMissingFinalRecipient(): Unit = {
+    val missing = "Disposition: automatic-action/MDN-sent-automatically;processed\r\n"
+    val actual = MDNReportParser.parse(missing).toOption
+    assertThat(actual.isEmpty)
+  }
+
+  @Test
+  def parseShouldReturnMdnReportWhenMaximalSubset(): Unit = {
+    val maximal = """Reporting-UA: UA_name; UA_product
+      |MDN-Gateway: smtp; apache.org
+      |Original-Recipient: rfc822; originalRecipient
+      |Final-Recipient: rfc822; final_recipient
+      |Original-Message-ID: <or...@message.id>
+      |Disposition: automatic-action/MDN-sent-automatically;processed/error,failed
+      |Error: Message1
+      |Error: Message2
+      |X-OPENPAAS-IP: 177.177.177.77
+      |X-OPENPAAS-PORT: 8000
+      |""".replaceAllLiterally(System.lineSeparator(), "\r\n")
+      .stripMargin
+    val expected = Some(MDNReport.builder
+      .reportingUserAgentField(ReportingUserAgent.builder
+        .userAgentName("UA_name")
+        .userAgentProduct("UA_product")
+        .build)
+      .gatewayField(Gateway.builder
+        .nameType(new AddressType("smtp"))
+        .name(Text.fromRawText("apache.org"))
+        .build)
+      .originalRecipientField("originalRecipient")
+      .finalRecipientField("final_recipient")
+      .originalMessageIdField("<or...@message.id>")
+      .dispositionField(Disposition.builder
+        .actionMode(DispositionActionMode.Automatic)
+        .sendingMode(DispositionSendingMode.Automatic)
+        .`type`(DispositionType.Processed)
+        .addModifier(DispositionModifier.Error)
+        .addModifier(DispositionModifier.Failed)
+        .build)
+      .addErrorField("Message1")
+      .addErrorField("Message2")
+      .withExtensionField(ExtensionField.builder
+        .fieldName("X-OPENPAAS-IP")
+        .rawValue(" 177.177.177.77")
+        .build)
+      .withExtensionField(ExtensionField.builder
+        .fieldName("X-OPENPAAS-PORT")
+        .rawValue(" 8000")
+        .build)
+      .build)
+    val actual = MDNReportParser.parse(maximal).toOption
+    assertThat(actual).isEqualTo(expected)
+  }
+
+  @Test
+  def parseShouldReturnMdnReportWhenMinimalSubset(): Unit = {
+    val minimal = """Final-Recipient: rfc822; final_recipient
+      |Disposition: automatic-action/MDN-sent-automatically;processed
+      |""".replaceAllLiterally(System.lineSeparator(), "\r\n")
+      .stripMargin
+    val disposition = Disposition.builder
+      .actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Automatic)
+      .`type`(DispositionType.Processed)
+      .build
+    val expected = Some(MDNReport.builder
+      .finalRecipientField("final_recipient")
+      .dispositionField(disposition)
+      .build)
+    val actual = MDNReportParser.parse(minimal).toOption
+    assertThat(actual).isEqualTo(expected)
+  }
+
+  @Test
+  def parseShouldReturnEmptyWhenDuplicatedFields(): Unit = {
+    val duplicated = """Final-Recipient: rfc822; final_recipient
+      |Final-Recipient: rfc822; final_recipient
+      |Disposition: automatic-action/MDN-sent-automatically;processed
+      |""".replaceAllLiterally(System.lineSeparator(), "\r\n")
+      .stripMargin
+    val actual = MDNReportParser.parse(duplicated).toOption
+    assertThat(actual.isEmpty)
+  }
+
+  @Test
+  def reportingUserAgentShouldParseWithoutProduct(): Unit = {
+    val userAgent = "Reporting-UA: UA_name"
+    val parser = new MDNReportParser(userAgent)
+    val result = parser.reportingUaField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(ReportingUserAgent.builder.userAgentName("UA_name").build)
+  }
+
+  @Test
+  def reportingUserAgentShouldParseWithProduct(): Unit = {
+    val userAgent = "Reporting-UA: UA_name; UA_product"
+    val parser = new MDNReportParser(userAgent)
+    val result = parser.reportingUaField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(ReportingUserAgent.builder.userAgentName("UA_name").userAgentProduct("UA_product").build)
+  }
+
+  @Test
+  def mdnGatewayFieldShouldParse(): Unit = {
+    val gateway = "MDN-Gateway: smtp; apache.org"
+    val parser = new MDNReportParser(gateway)
+    val result = parser.mdnGatewayField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(Gateway.builder.nameType(new AddressType("smtp")).name(Text.fromRawText("apache.org")).build)
+  }
+
+  @Test
+  def originalRecipientFieldShouldParse(): Unit = {
+    val originalRecipient = "Original-Recipient: rfc822; originalRecipient"
+    val parser = new MDNReportParser(originalRecipient)
+    val result = parser.originalRecipientField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(OriginalRecipient.builder.addressType(new AddressType("rfc822")).originalRecipient(Text.fromRawText("originalRecipient")).build)
+  }
+
+  @Test
+  def finalRecipientFieldShouldParse(): Unit = {
+    val finalRecipient = "Final-Recipient: rfc822; final_recipient"
+    val parser = new MDNReportParser(finalRecipient)
+    val result = parser.finalRecipientField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(FinalRecipient.builder.addressType(new AddressType("rfc822")).finalRecipient(Text.fromRawText("final_recipient")).build)
+  }
+
+  @Test
+  def originalMessageIdShouldParse(): Unit = {
+    val originalMessageId = "Original-Message-ID: <or...@message.id>"
+    val parser = new MDNReportParser(originalMessageId)
+    val result = parser.originalMessageIdField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(new OriginalMessageId("<or...@message.id>"))
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenMinimal(): Unit = {
+    val disposition = "Disposition: automatic-action/MDN-sent-automatically;processed"
+    val expected = Disposition.builder
+      .actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Automatic)
+      .`type`(DispositionType.Processed)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenMaximal(): Unit = {
+    val disposition = "Disposition: automatic-action/MDN-sent-automatically;processed/error,failed"
+    val expected = Disposition.builder.
+      actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Automatic)
+      .`type`(DispositionType.Processed)
+      .addModifier(DispositionModifier.Error)
+      .addModifier(DispositionModifier.Failed)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenManualAutomaticWithDisplayedType(): Unit = {
+    val disposition = "Disposition: manual-action/MDN-sent-automatically;processed"
+    val expected = Disposition.builder
+      .actionMode(DispositionActionMode.Manual)
+      .sendingMode(DispositionSendingMode.Automatic)
+      .`type`(DispositionType.Processed)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenAutomaticManualWithDisplayedType(): Unit = {
+    val disposition = "Disposition: automatic-action/MDN-sent-manually;processed"
+    val expected = Disposition.builder
+      .actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Manual)
+      .`type`(DispositionType.Processed)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenDeletedType(): Unit = {
+    val disposition = "Disposition: automatic-action/MDN-sent-manually;deleted"
+    val expected = Disposition.builder
+      .actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Manual)
+      .`type`(DispositionType.Deleted)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenDispatchedType(): Unit = {
+    val disposition = "Disposition: automatic-action/MDN-sent-manually;dispatched"
+    val expected = Disposition.builder
+      .actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Manual)
+      .`type`(DispositionType.Dispatched)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def dispositionFieldShouldParseWhenDisplayedType(): Unit = {
+    val disposition = "Disposition: automatic-action/MDN-sent-manually;displayed"
+    val expected = Disposition.builder
+      .actionMode(DispositionActionMode.Automatic)
+      .sendingMode(DispositionSendingMode.Manual)
+      .`type`(DispositionType.Displayed)
+      .build
+    val parser = new MDNReportParser(disposition)
+    val result = parser.dispositionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(expected)
+  }
+
+  @Test
+  def errorFieldShouldParse(): Unit = {
+    val error = "Error: Message1"
+    val parser = new MDNReportParser(error)
+    val result = parser.errorField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(new Error(Text.fromRawText("Message1")))
+  }
+
+  @Test
+  def extensionFieldShouldParse(): Unit = {
+    val extension = "X-OPENPAAS-IP: 177.177.177.77"
+    val parser = new MDNReportParser(extension)
+    val result = parser.extentionField.run()
+    assertThat(result.isSuccess).isTrue
+    assertThat(result.get).isEqualTo(ExtensionField.builder.fieldName("X-OPENPAAS-IP").rawValue(" 177.177.177.77").build)
+  }
+}
\ No newline at end of file
diff --git a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/AutomaticallySentMailDetectorImpl.java b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/AutomaticallySentMailDetectorImpl.java
index 40e1cfa..ab9f557 100644
--- a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/AutomaticallySentMailDetectorImpl.java
+++ b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/AutomaticallySentMailDetectorImpl.java
@@ -116,10 +116,9 @@ public class AutomaticallySentMailDetectorImpl implements AutomaticallySentMailD
             @Override
             public void body(BodyDescriptor bodyDescriptor, InputStream inputStream) throws MimeException, IOException {
                 if (bodyDescriptor.getMimeType().equalsIgnoreCase("message/disposition-notification")) {
-                    resultCollector.setResult(new MDNReportParser()
-                        .parse(inputStream, bodyDescriptor.getCharset())
+                    resultCollector.setResult(MDNReportParser.parse(inputStream, bodyDescriptor.getCharset())
                         .map(report -> report.getDispositionField().getSendingMode() == DispositionSendingMode.Automatic)
-                        .orElse(false));
+                        .getOrElse(() -> false));
                 }
             }
         };
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java
index b723f16..97f4b5b 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java
@@ -21,6 +21,7 @@ package org.apache.james.jmap.mailet;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
 import java.util.Optional;
 
@@ -54,6 +55,7 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Iterables;
 
 import reactor.core.publisher.Flux;
+import scala.util.Try;
 
 /**
  * This mailet handles MDN messages and define a header X-JAMES-MDN-JMAP-MESSAGE-ID referencing
@@ -118,8 +120,14 @@ public class ExtractMDNOriginalJMAPMessageId extends GenericMailet {
 
     private Optional<MDNReport> parseReport(Entity report) {
         LOGGER.debug("Parsing report");
-        try {
-            return new MDNReportParser().parse(((SingleBody)report.getBody()).getInputStream(), report.getCharset());
+        try (InputStream inputStream = ((SingleBody) report.getBody()).getInputStream()) {
+            Try<MDNReport> result = MDNReportParser.parse(inputStream, report.getCharset());
+            if (result.isSuccess()) {
+                return Optional.of(result.get());
+            } else {
+                LOGGER.error("unable to parse MESSAGE_DISPOSITION_NOTIFICATION part", result.failed().get());
+                return Optional.empty();
+            }
         } catch (IOException e) {
             LOGGER.error("unable to parse MESSAGE_DISPOSITION_NOTIFICATION part", e);
             return Optional.empty();


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


[james-project] 01/03: [Refactoring] don't use raw array for listRights

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

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

commit d73aa9c28e938c006c1e7709f1a288ff7a0ca597
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Mon Apr 27 23:14:11 2020 +0200

    [Refactoring] don't use raw array for listRights
---
 .../org/apache/james/mailbox/RightManager.java     |  4 ++-
 .../james/mailbox/acl/MailboxACLResolver.java      |  4 ++-
 .../james/mailbox/acl/UnionMailboxACLResolver.java | 30 ++++++++++++----------
 .../james/mailbox/store/StoreMailboxManager.java   |  2 +-
 .../james/mailbox/store/StoreRightManager.java     |  3 ++-
 .../imap/encode/ListRightsResponseEncoder.java     |  3 ++-
 .../imap/message/response/ListRightsResponse.java  | 12 ++++-----
 .../james/imap/processor/ListRightsProcessor.java  |  2 +-
 .../imap/processor/ListRightsProcessorTest.java    |  9 +++++--
 9 files changed, 42 insertions(+), 27 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java
index b37b212..3fd6d8a 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox;
 
+import java.util.List;
+
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxACL;
 import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
@@ -78,7 +80,7 @@ public interface RightManager {
      * @return result suitable for the LISTRIGHTS IMAP command
      * @throws MailboxException in case of unknown mailbox or unsupported right
      */
-    Rfc4314Rights[] listRights(MailboxPath mailboxPath, MailboxACL.EntryKey identifier, MailboxSession session) throws MailboxException;
+    List<MailboxACL.Rfc4314Rights> listRights(MailboxPath mailboxPath, MailboxACL.EntryKey identifier, MailboxSession session) throws MailboxException;
 
     MailboxACL listRights(MailboxPath mailboxPath, MailboxSession session) throws MailboxException;
 
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
index 0677c99..96b8869 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
@@ -20,6 +20,8 @@
 
 package org.apache.james.mailbox.acl;
 
+import java.util.List;
+
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.exception.UnsupportedRightException;
 import org.apache.james.mailbox.model.MailboxACL;
@@ -68,7 +70,7 @@ public interface MailboxACLResolver {
      *         explicitly for the given identifier. Further elements are groups
      *         of rights which can be set for the given identifier and resource.
      */
-    MailboxACL.Rfc4314Rights[] listRights(MailboxACL.EntryKey key, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
+    List<MailboxACL.Rfc4314Rights> listRights(MailboxACL.EntryKey key, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
 
     /**
      * Computes the rights which apply to the given user and resource. Global
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
index f13af64..ebefaf8 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
@@ -20,10 +20,12 @@
 
 package org.apache.james.mailbox.acl;
 
-import java.util.ArrayList;
+import static java.util.function.Predicate.not;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.stream.Stream;
 
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.exception.UnsupportedRightException;
@@ -35,6 +37,8 @@ import org.apache.james.mailbox.model.MailboxACL.Right;
 import org.apache.james.mailbox.model.MailboxACL.SpecialName;
 import org.apache.james.mime4j.dom.address.Mailbox;
 
+import com.github.steveash.guavate.Guavate;
+
 
 /**
  * An implementation which works with the union of the rights granted to the
@@ -266,28 +270,28 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      * authenticated.
      */
     @Override
-    public Rfc4314Rights[] listRights(EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
+    public List<Rfc4314Rights> listRights(EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
         Rfc4314Rights[] positiveNegativePair = { MailboxACL.NO_RIGHTS, MailboxACL.NO_RIGHTS };
 
         MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL;
         resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair);
 
         if (queryKey.isNegative()) {
-            return toListRightsArray(positiveNegativePair[NEGATIVE_INDEX]);
+            return toListRights(positiveNegativePair[NEGATIVE_INDEX]);
         } else {
-            return toListRightsArray(positiveNegativePair[POSITIVE_INDEX].except(positiveNegativePair[NEGATIVE_INDEX]));
+            return toListRights(positiveNegativePair[POSITIVE_INDEX].except(positiveNegativePair[NEGATIVE_INDEX]));
         }
     }
 
-    private static Rfc4314Rights[] toListRightsArray(Rfc4314Rights implicitRights) throws UnsupportedRightException {
-        List<Rfc4314Rights> result = new ArrayList<>();
-        result.add(implicitRights);
-        for (Right right : MailboxACL.FULL_RIGHTS.list()) {
-            if (!implicitRights.contains(right)) {
-                result.add(new Rfc4314Rights(right));
-            }
-        }
-        return result.toArray(Rfc4314Rights[]::new);
+    private static List<Rfc4314Rights> toListRights(Rfc4314Rights implicitRights) throws UnsupportedRightException {
+        return Stream.concat(
+            MailboxACL.FULL_RIGHTS
+                .list()
+                .stream()
+                .filter(not(implicitRights::contains))
+                .map(Rfc4314Rights::new),
+            Stream.of(implicitRights))
+        .collect(Guavate.toImmutableList());
     }
 
     @Override
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
index 901bda5..09ea5eb 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
@@ -783,7 +783,7 @@ public class StoreMailboxManager implements MailboxManager {
     }
 
     @Override
-    public Rfc4314Rights[] listRights(MailboxPath mailboxPath, MailboxACL.EntryKey key, MailboxSession session) throws MailboxException {
+    public List<Rfc4314Rights> listRights(MailboxPath mailboxPath, MailboxACL.EntryKey key, MailboxSession session) throws MailboxException {
         return storeRightManager.listRights(mailboxPath, key, session);
     }
 
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java
index 6b4c205..0d68316 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.store;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
@@ -126,7 +127,7 @@ public class StoreRightManager implements RightManager {
     }
 
     @Override
-    public Rfc4314Rights[] listRights(MailboxPath mailboxPath, EntryKey key, MailboxSession session) throws MailboxException {
+    public List<Rfc4314Rights> listRights(MailboxPath mailboxPath, EntryKey key, MailboxSession session) throws MailboxException {
         MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session);
         Mailbox mailbox = mapper.findMailboxByPathBlocking(mailboxPath);
 
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/encode/ListRightsResponseEncoder.java b/protocols/imap/src/main/java/org/apache/james/imap/encode/ListRightsResponseEncoder.java
index 5fdddce..790a21e 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/encode/ListRightsResponseEncoder.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/encode/ListRightsResponseEncoder.java
@@ -20,6 +20,7 @@
 package org.apache.james.imap.encode;
 
 import java.io.IOException;
+import java.util.List;
 
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.message.response.ListRightsResponse;
@@ -45,7 +46,7 @@ public class ListRightsResponseEncoder implements ImapResponseEncoder<ListRights
         String identifier = listRightsResponse.getIdentifier();
         composer.quote(identifier);
         
-        Rfc4314Rights[] rights = listRightsResponse.getRights();
+        List<Rfc4314Rights> rights = listRightsResponse.getRights();
         
         for (Rfc4314Rights entry : rights) {
             composer.quote(entry.serialize());
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListRightsResponse.java b/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListRightsResponse.java
index 5bba8a8..6565982 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListRightsResponse.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/response/ListRightsResponse.java
@@ -19,7 +19,7 @@
 
 package org.apache.james.imap.message.response;
 
-import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 import org.apache.james.imap.api.ImapConstants;
@@ -32,9 +32,9 @@ import org.apache.james.mailbox.model.MailboxACL;
 public final class ListRightsResponse implements ImapResponseMessage {
     private final String identifier;
     private final String mailboxName;
-    private final MailboxACL.Rfc4314Rights[] rights;
+    private final List<MailboxACL.Rfc4314Rights> rights;
 
-    public ListRightsResponse(String mailboxName, String identifier, MailboxACL.Rfc4314Rights[] rights) {
+    public ListRightsResponse(String mailboxName, String identifier, List<MailboxACL.Rfc4314Rights> rights) {
         super();
         this.mailboxName = mailboxName;
         this.identifier = identifier;
@@ -49,7 +49,7 @@ public final class ListRightsResponse implements ImapResponseMessage {
         return mailboxName;
     }
 
-    public MailboxACL.Rfc4314Rights[] getRights() {
+    public List<MailboxACL.Rfc4314Rights> getRights() {
         return rights;
     }
 
@@ -60,14 +60,14 @@ public final class ListRightsResponse implements ImapResponseMessage {
 
             return Objects.equals(this.mailboxName, other.mailboxName) &&
                 Objects.equals(this.identifier, other.identifier) &&
-                Arrays.equals(this.rights, other.rights);
+                Objects.equals(this.rights, other.rights);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(mailboxName, identifier, Arrays.hashCode(rights));
+        return Objects.hash(mailboxName, identifier, rights);
     }
 
     @Override
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListRightsProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListRightsProcessor.java
index 4c7a147..ca76e6f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListRightsProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListRightsProcessor.java
@@ -107,7 +107,7 @@ public class ListRightsProcessor extends AbstractMailboxProcessor<ListRightsRequ
                 // Note that Section 6 recommends additional identifier’s verification
                 // steps.
                 
-                Rfc4314Rights[] rights = mailboxManager.listRights(mailboxPath, key, mailboxSession);
+                List<Rfc4314Rights> rights = mailboxManager.listRights(mailboxPath, key, mailboxSession);
                 ListRightsResponse aclResponse = new ListRightsResponse(mailboxName, identifier, rights);
                 responder.respond(aclResponse);
                 okComplete(request, responder);
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/ListRightsProcessorTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/ListRightsProcessorTest.java
index 2104d9b..fafa0c1 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/ListRightsProcessorTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/ListRightsProcessorTest.java
@@ -29,6 +29,8 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import java.util.List;
+
 import org.apache.james.core.Username;
 import org.apache.james.imap.api.ImapConstants;
 import org.apache.james.imap.api.message.response.ImapResponseMessage;
@@ -69,7 +71,7 @@ public class ListRightsProcessorTest {
     private ListRightsRequest listRightsRequest;
     private ListRightsProcessor subject;
     private EntryKey user1Key;
-    private Rfc4314Rights[] listRights;
+    private List<Rfc4314Rights> listRights;
     private MailboxPath path;
     private Responder responder;
     private ArgumentCaptor<ImapResponseMessage> argumentCaptor;
@@ -98,7 +100,10 @@ public class ListRightsProcessorTest {
         listRightsRequest = new ListRightsRequest(TAG, MAILBOX_NAME, USER_1.asString());
 
         user1Key = EntryKey.deserialize(USER_1.asString());
-        listRights = new Rfc4314Rights[] {Rfc4314Rights.fromSerializedRfc4314Rights("ae"), Rfc4314Rights.fromSerializedRfc4314Rights("i"), Rfc4314Rights.fromSerializedRfc4314Rights("k")};
+        listRights = List.of(
+            Rfc4314Rights.fromSerializedRfc4314Rights("ae"),
+            Rfc4314Rights.fromSerializedRfc4314Rights("i"),
+            Rfc4314Rights.fromSerializedRfc4314Rights("k"));
     }
     
     @Test


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