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 ad...@apache.org on 2018/08/28 12:12:27 UTC

[10/13] james-project git commit: JAMES-2530 Implement JMAP methods for implementing filtering

JAMES-2530 Implement JMAP methods for implementing filtering


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/3bc2eecf
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/3bc2eecf
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/3bc2eecf

Branch: refs/heads/master
Commit: 3bc2eecf16108c3f4a6c29726d755d2b164b4fd0
Parents: 2224a02
Author: Benoit Tellier <bt...@linagora.com>
Authored: Tue Aug 28 10:17:21 2018 +0700
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Tue Aug 28 14:11:51 2018 +0200

----------------------------------------------------------------------
 .../james/jmap/methods/GetFilterMethod.java     | 118 ++++++++++
 .../james/jmap/methods/SetFilterMethod.java     | 216 +++++++++++++++++++
 2 files changed, 334 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/3bc2eecf/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetFilterMethod.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetFilterMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetFilterMethod.java
new file mode 100644
index 0000000..5c04c60
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetFilterMethod.java
@@ -0,0 +1,118 @@
+/****************************************************************
+ * 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.methods;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.User;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.jmap.model.ClientId;
+import org.apache.james.jmap.model.GetFilterRequest;
+import org.apache.james.jmap.model.GetFilterResponse;
+import org.apache.james.jmap.model.SetError;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+public class GetFilterMethod implements Method {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GetFilterMethod.class);
+
+    private static final Method.Request.Name METHOD_NAME = Method.Request.name("getFilter");
+    private static final Method.Response.Name RESPONSE_NAME = Method.Response.name("filter");
+
+    private final MetricFactory metricFactory;
+    private final FilteringManagement filteringManagement;
+
+    @Inject
+    private GetFilterMethod(MetricFactory metricFactory, FilteringManagement filteringManagement) {
+        this.metricFactory = metricFactory;
+        this.filteringManagement = filteringManagement;
+    }
+
+    @Override
+    public Request.Name requestHandled() {
+        return METHOD_NAME;
+    }
+
+    @Override
+    public Class<? extends JmapRequest> requestType() {
+        return GetFilterRequest.class;
+    }
+
+    @Override
+    public Stream<JmapResponse> process(JmapRequest request, ClientId clientId, MailboxSession mailboxSession) {
+        Preconditions.checkNotNull(request);
+        Preconditions.checkNotNull(clientId);
+        Preconditions.checkNotNull(mailboxSession);
+        Preconditions.checkArgument(request instanceof GetFilterRequest);
+
+        GetFilterRequest filterRequest = (GetFilterRequest) request;
+
+        return metricFactory.withMetric(JMAP_PREFIX + METHOD_NAME.getName(),
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "GET_FILTER")
+                .wrapArround(() -> process(clientId, mailboxSession, filterRequest)));
+    }
+
+    private Stream<JmapResponse> process(ClientId clientId, MailboxSession mailboxSession, GetFilterRequest request) {
+        try {
+            User user = User.fromUsername(mailboxSession.getUser().getUserName());
+
+            return retrieveFilter(clientId, user);
+        } catch (Exception e) {
+            LOGGER.warn("Failed to retrieve filter");
+
+            return Stream.of(unKnownError(clientId));
+        }
+    }
+
+    private Stream<JmapResponse> retrieveFilter(ClientId clientId, User user) {
+        List<Rule> rules = filteringManagement.listRulesForUser(user);
+
+        GetFilterResponse getFilterResponse = GetFilterResponse.builder()
+            .rules(rules)
+            .build();
+
+        return Stream.of(JmapResponse.builder()
+            .clientId(clientId)
+            .response(getFilterResponse)
+            .responseName(RESPONSE_NAME)
+            .build());
+    }
+
+    private JmapResponse unKnownError(ClientId clientId) {
+        return JmapResponse.builder()
+            .clientId(clientId)
+            .responseName(RESPONSE_NAME)
+            .response(ErrorResponse.builder()
+                .type(SetError.Type.ERROR.asString())
+                .description("Failed to retrieve filter")
+                .build())
+            .build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/3bc2eecf/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetFilterMethod.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetFilterMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetFilterMethod.java
new file mode 100644
index 0000000..b9eb9e7
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetFilterMethod.java
@@ -0,0 +1,216 @@
+/****************************************************************
+ * 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.methods;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.User;
+import org.apache.james.jmap.api.filtering.FilteringManagement;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.jmap.model.ClientId;
+import org.apache.james.jmap.model.JmapRuleDTO;
+import org.apache.james.jmap.model.SetError;
+import org.apache.james.jmap.model.SetFilterRequest;
+import org.apache.james.jmap.model.SetFilterResponse;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+
+public class SetFilterMethod implements Method {
+
+    public static class DuplicatedRuleException extends Exception {
+        private final ImmutableList<Rule.Id> duplicatedIds;
+
+        public DuplicatedRuleException(ImmutableList<Rule.Id> duplicatedIds) {
+            super("The following rules were duplicated:" + format(duplicatedIds));
+            this.duplicatedIds = duplicatedIds;
+        }
+    }
+
+    public static class MultipleMailboxIdException extends Exception {
+        private final ImmutableList<Rule.Id> idsWithMultipleMailboxes;
+
+        public MultipleMailboxIdException(ImmutableList<Rule.Id> idsWithMultipleMailboxes) {
+            super("The following rules were targeting several mailboxes:" + format(idsWithMultipleMailboxes));
+            this.idsWithMultipleMailboxes = idsWithMultipleMailboxes;
+        }
+    }
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SetFilterMethod.class);
+
+    private static final Request.Name METHOD_NAME = Request.name("setFilter");
+    private static final Response.Name RESPONSE_NAME = Response.name("filterSet");
+
+    private static String format(ImmutableList<Rule.Id> ids) {
+        return "[" + ids.stream()
+                .map(Rule.Id::asString)
+                .map(SetFilterMethod::quote)
+                .collect(Collectors.joining(","))
+                + "]";
+    }
+
+    private static String quote(String s) {
+        return "'" + s + "'";
+    }
+
+    private final MetricFactory metricFactory;
+    private final FilteringManagement filteringManagement;
+
+    @Inject
+    public SetFilterMethod(MetricFactory metricFactory, FilteringManagement filteringManagement) {
+        this.filteringManagement = filteringManagement;
+        this.metricFactory = metricFactory;
+    }
+
+    @Override
+    public Request.Name requestHandled() {
+        return METHOD_NAME;
+    }
+
+    @Override
+    public Class<? extends JmapRequest> requestType() {
+        return SetFilterRequest.class;
+    }
+
+    @Override
+    public Stream<JmapResponse> process(JmapRequest request, ClientId clientId, MailboxSession mailboxSession) {
+        Preconditions.checkNotNull(request);
+        Preconditions.checkNotNull(clientId);
+        Preconditions.checkNotNull(mailboxSession);
+
+        Preconditions.checkArgument(request instanceof SetFilterRequest);
+
+        SetFilterRequest setFilterRequest = (SetFilterRequest) request;
+
+        return metricFactory.withMetric(JMAP_PREFIX + METHOD_NAME.getName(),
+            MDCBuilder.create()
+                .addContext(MDCBuilder.ACTION, "SET_FILTER")
+                .addContext("update", setFilterRequest.getSingleton())
+                .wrapArround(
+                    () -> process(clientId, mailboxSession, setFilterRequest)));
+    }
+
+    private Stream<JmapResponse> process(ClientId clientId, MailboxSession mailboxSession, SetFilterRequest request) {
+        try {
+            User user = User.fromUsername(mailboxSession.getUser().getUserName());
+
+            return updateFilter(clientId, request, user);
+        } catch (MultipleMailboxIdException e) {
+            LOGGER.debug("Rule targeting several mailboxes", e);
+            return Stream.of(multipleMailboxesError(clientId, e));
+        }  catch (DuplicatedRuleException e) {
+            LOGGER.debug("Duplicated rules", e);
+            return Stream.of(duplicatedIdsError(clientId, e));
+        } catch (Exception e) {
+            LOGGER.warn("Failed setting Rules", e);
+            return Stream.of(unKnownError(clientId));
+        }
+    }
+
+    private Stream<JmapResponse> updateFilter(ClientId clientId, SetFilterRequest request, User user) throws DuplicatedRuleException, MultipleMailboxIdException {
+        ImmutableList<Rule> rules = request.getSingleton().stream()
+            .map(JmapRuleDTO::toRule)
+            .collect(ImmutableList.toImmutableList());
+
+        ensureNoDuplicatedRules(rules);
+        ensureNoMultipleMailboxesRules(rules);
+
+        filteringManagement.defineRulesForUser(user, rules);
+
+        return Stream.of(JmapResponse.builder()
+            .clientId(clientId)
+            .responseName(RESPONSE_NAME)
+            .response(SetFilterResponse.updated())
+            .build());
+    }
+
+    private void ensureNoMultipleMailboxesRules(ImmutableList<Rule> rules) throws MultipleMailboxIdException {
+        ImmutableList<Rule.Id> idWithMultipleMailboxes = rules.stream()
+            .filter(rule -> rule.getAction().getAppendInMailboxes().getMailboxIds().size() > 1)
+            .map(Rule::getId)
+            .collect(ImmutableList.toImmutableList());
+
+        if (!idWithMultipleMailboxes.isEmpty()) {
+            throw new MultipleMailboxIdException(idWithMultipleMailboxes);
+        }
+    }
+
+    private void ensureNoDuplicatedRules(List<Rule> rules) throws DuplicatedRuleException {
+        ImmutableList<Rule.Id> duplicatedIds = rules.stream()
+            .collect(ImmutableListMultimap.toImmutableListMultimap(
+                Rule::getId,
+                Function.identity()))
+            .asMap()
+            .entrySet()
+            .stream()
+            .filter(entry -> entry.getValue().size() > 1)
+            .map(Map.Entry::getKey)
+            .collect(ImmutableList.toImmutableList());
+
+        if (!duplicatedIds.isEmpty()) {
+            throw new DuplicatedRuleException(duplicatedIds);
+        }
+    }
+
+    private JmapResponse unKnownError(ClientId clientId) {
+        return JmapResponse.builder()
+            .clientId(clientId)
+            .responseName(RESPONSE_NAME)
+            .response(ErrorResponse.builder()
+                .type(SetError.Type.ERROR.asString())
+                .description("Failed to retrieve filter")
+                .build())
+            .build();
+    }
+
+    private JmapResponse duplicatedIdsError(ClientId clientId, DuplicatedRuleException e) {
+        return JmapResponse.builder()
+            .clientId(clientId)
+            .responseName(RESPONSE_NAME)
+            .response(SetFilterResponse.notUpdated(SetError.builder()
+                .type(SetError.Type.INVALID_ARGUMENTS)
+                .description("The following rules were duplicated: " + format(e.duplicatedIds))
+                .build()))
+            .build();
+    }
+
+    private JmapResponse multipleMailboxesError(ClientId clientId, MultipleMailboxIdException e) {
+        return JmapResponse.builder()
+            .clientId(clientId)
+            .responseName(RESPONSE_NAME)
+            .response(SetFilterResponse.notUpdated(SetError.builder()
+                .type(SetError.Type.INVALID_ARGUMENTS)
+                .description("The following rules targeted several mailboxes, which is not supported: " + format(e.idsWithMultipleMailboxes))
+                .build()))
+            .build();
+    }
+}


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