Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/****************************************************************
* 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.model;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.james.core.Username;
import org.apache.james.mailbox.exception.UnsupportedRightException;
import com.github.fge.lambdas.Throwing;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
/**
* Stores an Access Control List (ACL) applicable to a mailbox. Inspired by
* RFC4314 IMAP4 Access Control List (ACL) Extension.
*
* Implementations must be immutable. Implementations should override
* {@link #hashCode()} and {@link #equals(Object)}.
*
*/
public class MailboxACL {
public static ACLCommand.Builder command() {
return new ACLCommand.Builder();
}
private static EnumSet copyOf(Collection collection) {
if (collection.isEmpty()) {
return EnumSet.noneOf(Right.class);
}
return EnumSet.copyOf(collection);
}
/**
* SETACL command mode.
*/
public enum EditMode {
ADD, REMOVE, REPLACE;
public static Optional parse(String string) {
return Arrays.stream(values())
.filter(value -> value.toString().equalsIgnoreCase(string))
.findAny();
}
}
public enum NameType {
group, special, user
}
/**
* Special name literals.
*/
public enum SpecialName {
anybody, authenticated, owner
}
/**
* SETACL third argument prefix
*/
public static final char ADD_RIGHTS_MARKER = '+';
/**
* Marks groups when (de)serializing {@link MailboxACL.EntryKey}s.
*
* @see MailboxACL.EntryKey#serialize()
*/
public static final char DEFAULT_GROUP_MARKER = '$';
/**
* Marks negative when (de)serializing {@link MailboxACL.EntryKey}s.
*
* @see MailboxACL.EntryKey#serialize()
*/
public static final char DEFAULT_NEGATIVE_MARKER = '-';
/**
* SETACL third argument prefix
*/
public static final char REMOVE_RIGHTS_MARKER = '-';
/**
* Single right applicable to a mailbox.
*/
public enum Right {
Administer('a'), // (perform SETACL/DELETEACL/GETACL/LISTRIGHTS)
PerformExpunge('e'), //perform EXPUNGE and expunge as a part of CLOSE
Insert('i'), //insert (perform APPEND, COPY into mailbox)
/*
* create mailboxes (CREATE new sub-mailboxes in any
* implementation-defined hierarchy, parent mailbox for the new mailbox
* name in RENAME)
* */
CreateMailbox('k'),
Lookup('l'), //lookup (mailbox is visible to LIST/LSUB commands, SUBSCRIBE mailbox)
Post('p'), //post (send mail to submission address for mailbox, not enforced by IMAP4 itself)
Read('r'), //read (SELECT the mailbox, perform STATUS)
/**
* keep seen/unseen information across sessions (set or clear \SEEN
* flag via STORE, also set \SEEN during APPEND/COPY/ FETCH BODY[...])
*/
WriteSeenFlag('s'),
DeleteMessages('t'), //delete messages (set or clear \DELETED flag via STORE, set \DELETED flag during APPEND/COPY)
/**
* write (set or clear flags other than \SEEN and \DELETED via
* STORE, also set them during APPEND/COPY)
*/
Write('w'),
DeleteMailbox('x'); //delete mailbox (DELETE mailbox, old mailbox name in RENAME)
private final char rightCharacter;
Right(char rightCharacter) {
this.rightCharacter = rightCharacter;
}
/**
* Returns the char representation of this right.
*
* @return char representation of this right
*/
public char asCharacter() {
return rightCharacter;
}
public static final EnumSet allRights = EnumSet.allOf(Right.class);
public static Right forChar(char c) throws UnsupportedRightException {
return Right.allRights
.stream()
.filter(r -> r.asCharacter() == c)
.findFirst()
.orElseThrow(() -> new UnsupportedRightException(c));
}
}
/**
* Holds the collection of {@link MailboxACL.Right}s.
*
* Implementations may decide to support only a specific range of rights,
* e.g. the Standard Rights of RFC 4314 section 2.1.
*
* Implementations must not allow adding or removing of elements once this
* MailboxACLRights are initialized.
*/
public static class Rfc4314Rights {
public static Rfc4314Rights allExcept(Right... rights) throws UnsupportedRightException {
return MailboxACL.FULL_RIGHTS
.except(new Rfc4314Rights(rights));
}
public static Rfc4314Rights deserialize(String serialized) throws UnsupportedRightException {
return new Rfc4314Rights(serialized);
}
public static Rfc4314Rights of(Collection rights) {
return new Rfc4314Rights(rights);
}
private static final char c_ObsoleteCreate = 'c';
private static final char d_ObsoleteDelete = 'd';
private final EnumSet value;
public Rfc4314Rights(Collection rights) {
this.value = copyOf(rights);
}
public Rfc4314Rights(Right... rights) {
this(copyOf(Arrays.asList(rights)));
}
// JSON Deserialization
private Rfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException {
this(rightListFromSerializedRfc4314Rights(serializedRfc4314Rights));
}
public static Rfc4314Rights fromSerializedRfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException {
return new Rfc4314Rights(rightListFromSerializedRfc4314Rights(serializedRfc4314Rights));
}
public static List rightListFromSerializedRfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException {
return serializedRfc4314Rights.chars()
.mapToObj(i -> (char) i)
.flatMap(Throwing.function(Rfc4314Rights::convert).sneakyThrow())
.collect(Collectors.toList());
}
private static Stream convert(char flag) throws UnsupportedRightException {
switch (flag) {
case c_ObsoleteCreate:
return Stream.of(Right.CreateMailbox, Right.DeleteMailbox);
case d_ObsoleteDelete:
return Stream.of(Right.PerformExpunge, Right.DeleteMessages, Right.DeleteMailbox);
default:
return Stream.of(Right.forChar(flag));
}
}
/**
* Tells whether this contains the given right.
*
* @throws UnsupportedRightException if the given right is not supported.
*/
public boolean contains(char flag) throws UnsupportedRightException {
return contains(Right.forChar(flag));
}
public boolean contains(Right right) {
return value.contains(right);
}
public boolean contains(Right... rights) {
return value.containsAll(Arrays.asList(rights));
}
@Override
public final int hashCode() {
return Objects.hash(value);
}
@Override
public final boolean equals(Object o) {
if (o instanceof Rfc4314Rights) {
Rfc4314Rights that = (Rfc4314Rights) o;
return Objects.equals(this.value, that.value);
}
return false;
}
/**
* Performs the set theoretic operation of relative complement of
* toRemove MailboxACLRights in this MailboxACLRights.
*
* A schematic example: "lrw".except("w") returns "lr".
*
* Implementations must return a new unmodifiable instance of
* {@link MailboxACL.Rfc4314Rights}. However, implementations may decide to
* return this or toRemove parameter value in case the result would be
* equal to the respective one of those.
*/
public Rfc4314Rights except(Rfc4314Rights toRemove) throws UnsupportedRightException {
EnumSet copy = copyOf(value);
Optional.ofNullable(toRemove)
.ifPresent(rights -> copy.removeAll(rights.value));
return new Rfc4314Rights(copy);
}
/**
* Tells if this set of rights is empty.
*
* @return true if there are no rights in this set; false otherwise.
*/
public boolean isEmpty() {
return value.isEmpty();
}
/**
* Tells whether the implementation supports the given right.
*
* @return true if this supports the given right.
*/
public boolean isSupported(Right right) {
try {
contains(right.asCharacter());
return true;
} catch (UnsupportedRightException e) {
return false;
}
}
public Iterator iterator() {
ImmutableList rights = ImmutableList.copyOf(value);
return rights.iterator();
}
public List list() {
return ImmutableList.copyOf(value);
}
/**
* Returns a serialized form of this {@link MailboxACL.Right} as
* {@link String}.
*
* @return a {@link String}
*/
public String serialize() {
return value.stream()
.map(Right::asCharacter)
.map(String::valueOf)
.collect(Collectors.joining());
}
public String toString() {
return serialize();
}
/**
* Performs the theoretic operation of union of this
* Rfc4314Rights and toAdd Rfc4314Rights.
*
* A schematic example: "lr".union("rw") returns "lrw".
*
* Implementations must return a new unmodifiable instance of
* {@link MailboxACL.Rfc4314Rights}.
*
* @return union of this and toAdd
*
*/
public Rfc4314Rights union(Rfc4314Rights toAdd) throws UnsupportedRightException {
Preconditions.checkNotNull(toAdd);
EnumSet rightUnion = EnumSet.noneOf(Right.class);
rightUnion.addAll(value);
rightUnion.addAll(toAdd.value);
return new Rfc4314Rights(rightUnion);
}
}
/**
* A utility implementation of
* {@code Map.Entry}.
*/
public static class Entry extends AbstractMap.SimpleEntry {
public Entry(EntryKey key, Rfc4314Rights value) {
super(key, value);
}
public Entry(String key, String value) throws UnsupportedRightException {
this(EntryKey.deserialize(key), Rfc4314Rights.fromSerializedRfc4314Rights(value));
}
public Entry(String key, Right... rights) throws UnsupportedRightException {
this(EntryKey.deserialize(key), new Rfc4314Rights(rights));
}
}
/**
* The key used in {@link MailboxACL#getEntries()}. Implementations should
* override {@link #hashCode()} and {@link #equals(Object)} in such a way
* that all of {@link #getName()}, {@link #getNameType()} and
* {@link #isNegative()} are significant.
*
*/
public static class EntryKey {
public static EntryKey createGroupEntryKey(String name) {
return new EntryKey(name, NameType.group, false);
}
public static EntryKey createGroupEntryKey(String name, boolean negative) {
return new EntryKey(name, NameType.group, negative);
}
public static EntryKey createUserEntryKey(Username name) {
return new EntryKey(name.asString(), NameType.user, false);
}
public static EntryKey createUserEntryKey(Username name, boolean negative) {
return new EntryKey(name.asString(), NameType.user, negative);
}
private final String name;
private final NameType nameType;
private final boolean negative;
/**
* Creates a new instance of SimpleMailboxACLEntryKey from the given
* serialized {@link String}. It supposes that negative rights are
* marked with {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and that
* groups are marked with {@link MailboxACL#DEFAULT_GROUP_MARKER}.
*/
public static EntryKey deserialize(String serialized) {
Preconditions.checkNotNull(serialized, "Cannot parse null");
Preconditions.checkArgument(!serialized.isEmpty(), "Cannot parse an empty string");
boolean negative = serialized.charAt(0) == DEFAULT_NEGATIVE_MARKER;
int nameStart = negative ? 1 : 0;
boolean isGroup = serialized.charAt(nameStart) == DEFAULT_GROUP_MARKER;
Optional explicitNameType = isGroup ? Optional.of(NameType.group) : Optional.empty();
String name = isGroup ? serialized.substring(nameStart + 1) : serialized.substring(nameStart);
if (name.isEmpty()) {
throw new IllegalStateException("Cannot parse a string with empty name");
}
NameType nameType = explicitNameType.orElseGet(() -> computeImplicitNameType(name));
return new EntryKey(name, nameType, negative);
}
private static NameType computeImplicitNameType(String name) {
boolean isSpecialName = Arrays.stream(SpecialName.values())
.anyMatch(specialName -> specialName.name().equals(name));
if (isSpecialName) {
return NameType.special;
}
return NameType.user;
}
public EntryKey(String name, NameType nameType, boolean negative) {
Preconditions.checkNotNull(name, "Provide a name for this %s", getClass().getName());
Preconditions.checkNotNull(nameType, "Provide a nameType for this %s", getClass().getName());
this.name = name;
this.nameType = nameType;
this.negative = negative;
}
public boolean equals(Object o) {
if (o instanceof EntryKey) {
EntryKey other = (EntryKey) o;
return Objects.equals(this.name, other.getName())
&& Objects.equals(this.nameType, other.getNameType())
&& Objects.equals(this.negative, other.isNegative());
}
return false;
}
/**
* Returns the name of a user or of a group to which this
* {@link MailboxACL.EntryKey} applies.
*
* @return User name, group name or special name.
*/
public String getName() {
return name;
}
/**
* Tells of what type is the name returned by {@link #getName()}.
*
* @return type of the name returned by {@link #getName()}
*/
public NameType getNameType() {
return nameType;
}
public final int hashCode() {
return Objects.hash(negative, nameType, name);
}
/**
* If true the {@link MailboxACL.Rfc4314Rights} returned by
* {@link Entry#getValue()} should be interpreted as
* "negative rights" as described in RFC4314: If the identifier "-fred"
* is granted the "w" right, that indicates that the "w" right is to be
* removed from users matching the identifier "fred", even though the
* user "fred" might have the "w" right as a consequence of some other
* identifier in the ACL.
*
* Note that {@link Entry#getKey()} ()} does not start with "-"
* when {@link Entry#getValue()} returns true.
*/
public boolean isNegative() {
return negative;
}
/**
* Returns a serialized form of this {@link MailboxACL.EntryKey} as a
* {@link String}. Implementations should choose a consistent way how
* all of {@link #getName()}, {@link #getNameType()} and
* {@link #isNegative()} get serialized.
*
* RFC4314 sction 2. states: All user name strings accepted by the LOGIN
* or AUTHENTICATE commands to authenticate to the IMAP server are
* reserved as identifiers for the corresponding users. Identifiers
* starting with a dash ("-") are reserved for "negative rights",
* described below. All other identifier strings are interpreted in an
* implementation-defined manner.
*
* Dovecot and Cyrus mark groups with '$' prefix. See http://wiki2.dovecot.org/SharedMailboxes/Shared:
*
* The $group syntax is not a standard, but it is mentioned in RFC
* 4314 examples and is also understood by at least Cyrus IMAP. Having
* '-' before the identifier specifies negative rights.
*
* @see MailboxACL#DEFAULT_GROUP_MARKER
* @see MailboxACL#DEFAULT_NEGATIVE_MARKER
*
* @return serialized form as a {@link String}
*/
public String serialize() {
String negativePart = negative ? String.valueOf(DEFAULT_NEGATIVE_MARKER) : "";
String nameTypePart = nameType == NameType.group ? String.valueOf(DEFAULT_GROUP_MARKER) : "";
return negativePart + nameTypePart + name;
}
public String toString() {
return serialize();
}
}
public static class ACLCommand {
public static class Builder {
private EntryKey key;
private EditMode editMode;
private Rfc4314Rights rights;
private Builder() {
}
public Builder forUser(Username user) {
key = EntryKey.createUserEntryKey(user);
return this;
}
public Builder forGroup(String group) {
key = EntryKey.createGroupEntryKey(group);
return this;
}
public Builder forOwner() {
key = OWNER_KEY;
return this;
}
public Builder key(EntryKey key) {
this.key = key;
return this;
}
public Builder rights(Rfc4314Rights rights) {
this.rights = rights;
return this;
}
public Builder rights(Right... rights) throws UnsupportedRightException {
this.rights =
Optional.ofNullable(this.rights)
.orElseGet(Rfc4314Rights::new)
.union(new Rfc4314Rights(rights));
return this;
}
public Builder noRights() {
this.rights = new Rfc4314Rights();
return this;
}
public Builder mode(EditMode mode) {
editMode = mode;
return this;
}
public ACLCommand asReplacement() {
editMode = EditMode.REPLACE;
return build();
}
public ACLCommand asAddition() {
editMode = EditMode.ADD;
return build();
}
public ACLCommand asRemoval() {
editMode = EditMode.REMOVE;
return build();
}
public ACLCommand build() {
Preconditions.checkState(key != null);
Preconditions.checkState(editMode != null);
Preconditions.checkState(rights != null);
return new ACLCommand(key, editMode, rights);
}
}
private final EntryKey key;
private final EditMode editMode;
private final Rfc4314Rights rights;
private ACLCommand(EntryKey key, EditMode editMode, Rfc4314Rights rights) {
this.key = key;
this.editMode = editMode;
this.rights = rights;
}
public EntryKey getEntryKey() {
return key;
}
public EditMode getEditMode() {
return editMode;
}
public Rfc4314Rights getRights() {
return rights;
}
public final boolean equals(Object o) {
if (o instanceof ACLCommand) {
ACLCommand that = (ACLCommand) o;
return Objects.equals(this.key, that.key)
&& Objects.equals(this.editMode, that.editMode)
&& Objects.equals(this.rights, that.rights);
}
return false;
}
public final int hashCode() {
return Objects.hash(key, editMode, rights);
}
}
public static final EntryKey ANYBODY_KEY;
public static final EntryKey ANYBODY_NEGATIVE_KEY;
public static final EntryKey AUTHENTICATED_KEY;
public static final EntryKey AUTHENTICATED_NEGATIVE_KEY;
public static final MailboxACL EMPTY;
public static final Rfc4314Rights FULL_RIGHTS;
public static final Rfc4314Rights NO_RIGHTS;
public static final MailboxACL OWNER_FULL_ACL;
public static final MailboxACL OWNER_FULL_EXCEPT_ADMINISTRATION_ACL;
public static final EntryKey OWNER_KEY;
public static final EntryKey OWNER_NEGATIVE_KEY;
static {
try {
ANYBODY_KEY = new EntryKey(SpecialName.anybody.name(), NameType.special, false);
ANYBODY_NEGATIVE_KEY = new EntryKey(SpecialName.anybody.name(), NameType.special, true);
AUTHENTICATED_KEY = new EntryKey(SpecialName.authenticated.name(), NameType.special, false);
AUTHENTICATED_NEGATIVE_KEY = new EntryKey(SpecialName.authenticated.name(), NameType.special, true);
EMPTY = new MailboxACL();
FULL_RIGHTS = new Rfc4314Rights(Right.allRights);
NO_RIGHTS = new Rfc4314Rights();
OWNER_KEY = new EntryKey(SpecialName.owner.name(), NameType.special, false);
OWNER_NEGATIVE_KEY = new EntryKey(SpecialName.owner.name(), NameType.special, true);
OWNER_FULL_ACL = new MailboxACL(new Entry[] { new Entry(MailboxACL.OWNER_KEY, MailboxACL.FULL_RIGHTS) });
OWNER_FULL_EXCEPT_ADMINISTRATION_ACL = new MailboxACL(new Entry[] { new Entry(MailboxACL.OWNER_KEY, MailboxACL.FULL_RIGHTS.except(new Rfc4314Rights(Right.Administer))) });
} catch (UnsupportedRightException e) {
throw new RuntimeException(e);
}
}
private static Map toMap(Properties props) throws UnsupportedRightException {
ImmutableMap.Builder builder = ImmutableMap.builder();
if (props != null) {
for (Map.Entry, ?> prop : props.entrySet()) {
builder.put(EntryKey.deserialize((String) prop.getKey()), Rfc4314Rights.fromSerializedRfc4314Rights((String) prop.getValue()));
}
}
return builder.build();
}
private final Map entries;
/**
* Creates a new instance of SimpleMailboxACL containing no entries.
*
*/
public MailboxACL() {
this(ImmutableMap.of());
}
/**
* Creates a new instance of SimpleMailboxACL from the given array of
* entries.
*/
@SafeVarargs
public MailboxACL(Map.Entry... entries) {
this(Optional.ofNullable(entries)
.map(array -> Arrays.stream(array)
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)))
.orElseGet(ImmutableMap::of));
}
/**
* Creates a new instance of SimpleMailboxACL from the given {@link Map} of
* entries.
*/
public MailboxACL(Map entries) {
Preconditions.checkNotNull(entries);
this.entries = ImmutableMap.copyOf(entries);
}
/**
* Creates a new instance of SimpleMailboxACL from {@link Properties}. The
* keys and values from the props parameter are parsed by the
* {@link String} constructors of {@link EntryKey} and
* {@link Rfc4314Rights} respectively.
*/
public MailboxACL(Properties props) throws UnsupportedRightException {
this(toMap(props));
}
public final boolean equals(Object o) {
if (o instanceof MailboxACL) {
MailboxACL acl = (MailboxACL) o;
return Objects.equals(this.getEntries(), acl.getEntries());
}
return false;
}
public final int hashCode() {
return Objects.hash(entries);
}
/**
* Apply the given ACL update on current ACL and return the result as a new ACL.
*
* @param aclUpdate Update to perform
* @return Copy of current ACL updated
*/
public MailboxACL apply(ACLCommand aclUpdate) throws UnsupportedRightException {
switch (aclUpdate.getEditMode()) {
case ADD:
return union(aclUpdate.getEntryKey(), aclUpdate.getRights());
case REMOVE:
return except(aclUpdate.getEntryKey(), aclUpdate.getRights());
case REPLACE:
return replace(aclUpdate.getEntryKey(), aclUpdate.getRights());
}
throw new RuntimeException("Unknown edit mode");
}
/**
* Performs the set theoretic operation of relative complement of toRemove
* {@link MailboxACL} in this {@link MailboxACL}.
*
* A schematic example: "user1:lr;user2:lrwt".except("user1:w;user2:t")
* returns "user1:lr;user2:lrw".
*
* Implementations must return a new unmodifiable instance of
* {@link MailboxACL}. However, implementations may decide to return this or
* toRemove parameter value in case the result would be equal to the
* respective one of those.
*
* Implementations must ensure that the result does not contain entries with
* empty rigths. E.g. "user1:lr;user2:lrwt".except("user1:lr") should return
* "user2:lrwt" rather than "user1:;user2:lrwt"
*/
public MailboxACL except(MailboxACL other) throws UnsupportedRightException {
return new MailboxACL(entries.entrySet()
.stream()
.map(entry -> Pair.of(
entry.getKey(),
except(entry.getValue(), other.getEntries().get(entry.getKey()))))
.filter(pair -> !pair.getValue().isEmpty())
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
}
private Rfc4314Rights except(Rfc4314Rights thisRight, Rfc4314Rights exceptRights) {
return Optional.ofNullable(exceptRights)
.map(Throwing.function(thisRight::except))
.orElse(thisRight);
}
public MailboxACL except(EntryKey key, Rfc4314Rights mailboxACLRights) throws UnsupportedRightException {
return except(new MailboxACL(new Entry(key, mailboxACLRights)));
}
/**
* {@link Map} of entries.
*
* @return the entries.
*/
public Map getEntries() {
return entries;
}
/**
* Replaces the entry corresponding to the given {@code key} with
* {@code toAdd}link MailboxACLRights}.
*
* Implementations must return a new unmodifiable instance of
* {@link MailboxACL}. However, implementations may decide to return this in
* case the result would be equal to it.
*
* Implementations must ensure that the result does not contain entries with
* empty rigths. E.g. "user1:lr;user2:lrwt".replace("user1",
* MailboxACLRights.EMPTY) should return "user2:lrwt" rather than
* "user1:;user2:lrwt". The same result should be returned by
* "user1:lr;user2:lrwt".replace("user1", null).
*/
public MailboxACL replace(EntryKey key, Rfc4314Rights replacement) throws UnsupportedRightException {
if (entries.containsKey(key)) {
return new MailboxACL(
entries.entrySet()
.stream()
.map(entry -> Pair.of(entry.getKey(),
entry.getKey().equals(key) ? replacement : entry.getValue()))
.filter(pair -> pair.getValue() != null && !pair.getValue().isEmpty())
.collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue)));
} else {
return Optional.ofNullable(replacement)
.filter(Predicate.not(Rfc4314Rights::isEmpty))
.map(replacementValue -> new MailboxACL(
ImmutableMap.builder()
.putAll(entries)
.put(key, replacementValue)
.build()))
.orElse(this);
}
}
public String toString() {
return MoreObjects.toStringHelper(this)
.add("entries", entries)
.toString();
}
/**
* Performs the set theoretic operation of union of this {@link MailboxACL}
* and toAdd {@link MailboxACL}.
*
* A schematic example:
* "user1:lr;user2:lrwt".union("user1:at;-$group1:lrwt") returns
* "user1:alrt;user2:lrwt;-$group1:lrwt".
*
* Implementations must return a new unmodifiable instance of
* {@link MailboxACL}. However, implementations may decide to return this or
* toAdd parameter value in case the result would be equal to the respective
* one of those.
*/
public MailboxACL union(MailboxACL other) throws UnsupportedRightException {
if (entries.isEmpty()) {
return other;
}
return new MailboxACL(
Stream.concat(
this.entries.entrySet().stream(),
other.getEntries().entrySet().stream())
.collect(ImmutableListMultimap.toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue))
.asMap()
.entrySet()
.stream()
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, e -> union(e.getValue()))));
}
private Rfc4314Rights union(Collection rights) {
return rights.stream()
.reduce(
new Rfc4314Rights(),
Throwing.binaryOperator(Rfc4314Rights::union));
}
public MailboxACL union(EntryKey key, Rfc4314Rights mailboxACLRights) throws UnsupportedRightException {
return union(new MailboxACL(new Entry(key, mailboxACLRights)));
}
public Map ofPositiveNameType(NameType nameType) {
return this.entries.entrySet().stream()
.filter(entry -> !entry.getKey().isNegative())
.filter(entry -> entry.getKey().getNameType().equals(nameType))
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}
}