You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/04/30 04:11:23 UTC

[james-project] 03/21: [PERF] Fasten Username and Domain core object parsing

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

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

commit 294b822883978f534c855893428fd1f2205d8734
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sun Apr 17 22:24:07 2022 +0700

    [PERF] Fasten Username and Domain core object parsing
    
     - Avoid allocation of substring
     - Avoid stream logic
     - Groups charmatcher checks.
     - Avoid needless operations in Username
    
    This delivers a 5 fold performance enhancement on a code
    path often called (event deserialization, JMAP auth,
     Cassandra mailbox queries). Memory allocation is also
     greatly reduced.
---
 .../main/java/org/apache/james/core/Domain.java    | 51 +++++++++++++---------
 .../main/java/org/apache/james/core/Username.java  |  5 +--
 .../contract/MailboxSetMethodContract.scala        |  2 +-
 .../james/webadmin/routes/MappingRoutesTest.java   |  2 +-
 ...erFastViewProjectionItemsRequestToTaskTest.java |  3 +-
 .../webadmin/routes/UserMailboxesRoutesTest.java   |  2 +-
 .../service/MailboxesExportRequestToTaskTest.java  |  3 +-
 .../service/SubscribeAllRequestToTaskTest.java     |  2 +-
 8 files changed, 37 insertions(+), 33 deletions(-)

diff --git a/core/src/main/java/org/apache/james/core/Domain.java b/core/src/main/java/org/apache/james/core/Domain.java
index 7fb06d254f..80f0e50196 100644
--- a/core/src/main/java/org/apache/james/core/Domain.java
+++ b/core/src/main/java/org/apache/james/core/Domain.java
@@ -20,21 +20,22 @@
 package org.apache.james.core;
 
 import java.io.Serializable;
-import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterables;
 import com.google.common.net.InetAddresses;
 
 public class Domain implements Serializable {
     private static final CharMatcher DASH_MATCHER = CharMatcher.anyOf("-_");
     private static final CharMatcher DIGIT_MATCHER = CharMatcher.inRange('0', '9');
-    private static final CharMatcher LETTER_MATCHER = CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z'));
-    private static final CharMatcher PART_CHAR_MATCHER = DIGIT_MATCHER.or(LETTER_MATCHER).or(DASH_MATCHER);
+    private static final CharMatcher PART_CHAR_MATCHER = new CharMatcher() {
+        @Override
+        public boolean matches(char c) {
+            return Character.isLetterOrDigit(c) || '-' == c || '_' == c || '.' == c;
+        }
+    };
 
     public static final Domain LOCALHOST = Domain.of("localhost");
     public static final int MAXIMUM_DOMAIN_LENGTH = 253;
@@ -52,27 +53,35 @@ public class Domain implements Serializable {
             "Domain name length should not exceed %s characters", MAXIMUM_DOMAIN_LENGTH);
 
         String domainWithoutBrackets = removeBrackets(domain);
-        List<String> parts = Splitter.on('.').splitToList(domainWithoutBrackets);
-        parts.forEach(Domain::assertValidPart);
-        assertValidLastPart(Iterables.getLast(parts), domainWithoutBrackets);
-
+        Preconditions.checkArgument(PART_CHAR_MATCHER.matchesAllOf(domainWithoutBrackets), "Domain parts ASCII chars must be a-z A-Z 0-9 - or _");
+
+        int pos = 0;
+        int nextDot = domainWithoutBrackets.indexOf('.');
+
+        while (nextDot > -1) {
+            if (pos + 1 > domainWithoutBrackets.length()) {
+                throw new IllegalArgumentException("Last domain part should not be empty");
+            }
+            Domain.assertValidPart(domainWithoutBrackets, pos, nextDot);
+            pos = nextDot + 1;
+            nextDot = domainWithoutBrackets.indexOf('.', pos);
+        }
+        Domain.assertValidPart(domainWithoutBrackets, pos, domainWithoutBrackets.length());
+        assertValidLastPart(domainWithoutBrackets, pos);
         return new Domain(domainWithoutBrackets);
     }
 
