blob: 1345c6a1b7b035cecb7dff2101d644921627b5a8 [file] [log] [blame]
/****************************************************************
* 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.model;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jakarta.mail.Flags;
import org.apache.james.mailbox.FlagsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
public class Keywords {
public static final Keywords DEFAULT_VALUE = new Keywords(ImmutableSet.of());
private static final Logger LOGGER = LoggerFactory.getLogger(Keywords.class);
private static final KeywordsFactory STRICT_KEYWORDS_FACTORY = new KeywordsFactory(
KeywordsFactory.KeywordsValidator.THROW_ON_IMAP_NON_EXPOSED_KEYWORDS,
KeywordsFactory.KeywordFilter.KEEP_ALL,
KeywordsFactory.ToKeyword.STRICT);
private static final KeywordsFactory LENIENT_KEYWORDS_FACTORY = new KeywordsFactory(
KeywordsFactory.KeywordsValidator.IGNORE_NON_EXPOSED_IMAP_KEYWORDS,
KeywordsFactory.KeywordFilter.FILTER_IMAP_NON_EXPOSED_KEYWORDS,
KeywordsFactory.ToKeyword.LENIENT);
private FlagsBuilder combiner(FlagsBuilder firstBuilder, FlagsBuilder secondBuilder) {
return firstBuilder.add(secondBuilder.build());
}
private FlagsBuilder accumulator(FlagsBuilder accumulator, Keyword keyword) {
return accumulator.add(keyword.asFlags());
}
public static class KeywordsFactory {
@FunctionalInterface
interface KeywordsValidator {
KeywordsValidator THROW_ON_IMAP_NON_EXPOSED_KEYWORDS = keyword -> Preconditions.checkArgument(
keyword.isExposedImapKeyword(),
"Does not allow to update 'Deleted' or 'Recent' flag");
KeywordsValidator IGNORE_NON_EXPOSED_IMAP_KEYWORDS = keyword -> { };
void validate(Keyword keywords);
}
@FunctionalInterface
interface KeywordFilter extends Predicate<Keyword> {
KeywordFilter FILTER_IMAP_NON_EXPOSED_KEYWORDS = Keyword::isExposedImapKeyword;
KeywordFilter KEEP_ALL = keyword -> true;
}
@FunctionalInterface
interface ToKeyword {
ToKeyword STRICT = value -> Optional.of(Keyword.of(value));
ToKeyword LENIENT = value -> {
Optional<Keyword> result = Keyword.parse(value);
if (!result.isPresent()) {
LOGGER.warn("Fail to parse {} flag", value);
}
return result;
};
Optional<Keyword> toKeyword(String value);
default Stream<Keyword> asKeywordStream(String value) {
return toKeyword(value).stream();
}
}
private final KeywordsValidator validator;
private final Predicate<Keyword> filter;
private final ToKeyword toKeyword;
public KeywordsFactory(KeywordsValidator validator, Predicate<Keyword> filter, ToKeyword toKeyword) {
this.validator = validator;
this.filter = filter;
this.toKeyword = toKeyword;
}
public Keywords fromSet(Set<Keyword> setKeywords) {
return fromStream(setKeywords.stream());
}
public Keywords fromStream(Stream<Keyword> keywordStream) {
return new Keywords(keywordStream
.peek(validator::validate)
.filter(filter)
.collect(ImmutableSet.toImmutableSet()));
}
public Keywords from(Keyword... keywords) {
return fromStream(Arrays.stream(keywords));
}
public Keywords fromCollection(Collection<String> keywords) {
return fromStream(keywords.stream()
.flatMap(toKeyword::asKeywordStream));
}
public Keywords fromMap(Map<String, Boolean> mapKeywords) {
Preconditions.checkArgument(mapKeywords.values()
.stream()
.allMatch(keywordValue -> keywordValue), "Keyword must be true");
return fromCollection(mapKeywords.keySet());
}
public Keywords fromFlags(Flags flags) {
return fromStream(Stream.concat(
Stream.of(flags.getUserFlags())
.flatMap(toKeyword::asKeywordStream),
Stream.of(flags.getSystemFlags())
.map(Keyword::fromFlag)));
}
}
/**
* This Keywords factory will filter out invalid keywords (containing inavlid character, being too long or being non
* exposed IMAP flags)
*/
public static KeywordsFactory strictFactory() {
return STRICT_KEYWORDS_FACTORY;
}
/**
* This Keywords factory will throw upon invalid keywords (containing inavlid character, being too long or being non
* exposed IMAP flags)
*
* @throws IllegalArgumentException
*/
public static KeywordsFactory lenientFactory() {
return LENIENT_KEYWORDS_FACTORY;
}
private final ImmutableSet<Keyword> keywords;
private Keywords(ImmutableSet<Keyword> keywords) {
this.keywords = keywords;
}
public Flags asFlags() {
return keywords.stream()
.reduce(FlagsBuilder.builder(), this::accumulator, this::combiner)
.build();
}
public Flags asFlagsWithRecentAndDeletedFrom(Flags originFlags) {
Flags flags = asFlags();
if (originFlags.contains(Flags.Flag.DELETED)) {
flags.add(Flags.Flag.DELETED);
}
if (originFlags.contains(Flags.Flag.RECENT)) {
flags.add(Flags.Flag.RECENT);
}
return flags;
}
public ImmutableMap<String, Boolean> asMap() {
return keywords.stream()
.collect(ImmutableMap.toImmutableMap(Keyword::getFlagName, keyword -> Keyword.FLAG_VALUE));
}
public ImmutableSet<Keyword> getKeywords() {
return keywords;
}
public boolean contains(Keyword keyword) {
return keywords.contains(keyword);
}
@Override
public final boolean equals(Object other) {
if (other instanceof Keywords) {
Keywords otherKeyword = (Keywords) other;
return Objects.equal(keywords, otherKeyword.keywords);
}
return false;
}
@Override
public final int hashCode() {
return Objects.hashCode(keywords);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("keywords", keywords)
.toString();
}
}