/* * Copyright 2015 the original author or authors. * * Licensed 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 io.atomix.collections; import io.atomix.collections.internal.MultiMapCommands; import io.atomix.collections.util.DistributedMultiMapFactory; import io.atomix.copycat.client.CopycatClient; import io.atomix.resource.AbstractResource; import io.atomix.resource.ReadConsistency; import io.atomix.resource.Resource; import io.atomix.resource.ResourceTypeInfo; import java.time.Duration; import java.util.Collection; import java.util.Properties; import java.util.concurrent.CompletableFuture; /** * Stores a map of keys to multiple values. * <p> * The multi-map resource stores a map of keys that allows multiple values to be associated with each key. * Multimap entries are stored in memory on each stateful node and backed by disk, thus the size of a multi-map * is limited by the available memory on the smallest node in the cluster. * <p> * To create a multimap, use the {@code getMultiMap} factory method on an {@code Atomix} instance: * <pre> * {@code * DistributedMultiMap<String, String> multiMap = atomix.getMultiMap("foo").get(); * } * </pre> * The multi-map interface closely simulates that of {@link java.util.Map} except that values are {@link Collection}s * rather than {@code V}. * <h3>Value order</h3> * By default, the values associated with each key are stored in insertion order. However, this behavior can * be configured via the {@link io.atomix.collections.DistributedMultiMap.Config map configuration} by setting * the value {@link Order}. To set the order of values in a multi-map, create a configuration and provide the * configuration at the creation of the map. * <pre> * {@code * DistributedMultiMap.Config config = DistributedMultiMap.config() * .withValueOrder(DistributedMultiMap.Order.NATURAL); * DistributedMultiMap<String, String> multiMap = atomix.getMultiMap("foo", config).get(); * } * </pre> * Multi-maps support relaxed consistency levels for some read operations line {@link #size(ReadConsistency)} * and {@link #containsKey(Object, ReadConsistency)}. By default, read operations on a queue are linearizable * but require some level of communication between nodes. * * @author <a href="http://github.com/kuujo>Jordan Halterman</a> */ @ResourceTypeInfo(id=-12, factory=DistributedMultiMapFactory.class) public class DistributedMultiMap<K, V> extends AbstractResource<DistributedMultiMap<K, V>> { /** * Multimap configuration. */ public static class Config extends Resource.Config { public Config() { } public Config(Properties defaults) { super(defaults); } /** * Sets the map value order. * * @param order The map value order. * @return The map configuration. */ public Config withValueOrder(Order order) { setProperty("order", order.name().toLowerCase()); return this; } /** * Returns the map value order. * * @return The map value order. */ public Order getValueOrder() { return Order.valueOf(getProperty("order", Order.INSERT.name().toLowerCase()).toUpperCase()); } } /** * Represents the order of values in a multimap. */ public enum Order { /** * Indicates that values should be stored in natural order. */ NATURAL, /** * Indicates that values should be stored in insertion order. */ INSERT, /** * Indicates that no order is required for values. */ NONE } public DistributedMultiMap(CopycatClient client, Properties options) { super(client, options); } @Override public Resource.Config config() { return new Config(super.config()); } /** * Checks whether the map is empty. * * @return A completable future to be completed with a boolean value indicating whether the map is empty. */ public CompletableFuture<Boolean> isEmpty() { return client.submit(new MultiMapCommands.IsEmpty()); } /** * Checks whether the map is empty. * * @return A completable future to be completed with a boolean value indicating whether the map is empty. */ public CompletableFuture<Boolean> isEmpty(ReadConsistency consistency) { return client.submit(new MultiMapCommands.IsEmpty(consistency.level())); } /** * Gets the number of key-value pairs in the map. * * @return A completable future to be completed with the number of entries in the map. */ public CompletableFuture<Integer> size() { return client.submit(new MultiMapCommands.Size()); } /** * Gets the number of key-value pairs in the map. * * @param consistency The read consistency level. * @return A completable future to be completed with the number of entries in the map. */ public CompletableFuture<Integer> size(ReadConsistency consistency) { return client.submit(new MultiMapCommands.Size(consistency.level())); } /** * Gets the number of values for the given key. * * @param key The key to check. * @return A completable future to be completed with the number of entries in the map. */ public CompletableFuture<Integer> size(K key) { return client.submit(new MultiMapCommands.Size(key)); } /** * Gets the number of values for the given key. * * @param key The key to check. * @param consistency The read consistency level. * @return A completable future to be completed with the number of entries in the map. */ public CompletableFuture<Integer> size(K key, ReadConsistency consistency) { return client.submit(new MultiMapCommands.Size(key, consistency.level())); } /** * Checks whether the map contains a key. * * @param key The key to check. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> containsKey(K key) { return client.submit(new MultiMapCommands.ContainsKey(key)); } /** * Checks whether the map contains a key. * * @param key The key to check. * @param consistency The read consistency level. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> containsKey(K key, ReadConsistency consistency) { return client.submit(new MultiMapCommands.ContainsKey(key, consistency.level())); } /** * Checks whether the map contains an entry. * * @param key The key to check. * @param value The value to check. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> containsEntry(K key, V value) { return client.submit(new MultiMapCommands.ContainsEntry(key, value)); } /** * Checks whether the map contains an entry. * * @param key The key to check. * @param value The value to check. * @param consistency The read consistency level. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> containsEntry(K key, V value, ReadConsistency consistency) { return client.submit(new MultiMapCommands.ContainsEntry(key, value, consistency.level())); } /** * Checks whether the map contains a value. * * @param value The value to check. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> containsValue(V value) { return client.submit(new MultiMapCommands.ContainsValue(value)); } /** * Checks whether the map contains a value. * * @param value The value to check. * @param consistency The read consistency level. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> containsValue(V value, ReadConsistency consistency) { return client.submit(new MultiMapCommands.ContainsValue(value, consistency.level())); } /** * Gets a value from the map. * * @param key The key to get. * @return A completable future to be completed with the result once complete. */ @SuppressWarnings("unchecked") public CompletableFuture<Collection<V>> get(K key) { return client.submit(new MultiMapCommands.Get(key)).thenApply(result -> result); } /** * Gets a value from the map. * * @param key The key to get. * @param consistency The read consistency level. * @return A completable future to be completed with the result once complete. */ @SuppressWarnings("unchecked") public CompletableFuture<Collection<V>> get(K key, ReadConsistency consistency) { return client.submit(new MultiMapCommands.Get(key, consistency.level())).thenApply(result -> result); } /** * Puts a value in the map. * * @param key The key to set. * @param value The value to set. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> put(K key, V value) { return client.submit(new MultiMapCommands.Put(key, value)).thenApply(result -> result); } /** * Puts a value in the map. * * @param key The key to set. * @param value The value to set. * @param ttl The duration after which to expire the key. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> put(K key, V value, Duration ttl) { return client.submit(new MultiMapCommands.Put(key, value, ttl.toMillis())); } /** * Removes a value from the map. * * @param key The key to remove. * @return A completable future to be completed with the result once complete. */ @SuppressWarnings("unchecked") public CompletableFuture<Collection<V>> remove(Object key) { return client.submit(new MultiMapCommands.Remove(key)).thenApply(result -> (Collection<V>) result); } /** * Removes a key and value from the map. * * @param key The key to remove. * @param value The value to remove. * @return A completable future to be completed with the result once complete. */ public CompletableFuture<Boolean> remove(Object key, Object value) { return client.submit(new MultiMapCommands.Remove(key, value)).thenApply(result -> (boolean) result); } /** * Removes all instances of a value from the map. * * @param value The value to remove. * @return A completable future to be completed once the value has been removed. */ public CompletableFuture<Void> removeValue(Object value) { return client.submit(new MultiMapCommands.RemoveValue(value)); } /** * Removes all entries from the map. * * @return A completable future to be completed once the operation is complete. */ public CompletableFuture<Void> clear() { return client.submit(new MultiMapCommands.Clear()); } }