-    private static void assertValidPart(String domainPart) {
-        Preconditions.checkArgument(!domainPart.isEmpty(), "Domain part should not be empty");
-        Preconditions.checkArgument(!DASH_MATCHER.matches(domainPart.charAt(0)), "Domain part should not start with '-' or '_'");
-        Preconditions.checkArgument(!DASH_MATCHER.matches(domainPart.charAt(domainPart.length() - 1)), "Domain part should not end with '-' or '_'");
-        Preconditions.checkArgument(domainPart.length() <= 63, "Domain part should not not exceed 63 characters");
-
-        String asciiChars = CharMatcher.ascii().retainFrom(domainPart);
-        Preconditions.checkArgument(PART_CHAR_MATCHER.matchesAllOf(asciiChars), "Domain parts ASCII chars must be a-z A-Z 0-9 - or _");
+    private static void assertValidPart(String domainPart, int begin, int end) {
+        Preconditions.checkArgument(begin != end, "Domain part should not be empty");
+        Preconditions.checkArgument(!DASH_MATCHER.matches(domainPart.charAt(begin)), "Domain part should not start with '-' or '_'");
+        Preconditions.checkArgument(!DASH_MATCHER.matches(domainPart.charAt(end - 1)), "Domain part should not end with '-' or '_'");
+        Preconditions.checkArgument(end - begin <= 63, "Domain part should not not exceed 63 characters");
     }
 
-    private static void assertValidLastPart(String domainPart, String domain) {
-        boolean onlyDigits = DIGIT_MATCHER.matches(domainPart.charAt(0));
-        boolean invalid = onlyDigits && !validIPAddress(domain);
-        Preconditions.checkArgument(!invalid, "The k=last domain part must not start with 0-9");
+    private static void assertValidLastPart(String domainPart, int pos) {
+        boolean onlyDigits = DIGIT_MATCHER.matches(domainPart.charAt(pos));
+        boolean invalid = onlyDigits && !validIPAddress(domainPart);
+        Preconditions.checkArgument(!invalid, "The last domain part must not start with 0-9");
     }
 
     private static boolean validIPAddress(String value) {
diff --git a/core/src/main/java/org/apache/james/core/Username.java b/core/src/main/java/org/apache/james/core/Username.java
index 4a4c545f77..f1bc031210 100644
--- a/core/src/main/java/org/apache/james/core/Username.java
+++ b/core/src/main/java/org/apache/james/core/Username.java
@@ -33,7 +33,7 @@ public class Username {
 
     public static Username of(String username) {
         Preconditions.checkArgument(username != null, "username should not be null or empty");
-        Preconditions.checkArgument(!username.trim().isEmpty(), "username should not be null or empty after being trimmed");
+        Preconditions.checkArgument(!username.isBlank(), "username should not be null or empty after being trimmed");
         Preconditions.checkArgument(username.length() <= MAXIMUM_MAIL_ADDRESS_LENGTH,
             "username length should not be longer than %s characters", MAXIMUM_MAIL_ADDRESS_LENGTH);
 
@@ -43,9 +43,6 @@ public class Username {
         }
         String userPart = username.substring(0, atPosition);
         String domainPart = username.substring(atPosition + 1);
-        if (domainPart.indexOf('@') >= 0) {
-            throw new IllegalArgumentException("The username should not contain multiple domain delimiter. Value: " + username);
-        }
         return fromLocalPartWithDomain(userPart, domainPart);
     }
 
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
index cae88d6870..6ab298d0e1 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
@@ -5628,7 +5628,7 @@ trait MailboxSetMethodContract {
          |      "notUpdated": {
          |        "${mailboxId.serialize}": {
          |          "type": "invalidPatch",
-         |          "description": "The username should not contain multiple domain delimiter. Value: invalid@invalid@domain.tld"
+         |          "description": "Domain parts ASCII chars must be a-z A-Z 0-9 - or _"
          |        }
          |      }
          |    }, "c1"]
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
index 334c66e564..7cfee64db3 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/MappingRoutesTest.java
@@ -593,7 +593,7 @@ class MappingRoutesTest {
         .body("statusCode", is(400))
         .body("type", is("InvalidArgument"))
         .body("message", is("Invalid arguments supplied in the user request"))
-        .body("details", is("The username should not contain multiple domain delimiter. Value: alice123@domain@domain.tld"));
+        .body("details", is("Domain parts ASCII chars must be a-z A-Z 0-9 - or _"));
     }
 
     @Test
diff --git a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RecomputeUserFastViewProjectionItemsRequestToTaskTest.java b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RecomputeUserFastViewProjectionItemsRequestToTaskTest.java
index 192004eca3..e656b13b7f 100644
--- a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RecomputeUserFastViewProjectionItemsRequestToTaskTest.java
+++ b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RecomputeUserFastViewProjectionItemsRequestToTaskTest.java
@@ -66,7 +66,6 @@ import org.junit.jupiter.api.Test;
 import com.github.fge.lambdas.Throwing;
 
 import io.restassured.RestAssured;
-import io.restassured.filter.log.LogDetail;
 import spark.Service;
 
 class RecomputeUserFastViewProjectionItemsRequestToTaskTest {
@@ -243,7 +242,7 @@ class RecomputeUserFastViewProjectionItemsRequestToTaskTest {
             .body("statusCode", is(400))
             .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
             .body("message", is("Invalid arguments supplied in the user request"))
-            .body("details", is("The username should not contain multiple domain delimiter. Value: bad@bad@bad"));
+            .body("details", is("Domain parts ASCII chars must be a-z A-Z 0-9 - or _"));
     }
 
     @Test
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
index 861cb7bd54..d8211af8eb 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
@@ -1533,7 +1533,7 @@ class UserMailboxesRoutesTest {
                     .body("statusCode", Matchers.is(400))
                     .body("type", Matchers.is("InvalidArgument"))
                     .body("message", Matchers.is("Invalid arguments supplied in the user request"))
-                    .body("details", Matchers.is("The username should not contain multiple domain delimiter. Value: bad@bad@bad"));
+                    .body("details", Matchers.is("Domain parts ASCII chars must be a-z A-Z 0-9 - or _"));
             }
         }
 
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/MailboxesExportRequestToTaskTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/MailboxesExportRequestToTaskTest.java
index 752e1b3943..c52d8ffc1b 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/MailboxesExportRequestToTaskTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/MailboxesExportRequestToTaskTest.java
@@ -62,7 +62,6 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.restassured.RestAssured;
-import io.restassured.filter.log.LogDetail;
 import reactor.core.publisher.Mono;
 import spark.Service;
 
@@ -179,7 +178,7 @@ class MailboxesExportRequestToTaskTest {
             .body("statusCode", is(400))
             .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
             .body("message", is("Invalid arguments supplied in the user request"))
-            .body("details", is("The username should not contain multiple domain delimiter. Value: bad@bad@bad"));
+            .body("details", is("Domain parts ASCII chars must be a-z A-Z 0-9 - or _"));
     }
 
     @Test
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java
index f5aecfba18..96d52cdbf0 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java
@@ -174,7 +174,7 @@ class SubscribeAllRequestToTaskTest {
             .body("statusCode", is(400))
             .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
             .body("message", is("Invalid arguments supplied in the user request"))
-            .body("details", is("The username should not contain multiple domain delimiter. Value: bad@bad@bad"));
+            .body("details", is("Domain parts ASCII chars must be a-z A-Z 0-9 - or _"));
     }
 
     @Test


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