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 <btell...@linagora.com> Authored: Tue Aug 28 10:17:21 2018 +0700 Committer: Antoine Duprat <adup...@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-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org