JAMES-2529 Extract ContentMatcher and HeaderExtractor from MailMatcher
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/7527daec Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/7527daec Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/7527daec Branch: refs/heads/master Commit: 7527daec794b8e0022161e079e9a2bf710c58e77 Parents: aee5424 Author: Benoit Tellier <[email protected]> Authored: Thu Aug 30 15:02:55 2018 +0700 Committer: Antoine Duprat <[email protected]> Committed: Thu Aug 30 15:07:03 2018 +0200 ---------------------------------------------------------------------- .../jmap/mailet/filter/ContentMatcher.java | 118 +++++++++++++++ .../jmap/mailet/filter/HeaderExtractor.java | 93 ++++++++++++ .../james/jmap/mailet/filter/MailMatcher.java | 150 ------------------- 3 files changed, 211 insertions(+), 150 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/7527daec/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.java new file mode 100644 index 0000000..a388332 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ContentMatcher.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.mailet.filter; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; + +import org.apache.commons.lang3.StringUtils; +import org.apache.james.jmap.api.filtering.Rule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + +public interface ContentMatcher { + + class AddressHeader { + private static final Logger LOGGER = LoggerFactory.getLogger(AddressHeader.class); + + private final Optional<String> personal; + private final Optional<String> address; + private final String fullAddress; + + private AddressHeader(String fullAddress) { + this.fullAddress = fullAddress; + Optional<InternetAddress> internetAddress = parseFullAddress(); + this.personal = internetAddress.map(InternetAddress::getPersonal); + this.address = internetAddress.map(InternetAddress::getAddress); + } + + private Optional<InternetAddress> parseFullAddress() { + try { + return Optional.of(new InternetAddress(fullAddress)); + } catch (AddressException e) { + LOGGER.error("error while parsing full address {}", fullAddress, e); + return Optional.empty(); + } + } + } + + ContentMatcher STRING_CONTAINS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.contains(content, valueToMatch)); + ContentMatcher STRING_NOT_CONTAINS_MATCHER = negate(STRING_CONTAINS_MATCHER); + ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.equals(content, valueToMatch)); + ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = negate(STRING_EXACTLY_EQUALS_MATCHER); + + ContentMatcher ADDRESS_CONTAINS_MATCHER = (contents, valueToMatch) -> contents + .map(ContentMatcher::asAddressHeader) + .anyMatch(addressHeader -> StringUtils.containsIgnoreCase(addressHeader.fullAddress, valueToMatch)); + ContentMatcher ADDRESS_NOT_CONTAINS_MATCHER = negate(ADDRESS_CONTAINS_MATCHER); + ContentMatcher ADDRESS_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents + .map(ContentMatcher::asAddressHeader) + .anyMatch(addressHeader -> + valueToMatch.equalsIgnoreCase(addressHeader.fullAddress) + || addressHeader.address.map(valueToMatch::equalsIgnoreCase).orElse(false) + || addressHeader.personal.map(valueToMatch::equalsIgnoreCase).orElse(false)); + ContentMatcher ADDRESS_NOT_EXACTLY_EQUALS_MATCHER = negate(ADDRESS_EXACTLY_EQUALS_MATCHER); + + Map<Rule.Condition.Comparator, ContentMatcher> HEADER_ADDRESS_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() + .put(Rule.Condition.Comparator.CONTAINS, ADDRESS_CONTAINS_MATCHER) + .put(Rule.Condition.Comparator.NOT_CONTAINS, ADDRESS_NOT_CONTAINS_MATCHER) + .put(Rule.Condition.Comparator.EXACTLY_EQUALS, ADDRESS_EXACTLY_EQUALS_MATCHER) + .put(Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, ADDRESS_NOT_EXACTLY_EQUALS_MATCHER) + .build(); + + Map<Rule.Condition.Comparator, ContentMatcher> CONTENT_STRING_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() + .put(Rule.Condition.Comparator.CONTAINS, STRING_CONTAINS_MATCHER) + .put(Rule.Condition.Comparator.NOT_CONTAINS, STRING_NOT_CONTAINS_MATCHER) + .put(Rule.Condition.Comparator.EXACTLY_EQUALS, STRING_EXACTLY_EQUALS_MATCHER) + .put(Rule.Condition.Comparator.NOT_EXACTLY_EQUALS, STRING_NOT_EXACTLY_EQUALS_MATCHER) + .build(); + + Map<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>> CONTENT_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>>builder() + .put(Rule.Condition.Field.SUBJECT, CONTENT_STRING_MATCHER_REGISTRY) + .put(Rule.Condition.Field.TO, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Rule.Condition.Field.CC, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Rule.Condition.Field.RECIPIENT, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Rule.Condition.Field.FROM, HEADER_ADDRESS_MATCHER_REGISTRY) + .build(); + + static ContentMatcher negate(ContentMatcher contentMatcher) { + return (Stream<String> contents, String valueToMatch) -> + !contentMatcher.match(contents, valueToMatch); + } + + static Optional<ContentMatcher> asContentMatcher(Rule.Condition.Field field, Rule.Condition.Comparator comparator) { + return Optional + .ofNullable(CONTENT_MATCHER_REGISTRY.get(field)) + .map(matcherRegistry -> matcherRegistry.get(comparator)); + } + + static AddressHeader asAddressHeader(String addressAsString) { + return new AddressHeader(addressAsString); + } + + boolean match(Stream<String> contents, String valueToMatch); + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7527daec/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java new file mode 100644 index 0000000..65d1629 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/HeaderExtractor.java @@ -0,0 +1,93 @@ +/**************************************************************** + * 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.mailet.filter; + +import static org.apache.mailet.base.RFC2822Headers.FROM; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.mail.Address; +import javax.mail.Message; + +import org.apache.james.javax.AddressHelper; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.mime4j.util.MimeUtil; +import org.apache.james.util.StreamUtils; +import org.apache.mailet.Mail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.google.common.collect.ImmutableMap; + +public interface HeaderExtractor extends ThrowingFunction<Mail, Stream<String>> { + Logger LOGGER = LoggerFactory.getLogger(HeaderExtractor.class); + + HeaderExtractor SUBJECT_EXTRACTOR = mail -> + StreamUtils.ofNullables(mail.getMessage().getSubject()); + HeaderExtractor CC_EXTRACTOR = recipientExtractor(Message.RecipientType.CC); + HeaderExtractor TO_EXTRACTOR = recipientExtractor(Message.RecipientType.TO); + HeaderExtractor RECIPIENT_EXTRACTOR = and(TO_EXTRACTOR, CC_EXTRACTOR); + HeaderExtractor FROM_EXTRACTOR = addressExtractor(mail -> mail.getMessage().getFrom(), FROM); + + Map<Rule.Condition.Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Rule.Condition.Field, HeaderExtractor>builder() + .put(Rule.Condition.Field.SUBJECT, SUBJECT_EXTRACTOR) + .put(Rule.Condition.Field.RECIPIENT, RECIPIENT_EXTRACTOR) + .put(Rule.Condition.Field.FROM, FROM_EXTRACTOR) + .put(Rule.Condition.Field.CC, CC_EXTRACTOR) + .put(Rule.Condition.Field.TO, TO_EXTRACTOR) + .build(); + + static HeaderExtractor and(HeaderExtractor headerExtractor1, HeaderExtractor headerExtractor2) { + return (Mail mail) -> StreamUtils.flatten(headerExtractor1.apply(mail), headerExtractor2.apply(mail)); + } + + static HeaderExtractor recipientExtractor(Message.RecipientType type) { + ThrowingFunction<Mail, Address[]> addressGetter = mail -> mail.getMessage().getRecipients(type); + String fallbackHeaderName = type.toString(); + + return addressExtractor(addressGetter, fallbackHeaderName); + } + + static HeaderExtractor addressExtractor(ThrowingFunction<Mail, Address[]> addressGetter, String fallbackHeaderName) { + return mail -> { + try { + return toContent(addressGetter.apply(mail)); + } catch (Exception e) { + LOGGER.info("Failed parsing header. Falling back to unparsed header value matching", e); + return Stream.of(mail.getMessage().getHeader(fallbackHeaderName)) + .map(MimeUtil::unscrambleHeaderValue); + } + }; + } + + static Stream<String> toContent(Address[] addresses) { + return Optional.ofNullable(addresses) + .map(AddressHelper::asStringStream) + .orElse(Stream.empty()); + } + + static Optional<HeaderExtractor> asHeaderExtractor(Rule.Condition.Field field) { + return Optional.ofNullable( + HeaderExtractor.HEADER_EXTRACTOR_REGISTRY.get(field)); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7527daec/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java index 031e696..ed5db25 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java @@ -20,169 +20,19 @@ package org.apache.james.jmap.mailet.filter; import static org.apache.james.jmap.api.filtering.Rule.Condition; -import static org.apache.mailet.base.RFC2822Headers.FROM; -import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import javax.mail.Address; -import javax.mail.Message; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; - -import org.apache.commons.lang3.StringUtils; -import org.apache.james.javax.AddressHelper; import org.apache.james.jmap.api.filtering.Rule; -import org.apache.james.jmap.api.filtering.Rule.Condition.Field; -import org.apache.james.mime4j.util.MimeUtil; -import org.apache.james.util.StreamUtils; import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.github.fge.lambdas.functions.ThrowingFunction; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableMap; public interface MailMatcher { - interface HeaderExtractor extends ThrowingFunction<Mail, Stream<String>> { - Logger LOGGER = LoggerFactory.getLogger(HeaderExtractor.class); - - HeaderExtractor SUBJECT_EXTRACTOR = mail -> - StreamUtils.ofNullables(mail.getMessage().getSubject()); - HeaderExtractor CC_EXTRACTOR = recipientExtractor(Message.RecipientType.CC); - HeaderExtractor TO_EXTRACTOR = recipientExtractor(Message.RecipientType.TO); - HeaderExtractor RECIPIENT_EXTRACTOR = and(TO_EXTRACTOR, CC_EXTRACTOR); - HeaderExtractor FROM_EXTRACTOR = addressExtractor(mail -> mail.getMessage().getFrom(), FROM); - - Map<Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Field, HeaderExtractor>builder() - .put(Field.SUBJECT, SUBJECT_EXTRACTOR) - .put(Field.RECIPIENT, RECIPIENT_EXTRACTOR) - .put(Field.FROM, FROM_EXTRACTOR) - .put(Field.CC, CC_EXTRACTOR) - .put(Field.TO, TO_EXTRACTOR) - .build(); - - static HeaderExtractor and(HeaderExtractor headerExtractor1, HeaderExtractor headerExtractor2) { - return (Mail mail) -> StreamUtils.flatten(headerExtractor1.apply(mail), headerExtractor2.apply(mail)); - } - - static HeaderExtractor recipientExtractor(Message.RecipientType type) { - ThrowingFunction<Mail, Address[]> addressGetter = mail -> mail.getMessage().getRecipients(type); - String fallbackHeaderName = type.toString(); - - return addressExtractor(addressGetter, fallbackHeaderName); - } - - static HeaderExtractor addressExtractor(ThrowingFunction<Mail, Address[]> addressGetter, String fallbackHeaderName) { - return mail -> { - try { - return toContent(addressGetter.apply(mail)); - } catch (Exception e) { - LOGGER.info("Failed parsing header. Falling back to unparsed header value matching", e); - return Stream.of(mail.getMessage().getHeader(fallbackHeaderName)) - .map(MimeUtil::unscrambleHeaderValue); - } - }; - } - - static Stream<String> toContent(Address[] addresses) { - return Optional.ofNullable(addresses) - .map(AddressHelper::asStringStream) - .orElse(Stream.empty()); - } - - static Optional<HeaderExtractor> asHeaderExtractor(Field field) { - return Optional.ofNullable( - HeaderExtractor.HEADER_EXTRACTOR_REGISTRY.get(field)); - } - } - - interface ContentMatcher { - - class AddressHeader { - private static final Logger LOGGER = LoggerFactory.getLogger(AddressHeader.class); - - private final Optional<String> personal; - private final Optional<String> address; - private final String fullAddress; - - private AddressHeader(String fullAddress) { - this.fullAddress = fullAddress; - Optional<InternetAddress> internetAddress = parseFullAddress(); - this.personal = internetAddress.map(InternetAddress::getPersonal); - this.address = internetAddress.map(InternetAddress::getAddress); - } - - private Optional<InternetAddress> parseFullAddress() { - try { - return Optional.of(new InternetAddress(fullAddress)); - } catch (AddressException e) { - LOGGER.error("error while parsing full address {}", fullAddress, e); - return Optional.empty(); - } - } - } - - ContentMatcher STRING_CONTAINS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.contains(content, valueToMatch)); - ContentMatcher STRING_NOT_CONTAINS_MATCHER = negate(STRING_CONTAINS_MATCHER); - ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.equals(content, valueToMatch)); - ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = negate(STRING_EXACTLY_EQUALS_MATCHER); - - ContentMatcher ADDRESS_CONTAINS_MATCHER = (contents, valueToMatch) -> contents - .map(ContentMatcher::asAddressHeader) - .anyMatch(addressHeader -> StringUtils.containsIgnoreCase(addressHeader.fullAddress, valueToMatch)); - ContentMatcher ADDRESS_NOT_CONTAINS_MATCHER = negate(ADDRESS_CONTAINS_MATCHER); - ContentMatcher ADDRESS_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents - .map(ContentMatcher::asAddressHeader) - .anyMatch(addressHeader -> - valueToMatch.equalsIgnoreCase(addressHeader.fullAddress) - || addressHeader.address.map(valueToMatch::equalsIgnoreCase).orElse(false) - || addressHeader.personal.map(valueToMatch::equalsIgnoreCase).orElse(false)); - ContentMatcher ADDRESS_NOT_EXACTLY_EQUALS_MATCHER = negate(ADDRESS_EXACTLY_EQUALS_MATCHER); - - Map<Rule.Condition.Comparator, ContentMatcher> HEADER_ADDRESS_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() - .put(Condition.Comparator.CONTAINS, ADDRESS_CONTAINS_MATCHER) - .put(Condition.Comparator.NOT_CONTAINS, ADDRESS_NOT_CONTAINS_MATCHER) - .put(Condition.Comparator.EXACTLY_EQUALS, ADDRESS_EXACTLY_EQUALS_MATCHER) - .put(Condition.Comparator.NOT_EXACTLY_EQUALS, ADDRESS_NOT_EXACTLY_EQUALS_MATCHER) - .build(); - - Map<Rule.Condition.Comparator, ContentMatcher> CONTENT_STRING_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() - .put(Condition.Comparator.CONTAINS, STRING_CONTAINS_MATCHER) - .put(Condition.Comparator.NOT_CONTAINS, STRING_NOT_CONTAINS_MATCHER) - .put(Condition.Comparator.EXACTLY_EQUALS, STRING_EXACTLY_EQUALS_MATCHER) - .put(Condition.Comparator.NOT_EXACTLY_EQUALS, STRING_NOT_EXACTLY_EQUALS_MATCHER) - .build(); - - Map<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>> CONTENT_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>>builder() - .put(Condition.Field.SUBJECT, CONTENT_STRING_MATCHER_REGISTRY) - .put(Condition.Field.TO, HEADER_ADDRESS_MATCHER_REGISTRY) - .put(Condition.Field.CC, HEADER_ADDRESS_MATCHER_REGISTRY) - .put(Condition.Field.RECIPIENT, HEADER_ADDRESS_MATCHER_REGISTRY) - .put(Condition.Field.FROM, HEADER_ADDRESS_MATCHER_REGISTRY) - .build(); - - static ContentMatcher negate(ContentMatcher contentMatcher) { - return (Stream<String> contents, String valueToMatch) -> - !contentMatcher.match(contents, valueToMatch); - } - - static Optional<ContentMatcher> asContentMatcher(Condition.Field field, Condition.Comparator comparator) { - return Optional - .ofNullable(CONTENT_MATCHER_REGISTRY.get(field)) - .map(matcherRegistry -> matcherRegistry.get(comparator)); - } - - static AddressHeader asAddressHeader(String addressAsString) { - return new AddressHeader(addressAsString); - } - - boolean match(Stream<String> contents, String valueToMatch); - } - class HeaderMatcher implements MailMatcher { private static final Logger LOGGER = LoggerFactory.getLogger(HeaderMatcher.class); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
