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

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


The following commit(s) were added to refs/heads/droplist by this push:
     new ae8bfc4a35 JAMES-3946 add a memory implementation of the DropList 
(#2197)
ae8bfc4a35 is described below

commit ae8bfc4a35535a8f69147025ed1df28e368013ac
Author: Maksim <85022218+maxxx...@users.noreply.github.com>
AuthorDate: Tue Apr 16 12:00:22 2024 +0300

    JAMES-3946 add a memory implementation of the DropList (#2197)
---
 .../apache/james/droplists/api/DropListEntry.java  |  71 ++++----
 .../james/droplists/api/DropListContract.java      | 190 +++++++++++++++++++++
 .../james/droplists/api/DropListEntryTest.java     | 128 +++++---------
 .../james/droplists/memory/MemoryDropList.java     |  97 +++++++++++
 .../james/droplists/memory/MemoryDropListTest.java |  39 +++++
 5 files changed, 407 insertions(+), 118 deletions(-)

diff --git 
a/server/data/data-api/src/main/java/org/apache/james/droplists/api/DropListEntry.java
 
b/server/data/data-api/src/main/java/org/apache/james/droplists/api/DropListEntry.java
index ab73cc4e51..7bec40408f 100644
--- 
a/server/data/data-api/src/main/java/org/apache/james/droplists/api/DropListEntry.java
+++ 
b/server/data/data-api/src/main/java/org/apache/james/droplists/api/DropListEntry.java
@@ -18,12 +18,16 @@
  ****************************************************************/
 package org.apache.james.droplists.api;
 
-import static org.apache.james.core.Domain.MAXIMUM_DOMAIN_LENGTH;
+import static org.apache.james.droplists.api.OwnerScope.DOMAIN;
 import static org.apache.james.droplists.api.OwnerScope.GLOBAL;
+import static org.apache.james.droplists.api.OwnerScope.USER;
 
 import java.util.Objects;
 import java.util.Optional;
 
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 
@@ -34,53 +38,58 @@ public class DropListEntry {
     }
 
     public static class Builder {
-        private Optional<OwnerScope> ownerScope = Optional.empty();
-        private String owner;
+        private OwnerScope ownerScope;
+        private Optional<String> owner = Optional.empty();
         private DeniedEntityType deniedEntityType;
         private String deniedEntity;
 
-        public Builder ownerScope(OwnerScope ownerScope) {
-            this.ownerScope = Optional.ofNullable(ownerScope);
+        public Builder userOwner(MailAddress mailAddress) {
+            Preconditions.checkNotNull(mailAddress);
+            this.owner = Optional.of(mailAddress.toString());
+            this.ownerScope = USER;
+            return this;
+        }
+
+        public Builder domainOwner(Domain domain) {
+            Preconditions.checkNotNull(domain);
+            this.owner = Optional.of(domain.asString());
+            this.ownerScope = DOMAIN;
             return this;
         }
 
-        public Builder owner(String owner) {
-            Preconditions.checkNotNull(owner);
-            this.owner = owner;
+        public Builder forAll() {
+            this.ownerScope = GLOBAL;
             return this;
         }
 
-        public Builder deniedEntityType(DeniedEntityType deniedEntityType) {
-            Preconditions.checkNotNull(deniedEntityType);
-            this.deniedEntityType = deniedEntityType;
+        public Builder denyDomain(Domain domain) {
+            Preconditions.checkNotNull(domain);
+            this.deniedEntity = domain.asString();
+            this.deniedEntityType = DeniedEntityType.DOMAIN;
             return this;
         }
 
-        public Builder deniedEntity(String deniedEntity) {
-            Preconditions.checkNotNull(deniedEntity);
-            this.deniedEntity = deniedEntity;
+        public Builder denyAddress(MailAddress mailAddress) {
+            Preconditions.checkNotNull(mailAddress);
+            this.deniedEntity = mailAddress.toString();
+            this.deniedEntityType = DeniedEntityType.ADDRESS;
             return this;
         }
 
         public DropListEntry build() {
-            OwnerScope scope = ownerScope.orElse(GLOBAL);
-            Preconditions.checkArgument(owner != null && 
!owner.trim().isBlank(), "owner must not be null, empty, or blank");
-            Preconditions.checkArgument(owner.length() <= 
MAXIMUM_DOMAIN_LENGTH,
-                "owner length should not be longer than %s characters", 
MAXIMUM_DOMAIN_LENGTH);
             Preconditions.checkArgument(deniedEntityType != null, 
"`deniedEntityType` is mandatory");
+            Preconditions.checkArgument(ownerScope != null, "`ownerScope` is 
mandatory");
             Preconditions.checkArgument(deniedEntity != null && 
!deniedEntity.isBlank(), "`deniedEntity` must not be null, empty, or blank");
-            Preconditions.checkArgument(deniedEntity.length() <= 
MAXIMUM_DOMAIN_LENGTH,
-                "deniedEntity length should not be longer than %s characters", 
MAXIMUM_DOMAIN_LENGTH);
-            return new DropListEntry(scope, owner, deniedEntityType, 
deniedEntity);
+            return new DropListEntry(ownerScope, owner, deniedEntityType, 
deniedEntity);
         }
     }
 
     private final OwnerScope ownerScope;
-    private final String owner;
+    private final Optional<String> owner;
     private final DeniedEntityType deniedEntityType;
     private final String deniedEntity;
 
-    private DropListEntry(OwnerScope ownerScope, String owner, 
DeniedEntityType deniedEntityType, String deniedEntity) {
+    private DropListEntry(OwnerScope ownerScope, Optional<String> owner, 
DeniedEntityType deniedEntityType, String deniedEntity) {
         this.ownerScope = ownerScope;
         this.owner = owner;
         this.deniedEntityType = deniedEntityType;
@@ -92,7 +101,7 @@ public class DropListEntry {
     }
 
     public String getOwner() {
-        return owner;
+        return owner.orElse("");
     }
 
     public DeniedEntityType getDeniedEntityType() {
@@ -121,11 +130,11 @@ public class DropListEntry {
 
     @Override
     public String toString() {
-        return MoreObjects.toStringHelper(this)
-            .add("ownerScope", ownerScope)
-            .add("owner", owner)
-            .add("deniedType", deniedEntityType)
-            .add("deniedEntity", deniedEntity)
-            .toString();
+        MoreObjects.ToStringHelper result = MoreObjects.toStringHelper(this)
+            .add("ownerScope", ownerScope);
+        owner.ifPresent(o -> result.add("owner", o));
+        result.add("deniedType", deniedEntityType)
+            .add("deniedEntity", deniedEntity);
+        return result.toString();
     }
-}
+}
\ No newline at end of file
diff --git 
a/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListContract.java
 
b/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListContract.java
new file mode 100644
index 0000000000..a4993ff973
--- /dev/null
+++ 
b/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListContract.java
@@ -0,0 +1,190 @@
+/****************************************************************
+ * 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.droplists.api;
+
+import static org.apache.james.droplists.api.OwnerScope.GLOBAL;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.util.stream.Stream;
+
+import jakarta.mail.internet.AddressException;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public interface DropListContract {
+
+    DropList dropList();
+
+    @Test
+    default void shouldAddEntry() throws AddressException {
+        DropListEntry dropListEntry = DropListEntry.builder()
+            .forAll()
+            .denyAddress(new MailAddress("den...@denied.com"))
+            .build();
+
+        Mono<Void> result = dropList().add(dropListEntry);
+
+        assertThat(dropList().list(GLOBAL, 
dropListEntry.getOwner()).collectList().block().size()).isEqualTo(1);
+        assertThat(result).isEqualTo(Mono.empty());
+    }
+
+    @Test
+    default void shouldRemoveEntry() throws AddressException {
+        DropListEntry dropListEntry = DropListEntry.builder()
+            .forAll()
+            .denyAddress(new MailAddress("den...@denied.com"))
+            .build();
+
+        dropList().add(dropListEntry);
+
+        Mono<Void> result = dropList().remove(dropListEntry);
+
+        assertThat(dropList().list(GLOBAL, 
dropListEntry.getOwner()).collectList().block().size()).isZero();
+        assertThat(result).isEqualTo(Mono.empty());
+    }
+
+    @Test
+    default void shouldThrowWhenAddOnNullDropListEntry() {
+        assertThatThrownBy(() -> dropList().add(null))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void shouldThrowWhenRemoveOnNullDropListEntry() {
+        assertThatThrownBy(() -> dropList().remove(null))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void shouldThrowWhenListOnNullScope() {
+        assertThatThrownBy(() -> dropList().list(null, "owner"))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void shouldThrowWhenListOnNullOwner() {
+        assertThatThrownBy(() -> dropList().list(GLOBAL, null))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void shouldThrowWhenQueryOnNullScope() {
+        assertThatThrownBy(() -> dropList().query(null, "owner", new 
MailAddress("sen...@example.com")))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void shouldThrowWhenQueryOnNullOwner() {
+        assertThatThrownBy(() -> dropList().query(GLOBAL, null, new 
MailAddress("sen...@example.com")))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    default void shouldThrowWhenQueryOnNullSender() {
+        assertThatThrownBy(() -> dropList().query(GLOBAL, "owner", null))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @ParameterizedTest(name = "{index} {0}")
+    @MethodSource("provideParametersForGetEntryListTest")
+    default void shouldGetEntryListForSpecifiedScopeAndOwner(DropListEntry 
dropListEntry) {
+        dropList().add(dropListEntry);
+
+        Flux<DropListEntry> result = 
dropList().list(dropListEntry.getOwnerScope(), dropListEntry.getOwner());
+
+        assertThat(result.collectList().block().size()).isEqualTo(1);
+    }
+
+
+    @ParameterizedTest(name = "{index} {0}")
+    @MethodSource("provideParametersForReturnAllowedTest")
+    default void shouldReturnAllowed(DropListEntry dropListEntry, MailAddress 
senderMailAddress) {
+        dropList().add(dropListEntry);
+
+        Mono<DropList.Status> result = 
dropList().query(dropListEntry.getOwnerScope(), dropListEntry.getOwner(), 
senderMailAddress);
+
+        assertThat(result.block()).isEqualTo(DropList.Status.ALLOWED);
+    }
+
+    @ParameterizedTest(name = "{index} {0}")
+    @MethodSource("provideParametersForReturnBlockedTest")
+    default void shouldReturnBlocked(DropListEntry dropListEntry, MailAddress 
senderMailAddress) {
+        dropList().add(dropListEntry);
+
+        Mono<DropList.Status> result = 
dropList().query(dropListEntry.getOwnerScope(), dropListEntry.getOwner(), 
senderMailAddress);
+
+        assertThat(result.block()).isEqualTo(DropList.Status.BLOCKED);
+    }
+
+    static Stream<DropListEntry> getDropListTestEntries() throws 
AddressException {
+        return Stream.of(
+            DropListEntry.builder()
+                .forAll()
+                .denyAddress(new MailAddress("den...@denied.com"))
+                .build(),
+            DropListEntry.builder()
+                .forAll()
+                .denyDomain(Domain.of("denied.com"))
+                .build(),
+            DropListEntry.builder()
+                .domainOwner(Domain.of("example.com"))
+                .denyAddress(new MailAddress("den...@denied.com"))
+                .build(),
+            DropListEntry.builder()
+                .domainOwner(Domain.of("example.com"))
+                .denyDomain(Domain.of("denied.com"))
+                .build(),
+            DropListEntry.builder()
+                .userOwner(new MailAddress("ow...@example.com"))
+                .denyAddress(new MailAddress("den...@denied.com"))
+                .build(),
+            DropListEntry.builder()
+                .userOwner(new MailAddress("ow...@example.com"))
+                .denyDomain(Domain.of("denied.com"))
+                .build());
+    }
+
+    static Stream<Arguments> provideParametersForGetEntryListTest() throws 
AddressException {
+        return getDropListTestEntries().map(Arguments::of);
+    }
+
+    static Stream<Arguments> provideParametersForReturnAllowedTest() throws 
AddressException {
+        MailAddress allowedSenderAddress = new 
MailAddress("allo...@allowed.com");
+        return getDropListTestEntries().map(dropListEntry -> 
Arguments.of(dropListEntry, allowedSenderAddress));
+    }
+
+    static Stream<Arguments> provideParametersForReturnBlockedTest() throws 
AddressException {
+
+        MailAddress deniedSenderAddress = new MailAddress("den...@denied.com");
+        MailAddress deniedSenderDomain = new MailAddress("allo...@denied.com");
+        return getDropListTestEntries().map(dropListEntry ->
+            
dropListEntry.getDeniedEntityType().equals(DeniedEntityType.DOMAIN) ?
+                Arguments.of(dropListEntry, deniedSenderDomain) :
+                Arguments.of(dropListEntry, deniedSenderAddress));
+    }
+}
diff --git 
a/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListEntryTest.java
 
b/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListEntryTest.java
index 14e6ff5e60..79d6f2a4d3 100644
--- 
a/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListEntryTest.java
+++ 
b/server/data/data-api/src/test/java/org/apache/james/droplists/api/DropListEntryTest.java
@@ -21,15 +21,16 @@ package org.apache.james.droplists.api;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 
-import org.apache.commons.lang3.StringUtils;
+import jakarta.mail.internet.AddressException;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
 import org.junit.jupiter.api.Test;
 
 import nl.jqno.equalsverifier.EqualsVerifier;
 
 class DropListEntryTest {
 
-    private final String LONG_ENTITY = StringUtils.repeat('x', 254);
-
     @Test
     void shouldRespectEqualsContract() {
         EqualsVerifier.forClass(DropListEntry.class)
@@ -37,152 +38,105 @@ class DropListEntryTest {
     }
 
     @Test
-    void shouldThrowOnWithoutOwner() {
-        DropListEntry.Builder builder = DropListEntry.builder()
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("deniedEntity");
+    void shouldThrowOnWhenBuilderIsEmpty() {
+        DropListEntry.Builder builder = DropListEntry.builder();
 
         assertThatThrownBy(builder::build)
             .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
-    void shouldThrowOnEmptyOwner() {
+    void shouldThrowOnWithoutOwnerScope() {
         DropListEntry.Builder builder = DropListEntry.builder()
-            .owner("")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("deniedEntity");
+            .denyDomain(Domain.of("denied.com"));
 
         assertThatThrownBy(builder::build)
             .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
-    void shouldThrowOnBlankOwner() {
-        DropListEntry.Builder builder = DropListEntry.builder()
-            .owner(" ")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("deniedEntity");
+    void shouldThrowOnNullUserOwner() {
+        DropListEntry.Builder builder = DropListEntry.builder();
 
-        assertThatThrownBy(builder::build)
-            .isInstanceOf(IllegalArgumentException.class);
+        assertThatThrownBy(() -> builder.userOwner(null))
+            .isInstanceOf(NullPointerException.class);
     }
 
     @Test
-    void shouldThrowOnNullOwner() {
+    void shouldThrowOnNullDomainOwner() {
         DropListEntry.Builder builder = DropListEntry.builder();
 
-        assertThatThrownBy(() -> builder.owner(null))
+        assertThatThrownBy(() -> builder.domainOwner(null))
             .isInstanceOf(NullPointerException.class);
     }
 
     @Test
     void shouldThrowOnWithoutDeniedEntity() {
         DropListEntry.Builder builder = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("");
+            .forAll();
 
         assertThatThrownBy(builder::build)
             .isInstanceOf(IllegalArgumentException.class);
     }
 
     @Test
-    void shouldThrowOnEmptyDeniedEntity() {
+    void shouldThrowOnNullDeniedDomain() {
         DropListEntry.Builder builder = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("");
+            .forAll();
 
-        assertThatThrownBy(builder::build)
-            .isInstanceOf(IllegalArgumentException.class);
-    }
-
-    @Test
-    void shouldThrowOnBlankDeniedEntity() {
-        DropListEntry.Builder builder = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity(" ");
-
-        assertThatThrownBy(builder::build)
-            .isInstanceOf(IllegalArgumentException.class);
+        assertThatThrownBy(() -> builder.denyDomain(null))
+            .isInstanceOf(NullPointerException.class);
     }
 
     @Test
-    void shouldThrowOnNullDeniedEntity() {
+    void shouldThrowOnNullDeniedMailAddress() {
         DropListEntry.Builder builder = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity(" ");
+            .forAll();
 
-        assertThatThrownBy(() -> builder.deniedEntity(null))
+        assertThatThrownBy(() -> builder.denyAddress(null))
             .isInstanceOf(NullPointerException.class);
     }
 
     @Test
-    void shouldDefaultGlobalOwnerScopeWhenNotSpecified() {
+    void shouldGlobalOwnerScopeBeSetWhenForAllIsCalled() {
         DropListEntry dropListEntry = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("entity")
+            .forAll()
+            .denyDomain(Domain.of("denied.com"))
             .build();
 
         assertThat(dropListEntry.getOwnerScope()).isEqualTo(OwnerScope.GLOBAL);
     }
 
     @Test
-    void shouldDefaultGlobalOwnerScopeOnNull() {
+    void shouldEmptyOwnerBeSetWhenForAllIsCalled() throws AddressException {
         DropListEntry dropListEntry = DropListEntry.builder()
-            .ownerScope(null)
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("entity")
+            .forAll()
+            .denyAddress(new MailAddress("den...@example.com"))
             .build();
 
-        assertThat(dropListEntry.getOwnerScope()).isEqualTo(OwnerScope.GLOBAL);
-    }
-
-    @Test
-    void shouldThrowOnNullDeniedEntityType() {
-        DropListEntry.Builder builder = DropListEntry.builder();
-
-        assertThatThrownBy(() -> builder.deniedEntityType(null))
-            .isInstanceOf(NullPointerException.class);
+        assertThat(dropListEntry.getOwner()).isEmpty();
     }
 
     @Test
-    void shouldThrowWhenDeniedEntityTooLong() {
-        DropListEntry.Builder builder = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity(LONG_ENTITY);
-
-        assertThatThrownBy(builder::build)
-            .isInstanceOf(IllegalArgumentException.class);
-    }
-
-    @Test
-    void shouldThrowWhenOwnerEntityTooLong() {
-        DropListEntry.Builder builder = DropListEntry.builder()
-            .owner(LONG_ENTITY)
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("deniedEntity");
+    void shouldReturnDropListEntryAsString() throws AddressException {
+        String expectedString = "DropListEntry{ownerScope=USER, 
owner=ow...@example.com, deniedType=DOMAIN, deniedEntity=denied.com}";
+        DropListEntry dropListEntry = DropListEntry.builder()
+            .userOwner(new MailAddress("ow...@example.com"))
+            .denyDomain(Domain.of("denied.com"))
+            .build();
 
-        assertThatThrownBy(builder::build)
-            .isInstanceOf(IllegalArgumentException.class);
+        assertThat(dropListEntry).hasToString(expectedString);
     }
 
     @Test
-    void shouldReturnDropListEntryAsString() {
-        String expectedString = "DropListEntry{ownerScope=GLOBAL, owner=owner, 
deniedType=DOMAIN, deniedEntity=entity}";
+    void shouldReturnDropListEntryAsStringWithoutOwnerWhenScopeGlobal() {
+        String expectedString = "DropListEntry{ownerScope=GLOBAL, 
deniedType=DOMAIN, deniedEntity=denied.com}";
         DropListEntry dropListEntry = DropListEntry.builder()
-            .owner("owner")
-            .deniedEntityType(DeniedEntityType.DOMAIN)
-            .deniedEntity("entity")
+            .forAll()
+            .denyDomain(Domain.of("denied.com"))
             .build();
 
-        assertThat(expectedString).isEqualTo(dropListEntry.toString());
+        assertThat(dropListEntry).hasToString(expectedString);
     }
 
 }
\ No newline at end of file
diff --git 
a/server/data/data-memory/src/main/java/org/apache/james/droplists/memory/MemoryDropList.java
 
b/server/data/data-memory/src/main/java/org/apache/james/droplists/memory/MemoryDropList.java
new file mode 100644
index 0000000000..b413d4f919
--- /dev/null
+++ 
b/server/data/data-memory/src/main/java/org/apache/james/droplists/memory/MemoryDropList.java
@@ -0,0 +1,97 @@
+/****************************************************************
+ * 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.droplists.memory;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.droplists.api.DeniedEntityType;
+import org.apache.james.droplists.api.DropList;
+import org.apache.james.droplists.api.DropListEntry;
+import org.apache.james.droplists.api.OwnerScope;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class MemoryDropList implements DropList {
+
+    private final Multimap<OwnerScope, DropListEntry> globalDropList = 
Multimaps.synchronizedMultimap(HashMultimap.create());
+    private final Multimap<OwnerScope, DropListEntry> domainDropList = 
Multimaps.synchronizedMultimap(HashMultimap.create());
+    private final Multimap<OwnerScope, DropListEntry> userDropList = 
Multimaps.synchronizedMultimap(HashMultimap.create());
+
+    @Override
+    public Mono<Void> add(DropListEntry entry) {
+        Preconditions.checkArgument(entry != null);
+        OwnerScope ownerScope = entry.getOwnerScope();
+        Multimap<OwnerScope, DropListEntry> selectedDropList = 
getDropListByScope(ownerScope);
+        selectedDropList.put(ownerScope, entry);
+        return Mono.empty();
+    }
+
+    @Override
+    public Mono<Void> remove(DropListEntry entry) {
+        Preconditions.checkArgument(entry != null);
+        OwnerScope ownerScope = entry.getOwnerScope();
+        Multimap<OwnerScope, DropListEntry> selectedDropList = 
getDropListByScope(ownerScope);
+        selectedDropList.remove(ownerScope, entry);
+        return Mono.empty();
+    }
+
+    @Override
+    public Flux<DropListEntry> list(OwnerScope ownerScope, String owner) {
+        Preconditions.checkArgument(ownerScope != null);
+        Preconditions.checkArgument(owner != null);
+        Multimap<OwnerScope, DropListEntry> selectedDropList = 
getDropListByScope(ownerScope);
+        return Flux.fromIterable(selectedDropList.get(ownerScope))
+            .filter(entry -> entry.getOwner().equals(owner));
+    }
+
+    @Override
+    public Mono<Status> query(OwnerScope ownerScope, String owner, MailAddress 
sender) {
+        Preconditions.checkArgument(ownerScope != null);
+        Preconditions.checkArgument(owner != null);
+        Preconditions.checkArgument(sender != null);
+        Multimap<OwnerScope, DropListEntry> selectedDropList = 
getDropListByScope(ownerScope);
+        boolean isBlocked = selectedDropList.get(ownerScope).stream()
+            .anyMatch(entry -> isEntryMatchingOwner(owner, entry) && 
isEntryMatchingDeniedEntity(sender, entry));
+
+        return Mono.just(isBlocked ? Status.BLOCKED : Status.ALLOWED);
+    }
+
+    private Multimap<OwnerScope, DropListEntry> getDropListByScope(OwnerScope 
ownerScope) {
+        return switch (ownerScope) {
+            case GLOBAL -> globalDropList;
+            case DOMAIN -> domainDropList;
+            case USER -> userDropList;
+        };
+    }
+
+    private boolean isEntryMatchingOwner(String owner, DropListEntry entry) {
+        return entry.getOwner().equals(owner);
+    }
+
+    private boolean isEntryMatchingDeniedEntity(MailAddress sender, 
DropListEntry entry) {
+        String entityFromSender = entry.getDeniedEntityType() == 
DeniedEntityType.DOMAIN ? sender.getDomain().asString() : sender.asString();
+
+        return entry.getDeniedEntity().equals(entityFromSender);
+    }
+}
diff --git 
a/server/data/data-memory/src/test/java/org/apache/james/droplists/memory/MemoryDropListTest.java
 
b/server/data/data-memory/src/test/java/org/apache/james/droplists/memory/MemoryDropListTest.java
new file mode 100644
index 0000000000..d78cfcca82
--- /dev/null
+++ 
b/server/data/data-memory/src/test/java/org/apache/james/droplists/memory/MemoryDropListTest.java
@@ -0,0 +1,39 @@
+/****************************************************************
+ * 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.droplists.memory;
+
+import org.apache.james.droplists.api.DropList;
+import org.apache.james.droplists.api.DropListContract;
+import org.junit.jupiter.api.BeforeEach;
+
+class MemoryDropListTest implements DropListContract {
+
+    DropList dropList;
+
+    @BeforeEach
+    void setup() {
+        dropList = new MemoryDropList();
+    }
+
+    @Override
+    public DropList dropList() {
+        return dropList;
+    }
+
+}
\ No newline at end of file


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

Reply via email to