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 a8765e445e7510a585ebe2b83ce0ef62c0c8259a
Author: Benoit Tellier <btell...@linagora.com>
AuthorDate: Tue Feb 11 13:18:08 2020 +0700

    JAMES-2917 Avoid specifying an unbounded count of routing keys
    
    Specifying too much routing keys can create ridiculously long URL for
    ElasticSearch requests that will eventually get rejected.
    
    414 Request-URI Too Large
    
    Mitigation: omitting routing keys for searches on more than X mailboxes, X
    being a code defined constant. This will result in a search involving all
    shards by default.
---
 .../search/ElasticSearchSearcher.java              |  23 ++-
 .../search/ElasticSearchSearcherTest.java          | 171 +++++++++++++++++++++
 2 files changed, 186 insertions(+), 8 deletions(-)

diff --git 
a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcher.java
 
b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcher.java
index bd3e629..dc84cf9 100644
--- 
a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcher.java
+++ 
b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcher.java
@@ -53,6 +53,7 @@ public class ElasticSearchSearcher {
     private static final TimeValue TIMEOUT = TimeValue.timeValueMinutes(1);
     private static final ImmutableList<String> STORED_FIELDS = 
ImmutableList.of(JsonMessageConstants.MAILBOX_ID,
         JsonMessageConstants.UID, JsonMessageConstants.MESSAGE_ID);
+    private static final int MAX_ROUTING_KEY = 5;
 
     private final RestHighLevelClient client;
     private final QueryConverter queryConverter;
@@ -96,18 +97,24 @@ public class ElasticSearchSearcher {
             .map(SortConverter::convertSort)
             .forEach(searchSourceBuilder::sort);
 
-        return new SearchRequest(aliasName.getValue())
+        SearchRequest request = new SearchRequest(aliasName.getValue())
             .types(NodeMappingFactory.DEFAULT_MAPPING_NAME)
             .scroll(TIMEOUT)
-            .source(searchSourceBuilder)
-            .routing(toRoutingKeys(mailboxIds));
+            .source(searchSourceBuilder);
+
+        return toRoutingKey(mailboxIds)
+            .map(request::routing)
+            .orElse(request);
     }
 
-    private String[] toRoutingKeys(Collection<MailboxId> mailboxIds) {
-        return mailboxIds.stream()
-            .map(routingKeyFactory::from)
-            .map(RoutingKey::asString)
-            .toArray(String[]::new);
+    private Optional<String[]> toRoutingKey(Collection<MailboxId> mailboxIds) {
+        if (mailboxIds.size() < MAX_ROUTING_KEY) {
+            return Optional.of(mailboxIds.stream()
+                .map(routingKeyFactory::from)
+                .map(RoutingKey::asString)
+                .toArray(String[]::new));
+        }
+        return Optional.empty();
     }
 
     private int computeRequiredSize(Optional<Integer> limit) {
diff --git 
a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcherTest.java
 
b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcherTest.java
new file mode 100644
index 0000000..f8be9a0
--- /dev/null
+++ 
b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/search/ElasticSearchSearcherTest.java
@@ -0,0 +1,171 @@
+/**
+ * *************************************************************
+ * 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.mailbox.elasticsearch.search;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import org.apache.james.backends.es.DockerElasticSearchExtension;
+import org.apache.james.backends.es.ElasticSearchIndexer;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.elasticsearch.IndexAttachments;
+import org.apache.james.mailbox.elasticsearch.MailboxElasticSearchConstants;
+import org.apache.james.mailbox.elasticsearch.MailboxIdRoutingKeyFactory;
+import org.apache.james.mailbox.elasticsearch.MailboxIndexCreationUtil;
+import 
org.apache.james.mailbox.elasticsearch.events.ElasticSearchListeningMessageSearchIndex;
+import org.apache.james.mailbox.elasticsearch.json.MessageToElasticSearchJson;
+import org.apache.james.mailbox.elasticsearch.query.CriterionConverter;
+import org.apache.james.mailbox.elasticsearch.query.QueryConverter;
+import org.apache.james.mailbox.inmemory.InMemoryId;
+import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
+import org.apache.james.mailbox.inmemory.InMemoryMessageId;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.tika.TikaConfiguration;
+import org.apache.james.mailbox.tika.TikaExtension;
+import org.apache.james.mailbox.tika.TikaHttpClientImpl;
+import org.apache.james.mailbox.tika.TikaTextExtractor;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.mime4j.dom.Message;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+
+class ElasticSearchSearcherTest {
+
+    static final int BATCH_SIZE = 1;
+    static final int SEARCH_SIZE = 1;
+    private static final Username USERNAME = Username.of("user");
+
+    @RegisterExtension
+    static TikaExtension tika = new TikaExtension();
+
+    @RegisterExtension
+    DockerElasticSearchExtension elasticSearch = new 
DockerElasticSearchExtension();
+
+    TikaTextExtractor textExtractor;
+    RestHighLevelClient client;
+    private InMemoryMailboxManager storeMailboxManager;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        textExtractor = new TikaTextExtractor(new RecordingMetricFactory(),
+            new TikaHttpClientImpl(TikaConfiguration.builder()
+                .host(tika.getIp())
+                .port(tika.getPort())
+                .timeoutInMillis(tika.getTimeoutInMillis())
+                .build()));
+
+        client = MailboxIndexCreationUtil.prepareDefaultClient(
+            elasticSearch.getDockerElasticSearch().clientProvider().get(),
+            elasticSearch.getDockerElasticSearch().configuration());
+
+        InMemoryMessageId.Factory messageIdFactory = new 
InMemoryMessageId.Factory();
+        MailboxIdRoutingKeyFactory routingKeyFactory = new 
MailboxIdRoutingKeyFactory();
+
+        InMemoryIntegrationResources resources = 
InMemoryIntegrationResources.builder()
+            .preProvisionnedFakeAuthenticator()
+            .fakeAuthorizator()
+            .inVmEventBus()
+            .defaultAnnotationLimits()
+            .defaultMessageParser()
+            .listeningSearchIndex(preInstanciationStage -> new 
ElasticSearchListeningMessageSearchIndex(
+                preInstanciationStage.getMapperFactory(),
+                new ElasticSearchIndexer(client,
+                    MailboxElasticSearchConstants.DEFAULT_MAILBOX_WRITE_ALIAS,
+                    BATCH_SIZE),
+                new ElasticSearchSearcher(client, new QueryConverter(new 
CriterionConverter()), SEARCH_SIZE,
+                    new InMemoryId.Factory(), messageIdFactory,
+                    MailboxElasticSearchConstants.DEFAULT_MAILBOX_READ_ALIAS, 
routingKeyFactory),
+                new MessageToElasticSearchJson(textExtractor, 
ZoneId.of("Europe/Paris"), IndexAttachments.YES),
+                preInstanciationStage.getSessionProvider(), routingKeyFactory))
+            .noPreDeletionHooks()
+            .storeQuotaManager()
+            .build();
+
+        storeMailboxManager = resources.getMailboxManager();
+    }
+
+    @AfterEach
+    void tearDown() throws IOException {
+        client.close();
+    }
+
+    @Test
+    void 
searchingInALargeNumberOfMailboxesShouldReturnAllMailboxesMessagesUid() throws 
Exception {
+        MailboxSession session = MailboxSessionUtil.create(USERNAME);
+        int numberOfMailboxes = 700;
+        List<MailboxPath> mailboxPaths = IntStream
+            .range(0, numberOfMailboxes)
+            .mapToObj(index -> MailboxPath.forUser(USERNAME, "mailbox" + 
index))
+            .collect(Guavate.toImmutableList());
+
+        List<MailboxId> mailboxIds = mailboxPaths.stream()
+            .map(Throwing.<MailboxPath, MailboxId>function(mailboxPath -> 
storeMailboxManager.createMailbox(mailboxPath, session).get()).sneakyThrow())
+            .collect(Guavate.toImmutableList());
+
+        List<ComposedMessageId> composedMessageIds = mailboxPaths.stream()
+            .map(Throwing.<MailboxPath, ComposedMessageId>function(mailboxPath 
-> addMessage(session, mailboxPath)).sneakyThrow())
+            .collect(Guavate.toImmutableList());
+
+        elasticSearch.awaitForElasticSearch();
+
+        MultimailboxesSearchQuery multimailboxesSearchQuery = 
MultimailboxesSearchQuery
+            .from(new SearchQuery(SearchQuery.all()))
+            .inMailboxes(mailboxIds)
+            .build();
+        List<MessageId> expectedMessageIds = composedMessageIds
+            .stream()
+            .map(ComposedMessageId::getMessageId)
+            .collect(Guavate.toImmutableList());
+        assertThat(storeMailboxManager.search(multimailboxesSearchQuery, 
session, numberOfMailboxes + 1))
+            .containsExactlyInAnyOrderElementsOf(expectedMessageIds);
+    }
+
+    private ComposedMessageId addMessage(MailboxSession session, MailboxPath 
mailboxPath) throws Exception {
+        MessageManager messageManager = 
storeMailboxManager.getMailbox(mailboxPath, session);
+
+        String recipient = "u...@example.com";
+        return messageManager.appendMessage(MessageManager.AppendCommand.from(
+            Message.Builder.of()
+                .setTo(recipient)
+                .setBody("Hello", StandardCharsets.UTF_8)),
+            session);
+    }
+}
\ No newline at end of file


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

Reply via email to