/*
* Copyright Terracotta, Inc.
*
* 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 org.ehcache.core.spi.store;
import org.ehcache.Cache;
import org.ehcache.ValueSupplier;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.config.ResourcePools;
import org.ehcache.config.ResourceType;
import org.ehcache.expiry.Expiry;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.core.spi.function.Function;
import org.ehcache.core.spi.function.NullaryFunction;
import org.ehcache.core.spi.store.events.StoreEventSource;
import org.ehcache.spi.serialization.Serializer;
import org.ehcache.spi.service.PluralService;
import org.ehcache.spi.service.Service;
import org.ehcache.spi.service.ServiceConfiguration;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* The {@code Store} interface represents the backing storage of a {@link Cache}. It abstracts the support for multiple
* tiers, eventing, eviction and expiry.
* <p>
* It maps key of type {@code K} to {@link ValueHolder value holder} which contains value of type {@code V} and
* associated metadata.
* <p>
* Store implementations must not handle {@code null} keys or values.
*
* @param <K> the key type
* @param <V> the value type
*/
public interface Store<K, V> extends ConfigurationChangeSupport {
/**
* Returns the {@link Store.ValueHolder ValueHolder} to
* which the specified key is mapped, or {@code null} if this store contains no
* mapping for the key or if it was evicted (or became expired) since it was
* initially installed.
* <p>
* More formally, if this store contains a non-expired mapping from a key
* {@code k} to a {@link Store.ValueHolder ValueHolder}
* {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
* <p>
* The key cannot be {@code null}.
*
* @param key the key of the mapping to lookup
* @return the value mapped to this key or {@code null} if no mapping exists or is expired
*
* @throws NullPointerException if the argument is {@code null}
* @throws ClassCastException if the specified key is not an instance of {@code K}
* @throws StoreAccessException if the mapping can't be retrieved
*/
ValueHolder<V> get(K key) throws StoreAccessException;
/**
* Returns {@code true} if this store contains the specified key
* and the entry is not expired.
* <p>
* More formally, returns {@code true} if and only if this store
* contains a key {@code k} such that {@code (o.equals(k))}.
* <p>
* The key cannot be {@code null}.
*
* @param key key whose presence in this store is to be tested
* @return {@code true} if this store contains the specified non-expired element, {@code false} otherwise
*
* @throws NullPointerException if the argument is {@code null}
* @throws ClassCastException if the specified key is not an instance of {@code K}
* @throws StoreAccessException if the presence can't be tested for
*/
boolean containsKey(K key) throws StoreAccessException;
/**
* Maps the specified key to the specified value in this store.
* Neither the key nor the value can be null.
* <p>
* The ValueHolder can be retrieved by calling the {@code get} method
* with a key that is equal to the original key.
* <p>
* Neither the key nor the value can be {@code null}.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return {@link PutStatus} based on the result of the operation in store
*
* @throws NullPointerException if any of the arguments is {@code null}
* @throws ClassCastException if the specified key or value are not of the correct types ({@code K} or {@code V})
* @throws StoreAccessException if the mapping can't be installed
*/
PutStatus put(K key, V value) throws StoreAccessException;
/**
* Maps the specified key to the specified value in this store, unless a non-expired mapping
* already exists.
* <p>
* This is equivalent to
* <pre>
* if (!store.containsKey(key))
* store.put(key, value);
* return null;
* else
* return store.get(key);
* </pre>
* except that the action is performed atomically.
* <p>
* The ValueHolder can be retrieved by calling the {@code get} method
* with a key that is equal to the original key.
* <p>
* Neither the key nor the value can be {@code null}.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the {@link Store.ValueHolder ValueHolder} to
* which the specified key was previously mapped, or {@code null} if no such mapping existed or the mapping was expired
*
* @throws NullPointerException if any of the arguments is {@code null}
* @throws ClassCastException if the specified key or value are not of the correct types ({@code K} or {@code V})
* @throws StoreAccessException if the mapping can't be installed
*
* @see #replace(Object, Object)
*/
ValueHolder<V> putIfAbsent(K key, V value) throws StoreAccessException;
/**
* Removes the key (and its corresponding value) from this store.
* This method does nothing if the key is not mapped.
* <p>
* The key cannot be {@code null}.
*
* @param key the key that needs to be removed
* @return {@code true} if the mapping existed and was successfully removed, {@code false} otherwise
*
* @throws NullPointerException if the specified key is null
* @throws NullPointerException if the argument is {@code null}
* @throws StoreAccessException if the mapping can't be removed
*/
boolean remove(K key) throws StoreAccessException;
/**
* Removes the entry for a key only if currently mapped to the given value
* and the entry is not expired.
* <p>
* This is equivalent to
* <pre>
* if (store.containsKey(key)) {
* if (store.get(key).equals(value)) {
* store.remove(key);
* return REMOVED;
* } else return KEY_PRESENT;
* } else return KEY_MISSING;</pre>
* except that the action is performed atomically.
* <p>
* The key cannot be {@code null}.
*
* @param key key with which the specified value is associated
* @param value value expected to be associated with the specified key
* @return {@link RemoveStatus} based on the result of the remove operation in store
*
* @throws ClassCastException if the specified key or value are not of the correct types ({@code K} or {@code V})
* @throws NullPointerException if any of the arguments is {@code null}
* @throws StoreAccessException if the mapping can't be removed
*/
RemoveStatus remove(K key, V value) throws StoreAccessException;
/**
* Replaces the entry for a key only if currently mapped to some value and the entry is not expired.
* <p>
* This is equivalent to
* <pre>
* ValueHolder<V> oldValue = store.get(key);
* if (oldValue != null) {
* store.put(key, value);
* }
* return oldValue; </pre>
* except that the action is performed atomically.
* <p>
* Neither the key nor the value can be {@code null}.
*
* @param key key with which the specified value is associated
* @param value value expected to be associated with the specified key
* @return the {@link Store.ValueHolder ValueHolder} to which the specified key was previously mapped,
* or {@code null} if no such mapping existed
*
* @throws ClassCastException if the specified key or value are not of the correct types ({@code K} or {@code V})
* @throws NullPointerException if any of the arguments is {@code null}
* @throws StoreAccessException if the mapping can't be replaced
*/
ValueHolder<V> replace(K key, V value) throws StoreAccessException;
/**
* Replaces the entry for a key only if currently mapped to the given value
* and the entry is not expired.
* <p>
* This is equivalent to
* <pre>
* if (store.containsKey(key)) {
* if (store.get(key).equals(oldValue)) {
* store.put(key, newValue);
* return HIT;
* } else {
* return MISS_PRESENT;
* }
* } else {
* return MISS_NOT_PRESENT;
* }</pre>
* except that the action is performed atomically.
* <p>
* Neither the key nor the value can be {@code null}.
*
* @param key key with which the specified value is associated
* @param oldValue value expected to be associated with the specified key
* @param newValue value to be associated with the specified key
* @return {@link ReplaceStatus} based on the result of the replace operation in store
*
* @throws ClassCastException if the specified key or values are not of the correct types ({@code K} or {@code V})
* @throws NullPointerException if any of the arguments is {@code null}
* @throws StoreAccessException if the mapping can't be replaced
*/
ReplaceStatus replace(K key, V oldValue, V newValue) throws StoreAccessException;
/**
* Removes all of the mappings from this {@code Store}.
* <p>
* This method provides no guarantee of atomicity.
*
* @throws StoreAccessException if the store couldn't be partially or entirely be cleared.
*/
void clear() throws StoreAccessException;
/**
* Exposes the {@code Store} eventing system to allow configuration and registration of listeners.
*
* @return the {@code StoreEventSource} of this {@code Store}
*/
StoreEventSource<K, V> getStoreEventSource();
/**
* Returns an iterator over the elements in this store.
* <p>
* The elements are returned in no particular order.
*
* @return an iterator over the mappings in this Store
*/
Store.Iterator<Cache.Entry<K, ValueHolder<V>>> iterator();
/**
* Compute the value for the given key by invoking the given function to produce the value.
* <p>
* The function will be supplied with the key and existing value (or {@code null} if no entry exists) as parameters.
* The function should return the desired new value for the entry or {@code null} to remove the entry.
* If the function throws an unchecked exception the Store will not be modified and a {@link StoreAccessException} will
* be thrown.
* <p>
* This is equivalent to
* <pre>
* V newValue = mappingFunction.apply(key, store.get(key));
* if (newValue != null) {
* store.put(key, newValue);
* } else {
* store.remove(key);
* }
* return newValue;
* </pre>
* except that the action is performed atomically.
* <p>
* This is equivalent to calling {@link Store#compute(Object, BiFunction, NullaryFunction)}
* with a "replaceEquals" function that returns {@link Boolean#TRUE true}.
* <p>
* Neither the key nor the function can be {@code null}
*
* @param key the key to update the mapping for
* @param mappingFunction the function that will produce the new value.
* @return the new value associated with the key or {@code null} if none
*
* @throws ClassCastException if the specified key is not of the correct type {@code K}
* @throws NullPointerException if any of the arguments is {@code null}
* @throws StoreAccessException if the mapping can't be changed
*
* @see #compute(Object, BiFunction, NullaryFunction)
*/
ValueHolder<V> compute(K key, BiFunction<? super K, ? super V, ? extends V> mappingFunction) throws StoreAccessException;
/**
* Compute the value for the given key by invoking the given function to produce the value.
* <p>
* The {@code mappingFunction} will be supplied with the key and existing value (or {@code null} if no entry exists) as parameters.
* The {@code mappingFunction} should return the desired new value for the entry or {@code null} to remove the entry.
* <p>
* The {@code replaceEqual} function will be invoked if the {@code mappingFunction} returns a value that is
* {@link Object#equals(Object) equal} to the existing value. If the {@code replaceEqual} function returns
* {@code false} then the existing mapping will not be replaced and will have its metadata updated.
* <p>
* If either function throws an unchecked exception the {@code Store} will not be modified and a {@link StoreAccessException}
* will be thrown.
* <p>
* This is equivalent to
* <pre>
* V oldValue = store.get(key);
* V newValue = mappingFunction.apply(key, oldValue);
* if (newValue != null) {
* if (!newValue.equals(oldValue) || replaceEqual.apply()) {
* store.put(key, newValue);
* }
* } else {
* store.remove(key);
* }
* return newValue;
* </pre>
* except that the action is performed atomically.
* <p>
* Neither the key nor the functions can be {@code null}
*
* @param key the key to operate on
* @param mappingFunction the function that will produce the new value.
* @param replaceEqual indicates if an equal value replaces the existing one
* @return the new value associated with the key or {@code null} if none
*
* @throws ClassCastException if the specified key is not of the correct type {@code K}
* @throws NullPointerException if any of the arguments is {@code null}
* @throws StoreAccessException if the mapping can't be changed
*
* @see #compute(Object, BiFunction)
*/
ValueHolder<V> compute(K key, BiFunction<? super K, ? super V, ? extends V> mappingFunction, NullaryFunction<Boolean> replaceEqual) throws StoreAccessException;
/**
* Compute the value for the given key (only if absent or expired) by invoking the given function to produce the value.
* <p>
* The function will be supplied with the key only if no mapping exists.
* The function should return the desired new value for the entry. {@code null} will result in a no-op.
* If the function throws an unchecked exception the Store will not be modified and a {@link StoreAccessException}
* will be thrown.
* <p>
* This is equivalent to
* <pre>
* if (!store.containsKey(key)) {
* V newValue = mappingFunction.apply(key);
* if (newValue != null) {
* store.put(key, newValue);
* }
* return newValue;
* }
* return store.get(key);
* </pre>
* except that the action is performed atomically.
* <p>
* Neither the key nor the function can be {@code null}
*
* @param key the key to operate on
* @param mappingFunction the function that will produce the value.
* @return the new value associated with the key or {@code null} if none
*
* @throws ClassCastException If the specified key is not of the correct type ({@code K}) or if the
* function returns a value that is not of type ({@code V})
* @throws NullPointerException if any of the arguments is {@code null}
* @throws StoreAccessException if the mapping can't be changed
*/
ValueHolder<V> computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) throws StoreAccessException;
/**
* Compute a value for every key passed in the {@link Set} {@code keys} argument, using the {@code remappingFunction} to compute the value.
* <p>
* The function gets an {@link Iterable} of {@link java.util.Map.Entry} key/value pairs, where each entry's value is its currently stored value,
* or null if nothing is stored under the key. It is expected that the function returns an {@link Iterable} of {@link java.util.Map.Entry}
* key/value pairs containing an entry for each key that was passed to it. This returned {@link Iterable} should also iterate in the same order as the input {@link Iterable}.
* If an entry's value is null, its mapping will be removed from the store.
* <p>
* Note that the remapping function can be invoked multiple times with key subsets but that it will never the see
* the same key in different invocations.
* <p>
* Behaviour is equivalent to compute invocations in an external loop. There is no cross key atomicity
* guarantee / requirement. Implementations may provide coarser grained guarantees.
* <p>
* This is equivalent to calling {@link Store#bulkCompute(Set, Function, NullaryFunction)}
* with a "replaceEquals" function that returns {@link Boolean#TRUE true}
*
* @param keys the set of keys on which to compute values
* @param remappingFunction the function that generates new values
* @return a {@link Map} of key/value pairs for each key in {@code keys} to the value computed.
*
* @throws ClassCastException if the specified key(s) are not of the correct type ({@code K}). Also thrown if the
* given function produces entries with either incorrect key or value types
* @throws NullPointerException if any of the arguments is null
* @throws StoreAccessException if mappings can't be changed
*/
Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> remappingFunction) throws StoreAccessException;
/**
* Compute a value for every key passed in the {@link Set} {@code keys} argument, using the {@code remappingFunction} to compute the value.
* <p>
* The function gets an {@link Iterable} of {@link java.util.Map.Entry} key/value pairs, where each entry's value is its currently stored value,
* or null if nothing is stored under the key. It is expected that the function returns an {@link Iterable} of {@link java.util.Map.Entry}
* key/value pairs containing an entry for each key that was passed to it. This returned {@link Iterable} should also iterate in the same order as the input {@link Iterable}.
* If an entry's value is null, its mapping will be removed from the store.
* <p>
* Note that the remapping function can be invoked multiple times with key subsets but that it will never the see
* the same key in different invocations.
* <p>
* Behaviour is equivalent to compute invocations in an external loop. There is no cross key atomicity
* guarantee / requirement. Implementations may provide coarser grained guarantees.
*
* @param keys the set of keys on which to compute values
* @param remappingFunction the function that generates new values
* @param replaceEqual If the existing value in the store is {@link java.lang.Object#equals(Object)} to
* the value returned from the mappingFunction this function will be invoked. If this function
* returns {@link java.lang.Boolean#FALSE} then the existing entry in the store will not be replaced
* with a new entry and the existing entry will have its access time updated
* @return a {@link Map} of key/value pairs for each key in {@code keys} to the value computed.
*
* @throws ClassCastException if the specified key(s) are not of the correct type ({@code K}). Also thrown if the given function produces
* entries with either incorrect key or value types
* @throws NullPointerException if any of the arguments is null
* @throws StoreAccessException if mappings can't be changed
*/
Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> remappingFunction, NullaryFunction<Boolean> replaceEqual) throws StoreAccessException;
/**
* Compute a value for every key passed in the {@link Set} <code>keys</code> argument using the <code>mappingFunction</code>
* to compute the value.
* <p>
* The function gets an {@link Iterable} of {@link java.util.Map.Entry} key/value pairs, where each entry's value is its currently stored value
* for each key that is not mapped in the store. It is expected that the function returns an {@link Iterable} of {@link java.util.Map.Entry}
* key/value pairs containing an entry for each key that was passed to it. This returned {@link Iterable} should also iterate in the same order as the input {@link Iterable}.
* <p>
* The function may be called multiple times per <code>bulkComputeIfAbsent</code> call, depending on how the store wants or does not want to batch computations.
* <p>
* Note: This method guarantees atomicity of computations for each individual key in {@code keys}. Implementations may choose to provide coarser grained atomicity.
*
* @param keys the keys to compute a new value for, if they're not in the store.
* @param mappingFunction the function that generates new values.
* @return a {@link Map} of key/value pairs for each key in <code>keys</code> to the previously missing value.
* @throws ClassCastException if the specified key(s) are not of the correct type ({@code K}). Also thrown if the given function produces
* entries with either incorrect key or value types
* @throws StoreAccessException
*/
Map<K, ValueHolder<V>> bulkComputeIfAbsent(Set<? extends K> keys, Function<Iterable<? extends K>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> mappingFunction) throws StoreAccessException;
/**
* Holds both a value, and all the metadata associated with a mapping in a Store.
*
* @param <V> the value type
*/
interface ValueHolder<V> extends ValueSupplier<V> {
/**
* Constant value indicating no expiration - an eternal mapping.
*/
long NO_EXPIRE = -1;
/**
* Accessor to the creation time of this ValueHolder
*
* @param unit the timeUnit to return the creation time in
* @return the creation time in the given unit
*/
long creationTime(TimeUnit unit);
/**
* Accessor to the expiration time of this ValueHolder
*
* @param unit the timeUnit to return the creation time in
* @return the expiration time in the given unit. A value of {@link #NO_EXPIRE} means that the ValueHolder will never expire.
*/
long expirationTime(TimeUnit unit);
/**
* Check if the ValueHolder is expired relative to the specified time
*
* @param expirationTime the expiration time relative to which the expiry check must be made
* @param unit the unit of the expiration time
* @return true if the ValueHolder expired relative to the given expiration time
*/
boolean isExpired(long expirationTime, TimeUnit unit);
/**
* Accessor to the last access time of the Value held in this ValueHolder
*
* @param unit the timeUnit to return the last access time in
* @return the last access time in the given unit
*/
long lastAccessTime(TimeUnit unit);
/**
* Accessor to the hit rate of the value held in this {@code ValueHolder}.
*
* @param now the time in {@link TimeUnit#MILLISECONDS} upto which the rate needs to be calculated
* @param unit the {@link TimeUnit} in which the rate is to returned
* @return the hit rate in the given unit
*/
float hitRate(long now, TimeUnit unit);
/**
* @return hit counter of the Value held in this ValueHolder
*/
long hits();
/**
* The combination of this identifier and the <code>key</code> that ValueHolder is mapped to should to be
* unique at a given time.
*
* @return a unique identifier
*
*/
long getId();
}
/**
* The Service used to create Stores.
* Implementation of {@link Provider} have be thread-safe.
*/
@PluralService
interface Provider extends Service {
/**
* Creates a new Store instance
*
* @param storeConfig the basic configuration for the Store
* @param serviceConfigs the configurations the Provider may need to configure the Store
* @return the Store honoring the configurations passed in
*/
<K, V> Store<K, V> createStore(Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs);
/**
* Informs this Provider, a Store it created is being disposed (i.e. closed)
* @param resource the store to release
*/
void releaseStore(Store<?, ?> resource);
/**
* Informs this Provider, a Store it created is being initialized
* @param resource the store to initialize
*/
void initStore(Store<?, ?> resource);
/**
* Gets the internal ranking for the {@link Store} instances provided by this {@code Provider} of the store's
* ability to handle the specified resources. A higher rank value indicates a more capable {@code Store}.
*
* @param resourceTypes the set of {@code ResourceType}s for the store to handle
* @param serviceConfigs the collection of {@code ServiceConfiguration} instances that may contribute
* to the ranking
*
* @return a non-negative rank indicating the ability of a {@code Store} created by this {@code Provider}
* to handle the resource types specified by {@code resourceTypes}; a rank of 0 indicates the store
* can not handle all types specified in {@code resourceTypes}
*/
int rank(Set<ResourceType<?>> resourceTypes, Collection<ServiceConfiguration<?>> serviceConfigs);
}
/**
* The basic configuration for a Store.
*
* @param <K> key type
* @param <V> value type
*/
interface Configuration<K, V> {
/**
* The {@link java.lang.Class type} of the keys that a Store will hold.
*
* @return the key type
*/
Class<K> getKeyType();
/**
* The {@link java.lang.Class type} of the values that a Store will hold.
*
* @return the value type
*/
Class<V> getValueType();
/**
* The {@link EvictionAdvisor} indicates if mappings should be advised against eviction.
* <p>
* The {@code Store} will use best effort to prevent eviction of advised mappings.
*
* @return the eviction advisor
*/
EvictionAdvisor<? super K, ? super V> getEvictionAdvisor();
/**
* The Classloader for this store. This classloader will be used to deserialize cache entries when required
*/
ClassLoader getClassLoader();
/**
* The expiration policy instance for this store
*/
Expiry<? super K, ? super V> getExpiry();
/**
* The resource pools this store can make use of
*/
ResourcePools getResourcePools();
/**
* The serializer for key instances
*/
Serializer<K> getKeySerializer();
/**
* The serializer for value instances
*/
Serializer<V> getValueSerializer();
/**
* The concurrency level of the dispatcher that processes events
*/
int getDispatcherConcurrency();
}
/**
* An iterator over a Store.
* @param <T> the type of the elements iterated over
*/
interface Iterator<T> {
/**
* Returns <tt>true</tt> if the iteration has more elements. (In other
* words, returns <tt>true</tt> if <tt>next</tt> would return an element
* rather than throwing a {@link java.util.NoSuchElementException}.)
*
* @return <tt>true</tt> if the iterator has more elements.
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration.
* @throws java.util.NoSuchElementException iteration has no more elements.
* @throws StoreAccessException if accessing the next element failed
*/
T next() throws StoreAccessException;
}
/**
* Put operation status
*/
enum PutStatus {
/**
* New value was put
*/
PUT,
/**
* New value was put and replace old value
*/
UPDATE,
/**
* New value was dropped
*/
NOOP
}
/**
* Conditional Remove operation status
*/
enum RemoveStatus {
/**
* Mapping was removed
*/
REMOVED,
/**
* Mapping was not removed although there was one
*/
KEY_PRESENT,
/**
* Mapping was not remove as there was no mapping
*/
KEY_MISSING
}
/**
* Conditional Replace operation status
*/
enum ReplaceStatus {
/**
* Mapping was replaced
*/
HIT,
/**
* Mapping was not replaced although there was one
*/
MISS_PRESENT,
/**
* Mapping was not replace as there was no mapping
*/
MISS_NOT_PRESENT
}
}