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/03/18 03:03:43 UTC

[james-project] 10/15: JAMES-3078 DefaultMailboxesReactiveProvisioner and tests

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 591aa677b73f24f8e31d54b4af849f5ca97ec643
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Mar 11 13:47:14 2020 +0700

    JAMES-3078 DefaultMailboxesReactiveProvisioner and tests
---
 .../http/DefaultMailboxesReactiveProvisioner.java  | 107 +++++++++++++++++++++
 .../DefaultMailboxesReactiveProvisionerTest.java   | 103 ++++++++++++++++++++
 ...aultMailboxesReactiveProvisionerThreadTest.java |  74 ++++++++++++++
 3 files changed, 284 insertions(+)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisioner.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisioner.java
new file mode 100644
index 0000000..7b0568d
--- /dev/null
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisioner.java
@@ -0,0 +1,107 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.jmap.http;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.DefaultMailboxes;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.SubscriptionManager;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.MailboxExistsException;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.api.TimeMetric;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import reactor.core.publisher.Mono;
+
+public class DefaultMailboxesReactiveProvisioner {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailboxesReactiveProvisioner.class);
+    private final MailboxManager mailboxManager;
+    private final SubscriptionManager subscriptionManager;
+    private final MetricFactory metricFactory;
+
+    @Inject
+    @VisibleForTesting
+    DefaultMailboxesReactiveProvisioner(MailboxManager mailboxManager,
+                                        SubscriptionManager subscriptionManager,
+                                        MetricFactory metricFactory) {
+        this.mailboxManager = mailboxManager;
+        this.subscriptionManager = subscriptionManager;
+        this.metricFactory = metricFactory;
+    }
+
+    public Mono<Void> createMailboxesIfNeeded(MailboxSession session) {
+        return Mono.fromRunnable(() -> {
+            TimeMetric timeMetric = metricFactory.timer("JMAP-mailboxes-provisioning");
+            try {
+                Username username = session.getUser();
+                createDefaultMailboxes(username);
+            } catch (MailboxException e) {
+                throw new RuntimeException(e);
+            } finally {
+                timeMetric.stopAndPublish();
+            }
+        });
+    }
+
+    private void createDefaultMailboxes(Username username) throws MailboxException {
+        MailboxSession session = mailboxManager.createSystemSession(username);
+        DefaultMailboxes.DEFAULT_MAILBOXES.stream()
+            .map(toMailboxPath(session))
+            .filter(mailboxPath -> mailboxDoesntExist(mailboxPath, session))
+            .forEach(mailboxPath -> createMailbox(mailboxPath, session));
+    }
+
+    private boolean mailboxDoesntExist(MailboxPath mailboxPath, MailboxSession session) {
+        try {
+            return !mailboxManager.mailboxExists(mailboxPath, session);
+        } catch (MailboxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Function<String, MailboxPath> toMailboxPath(MailboxSession session) {
+        return mailbox -> MailboxPath.forUser(session.getUser(), mailbox);
+    }
+    
+    private void createMailbox(MailboxPath mailboxPath, MailboxSession session) {
+        try {
+            Optional<MailboxId> mailboxId = mailboxManager.createMailbox(mailboxPath, session);
+            if (mailboxId.isPresent()) {
+                subscriptionManager.subscribe(session, mailboxPath.getName());
+            }
+            LOGGER.info("Provisioning {}. {} created.", mailboxPath, mailboxId);
+        } catch (MailboxExistsException e) {
+            LOGGER.info("Mailbox {} have been created concurrently", mailboxPath);
+        } catch (MailboxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisionerTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisionerTest.java
new file mode 100644
index 0000000..97b9b0f
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisionerTest.java
@@ -0,0 +1,103 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.jmap.http;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.Duration;
+
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.DefaultMailboxes;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.StoreSubscriptionManager;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+
+public class DefaultMailboxesReactiveProvisionerTest {
+
+    public static final Username USERNAME = Username.of("username");
+    private DefaultMailboxesReactiveProvisioner testee;
+    private MailboxSession session;
+    private InMemoryMailboxManager mailboxManager;
+    private StoreSubscriptionManager subscriptionManager;
+
+    @Before
+    public void before() {
+        session = MailboxSessionUtil.create(USERNAME);
+
+        mailboxManager = InMemoryIntegrationResources.defaultResources().getMailboxManager();
+        subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory());
+        testee = new DefaultMailboxesReactiveProvisioner(mailboxManager, subscriptionManager, new RecordingMetricFactory());
+    }
+
+    @Test
+    public void createMailboxesIfNeededShouldCreateSystemMailboxes() throws Exception {
+        testee.createMailboxesIfNeeded(session).block();
+
+        assertThat(mailboxManager.list(session))
+            .containsOnlyElementsOf(DefaultMailboxes.DEFAULT_MAILBOXES
+                .stream()
+                .map(mailboxName -> MailboxPath.forUser(USERNAME, mailboxName))
+                .collect(Guavate.toImmutableList()));
+    }
+
+    @Test
+    public void createMailboxesIfNeededShouldCreateSpamWhenOtherSystemMailboxesExist() throws Exception {
+        DefaultMailboxes.DEFAULT_MAILBOXES
+            .stream()
+            .filter(mailbox -> !DefaultMailboxes.SPAM.equals(mailbox))
+            .forEach(Throwing.consumer(mailbox -> mailboxManager.createMailbox(MailboxPath.forUser(USERNAME, mailbox), session)));
+
+        testee.createMailboxesIfNeeded(session).block();
+
+        assertThat(mailboxManager.list(session)).contains(MailboxPath.forUser(USERNAME, DefaultMailboxes.SPAM));
+    }
+
+    @Test
+    public void createMailboxesIfNeededShouldSubscribeMailboxes() throws Exception {
+        testee.createMailboxesIfNeeded(session).block();
+
+        assertThat(subscriptionManager.subscriptions(session))
+            .containsOnlyElementsOf(DefaultMailboxes.DEFAULT_MAILBOXES);
+    }
+
+    @Test
+    public void createMailboxesIfNeededShouldNotGenerateExceptionsInConcurrentEnvironment() throws Exception {
+        ConcurrentTestRunner.builder()
+            .operation((threadNumber, step) -> testee.createMailboxesIfNeeded(session).block())
+            .threadCount(10)
+            .runSuccessfullyWithin(Duration.ofSeconds(10));
+
+        assertThat(mailboxManager.list(session))
+            .containsOnlyElementsOf(DefaultMailboxes.DEFAULT_MAILBOXES
+                .stream()
+                .map(mailboxName -> MailboxPath.forUser(USERNAME, mailboxName))
+                .collect(Guavate.toImmutableList()));
+    }
+
+}
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisionerThreadTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisionerThreadTest.java
new file mode 100644
index 0000000..d0a5904
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesReactiveProvisionerThreadTest.java
@@ -0,0 +1,74 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.jmap.http;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.SubscriptionManager;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.TestId;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DefaultMailboxesReactiveProvisionerThreadTest {
+
+    private static final Username USERNAME = Username.of("username");
+
+    private DefaultMailboxesReactiveProvisioner testee;
+    private MailboxSession session;
+    private MailboxManager mailboxManager;
+    private SubscriptionManager subscriptionManager;
+
+    @Before
+    public void before() {
+        session = MailboxSessionUtil.create(USERNAME);
+        mailboxManager = mock(MailboxManager.class);
+        subscriptionManager = mock(SubscriptionManager.class);
+        testee = new DefaultMailboxesReactiveProvisioner(mailboxManager, subscriptionManager, new RecordingMetricFactory());
+    }
+
+    @Test
+    public void testConcurrentAccessToFilterShouldNotThrow() throws Exception {
+        doNothing().when(subscriptionManager).subscribe(eq(session), anyString());
+
+        when(mailboxManager.createMailbox(any(MailboxPath.class), eq(session))).thenReturn(Optional.of(TestId.of(18L)));
+        when(mailboxManager.mailboxExists(any(MailboxPath.class), eq(session))).thenReturn(false);
+        when(mailboxManager.createSystemSession(USERNAME)).thenReturn(session);
+
+        ConcurrentTestRunner
+            .builder()
+            .operation((threadNumber, step) -> testee.createMailboxesIfNeeded(session).block())
+            .threadCount(2)
+            .runSuccessfullyWithin(Duration.ofMinutes(1));
+    }
+}


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