package de.axone.cache.ng;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
/**
* Mögliche Cache-Arten nach Key
*
* <pre>
* TID
* -* TreeItem
* -* L:AID : Articles
* -* L:AID : TopSeller
* [ -* L:Comments z.B. neueste ]
* [ -* L:Tags ] <-Articles
* [ -* M:Parameters ] <-Articles
* -* L:PID : Pages
*
* Top (tranlates to List<TID>)
* s. o. ausser TreeItems, Pages
*
* AID
* -* Article
* -* L:Comments / Rating
* -* Bestand
* -* XREF: XSell: L:Articles
*
* Article beinhaltet schon: (deswegen nicht f. AID)
* -* L:Para
* -* L:Tags
*
* Sonstiges:
*
* Query:
* * para.name
* * tag
* * DESC.x
* * TID
* * Top
* -* alle L:Article
* caching verm. nur auf root-query ebene.
* Die subqueries können aber wieder auf gecachte backends
* zugreifen wenn nötig.
* Die *ganz große* Frage hier ist, wie invalidieren.
* Was wir da brauchen ist invalidation propagation upward
* Das geht vermutlich sowieso nicht. Besser: Timeout
* für die Cache-Entries. Der sollte aber "freundlich"#
* sein und das cache-invalidate möglichst über einen
* Zeitrum verteilen.
*
* D.h.
* ROOT
* AND
* TID=123
* PARA.name=blah
* tag=bier
*
* invalidate( tid=123 )-* invalidate( root )
* genau wie
* invalidate( para.name=blah )-*invalidate( root )
* invalidate( tag=bier )-*invalidate( root )
*
* XRef
* ART_XSELL, ART_TOP,
* TREE_VIEWS, ART_VIEWS,
* TREE_SEARCH, ART_SEARCH,
* ART_USER, TREE_USER
*
* Templates:
* File -* Pair<FileWatcher,DataHolder>
* URL -* Pair<HttpWatcher,DataHolder>
* .
*
* MISSING:
*
* * country.csv
* V.A. Template caching.
* Das geht auch nur, wenn da eine "dynamische" invalidation-Chain aufgebaut werden kann,
* d.h. änderungne am template-inhalt den template-cache invalidieren
*
*
*
*
* IDEE:
*
* InvalidationListener wird mit dem cachen des ergebnisses
* registriert und bei invalidation ereigenissen informiert.
*
* also
* WebTemplate -* register( Article:123 )
* -* register(
*
*
* CacheDataHolder {
*
* data : WebTemplate
* inval : Article:123 : count_4711
* inval : Comment:123 : count_0815
* }
*
* notify evtl. über einen invalidation-counter im object und im gecachten der dann überprüft wird.
*
* More:
* * include Passive kann teil des Keys sein.
*
*
*
* Cache-Names
* -------------------------------------------------------------------------------
* TID-*TreeItem
* TID-*L:AID
* TID-*L:AID(Topseller)
* TID-*L:Comments
* TID-*L:Comments(Newest)
* TID-*L:Tags
* TID-*M:Parameters (???)
* TID-*L:PID
*
* AID-*Article
* AID-*L:Comments
* </pre>
*
* @author flo
*/
public abstract class CacheNG {
/**
* Identifies a realm for the backend
*
* @author flo
* @param <K> The key type
* @param <O> The value type
*/
public interface Realm<K,O> extends Comparable<Realm<?,?>>{
/**
* @return String representation of this realm.
* Can be used by cache backends to identify the cache
*/
public String name();
/**
* @return String representaitons of this realm as used
* for the config
*/
public String[] config();
/**
* @return the basic realm without client
*/
public String realm();
}
// TODO: Evtl. DataHolder o.ä. um mit einem CacheQuery z.B. nach TID
// gleich noch die restlichen einträge (topselller etc.) zu kommen. Die werden nämlich i.d.R
// sowieso gebraucht.
// Keep it simple for starters
/**
* Interface for a Cache client. A.k.a. an Cache.
*
* Note that multiple clients can operate on the same backend cache.
* TODO: Ist das so?
*
* @author flo
*
* @param <K> Cache key
* @param <O> Cached Object
*/
public interface Cache<K,O> {
/**
* Get the chache entry identified by key
*
* @param key
* @return the cached object or null if not found
*/
public Entry<O> fetchEntry( K key );
/**
* Get the chache object identified by key
*
* @param key
* @return the cached object or null if not found
*/
public O fetch( K key );
/**
* @return true if the object identified by key exists
*
* @param key
*/
public boolean isCached( K key );
/**
* Remove the entry identified by key
*
* @param key
*/
public void invalidate( K key );
/**
* Put the given entry in the cache
*
* @param key
* @param object
*/
public void put( K key, O object );
/**
* Clear the complete cache
*
* @param force set to <tt>true</tt> to invalidate immediately, circumventing
* any possible invalidation delay
*/
public void invalidateAll( boolean force );
/**
* @return the used size of the cache
*/
public int size();
/**
* @return the available capacity of the cache
* or -1 for infinite
*/
public int capacity();
/**
* @return the usage ratio or -1 if not supported
*/
public double ratio();
/**
* @return some meaningful information.
*
* (At least the size should be returned)
*/
public String info();
/**
* Implements more of Map interface for direct access
*
* This is additional because we cannot expect distributed caches
* to have a keyset fast (if at all) available.
*
* @see java.util.Map#keySet()
* @return A set of all keys
* @throws UnsupportedOperationException if not supported
*/
public Set<K> keySet();
/**
* @return an Iterable instance for this caches values
*
* @see java.util.Map#values()
* @throws UnsupportedOperationException if not supported
*/
public Iterable<O> values();
/**
* One cache entry
*
* @author flo
*
* @param <O>
*/
public interface Entry<O> {
/**
* @return The entries data
*/
public O data();
/**
* @return The entries creation time
*/
public long creation();
}
}
/**
* Cache client which automatically fetches it's content
*
* @author flo
*
* @param <K> Cache key
* @param <O> Cached Object
*/
public interface AutomaticClient<K,O> {
/**
* @return Get one entry from cache. If the entry is not cached try
* to get it using the Accessor
*
* @param key
* @param accessor to use to fetch the value
*/
public O fetch( K key, SingleValueAccessor<K,O> accessor );
/**
* @return a bunch of entries from the cache. If the entries are
* not cached try to fetch them using the Accessor.
*
* @param keys
* @param accessor
*/
public Map<K,O> fetch( Collection<K> keys, MultiValueAccessor<K,O> accessor );
/**
* @return true if this entry is already stored.
*
* Dues not try to fetch from Accessor
*
* @param key
*/
public boolean isCached( K key );
/**
* Remove this entry from the cache.
*
* @param key
*/
public void invalidate( K key );
public abstract int size();
public abstract int capacity();
public abstract void invalidateAll( boolean force );
public abstract Stats stats();
public interface Stats {
void hit();
void miss();
}
}
/**
* Accesses the Backend and get multiple values as Map
*
* @author flo
*
* @param <K> Key-Type
* @param <O> Value-Type
*/
@FunctionalInterface
public interface MultiValueAccessor<K,O> {
/**
* Fetch multiple entries at once.
*
* Frequently this is more efficient than fetching single values
*
* @param keys
* @return the entries found
*/
public Map<K,O> fetch( Collection<K> keys ) ;
}
/**
* Accesses the Backend and get one value
*
* @author flo
*
* @param <K> Key-Type
* @param <O> Value-Type
*/
@FunctionalInterface
public interface SingleValueAccessor<K,O> {
/**
* @return a single entry
*
* @param key
*/
public O fetch( K key ) ;
}
/**
* Accesses the Backend and get one value
*
* @author flo
*
* @param <K> Key-Type
* @param <O> Value-Type
*/
public interface UniversalAccessor<K,O>
extends SingleValueAccessor<K,O>, MultiValueAccessor<K,O> {
@Override
default public O fetch( K key ) {
return fetch( Arrays.asList( key ) ).get( key );
}
@Override
default public Map<K, O> fetch( Collection<K> keys ) {
return keys.stream()
.collect( Collectors.toMap(
key -> key,
key -> fetch( key ) ) );
}
}
public static <K,O> SingleValueAccessor<K,O> multi2single( MultiValueAccessor<K,O> multi ){
return key -> multi.fetch( Arrays.asList( key ) ).get( key );
}
public static <K,O> MultiValueAccessor<K,O> single2multi( SingleValueAccessor<K,O> single ){
return keys -> keys.stream()
.collect( Collectors.toMap(
k -> k,
k -> single.fetch( k ) ) );
}
public static <K,O> UniversalAccessor<K,O> multi2universal( MultiValueAccessor<K,O> multi ){
return new UniversalAccessor<K,O>() {
@Override
public O fetch( K key ) {
return multi.fetch( Arrays.asList( key ) ).get( key );
}
@Override
public Map<K, O> fetch( Collection<K> keys ) {
return multi.fetch( keys );
}
};
}
public static <K,O> UniversalAccessor<K,O> single2universal( SingleValueAccessor<K,O> single ){
return new UniversalAccessor<K,O>() {
@Override
public O fetch( K key ) {
return single.fetch( key );
}
@Override
public Map<K, O> fetch( Collection<K> keys ) {
return keys.stream()
.collect( Collectors.toMap(
k -> k,
k -> single.fetch( k ) ) );
}
};
}
/**
*
* @author flo
*
* @param <K> Key-Type
* @param <O> Value type
*/
public interface BackendAccessor<K,O> extends Cache<K,O> {
// NOP
}
/**
* Passes cache invalidation events to listeners
*
* @author flo
*
* @param <K>
*/
public interface CacheEventProvider<K> {
public void registerListener( CacheEventListener<K> listener );
void listenersInvalidate( K key );
void listenersInvalidateAll( boolean force );
}
/**
* receives cache invalidation events
*
* @author flo
*
* @param <K>
*/
public interface CacheEventListener<K> {
public void invalidateEvent( K key );
public void invalidateAllEvent( boolean force );
}
public interface CacheRemoveListener {
public void goingToRemove();
}
/**
* Connects two caches and translates invalidation events
*
* @author flo
*
* @param <S> Source-type
* @param <T> Target-type
*/
public abstract static class CacheBridge<S,T> implements CacheEventListener<S> {
private final CacheEventListener<T> target;
public CacheBridge( CacheEventListener<T> target ){
this.target = target;
}
@Override
public void invalidateEvent( S sourceKey ) {
T targetKey = bridge( sourceKey );
target.invalidateEvent( targetKey );
}
@Override
public void invalidateAllEvent( boolean force ) {
target.invalidateAllEvent( force );
}
/**
* Convert source key to target key
*
* Override this
*
* @param key
* @return
*/
protected abstract T bridge( S key );
}
/**
* Helper interface for other classes.
*
* <em>Not used here!</em>
*
* @author flo
*/
public interface HasCacheKey {
/**
* Return an object which will be used as cache key.
* Note that this object must fullfill the contract from 'CacheKey'
*
* The return value is object because everything which could validly
* be used as cache key can be returned here.
*
* This is particularily useful for returning something like <tt>HashMap<String></tt>
* which in turn will do the equals / hashcode calculation.
*
* @return the object
*/
public @Nonnull Object cacheKey();
}
/**
* Reminder interface what needs to be implemented in an cache key.
*
* <em>Not used here!</em>
*
* @author flo
*/
public interface CacheKey extends Serializable {
@Override
public int hashCode();
@Override
public boolean equals( Object other );
}
}