package de.axone.cache.ng; import static de.axone.cache.ng.CacheNGAssert.*; import static org.assertj.core.api.Assertions.*; import static org.testng.Assert.*; import java.util.Set; import org.testng.annotations.Test; import de.axone.cache.ng.CacheNG.Cache; import de.axone.cache.ng.CacheNGTestHelpers.TestRealm; @Test( groups="cacheng.multivalue" ) public class CacheNGTest_MultiValue { private static final String NOT_HERE = "NOT_HERE"; private static final String TEST123 = "test123"; public void accessMultiValuesViaOneBackendCache() { CacheNG.Cache<String, MultiValueData> backendMulti = new CacheHashMap<>( new TestRealm<String,MultiValueData>( "S->MV" ) ); TestStringAccessor strToStrAcc = new TestStringAccessor(); CacheNG.AutomaticClient<String,String> frontendString = new AutomaticClientImpl<>( new MultiStringAccessor( backendMulti ) ); TestIntegerAccessor strToIntAcc = new TestIntegerAccessor(); CacheNG.AutomaticClient<String,Integer> frontendInteger = new AutomaticClientImpl<>( new MultiIntegerAccessor( backendMulti ) ); assertThat( backendMulti ).hasNotCached( TEST123 ); assertThat( frontendString ).hasNotCached( TEST123 ); assertThat( frontendInteger ).hasNotCached( TEST123 ); String valString = frontendString.fetch( TEST123, strToStrAcc ); assertEquals( valString, TEST123 ); assertThat( backendMulti ).hasCached( TEST123 ); assertThat( frontendString ).hasCached( TEST123 ); assertThat( frontendInteger ).hasNotCached( TEST123 ); Integer valInteger = frontendInteger.fetch( TEST123, strToIntAcc ); assertThat( valInteger ).isEqualTo( 7 ); assertThat( backendMulti ).hasCached( TEST123 ); assertThat( frontendString ).hasCached( TEST123 ); assertThat( frontendInteger ).hasCached( TEST123 ); frontendString.invalidate( TEST123 ); assertThat( backendMulti ).hasCached( TEST123 ); assertThat( frontendString ).hasNotCached( TEST123 ); assertThat( frontendInteger ).hasCached( TEST123 ); frontendInteger.invalidate( TEST123 ); assertThat( backendMulti ).hasCached( TEST123 ); assertThat( frontendString ).hasNotCached( TEST123 ); assertThat( frontendInteger ).hasNotCached( TEST123 ); backendMulti.invalidate( TEST123 ); assertThat( backendMulti ).hasNotCached( TEST123 ); assertThat( frontendString ).hasNotCached( TEST123 ); assertThat( frontendInteger ).hasNotCached( TEST123 ); // --------------- valString = frontendString.fetch( TEST123, strToStrAcc ); assertEquals( valString, TEST123 ); assertThat( backendMulti ).hasCached( TEST123 ); assertThat( frontendString ).hasCached( TEST123 ); assertThat( frontendInteger ).hasNotCached( TEST123 ); backendMulti.invalidate( TEST123 ); assertThat( backendMulti ).hasNotCached( TEST123 ); assertThat( frontendString ).hasNotCached( TEST123 ); assertThat( frontendInteger ).hasNotCached( TEST123 ); } public void invalidationDoesTheRightThing() { CacheNG.Cache<String, MultiValueData> backendMulti = new CacheHashMap<>( new TestRealm<String,MultiValueData>( "S->MV" ) ); TestStringAccessor strToStrAcc = new TestStringAccessor(); CacheNG.AutomaticClient<String,String> frontendString = new AutomaticClientImpl<String,String>( new MultiStringAccessor( backendMulti ) ); assertThat( frontendString ).hasNotCached( NOT_HERE ); assertThat( backendMulti ).hasNotCached( NOT_HERE ); String value = frontendString.fetch( NOT_HERE, strToStrAcc ); assertEquals( value, null ); assertThat( frontendString ).hasCached( NOT_HERE ); assertThat( backendMulti ).hasCached( NOT_HERE ); frontendString.invalidate( NOT_HERE ); assertThat( frontendString ).hasNotCached( NOT_HERE ); assertThat( backendMulti ).hasCached( NOT_HERE ); backendMulti.invalidate( NOT_HERE ); assertThat( frontendString ).hasNotCached( NOT_HERE ); assertThat( backendMulti ).hasNotCached( NOT_HERE ); } static class MultiValueData { CacheNG.Cache.Entry<String> stringValue; CacheNG.Cache.Entry<Integer> integerValue; } static abstract class AbstractMultiDataAccessor<MV,O> implements CacheNG.Cache<String,O> { private final CacheNG.Cache<String,MV> wrapped; public AbstractMultiDataAccessor( Cache<String, MV> wrapped ) { this.wrapped = wrapped; } protected abstract CacheNG.Cache.Entry<O> getFromMultivalue( MV data ); protected abstract void putInMultivalue( MV data, CacheNG.Cache.Entry<O> value ); protected abstract MV create(); @Override public synchronized CacheNG.Cache.Entry<O> fetchEntry( String key ) { MV data = wrapped.fetch( key ); if( data == null ) return null; CacheNG.Cache.Entry<O> result = getFromMultivalue( data ); return result; } @Override public synchronized O fetch( String key ) { CacheNG.Cache.Entry<O> entry = fetchEntry( key ); if( entry == null ) return null; return entry.data(); } @Override public synchronized boolean isCached( String key ) { MV data = wrapped.fetch( key ); if( data == null ) return false; CacheNG.Cache.Entry<O> value = getFromMultivalue( data ); return value != null; } @Override public synchronized void invalidate( String key ) { MV data = wrapped.fetch( key ); if( data == null ) return; putInMultivalue( data, null ); wrapped.put( key, data ); } @Override public void put( String key, O value ) { MV data = wrapped.fetch( key ); if( data == null ){ data = create(); } putInMultivalue( data, new DefaultEntry<>( value ) ); // store again (probably needed for distributed caches) wrapped.put( key, data ); } @Override public void invalidateAll( boolean force ) { wrapped.invalidateAll( force ); } @Override public int size() { return wrapped.size(); } @Override public int capacity() { return wrapped.capacity(); } @Override public String info() { return wrapped.info(); } @Override public double ratio(){ return wrapped.ratio(); } @Override public Set<String> keySet() { return wrapped.keySet(); } @Override public Iterable<O> values() { throw new UnsupportedOperationException( "Cannot iterate over values" ); } } static class MultiStringAccessor extends AbstractMultiDataAccessor<MultiValueData,String> { public MultiStringAccessor( Cache<String, MultiValueData> wrapped ) { super( wrapped ); } @Override protected CacheNG.Cache.Entry<String> getFromMultivalue( MultiValueData data ) { return data.stringValue; } @Override protected void putInMultivalue( MultiValueData data, CacheNG.Cache.Entry<String> value ) { data.stringValue = value; } @Override protected MultiValueData create() { return new MultiValueData(); } } static class MultiIntegerAccessor extends AbstractMultiDataAccessor<MultiValueData,Integer> { public MultiIntegerAccessor( Cache<String, MultiValueData> wrapped ) { super( wrapped ); } @Override protected CacheNG.Cache.Entry<Integer> getFromMultivalue( MultiValueData data ) { return data.integerValue; } @Override protected void putInMultivalue( MultiValueData data, CacheNG.Cache.Entry<Integer> value ) { data.integerValue = value; } @Override protected MultiValueData create() { return new MultiValueData(); } } static class TestStringAccessor implements CacheNG.SingleValueAccessor<String,String> { @Override public String fetch( String key ) { if( key.equals( NOT_HERE ) ) return null; return key; } } static class TestIntegerAccessor implements CacheNG.SingleValueAccessor<String,Integer> { @Override public Integer fetch( String key ) { if( key.equals( NOT_HERE ) ) return null; return key.length(); } } }